Projet

Général

Profil

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

root / drupal7 / sites / all / modules / views / includes / admin.inc @ 6eb8d15f

1
<?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
  if ($value !== '' && (!is_numeric($value) || intval($value) != $value || abs($value) != $value)) {
412
    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
  if (!empty($_REQUEST['cancel'])) {
859
    $cancel = $_REQUEST['cancel'];
860
  }
861

    
862
  $account = user_load($view->locked->uid);
863
  return confirm_form($form,
864
                  t('Are you sure you want to break the lock on view %name?',
865
                  array('%name' => $view->name)),
866
                  $cancel,
867
                  t('By breaking this lock, any unsaved changes made by !user will be lost!', array('!user' => theme('username', array('account' => $account)))),
868
                  t('Break lock'),
869
                  t('Cancel'));
870
}
871

    
872
/**
873
 * Submit handler to break_lock a view.
874
 */
875
function views_ui_break_lock_confirm_submit(&$form, &$form_state) {
876
  ctools_object_cache_clear_all('view', $form_state['view']->name);
877
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
878
  drupal_set_message(t('The lock has been broken and you may now edit this view.'));
879
}
880

    
881
/**
882
 * Helper function to return the used display_id for the edit page
883
 *
884
 * This function handles access to the display.
885
 */
886
function views_ui_edit_page_display($view, $display_id) {
887
  // Determine the displays available for editing.
888
  if ($tabs = views_ui_edit_page_display_tabs($view, $display_id)) {
889
    // If a display isn't specified, use the first one.
890
    if (empty($display_id)) {
891
      foreach ($tabs as $id => $tab) {
892
        if (!isset($tab['#access']) || $tab['#access']) {
893
          $display_id = $id;
894
          break;
895
        }
896
      }
897
    }
898
    // If a display is specified, but we don't have access to it, return
899
    // an access denied page.
900
    if ($display_id && (!isset($tabs[$display_id]) || (isset($tabs[$display_id]['#access']) && !$tabs[$display_id]['#access']))) {
901
      return MENU_ACCESS_DENIED;
902
    }
903

    
904
    return $display_id;
905
  }
906
  elseif ($display_id) {
907
    return MENU_ACCESS_DENIED;
908
  }
909
  else {
910
    $display_id = NULL;
911
  }
912

    
913
  return $display_id;
914
}
915

    
916
/**
917
 * Page callback for the Edit View page.
918
 */
919
function views_ui_edit_page($view, $display_id = NULL) {
920
  $display_id = views_ui_edit_page_display($view, $display_id);
921
  if (!in_array($display_id, array(MENU_ACCESS_DENIED, MENU_NOT_FOUND))) {
922
    $build = array();
923
    $build['edit_form'] = drupal_get_form('views_ui_edit_form', $view, $display_id);
924
    $build['preview'] = views_ui_build_preview($view, $display_id, FALSE);
925
  }
926
  else {
927
    $build = $display_id;
928
  }
929

    
930
  return $build;
931
}
932

    
933
function views_ui_build_preview($view, $display_id, $render = TRUE) {
934
  if (isset($_POST['ajax_html_ids'])) {
935
    unset($_POST['ajax_html_ids']);
936
  }
937

    
938
  $build = array(
939
    '#theme_wrappers' => array('container'),
940
    '#attributes' => array('id' => 'views-preview-wrapper', 'class' => 'views-admin clearfix'),
941
  );
942

    
943
  $form_state = array('build_info' => array('args' => array($view, $display_id)));
944
  $build['controls'] = drupal_build_form('views_ui_preview_form', $form_state);
945

    
946
  $args = array();
947
  if (!empty($form_state['values']['view_args'])) {
948
    $args = explode('/', $form_state['values']['view_args']);
949
  }
950

    
951
  $build['preview'] = array(
952
    '#theme_wrappers' => array('container'),
953
    '#attributes' => array('id' => 'views-live-preview'),
954
    '#markup' => $render ? views_ui_preview($view->clone_view(), $display_id, $args) : '',
955
  );
956

    
957
  return $build;
958
}
959

    
960
/**
961
 * Form builder callback for editing a View.
962
 *
963
 * @todo Remove as many #prefix/#suffix lines as possible. Use #theme_wrappers
964
 *   instead.
965
 *
966
 * @todo Rename to views_ui_edit_view_form(). See that function for the "old"
967
 *   version.
968
 *
969
 * @see views_ui_ajax_get_form()
970
 */
971
function views_ui_edit_form($form, &$form_state, $view, $display_id = NULL) {
972
  // Do not allow the form to be cached, because $form_state['view'] can become
973
  // stale between page requests.
974
  // See views_ui_ajax_get_form() for how this affects #ajax.
975
  // @todo To remove this and allow the form to be cacheable:
976
  //   - Change $form_state['view'] to $form_state['temporary']['view'].
977
  //   - Add a #process function to initialize $form_state['temporary']['view']
978
  //     on cached form submissions.
979
  //   - Update ctools_include() to support cached forms, or else use
980
  //     form_load_include().
981
  $form_state['no_cache'] = TRUE;
982

    
983
  if ($display_id) {
984
    if (!$view->set_display($display_id)) {
985
      $form['#markup'] = t('Invalid display id @display', array('@display' => $display_id));
986
      return $form;
987
    }
988

    
989
    $view->fix_missing_relationships();
990
  }
991

    
992
  ctools_include('dependent');
993
  $form['#attached']['js'][] = ctools_attach_js('dependent');
994
  $form['#attached']['js'][] = ctools_attach_js('collapsible-div');
995

    
996
  $form['#tree'] = TRUE;
997
  // @todo When more functionality is added to this form, cloning here may be
998
  //   too soon. But some of what we do with $view later in this function
999
  //   results in making it unserializable due to PDO limitations.
1000
  $form_state['view'] = clone($view);
1001

    
1002
  $form['#attached']['library'][] = array('system', 'ui.tabs');
1003
  $form['#attached']['library'][] = array('system', 'ui.dialog');
1004
  $form['#attached']['library'][] = array('system', 'drupal.ajax');
1005
  $form['#attached']['library'][] = array('system', 'jquery.form');
1006
  // TODO: This should be getting added to the page when an ajax popup calls
1007
  // for it, instead of having to add it manually here.
1008
  $form['#attached']['js'][] = 'misc/tabledrag.js';
1009

    
1010
  $form['#attached']['css'] = views_ui_get_admin_css();
1011
  $module_path = drupal_get_path('module', 'views_ui');
1012

    
1013
  $form['#attached']['js'][] = $module_path . '/js/views-admin.js';
1014
  $form['#attached']['js'][] = array(
1015
    'data' => array('views' => array('ajax' => array(
1016
      'id' => '#views-ajax-body',
1017
      'title' => '#views-ajax-title',
1018
      'popup' => '#views-ajax-popup',
1019
      'defaultForm' => views_ui_get_default_ajax_message(),
1020
    ))),
1021
    'type' => 'setting',
1022
  );
1023

    
1024
  $form += array(
1025
    '#prefix' => '',
1026
    '#suffix' => '',
1027
  );
1028
  $form['#prefix'] = $form['#prefix'] . '<div class="views-edit-view views-admin clearfix">';
1029
  $form['#suffix'] = '</div>' . $form['#suffix'];
1030

    
1031
  $form['#attributes']['class'] = array('form-edit');
1032

    
1033
  if (isset($view->locked) && is_object($view->locked)) {
1034
    $form['locked'] = array(
1035
      '#theme_wrappers' => array('container'),
1036
      '#attributes' => array('class' => array('view-locked', 'messages', 'warning')),
1037
      '#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'))),
1038
    );
1039
  }
1040
  if (isset($view->vid) && $view->vid == 'new') {
1041
    $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard the view.');
1042
  }
1043
  else {
1044
    $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard your changes.');
1045
  }
1046

    
1047
  $form['changed'] = array(
1048
    '#theme_wrappers' => array('container'),
1049
    '#attributes' => array('class' => array('view-changed', 'messages', 'warning')),
1050
    '#markup' => $message,
1051
  );
1052
  if (empty($view->changed)) {
1053
    $form['changed']['#attributes']['class'][] = 'js-hide';
1054
  }
1055

    
1056
  $form['help_text'] = array(
1057
    '#prefix' => '<div>',
1058
    '#suffix' => '</div>',
1059
    '#markup' => t('Modify the display(s) of your view below or add new displays.'),
1060
  );
1061

    
1062
  $form['actions'] = array(
1063
    '#type' => 'actions',
1064
    '#weight' => 0,
1065
  );
1066

    
1067
  if (empty($view->changed)) {
1068
    $form['actions']['#attributes'] = array(
1069
      'class' => array(
1070
        'js-hide',
1071
      ),
1072
    );
1073
  }
1074

    
1075
  $form['actions']['save'] = array(
1076
    '#type' => 'submit',
1077
    '#value' => t('Save'),
1078
    // Taken from the "old" UI. @TODO: Review and rename.
1079
    '#validate' => array('views_ui_edit_view_form_validate'),
1080
    '#submit' => array('views_ui_edit_view_form_submit'),
1081
  );
1082
  $form['actions']['cancel'] = array(
1083
    '#type' => 'submit',
1084
    '#value' => t('Cancel'),
1085
    '#submit' => array('views_ui_edit_view_form_cancel'),
1086
  );
1087

    
1088
  $form['displays'] = array(
1089
    '#prefix' => '<h1 class="unit-title clearfix">' . t('Displays') . '</h1>' . "\n" . '<div class="views-displays">',
1090
    '#suffix' => '</div>',
1091
  );
1092

    
1093
  $form['displays']['top'] = views_ui_render_display_top($view, $display_id);
1094

    
1095
  // The rest requires a display to be selected.
1096
  if ($display_id) {
1097
    $form_state['display_id'] = $display_id;
1098

    
1099
    // The part of the page where editing will take place.
1100
    // This element is the ctools collapsible-div container for the display edit elements.
1101
    $form['displays']['settings'] = array(
1102
      '#theme_wrappers' => array('container'),
1103
      '#attributes' => array(
1104
        'class' => array(
1105
          'views-display-settings',
1106
          'box-margin',
1107
          'ctools-collapsible-container',
1108
        ),
1109
      ),
1110
      '#id' => 'edit-display-settings',
1111
    );
1112
    $display_title = views_ui_get_display_label($view, $display_id, FALSE);
1113
    // Add a handle for the ctools collapsible-div. The handle is the title of the display
1114
    $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>';
1115
    // Add a text that the display is disabled.
1116
    if (!empty($view->display[$display_id]->handler)) {
1117
      $enabled = $view->display[$display_id]->handler->get_option('enabled');
1118
      if (empty($enabled)) {
1119
        $form['displays']['settings']['disabled']['#markup'] = t('This display is disabled.');
1120
      }
1121
    }
1122
    // The ctools collapsible-div content
1123
    $form['displays']['settings']['settings_content']= array(
1124
      '#theme_wrappers' => array('container'),
1125
      '#id' => 'edit-display-settings-content',
1126
      '#attributes' => array(
1127
        'class' => array(
1128
          'ctools-collapsible-content',
1129
        ),
1130
      ),
1131
    );
1132
    // Add the edit display content
1133
    $form['displays']['settings']['settings_content']['tab_content'] = views_ui_get_display_tab($view, $display_id);
1134
    $form['displays']['settings']['settings_content']['tab_content']['#theme_wrappers'] = array('container');
1135
    $form['displays']['settings']['settings_content']['tab_content']['#attributes'] = array('class' => array('views-display-tab'));
1136
    $form['displays']['settings']['settings_content']['tab_content']['#id'] = 'views-tab-' . $display_id;
1137
    // Mark deleted displays as such.
1138
    if (!empty($view->display[$display_id]->deleted)) {
1139
      $form['displays']['settings']['settings_content']['tab_content']['#attributes']['class'][] = 'views-display-deleted';
1140
    }
1141
    // Mark disabled displays as such.
1142
    if (empty($enabled)) {
1143
      $form['displays']['settings']['settings_content']['tab_content']['#attributes']['class'][] = 'views-display-disabled';
1144
    }
1145

    
1146
    // The content of the popup dialog.
1147
    $form['ajax-area'] = array(
1148
      '#theme_wrappers' => array('container'),
1149
      '#id' => 'views-ajax-popup',
1150
    );
1151
    $form['ajax-area']['ajax-title'] = array(
1152
      '#markup' => '<h2 id="views-ajax-title"></h2>',
1153
    );
1154
    $form['ajax-area']['ajax-body'] = array(
1155
      '#theme_wrappers' => array('container'),
1156
      '#id' => 'views-ajax-body',
1157
      '#markup' => views_ui_get_default_ajax_message(),
1158
    );
1159
  }
1160

    
1161
  // If relationships had to be fixed, we want to get that into the cache
1162
  // so that edits work properly, and to try to get the user to save it
1163
  // so that it's not using weird fixed up relationships.
1164
  if (!empty($view->relationships_changed) && empty($_POST)) {
1165
    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.'));
1166
    views_ui_cache_set($view);
1167
  }
1168
  return $form;
1169
}
1170

    
1171
/**
1172
 * Provide the preview formulas and the preview output, too.
1173
 */
1174
function views_ui_preview_form($form, &$form_state, $view, $display_id = 'default') {
1175
  $form_state['no_cache'] = TRUE;
1176
  $form_state['view'] = $view;
1177

    
1178
  $form['#attributes'] = array('class' => array('clearfix',));
1179

    
1180
  // Add a checkbox controlling whether or not this display auto-previews.
1181
  $form['live_preview'] = array(
1182
    '#type' => 'checkbox',
1183
    '#id' => 'edit-displays-live-preview',
1184
    '#title' => t('Auto preview'),
1185
    '#default_value' => variable_get('views_ui_always_live_preview', TRUE),
1186
  );
1187

    
1188
  // Add the arguments textfield
1189
  $form['view_args'] = array(
1190
    '#type' => 'textfield',
1191
    '#title' => t('Preview with contextual filters:'),
1192
    '#description' => t('Separate contextual filter values with a "/". For example, %example.', array('%example' => '40/12/10')),
1193
    '#id' => 'preview-args',
1194
//      '#attributes' => array('class' => array('ctools-auto-submit')),
1195
  );
1196

    
1197
  // Add the preview button
1198
  $form['button'] = array(
1199
    '#type' => 'submit',
1200
    '#value' => t('Update preview'),
1201
    '#attributes' => array('class' => array('arguments-preview', 'ctools-auto-submit-click')),
1202
    '#pre_render' => array('ctools_dependent_pre_render'),
1203
    '#prefix' => '<div id="preview-submit-wrapper">',
1204
    '#suffix' => '</div>',
1205
    '#id' => 'preview-submit',
1206
    '#submit' => array('views_ui_edit_form_submit_preview'),
1207
    '#ajax' => array(
1208
      'path' => 'admin/structure/views/view/' . $view->name . '/preview/' . $display_id . '/ajax',
1209
      'wrapper' => 'views-preview-wrapper',
1210
      'event' => 'click',
1211
      'progress' => array('type' => 'throbber'),
1212
      'method' => 'replace',
1213
    ),
1214
    // Make ENTER in arguments textfield (and other controls) submit the form
1215
    // as this button, not the Save button.
1216
    // @todo This only works for JS users. To make this work for nojs users,
1217
    //   we may need to split Preview into a separate form.
1218
    '#process' => array_merge(array('views_ui_default_button'), element_info_property('submit', '#process', array())),
1219
  );
1220
  $form['#action'] = url('admin/structure/views/view/' . $view->name .'/preview/' . $display_id);
1221

    
1222
  return $form;
1223
}
1224

    
1225
/**
1226
 * Render the top of the display so it can be updated during ajax operations.
1227
 */
1228
function views_ui_render_display_top($view, $display_id) {
1229
  $element['#theme_wrappers'] = array('views_container');
1230
  $element['#attributes']['class'] = array('views-display-top', 'clearfix');
1231
  $element['#attributes']['id'] = array('views-display-top');
1232

    
1233
  // Extra actions for the display
1234
  $element['extra_actions'] = array(
1235
    '#theme' => 'links__ctools_dropbutton',
1236
    '#attributes' => array(
1237
        'id' => 'views-display-extra-actions',
1238
        'class' => array(
1239
          'horizontal', 'right', 'links', 'actions',
1240
        ),
1241
      ),
1242
    '#links' => array(
1243
      'edit-details' => array(
1244
        'title' => t('edit view name/description'),
1245
        'href' => "admin/structure/views/nojs/edit-details/$view->name",
1246
        'attributes' => array('class' => array('views-ajax-link')),
1247
      ),
1248
      'analyze' => array(
1249
        'title' => t('analyze view'),
1250
        'href' => "admin/structure/views/nojs/analyze/$view->name/$display_id",
1251
        'attributes' => array('class' => array('views-ajax-link')),
1252
      ),
1253
      'clone' => array(
1254
        'title' => t('clone view'),
1255
        'href' => "admin/structure/views/view/$view->name/clone",
1256
      ),
1257
      'export' => array(
1258
        'title' => t('export view'),
1259
        'href' => "admin/structure/views/view/$view->name/export",
1260
      ),
1261
      'reorder' => array(
1262
        'title' => t('reorder displays'),
1263
        'href' => "admin/structure/views/nojs/reorder-displays/$view->name/$display_id",
1264
        'attributes' => array('class' => array('views-ajax-link')),
1265
      ),
1266
    ),
1267
  );
1268

    
1269
  // Let other modules add additional links here.
1270
  drupal_alter('views_ui_display_top_links', $element['extra_actions']['#links'], $view, $display_id);
1271

    
1272
  if (isset($view->type) && $view->type != t('Default')) {
1273
    if ($view->type == t('Overridden')) {
1274
      $element['extra_actions']['#links']['revert'] = array(
1275
        'title' => t('revert view'),
1276
        'href' => "admin/structure/views/view/$view->name/revert",
1277
        'query' => array('destination' => "admin/structure/views/view/$view->name"),
1278
      );
1279
    }
1280
    else {
1281
      $element['extra_actions']['#links']['delete'] = array(
1282
        'title' => t('delete view'),
1283
        'href' => "admin/structure/views/view/$view->name/delete",
1284
      );
1285
    }
1286
  }
1287

    
1288
  // Determine the displays available for editing.
1289
  if ($tabs = views_ui_edit_page_display_tabs($view, $display_id)) {
1290
    if ($display_id) {
1291
      $tabs[$display_id]['#active'] = TRUE;
1292
    }
1293
    $tabs['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2><ul id = "views-display-menu-tabs" class="tabs secondary">';
1294
    $tabs['#suffix'] = '</ul>';
1295
    $element['tabs'] = $tabs;
1296
  }
1297

    
1298
  // Buttons for adding a new display.
1299
  foreach (views_fetch_plugin_names('display', NULL, array($view->base_table)) as $type => $label) {
1300
    $element['add_display'][$type] = array(
1301
      '#type' => 'submit',
1302
      '#value' => t('Add !display', array('!display' => $label)),
1303
      '#limit_validation_errors' => array(),
1304
      '#submit' => array('views_ui_edit_form_submit_add_display', 'views_ui_edit_form_submit_delay_destination'),
1305
      '#attributes' => array('class' => array('add-display')),
1306
      // Allow JavaScript to remove the 'Add ' prefix from the button label when
1307
      // placing the button in a "Add" dropdown menu.
1308
      '#process' => array_merge(array('views_ui_form_button_was_clicked'), element_info_property('submit', '#process', array())),
1309
      '#values' => array(t('Add !display', array('!display' => $label)), $label),
1310
    );
1311
  }
1312

    
1313
  return $element;
1314
}
1315

    
1316
function views_ui_get_default_ajax_message() {
1317
  return '<div class="message">' . t("Click on an item to edit that item's details.") . '</div>';
1318
}
1319

    
1320
/**
1321
 * Submit handler to add a display to a view.
1322
 */
1323
function views_ui_edit_form_submit_add_display($form, &$form_state) {
1324
  $view = $form_state['view'];
1325

    
1326
  // Create the new display.
1327
  $parents = $form_state['triggering_element']['#parents'];
1328
  $display_type = array_pop($parents);
1329
  $display_id = $view->add_display($display_type);
1330
  views_ui_cache_set($view);
1331

    
1332
  // Redirect to the new display's edit page.
1333
  $form_state['redirect'] = 'admin/structure/views/view/' . $view->name . '/edit/' . $display_id;
1334
}
1335

    
1336
/**
1337
 * Submit handler to duplicate a display for a view.
1338
 */
1339
function views_ui_edit_form_submit_duplicate_display($form, &$form_state) {
1340
  $view = $form_state['view'];
1341
  $display_id = $form_state['display_id'];
1342

    
1343
  // Create the new display.
1344
  $display = $view->display[$display_id];
1345
  $new_display_id = $view->add_display($display->display_plugin);
1346
  $view->display[$new_display_id] = clone $display;
1347
  $view->display[$new_display_id]->id = $new_display_id;
1348

    
1349
  // By setting the current display the changed marker will appear on the new
1350
  // display.
1351
  $view->current_display = $new_display_id;
1352
  views_ui_cache_set($view);
1353

    
1354
  // Redirect to the new display's edit page.
1355
  $form_state['redirect'] = 'admin/structure/views/view/' . $view->name . '/edit/' . $new_display_id;
1356
}
1357

    
1358
/**
1359
 * Submit handler to delete a display from a view.
1360
 */
1361
function views_ui_edit_form_submit_delete_display($form, &$form_state) {
1362
  $view = $form_state['view'];
1363
  $display_id = $form_state['display_id'];
1364

    
1365
  // Mark the display for deletion.
1366
  $view->display[$display_id]->deleted = TRUE;
1367
  views_ui_cache_set($view);
1368

    
1369
  // Redirect to the top-level edit page. The first remaining display will
1370
  // become the active display.
1371
  $form_state['redirect'] = 'admin/structure/views/view/' . $view->name;
1372
}
1373

    
1374
/**
1375
 * Submit handler to add a restore a removed display to a view.
1376
 */
1377
function views_ui_edit_form_submit_undo_delete_display($form, &$form_state) {
1378
  // Create the new display
1379
  $id = $form_state['display_id'];
1380
  $form_state['view']->display[$id]->deleted = FALSE;
1381

    
1382
  // Store in cache
1383
  views_ui_cache_set($form_state['view']);
1384

    
1385
  // Redirect to the top-level edit page.
1386
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
1387
}
1388

    
1389
/**
1390
 * Submit handler to enable a disabled display.
1391
 */
1392
function views_ui_edit_form_submit_enable_display($form, &$form_state) {
1393
  $id = $form_state['display_id'];
1394
  // set_option doesn't work because this would might affect upper displays
1395
  $form_state['view']->display[$id]->handler->set_option('enabled', TRUE);
1396

    
1397
  // Store in cache
1398
  views_ui_cache_set($form_state['view']);
1399

    
1400
  // Redirect to the top-level edit page.
1401
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
1402
}
1403

    
1404
/**
1405
 * Submit handler to disable display.
1406
 */
1407
function views_ui_edit_form_submit_disable_display($form, &$form_state) {
1408
  $id = $form_state['display_id'];
1409
  $form_state['view']->display[$id]->handler->set_option('enabled', FALSE);
1410

    
1411
  // Store in cache
1412
  views_ui_cache_set($form_state['view']);
1413

    
1414
  // Redirect to the top-level edit page.
1415
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
1416
}
1417

    
1418
/**
1419
 * Submit handler when Preview button is clicked.
1420
 */
1421
function views_ui_edit_form_submit_preview($form, &$form_state) {
1422
  // Rebuild the form with a pristine $view object.
1423
  $form_state['build_info']['args'][0] = views_ui_cache_load($form_state['view']->name);
1424
  $form_state['show_preview'] = TRUE;
1425
  $form_state['rebuild'] = TRUE;
1426
}
1427

    
1428
/**
1429
 * Submit handler for form buttons that do not complete a form workflow.
1430
 *
1431
 * The Edit View form is a multistep form workflow, but with state managed by
1432
 * the CTools object cache rather than $form_state['rebuild']. Without this
1433
 * submit handler, buttons that add or remove displays would redirect to the
1434
 * destination parameter (e.g., when the Edit View form is linked to from a
1435
 * contextual link). This handler can be added to buttons whose form submission
1436
 * should not yet redirect to the destination.
1437
 */
1438
function views_ui_edit_form_submit_delay_destination($form, &$form_state) {
1439
  if (isset($_GET['destination']) && $form_state['redirect'] !== FALSE) {
1440
    if (!isset($form_state['redirect'])) {
1441
      $form_state['redirect'] = $_GET['q'];
1442
    }
1443
    if (is_string($form_state['redirect'])) {
1444
      $form_state['redirect'] = array($form_state['redirect']);
1445
    }
1446
    $options = isset($form_state['redirect'][1]) ? $form_state['redirect'][1] : array();
1447
    if (!isset($options['query']['destination'])) {
1448
      $options['query']['destination'] = $_GET['destination'];
1449
    }
1450
    $form_state['redirect'][1] = $options;
1451
    unset($_GET['destination']);
1452
  }
1453
}
1454

    
1455
/**
1456
 * Adds tabs for navigating across Displays when editing a View.
1457
 *
1458
 * This function can be called from hook_menu_local_tasks_alter() to implement
1459
 * these tabs as secondary local tasks, or it can be called from elsewhere if
1460
 * having them as secondary local tasks isn't desired. The caller is responsible
1461
 * for setting the active tab's #active property to TRUE.
1462
 *
1463
 * @param view $view
1464
 *    The view which will be edited.
1465
 * @param $display_id
1466
 *    The display_id which is edited on the current request.
1467
 */
1468
function views_ui_edit_page_display_tabs($view, $display_id = NULL) {
1469
  $tabs = array();
1470

    
1471
  // Create a tab for each display.
1472
  foreach ($view->display as $id => $display) {
1473
    $tabs[$id] = array(
1474
      '#theme' => 'menu_local_task',
1475
      '#link' => array(
1476
        'title' => views_ui_get_display_label($view, $id),
1477
        'href' => 'admin/structure/views/view/' . $view->name . '/edit/' . $id,
1478
        'localized_options' => array(),
1479
      ),
1480
    );
1481
    if (!empty($display->deleted)) {
1482
      $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-deleted-link';
1483
    }
1484
    if (isset($display->display_options['enabled']) && !$display->display_options['enabled']) {
1485
      $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-disabled-link';
1486
    }
1487
  }
1488

    
1489
  // If the default display isn't supposed to be shown, don't display its tab, unless it's the only display.
1490
  if ((!views_ui_show_default_display($view) && $display_id != 'default') && count($tabs) > 1) {
1491
    $tabs['default']['#access'] = FALSE;
1492
  }
1493

    
1494
  // Mark the display tab as red to show validation errors.
1495
  $view->validate();
1496
  foreach ($view->display as $id => $display) {
1497
    if (!empty($view->display_errors[$id])) {
1498
      // Always show the tab.
1499
      $tabs[$id]['#access'] = TRUE;
1500
      // Add a class to mark the error and a title to make a hover tip.
1501
      $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'error';
1502
      $tabs[$id]['#link']['localized_options']['attributes']['title'] = t('This display has one or more validation errors; please review it.');
1503
    }
1504
  }
1505

    
1506
  return $tabs;
1507
}
1508

    
1509
/**
1510
 * Controls whether or not the default display should have its own tab on edit.
1511
 */
1512
function views_ui_show_default_display($view) {
1513
  // Always show the default display for advanced users who prefer that mode.
1514
  $advanced_mode = variable_get('views_ui_show_master_display', FALSE);
1515
  // For other users, show the default display only if there are no others, and
1516
  // hide it if there's at least one "real" display.
1517
  $additional_displays = (count($view->display) == 1);
1518

    
1519
  return $advanced_mode || $additional_displays;
1520
}
1521

    
1522
/**
1523
 * Returns a renderable array representing the edit page for one display.
1524
 */
1525
function views_ui_get_display_tab($view, $display_id) {
1526
  $build = array();
1527
  $display = $view->display[$display_id];
1528
  // If the plugin doesn't exist, display an error message instead of an edit
1529
  // page.
1530
  if (empty($display->handler)) {
1531
    $title = isset($display->display_title) ? $display->display_title : t('Invalid');
1532
    // @TODO: Improved UX for the case where a plugin is missing.
1533
    $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));
1534
  }
1535
  // Build the content of the edit page.
1536
  else {
1537
    $build['details'] = views_ui_get_display_tab_details($view, $display);
1538
  }
1539
  // In AJAX context, views_ui_regenerate_tab() returns this outside of form
1540
  // context, so hook_form_views_ui_edit_form_alter() is insufficient.
1541
  drupal_alter('views_ui_display_tab', $build, $view, $display_id);
1542
  return $build;
1543
}
1544

    
1545
/**
1546
 * Helper function to get the display details section of the edit UI.
1547
 *
1548
 * @param $view
1549
 * @param $display
1550
 *
1551
 * @return array
1552
 *   A renderable page build array.
1553
 */
1554
function views_ui_get_display_tab_details($view, $display) {
1555
  $display_title = views_ui_get_display_label($view, $display->id, FALSE);
1556
  $build = array(
1557
    '#theme_wrappers' => array('container'),
1558
    '#attributes' => array('id' => 'edit-display-settings-details',),
1559
  );
1560

    
1561
  $plugin = views_fetch_plugin_data('display', $view->display[$display->id]->display_plugin);
1562
  // The following is for display purposes only. We need to determine if there is more than one button and wrap
1563
  // the buttons in a .ctools-dropbutton class if more than one is present.  Otherwise, we'll just wrap the
1564
  // actions in the .ctools-button class.
1565
  $isDisplayDeleted = !empty($display->deleted);
1566
  $isDeletable = empty($plugin['no remove']);
1567
  // The master display cannot be cloned.
1568
  $isDefault = $display->id == 'default';
1569
  // @todo: Figure out why get_option doesn't work here.
1570
  $isEnabled = $display->handler->get_option('enabled');
1571

    
1572
  if (!$isDisplayDeleted && $isDeletable && !$isDefault) {
1573
    $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">';
1574
    $suffix = '</ul></div></div>';
1575
    $itemElement = 'li';
1576
  }
1577
  else {
1578
    $prefix = '<div class="ctools-button"><div class="ctools-content"><ul class="horizontal right actions">';
1579
    $suffix = '</ul></div></div>';
1580
    $itemElement = 'li';
1581
  }
1582

    
1583
  if ($display->id != 'default') {
1584
    $build['top']['#theme_wrappers'] = array('container');
1585
    $build['top']['#attributes']['id'] = 'edit-display-settings-top';
1586
    $build['top']['#attributes']['class'] = array('views-ui-display-tab-actions', 'views-ui-display-tab-bucket', 'clearfix');
1587

    
1588
    // The Delete, Duplicate and Undo Delete buttons.
1589
    $build['top']['actions'] = array(
1590
      '#prefix' => $prefix,
1591
      '#suffix' => $suffix,
1592
    );
1593

    
1594
    if (!$isDisplayDeleted) {
1595
      if (!$isEnabled) {
1596
        $build['top']['actions']['enable'] = array(
1597
          '#type' => 'submit',
1598
          '#value' => t('enable @display_title', array('@display_title' => $display_title)),
1599
          '#limit_validation_errors' => array(),
1600
          '#submit' => array('views_ui_edit_form_submit_enable_display', 'views_ui_edit_form_submit_delay_destination'),
1601
          '#prefix' => '<' . $itemElement . ' class="enable">',
1602
          "#suffix" => '</' . $itemElement . '>',
1603
        );
1604
      }
1605
      // Add a link to view the page.
1606
      elseif ($display->handler->has_path()) {
1607
        $path = $display->handler->get_path();
1608
        if (strpos($path, '%') === FALSE) {
1609
          $build['top']['actions']['path'] = array(
1610
            '#type' => 'link',
1611
            '#title' => t('view @display', array('@display' => $display->display_title)),
1612
            '#options' => array('alt' => array(t("Go to the real page for this display"))),
1613
            '#href' => $path,
1614
            '#prefix' => '<' . $itemElement . ' class="view">',
1615
            "#suffix" => '</' . $itemElement . '>',
1616
          );
1617
        }
1618
      }
1619
      if (!$isDefault) {
1620
        $build['top']['actions']['duplicate'] = array(
1621
          '#type' => 'submit',
1622
          '#value' => t('clone @display_title', array('@display_title' => $display_title)),
1623
          '#limit_validation_errors' => array(),
1624
          '#submit' => array('views_ui_edit_form_submit_duplicate_display', 'views_ui_edit_form_submit_delay_destination'),
1625
          '#prefix' => '<' . $itemElement . ' class="duplicate">',
1626
          "#suffix" => '</' . $itemElement . '>',
1627
        );
1628
      }
1629
      if ($isDeletable) {
1630
        $build['top']['actions']['delete'] = array(
1631
          '#type' => 'submit',
1632
          '#value' => t('delete @display_title', array('@display_title' => $display_title)),
1633
          '#limit_validation_errors' => array(),
1634
          '#submit' => array('views_ui_edit_form_submit_delete_display', 'views_ui_edit_form_submit_delay_destination'),
1635
          '#prefix' => '<' . $itemElement . ' class="delete">',
1636
          "#suffix" => '</' . $itemElement . '>',
1637
        );
1638
      }
1639
      if ($isEnabled) {
1640
        $build['top']['actions']['disable'] = array(
1641
          '#type' => 'submit',
1642
          '#value' => t('disable @display_title', array('@display_title' => $display_title)),
1643
          '#limit_validation_errors' => array(),
1644
          '#submit' => array('views_ui_edit_form_submit_disable_display', 'views_ui_edit_form_submit_delay_destination'),
1645
          '#prefix' => '<' . $itemElement . ' class="disable">',
1646
          "#suffix" => '</' . $itemElement . '>',
1647
        );
1648
      }
1649
    }
1650
    else {
1651
      $build['top']['actions']['undo_delete'] = array(
1652
        '#type' => 'submit',
1653
        '#value' => t('undo delete of @display_title', array('@display_title' => $display_title)),
1654
        '#limit_validation_errors' => array(),
1655
        '#submit' => array('views_ui_edit_form_submit_undo_delete_display', 'views_ui_edit_form_submit_delay_destination'),
1656
        '#prefix' => '<' . $itemElement . ' class="undo-delete">',
1657
        "#suffix" => '</' . $itemElement . '>',
1658
      );
1659
    }
1660

    
1661
    // The area above the three columns.
1662
    $build['top']['display_title'] = array(
1663
      '#theme' => 'views_ui_display_tab_setting',
1664
      '#description' => t('Display name'),
1665
      '#link' => $display->handler->option_link(check_plain($display_title), 'display_title'),
1666
    );
1667
  }
1668

    
1669
  $build['columns'] = array();
1670
  $build['columns']['#theme_wrappers'] = array('container');
1671
  $build['columns']['#attributes'] = array('id' => 'edit-display-settings-main', 'class' => array('clearfix', 'views-display-columns'),);
1672

    
1673
  $build['columns']['first']['#theme_wrappers'] = array('container');
1674
  $build['columns']['first']['#attributes'] = array('class' => array('views-display-column', 'first'));
1675

    
1676
  $build['columns']['second']['#theme_wrappers'] = array('container');
1677
  $build['columns']['second']['#attributes'] = array('class' => array('views-display-column', 'second'));
1678

    
1679
  $build['columns']['second']['settings'] = array();
1680
  $build['columns']['second']['header'] = array();
1681
  $build['columns']['second']['footer'] = array();
1682
  $build['columns']['second']['pager'] = array();
1683

    
1684
  // The third column buckets are wrapped in a CTools collapsible div
1685
  $build['columns']['third']['#theme_wrappers'] = array('container');
1686
  $build['columns']['third']['#attributes'] = array('class' => array('views-display-column', 'third', 'ctools-collapsible-container', 'ctools-collapsible-remember'));
1687
  // Specify an id that won't change after AJAX requests, so ctools can keep
1688
  // track of the user's preferred collapsible state. Use the same id across
1689
  // different displays of the same view, so changing displays doesn't
1690
  // recollapse the column.
1691
  $build['columns']['third']['#attributes']['id'] = 'views-ui-advanced-column-' . $view->name;
1692
  // Collapse the div by default.
1693
  if (!variable_get('views_ui_show_advanced_column', FALSE)) {
1694
    $build['columns']['third']['#attributes']['class'][] = 'ctools-collapsed';
1695
  }
1696
  $build['columns']['third']['advanced'] = array('#markup' => '<h3 class="ctools-collapsible-handle"><a href="">' . t('Advanced') . '</a></h3>',);
1697
  $build['columns']['third']['collapse']['#theme_wrappers'] = array('container');
1698
  $build['columns']['third']['collapse']['#attributes'] = array('class' => array('ctools-collapsible-content',),);
1699

    
1700
  // Each option (e.g. title, access, display as grid/table/list) fits into one
1701
  // of several "buckets," or boxes (Format, Fields, Sort, and so on).
1702
  $buckets = array();
1703

    
1704
  // Fetch options from the display plugin, with a list of buckets they go into.
1705
  $options = array();
1706
  $display->handler->options_summary($buckets, $options);
1707

    
1708
  // Place each option into its bucket.
1709
  foreach ($options as $id => $option) {
1710
    // Each option self-identifies as belonging in a particular bucket.
1711
    $buckets[$option['category']]['build'][$id] = views_ui_edit_form_get_build_from_option($id, $option, $view, $display);
1712
  }
1713

    
1714
  // Place each bucket into the proper column.
1715
  foreach ($buckets as $id => $bucket) {
1716
    // Let buckets identify themselves as belonging in a column.
1717
    if (isset($bucket['column']) && isset($build['columns'][$bucket['column']])) {
1718
      $column = $bucket['column'];
1719
    }
1720
    // If a bucket doesn't pick one of our predefined columns to belong to, put
1721
    // it in the last one.
1722
    else {
1723
      $column = 'third';
1724
    }
1725
    if (isset($bucket['build']) && is_array($bucket['build'])) {
1726
      // The third column is a CTools collapsible div, so
1727
      // the structure of the form is a little different for this column
1728
      if ($column === 'third') {
1729
        $build['columns'][$column]['collapse'][$id] = $bucket['build'];
1730
        $build['columns'][$column]['collapse'][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
1731
        $build['columns'][$column]['collapse'][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
1732
        $build['columns'][$column]['collapse'][$id]['#name'] = !empty($bucket['title']) ? $bucket['title'] : $id;
1733
      }
1734
      else {
1735
        $build['columns'][$column][$id] = $bucket['build'];
1736
        $build['columns'][$column][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
1737
        $build['columns'][$column][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
1738
        $build['columns'][$column][$id]['#name'] = !empty($bucket['title']) ? $bucket['title'] : $id;
1739
      }
1740
    }
1741
  }
1742

    
1743
  $build['columns']['first']['fields'] = views_ui_edit_form_get_bucket('field', $view, $display);
1744
  $build['columns']['first']['filters'] = views_ui_edit_form_get_bucket('filter', $view, $display);
1745
  $build['columns']['first']['sorts'] = views_ui_edit_form_get_bucket('sort', $view, $display);
1746
  $build['columns']['second']['header'] = views_ui_edit_form_get_bucket('header', $view, $display);
1747
  $build['columns']['second']['footer'] = views_ui_edit_form_get_bucket('footer', $view, $display);
1748
  $build['columns']['third']['collapse']['arguments'] = views_ui_edit_form_get_bucket('argument', $view, $display);
1749
  $build['columns']['third']['collapse']['relationships'] = views_ui_edit_form_get_bucket('relationship', $view, $display);
1750
  $build['columns']['third']['collapse']['empty'] = views_ui_edit_form_get_bucket('empty', $view, $display);
1751

    
1752
  return $build;
1753
}
1754

    
1755
/**
1756
 * Build a renderable array representing one option on the edit form.
1757
 *
1758
 * This function might be more logical as a method on an object, if a suitable
1759
 * object emerges out of refactoring.
1760
 */
1761
function views_ui_edit_form_get_build_from_option($id, $option, $view, $display) {
1762
  $option_build = array();
1763
  $option_build['#theme'] = 'views_ui_display_tab_setting';
1764

    
1765
  $option_build['#description'] = $option['title'];
1766

    
1767
  $option_build['#link'] = $display->handler->option_link($option['value'], $id, '', empty($option['desc']) ? '' : $option['desc']);
1768

    
1769
  $option_build['#links'] = array();
1770
  if (!empty($option['links']) && is_array($option['links'])) {
1771
    foreach ($option['links'] as $link_id => $link_value) {
1772
      $option_build['#settings_links'][] = $display->handler->option_link($option['setting'], $link_id, 'views-button-configure', $link_value);
1773
    }
1774
  }
1775

    
1776
  if (!empty($display->handler->options['defaults'][$id])) {
1777
    $display_id = 'default';
1778
    $option_build['#defaulted'] = TRUE;
1779
  }
1780
  else {
1781
    $display_id = $display->id;
1782
    if (!$display->handler->is_default_display()) {
1783
      if ($display->handler->defaultable_sections($id)) {
1784
        $option_build['#overridden'] = TRUE;
1785
      }
1786
    }
1787
  }
1788
  $option_build['#attributes']['class'][] = drupal_clean_css_identifier($display_id . '-' . $id);
1789
  if (!empty($view->changed_sections[$display_id . '-' . $id])) {
1790
    $option_build['#changed'] = TRUE;
1791
  }
1792
  return $option_build;
1793
}
1794

    
1795
function template_preprocess_views_ui_display_tab_setting(&$variables) {
1796
  static $zebra = 0;
1797
  $variables['zebra'] = ($zebra % 2 === 0 ? 'odd' : 'even');
1798
  $zebra++;
1799

    
1800
  // Put the main link to the left side
1801
  array_unshift($variables['settings_links'], $variables['link']);
1802
  $variables['settings_links'] = implode('<span class="label">&nbsp;|&nbsp;</span>', $variables['settings_links']);
1803

    
1804
  // Add classes associated with this display tab to the overall list.
1805
  $variables['classes_array'] = array_merge($variables['classes_array'], $variables['class']);
1806

    
1807
  if (!empty($variables['defaulted'])) {
1808
    $variables['classes_array'][] = 'defaulted';
1809
  }
1810
  if (!empty($variables['overridden'])) {
1811
    $variables['classes_array'][] = 'overridden';
1812
    $variables['attributes_array']['title'][] = t('Overridden');
1813
  }
1814

    
1815
  // Append a colon to the description, if requested.
1816
  if ($variables['description'] && $variables['description_separator']) {
1817
    $variables['description'] .= t(':');
1818
  }
1819
}
1820

    
1821
function template_preprocess_views_ui_display_tab_bucket(&$variables) {
1822
  $element = $variables['element'];
1823

    
1824
  $variables['item_help_icon'] = '';
1825
  if (!empty($element['#item_help_icon'])) {
1826
    $variables['item_help_icon'] = render($element['#item_help_icon']);
1827
  }
1828
  if (!empty($element['#name'])) {
1829
    $variables['classes_array'][] = drupal_clean_css_identifier(strtolower($element['#name']));
1830
  }
1831
  if (!empty($element['#overridden'])) {
1832
    $variables['classes_array'][] = 'overridden';
1833
    $variables['attributes_array']['title'][] = t('Overridden');
1834
  }
1835

    
1836
  $variables['content'] = $element['#children'];
1837
  $variables['title'] = $element['#title'];
1838
  $variables['actions'] = !empty($element['#actions']) ? $element['#actions'] : '';
1839
}
1840

    
1841
function template_preprocess_views_ui_display_tab_column(&$variables) {
1842
  $element = $variables['element'];
1843

    
1844
  $variables['content'] = $element['#children'];
1845
  $variables['column'] = $element['#column'];
1846
}
1847

    
1848
/**
1849
 * Move form elements into fieldsets for presentation purposes.
1850
 *
1851
 * Many views forms use #tree = TRUE to keep their values in a hierarchy for
1852
 * easier storage. Moving the form elements into fieldsets during form building
1853
 * would break up that hierarchy. Therefore, we wait until the pre_render stage,
1854
 * where any changes we make affect presentation only and aren't reflected in
1855
 * $form_state['values'].
1856
 */
1857
function views_ui_pre_render_add_fieldset_markup($form) {
1858
  foreach (element_children($form) as $key) {
1859
    $element = $form[$key];
1860
    // In our form builder functions, we added an arbitrary #fieldset property
1861
    // to any element that belongs in a fieldset. If this form element has that
1862
    // property, move it into its fieldset.
1863
    if (isset($element['#fieldset']) && isset($form[$element['#fieldset']])) {
1864
      $form[$element['#fieldset']][$key] = $element;
1865
      // Remove the original element this duplicates.
1866
      unset($form[$key]);
1867
    }
1868
  }
1869

    
1870
  return $form;
1871
}
1872

    
1873
/**
1874
 * Flattens the structure of an element containing the #flatten property.
1875
 *
1876
 * If a form element has #flatten = TRUE, then all of it's children
1877
 * get moved to the same level as the element itself.
1878
 * So $form['to_be_flattened'][$key] becomes $form[$key], and
1879
 * $form['to_be_flattened'] gets unset.
1880
 */
1881
function views_ui_pre_render_flatten_data($form) {
1882
  foreach (element_children($form) as $key) {
1883
    $element = $form[$key];
1884
    if (!empty($element['#flatten'])) {
1885
      foreach (element_children($element) as $child_key) {
1886
        $form[$child_key] = $form[$key][$child_key];
1887
      }
1888
      // All done, remove the now-empty parent.
1889
      unset($form[$key]);
1890
    }
1891
  }
1892

    
1893
  return $form;
1894
}
1895

    
1896
/**
1897
 * Moves argument options into their place.
1898
 *
1899
 * When configuring the default argument behavior, almost each of the radio
1900
 * buttons has its own fieldset shown bellow it when the radio button is
1901
 * clicked. That fieldset is created through a custom form process callback.
1902
 * Each element that has #argument_option defined and pointing to a default
1903
 * behavior gets moved to the appropriate fieldset.
1904
 * So if #argument_option is specified as 'default', the element is moved
1905
 * to the 'default_options' fieldset.
1906
 */
1907
function views_ui_pre_render_move_argument_options($form) {
1908
  foreach (element_children($form) as $key) {
1909
    $element = $form[$key];
1910
    if (!empty($element['#argument_option'])) {
1911
      $container_name = $element['#argument_option'] . '_options';
1912
      if (isset($form['no_argument']['default_action'][$container_name])) {
1913
        $form['no_argument']['default_action'][$container_name][$key] = $element;
1914
      }
1915
      // Remove the original element this duplicates.
1916
      unset($form[$key]);
1917
    }
1918
  }
1919
  return $form;
1920
}
1921

    
1922
/**
1923
 * Custom form radios process function.
1924
 *
1925
 * Roll out a single radios element to a list of radios,
1926
 * using the options array as index.
1927
 * While doing that, create a container element underneath each option, which
1928
 * contains the settings related to that option.
1929
 *
1930
 * @see form_process_radios()
1931
 */
1932
function views_ui_process_container_radios($element) {
1933
  if (count($element['#options']) > 0) {
1934
    foreach ($element['#options'] as $key => $choice) {
1935
      $element += array($key => array());
1936
      // Generate the parents as the autogenerator does, so we will have a
1937
      // unique id for each radio button.
1938
      $parents_for_id = array_merge($element['#parents'], array($key));
1939

    
1940
      $element[$key] += array(
1941
        '#type' => 'radio',
1942
        '#title' => $choice,
1943
        // The key is sanitized in drupal_attributes() during output from the
1944
        // theme function.
1945
        '#return_value' => $key,
1946
        '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
1947
        '#attributes' => $element['#attributes'],
1948
        '#parents' => $element['#parents'],
1949
        '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
1950
        '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
1951
      );
1952
      $element[$key . '_options'] = array(
1953
        '#type' => 'container',
1954
        '#attributes' => array('class' => array('views-admin-dependent')),
1955
      );
1956
    }
1957
  }
1958
  return $element;
1959
}
1960

    
1961
/**
1962
 * Import a view from cut & paste.
1963
 */
1964
function views_ui_import_page($form, &$form_state) {
1965
  $form['name'] = array(
1966
    '#type' => 'textfield',
1967
    '#title' => t('View name'),
1968
    '#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.'),
1969
  );
1970

    
1971
  $form['name_override'] = array(
1972
    '#type' => 'checkbox',
1973
    '#title' => t('Replace an existing view if one exists with the same name'),
1974
  );
1975

    
1976
  $form['bypass_validation'] = array(
1977
    '#type' => 'checkbox',
1978
    '#title' => t('Bypass view validation'),
1979
    '#description' => t('Bypass the validation of plugins and handlers when importing this view.'),
1980
  );
1981

    
1982
  $form['view'] = array(
1983
    '#type' => 'textarea',
1984
    '#title' => t('Paste view code here'),
1985
    '#required' => TRUE,
1986
  );
1987

    
1988
  $form['submit'] = array(
1989
    '#type' => 'submit',
1990
    '#value' => t('Import'),
1991
    '#submit' => array('views_ui_import_submit'),
1992
    '#validate' => array('views_ui_import_validate'),
1993
  );
1994
  return $form;
1995
}
1996

    
1997
/**
1998
 * Validate handler to import a view.
1999
 */
2000
function views_ui_import_validate($form, &$form_state) {
2001
  $view = '';
2002
  views_include('view');
2003
  // Be forgiving if someone pastes views code that starts with '<?php'.
2004
  if (substr($form_state['values']['view'], 0, 5) == '<?php') {
2005
    $form_state['values']['view'] = substr($form_state['values']['view'], 5);
2006
  }
2007
  ob_start();
2008
  eval($form_state['values']['view']);
2009
  ob_end_clean();
2010

    
2011
  if (!is_object($view)) {
2012
    return form_error($form['view'], t('Unable to interpret view code.'));
2013
  }
2014

    
2015
  if (empty($view->api_version) || $view->api_version < 2) {
2016
    form_error($form['view'], t('That view is not compatible with this version of Views.
2017
      If you have a view from views1 you have to go to a drupal6 installation and import it there.'));
2018
  }
2019
  elseif (version_compare($view->api_version, views_api_version(), '>')) {
2020
    form_error($form['view'], t('That view is created for the version @import_version of views, but you only have @api_version', array(
2021
      '@import_version' => $view->api_version,
2022
      '@api_version' => views_api_version())));
2023
  }
2024

    
2025
  // View name must be alphanumeric or underscores, no other punctuation.
2026
  if (!empty($form_state['values']['name']) && preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
2027
    form_error($form['name'], t('View name must be alphanumeric or underscores only.'));
2028
  }
2029

    
2030
  if ($form_state['values']['name']) {
2031
    $view->name = $form_state['values']['name'];
2032
  }
2033

    
2034
  $test = views_get_view($view->name);
2035
  if (!$form_state['values']['name_override']) {
2036
    if ($test && $test->type != t('Default')) {
2037
      form_set_error('', t('A view by that name already exists; please choose a different name'));
2038
    }
2039
  }
2040
  else {
2041
    if ($test->vid) {
2042
      $view->vid = $test->vid;
2043
    }
2044
  }
2045

    
2046
  // Make sure base table gets set properly if it got moved.
2047
  $view->update();
2048

    
2049
  $view->init_display();
2050

    
2051
  $broken = FALSE;
2052

    
2053
  // Bypass the validation of view pluigns/handlers if option is checked.
2054
  if (!$form_state['values']['bypass_validation']) {
2055
    // Make sure that all plugins and handlers needed by this view actually exist.
2056
    foreach ($view->display as $id => $display) {
2057
      if (empty($display->handler) || !empty($display->handler->broken)) {
2058
        drupal_set_message(t('Display plugin @plugin is not available.', array('@plugin' => $display->display_plugin)), 'error');
2059
        $broken = TRUE;
2060
        continue;
2061
      }
2062

    
2063
      $plugin = views_get_plugin('style', $display->handler->get_option('style_plugin'));
2064
      if (!$plugin) {
2065
        drupal_set_message(t('Style plugin @plugin is not available.', array('@plugin' => $display->handler->get_option('style_plugin'))), 'error');
2066
        $broken = TRUE;
2067
      }
2068
      elseif ($plugin->uses_row_plugin()) {
2069
        $plugin = views_get_plugin('row', $display->handler->get_option('row_plugin'));
2070
        if (!$plugin) {
2071
          drupal_set_message(t('Row plugin @plugin is not available.', array('@plugin' => $display->handler->get_option('row_plugin'))), 'error');
2072
          $broken = TRUE;
2073
        }
2074
      }
2075

    
2076
      foreach (views_object_types() as $type => $info) {
2077
        $handlers = $display->handler->get_handlers($type);
2078
        if ($handlers) {
2079
          foreach ($handlers as $id => $handler) {
2080
            if ($handler->broken()) {
2081
              drupal_set_message(t('@type handler @table.@field is not available.', array(
2082
                '@type' => $info['stitle'],
2083
                '@table' => $handler->table,
2084
                '@field' => $handler->field,
2085
              )), 'error');
2086
              $broken = TRUE;
2087
            }
2088
          }
2089
        }
2090
      }
2091
    }
2092
  }
2093

    
2094
  if ($broken) {
2095
    form_set_error('', t('Unable to import view.'));
2096
  }
2097

    
2098
  $form_state['view'] = &$view;
2099
}
2100

    
2101
/**
2102
 * Submit handler for view import.
2103
 */
2104
function views_ui_import_submit($form, &$form_state) {
2105
  // Store in cache and then go to edit.
2106
  views_ui_cache_set($form_state['view']);
2107
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
2108
}
2109

    
2110
/**
2111
 * Validate that a view is complete and whole.
2112
 */
2113
function views_ui_edit_view_form_validate($form, &$form_state) {
2114
  // Do not validate cancel or delete or revert.
2115
  if (empty($form_state['clicked_button']['#value']) || $form_state['clicked_button']['#value'] != t('Save')) {
2116
    return;
2117
  }
2118

    
2119
  $errors = $form_state['view']->validate();
2120
  if ($errors !== TRUE) {
2121
    foreach ($errors as $error) {
2122
      form_set_error('', $error);
2123
    }
2124
  }
2125
}
2126

    
2127
/**
2128
 * Submit handler for the edit view form.
2129
 */
2130
function views_ui_edit_view_form_submit($form, &$form_state) {
2131
  // Go through and remove displayed scheduled for removal.
2132
  foreach ($form_state['view']->display as $id => $display) {
2133
    if (!empty($display->deleted)) {
2134
      unset($form_state['view']->display[$id]);
2135
    }
2136
  }
2137
  // Rename display ids if needed.
2138
  foreach ($form_state['view']->display as $id => $display) {
2139
    if (!empty($display->new_id)) {
2140
      $form_state['view']->display[$id]->id = $display->new_id;
2141
      // Redirect the user to the renamed display to be sure that the page itself exists and doesn't throw errors.
2142
      $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $display->new_id;
2143
    }
2144
  }
2145

    
2146
  // Direct the user to the right url, if the path of the display has changed.
2147
  if (!empty($_GET['destination'])) {
2148
    $destination = $_GET['destination'];
2149
    // Find out the first display which has a changed path and redirect to this url.
2150
    $old_view = views_get_view($form_state['view']->name);
2151
    foreach ($old_view->display as $id => $display) {
2152
      // Only check for displays with a path.
2153
      if (!isset($display->display_options['path'])) {
2154
        continue;
2155
      }
2156
      $old_path = $display->display_options['path'];
2157
      if (($display->display_plugin == 'page') && ($old_path == $destination) && ($old_path != $form_state['view']->display[$id]->display_options['path'])) {
2158
        $destination = $form_state['view']->display[$id]->display_options['path'];
2159
        unset($_GET['destination']);
2160
      }
2161
    }
2162
    $form_state['redirect'] = $destination;
2163
  }
2164

    
2165
  $form_state['view']->save();
2166
  drupal_set_message(t('The view %name has been saved.', array('%name' => $form_state['view']->get_human_name())));
2167

    
2168
  // Remove this view from cache so we can edit it properly.
2169
  ctools_object_cache_clear('view', $form_state['view']->name);
2170
}
2171

    
2172
/**
2173
 * Submit handler for the edit view form.
2174
 */
2175
function views_ui_edit_view_form_cancel($form, &$form_state) {
2176
  // Remove this view from cache so edits will be lost.
2177
  ctools_object_cache_clear('view', $form_state['view']->name);
2178
  if (empty($form['view']->vid)) {
2179
    // I seem to have to drupal_goto here because I can't get fapi to
2180
    // honor the redirect target. Not sure what I screwed up here.
2181
    drupal_goto('admin/structure/views');
2182
  }
2183
}
2184

    
2185
function views_ui_edit_view_form_delete($form, &$form_state) {
2186
  unset($_REQUEST['destination']);
2187
  // Redirect to the delete confirm page
2188
  $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')));
2189
}
2190

    
2191
/**
2192
 * Add information about a section to a display.
2193
 */
2194
function views_ui_edit_form_get_bucket($type, $view, $display) {
2195
  $build = array(
2196
    '#theme_wrappers' => array('views_ui_display_tab_bucket'),
2197
  );
2198
  $types = views_object_types();
2199

    
2200
  $build['#overridden'] = FALSE;
2201
  $build['#defaulted'] = FALSE;
2202
  if (module_exists('advanced_help')) {
2203
    $build['#item_help_icon'] = array(
2204
      '#theme' => 'advanced_help_topic',
2205
      '#module' => 'views',
2206
      '#topic' => $type,
2207
    );
2208
  }
2209

    
2210
  $build['#name'] = $build['#title'] = $types[$type]['title'];
2211

    
2212
  // Different types now have different rearrange forms, so we use this switch
2213
  // to get the right one.
2214
  switch ($type) {
2215
    case 'filter':
2216
      $rearrange_url = "admin/structure/views/nojs/rearrange-$type/$view->name/$display->id/$type";
2217
      $rearrange_text = t('And/Or, Rearrange');
2218
      // TODO: Add another class to have another symbol for filter rearrange.
2219
      $class = 'icon compact rearrange';
2220
      break;
2221
    case 'field':
2222
      // Fetch the style plugin info so we know whether to list fields or not.
2223
      $style_plugin = $display->handler->get_plugin();
2224
      $uses_fields = $style_plugin && $style_plugin->uses_fields();
2225
      if (!$uses_fields) {
2226
        $build['fields'][] = array(
2227
          '#markup' => t('The selected style or row format does not utilize fields.'),
2228
          '#theme_wrappers' => array('views_container'),
2229
          '#attributes' => array('class' => array('views-display-setting')),
2230
        );
2231
        return $build;
2232
      }
2233

    
2234
    default:
2235
      $rearrange_url = "admin/structure/views/nojs/rearrange/$view->name/$display->id/$type";
2236
      $rearrange_text = t('Rearrange');
2237
      $class = 'icon compact rearrange';
2238
  }
2239

    
2240
  // Create an array of actions to pass to theme_links
2241
  $actions = array();
2242
  $count_handlers = count($display->handler->get_handlers($type));
2243
  $actions['add'] = array(
2244
    'title' => t('Add'),
2245
    'href' => "admin/structure/views/nojs/add-item/$view->name/$display->id/$type",
2246
    'attributes'=> array('class' => array('icon compact add', 'views-ajax-link'), 'title' => t('Add'), 'id' => 'views-add-' . $type),
2247
    'html' => TRUE,
2248
  );
2249
  if ($count_handlers > 0) {
2250
    $actions['rearrange'] = array(
2251
      'title' => $rearrange_text,
2252
      'href' => $rearrange_url,
2253
      'attributes' => array('class' => array($class, 'views-ajax-link'), 'title' => t('Rearrange'), 'id' => 'views-rearrange-' . $type),
2254
      'html' => TRUE,
2255
    );
2256
  }
2257

    
2258
  // Render the array of links
2259
  $build['#actions'] = theme('links__ctools_dropbutton',
2260
    array(
2261
      'links' => $actions,
2262
      'attributes' => array(
2263
        'class' => array('inline', 'links', 'actions', 'horizontal', 'right')
2264
      ),
2265
      'class' => array('views-ui-settings-bucket-operations'),
2266
    )
2267
  );
2268

    
2269
  if (!$display->handler->is_default_display()) {
2270
    if (!$display->handler->is_defaulted($types[$type]['plural'])) {
2271
      $build['#overridden'] = TRUE;
2272
    }
2273
    else {
2274
      $build['#defaulted'] = TRUE;
2275
    }
2276
  }
2277

    
2278
  // If there's an options form for the bucket, link to it.
2279
  if (!empty($types[$type]['options'])) {
2280
    $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)));
2281
  }
2282

    
2283
  static $relationships = NULL;
2284
  if (!isset($relationships)) {
2285
    // Get relationship labels
2286
    $relationships = array();
2287
    // @todo: get_handlers()
2288
    $handlers = $display->handler->get_option('relationships');
2289
    if ($handlers) {
2290
      foreach ($handlers as $id => $info) {
2291
        $handler = $display->handler->get_handler('relationship', $id);
2292
        $relationships[$id] = $handler->label();
2293
      }
2294
    }
2295
  }
2296

    
2297
  // Filters can now be grouped so we do a little bit extra:
2298
  $groups = array();
2299
  $grouping = FALSE;
2300
  if ($type == 'filter') {
2301
    $group_info = $view->display_handler->get_option('filter_groups');
2302
    // If there is only one group but it is using the "OR" filter, we still
2303
    // treat it as a group for display purposes, since we want to display the
2304
    // "OR" label next to items within the group.
2305
    if (!empty($group_info['groups']) && (count($group_info['groups']) > 1 || current($group_info['groups']) == 'OR')) {
2306
      $grouping = TRUE;
2307
      $groups = array(0 => array());
2308
    }
2309
  }
2310

    
2311
  $build['fields'] = array();
2312

    
2313
  foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
2314
    // Build the option link for this handler ("Node: ID = article").
2315
    $build['fields'][$id] = array();
2316
    $build['fields'][$id]['#theme'] = 'views_ui_display_tab_setting';
2317

    
2318
    $handler = $display->handler->get_handler($type, $id);
2319
    if (empty($handler)) {
2320
      $build['fields'][$id]['#class'][] = 'broken';
2321
      $field_name = t('Broken/missing handler: @table > @field', array('@table' => $field['table'], '@field' => $field['field']));
2322
      $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));
2323
      continue;
2324
    }
2325

    
2326
    $field_name = check_plain($handler->ui_name(TRUE));
2327
    if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
2328
      $field_name = '(' . $relationships[$field['relationship']] . ') ' . $field_name;
2329
    }
2330

    
2331
    $description = filter_xss_admin($handler->admin_summary());
2332
    $link_text = $field_name . (empty($description) ? '' : " ($description)");
2333
    $link_attributes = array('class' => array('views-ajax-link'));
2334
    if (!empty($field['exclude'])) {
2335
      $link_attributes['class'][] = 'views-field-excluded';
2336
    }
2337
    $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));
2338
    $build['fields'][$id]['#class'][] = drupal_clean_css_identifier($display->id . '-' . $type . '-' . $id);
2339
    if (!empty($view->changed_sections[$display->id . '-' . $type . '-' . $id])) {
2340
      // @TODO: #changed is no longer being used?
2341
      $build['fields'][$id]['#changed'] = TRUE;
2342
    }
2343

    
2344
    if ($display->handler->use_group_by() && $handler->use_group_by()) {
2345
      $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));
2346
    }
2347

    
2348
    if ($handler->has_extra_options()) {
2349
      $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));
2350
    }
2351

    
2352
    if ($grouping) {
2353
      $gid = $handler->options['group'];
2354

    
2355
      // Show in default group if the group does not exist.
2356
      if (empty($group_info['groups'][$gid])) {
2357
        $gid = 0;
2358
      }
2359
      $groups[$gid][] = $id;
2360
    }
2361
  }
2362

    
2363
  // If using grouping, re-order fields so that they show up properly in the list.
2364
  if ($type == 'filter' && $grouping) {
2365
    $store = $build['fields'];
2366
    $build['fields'] = array();
2367
    foreach ($groups as $gid => $contents) {
2368
      // Display an operator between each group.
2369
      if (!empty($build['fields'])) {
2370
        $build['fields'][] = array(
2371
          '#theme' => 'views_ui_display_tab_setting',
2372
          '#class' => array('views-group-text'),
2373
          '#link' => ($group_info['operator'] == 'OR' ? t('OR') : t('AND')),
2374
        );
2375
      }
2376
      // Display an operator between each pair of filters within the group.
2377
      $keys = array_keys($contents);
2378
      $last = end($keys);
2379
      foreach ($contents as $key => $pid) {
2380
        if ($key != $last) {
2381
          $store[$pid]['#link'] .= '&nbsp;&nbsp;' . ($group_info['groups'][$gid] == 'OR' ? t('OR') : t('AND'));
2382
        }
2383
        $build['fields'][$pid] = $store[$pid];
2384
      }
2385
    }
2386
  }
2387

    
2388
  return $build;
2389
}
2390

    
2391
/**
2392
 * Regenerate the current tab for AJAX updates.
2393
 */
2394
function views_ui_regenerate_tab(&$view, &$output, $display_id) {
2395
  if (!$view->set_display('default')) {
2396
    return;
2397
  }
2398

    
2399
  // Regenerate the main display area.
2400
  $build = views_ui_get_display_tab($view, $display_id);
2401
  views_ui_add_microweights($build);
2402
  $output[] = ajax_command_html('#views-tab-' . $display_id, drupal_render($build));
2403

    
2404
  // Regenerate the top area so changes to display names and order will appear.
2405
  $build = views_ui_render_display_top($view, $display_id);
2406
  views_ui_add_microweights($build);
2407
  $output[] = ajax_command_replace('#views-display-top', drupal_render($build));
2408
}
2409

    
2410
/**
2411
 * Recursively adds microweights to a render array, similar to what form_builder() does for forms.
2412
 *
2413
 * @todo Submit a core patch to fix drupal_render() to do this, so that all
2414
 *   render arrays automatically preserve array insertion order, as forms do.
2415
 */
2416
function views_ui_add_microweights(&$build) {
2417
  $count = 0;
2418
  foreach (element_children($build) as $key) {
2419
    if (!isset($build[$key]['#weight'])) {
2420
      $build[$key]['#weight'] = $count/1000;
2421
    }
2422
    views_ui_add_microweights($build[$key]);
2423
    $count++;
2424
  }
2425
}
2426

    
2427
/**
2428
 * Provide a standard set of Apply/Cancel/OK buttons for the forms. Also provide
2429
 * a hidden op operator because the forms plugin doesn't seem to properly
2430
 * provide which button was clicked.
2431
 *
2432
 * TODO: Is the hidden op operator still here somewhere, or is that part of the
2433
 * docblock outdated?
2434
 */
2435
function views_ui_standard_form_buttons(&$form, &$form_state, $form_id, $name = NULL, $third = NULL, $submit = NULL) {
2436
  $form['buttons'] = array(
2437
    '#prefix' => '<div class="clearfix"><div class="form-buttons">',
2438
    '#suffix' => '</div></div>',
2439
  );
2440

    
2441
  if (empty($name)) {
2442
    $name = t('Apply');
2443
    $view = $form_state['view'];
2444
    if (!empty($view->stack) && count($view->stack) > 1) {
2445
      $name = t('Apply and continue');
2446
    }
2447
    $names = array(t('Apply'), t('Apply and continue'));
2448
  }
2449

    
2450
  // Forms that are purely informational set an ok_button flag, so we know not
2451
  // to create an "Apply" button for them.
2452
  if (empty($form_state['ok_button'])) {
2453
    $form['buttons']['submit'] = array(
2454
      '#type' => 'submit',
2455
      '#value' => $name,
2456
      // The regular submit handler ($form_id . '_submit') does not apply if
2457
      // we're updating the default display. It does apply if we're updating
2458
      // the current display. Since we have no way of knowing at this point
2459
      // which display the user wants to update, views_ui_standard_submit will
2460
      // take care of running the regular submit handler as appropriate.
2461
      '#submit' => array('views_ui_standard_submit'),
2462
    );
2463
    // Form API button click detection requires the button's #value to be the
2464
    // same between the form build of the initial page request, and the initial
2465
    // form build of the request processing the form submission. Ideally, the
2466
    // button's #value shouldn't change until the form rebuild step. However,
2467
    // views_ui_ajax_form() implements a different multistep form workflow than
2468
    // the Form API does, and adjusts $view->stack prior to form processing, so
2469
    // we compensate by extending button click detection code to support any of
2470
    // the possible button labels.
2471
    if (isset($names)) {
2472
      $form['buttons']['submit']['#values'] = $names;
2473
      $form['buttons']['submit']['#process'] = array_merge(array('views_ui_form_button_was_clicked'), element_info_property($form['buttons']['submit']['#type'], '#process', array()));
2474
    }
2475
    // If a validation handler exists for the form, assign it to this button.
2476
    if (function_exists($form_id . '_validate')) {
2477
      $form['buttons']['submit']['#validate'][] = $form_id . '_validate';
2478
    }
2479
  }
2480

    
2481
  // Create a "Cancel" button. For purely informational forms, label it "OK".
2482
  $cancel_submit = function_exists($form_id . '_cancel') ? $form_id . '_cancel' : 'views_ui_standard_cancel';
2483
  $form['buttons']['cancel'] = array(
2484
    '#type' => 'submit',
2485
    '#value' => empty($form_state['ok_button']) ? t('Cancel') : t('Ok'),
2486
    '#submit' => array($cancel_submit),
2487
    '#validate' => array(),
2488
  );
2489

    
2490
  // Some forms specify a third button, with a name and submit handler.
2491
  if ($third) {
2492
    if (empty($submit)) {
2493
      $submit = 'third';
2494
    }
2495
    $third_submit = function_exists($form_id . '_' . $submit) ? $form_id . '_' . $submit : 'views_ui_standard_cancel';
2496

    
2497
    $form['buttons'][$submit] = array(
2498
      '#type' => 'submit',
2499
      '#value' => $third,
2500
      '#validate' => array(),
2501
      '#submit' => array($third_submit),
2502
    );
2503
  }
2504

    
2505
  // Compatibility, to be removed later: // TODO: When is "later"?
2506
  // We used to set these items on the form, but now we want them on the $form_state:
2507
  if (isset($form['#title'])) {
2508
    $form_state['title'] = $form['#title'];
2509
  }
2510
  if (isset($form['#help_topic'])) {
2511
    $form_state['help_topic'] = $form['#help_topic'];
2512
  }
2513
  if (isset($form['#help_module'])) {
2514
    $form_state['help_module'] = $form['#help_module'];
2515
  }
2516
  if (isset($form['#url'])) {
2517
    $form_state['url'] = $form['#url'];
2518
  }
2519
  if (isset($form['#section'])) {
2520
    $form_state['#section'] = $form['#section'];
2521
  }
2522
  // Finally, we never want these cached -- our object cache does that for us.
2523
  $form['#no_cache'] = TRUE;
2524

    
2525
  // If this isn't an ajaxy form, then we want to set the title.
2526
  if (!empty($form['#title'])) {
2527
    drupal_set_title($form['#title']);
2528
  }
2529
}
2530

    
2531
/**
2532
 * Basic submit handler applicable to all 'standard' forms.
2533
 *
2534
 * This submit handler determines whether the user wants the submitted changes
2535
 * to apply to the default display or to the current display, and dispatches
2536
 * control appropriately.
2537
 */
2538
function views_ui_standard_submit($form, &$form_state) {
2539
  // Determine whether the values the user entered are intended to apply to
2540
  // the current display or the default display.
2541

    
2542
  list($was_defaulted, $is_defaulted, $revert) = views_ui_standard_override_values($form, $form_state);
2543

    
2544
  // Mark the changed section of the view as changed.
2545
  // TODO: Document why we are doing this, and see if we still need it.
2546
  if (!empty($form['#section'])) {
2547
    $form_state['view']->changed_sections[$form['#section']] = TRUE;
2548
  }
2549

    
2550
  // Based on the user's choice in the display dropdown, determine which display
2551
  // these changes apply to.
2552
  if ($revert) {
2553
    // If it's revert just change the override and return.
2554
    $display = &$form_state['view']->display[$form_state['display_id']];
2555
    $display->handler->options_override($form, $form_state);
2556

    
2557
    // Don't execute the normal submit handling but still store the changed view into cache.
2558
    views_ui_cache_set($form_state['view']);
2559
    return;
2560
  }
2561
  elseif ($was_defaulted === $is_defaulted) {
2562
    // We're not changing which display these form values apply to.
2563
    // Run the regular submit handler for this form.
2564
  }
2565
  elseif ($was_defaulted && !$is_defaulted) {
2566
    // We were using the default display's values, but we're now overriding
2567
    // the default display and saving values specific to this display.
2568
    $display = &$form_state['view']->display[$form_state['display_id']];
2569
    // options_override toggles the override of this section.
2570
    $display->handler->options_override($form, $form_state);
2571
    $display->handler->options_submit($form, $form_state);
2572
  }
2573
  elseif (!$was_defaulted && $is_defaulted) {
2574
    // We used to have an override for this display, but the user now wants
2575
    // to go back to the default display.
2576
    // Overwrite the default display with the current form values, and make
2577
    // the current display use the new default values.
2578
    $display = &$form_state['view']->display[$form_state['display_id']];
2579
    // options_override toggles the override of this section.
2580
    $display->handler->options_override($form, $form_state);
2581
    $display->handler->options_submit($form, $form_state);
2582
  }
2583

    
2584
  $submit_handler = $form['#form_id'] . '_submit';
2585
  if (function_exists($submit_handler)) {
2586
    $submit_handler($form, $form_state);
2587
  }
2588
}
2589

    
2590
/**
2591
 * Return the was_defaulted, is_defaulted and revert state of a form.
2592
 */
2593
function views_ui_standard_override_values($form, $form_state) {
2594
  // Make sure the dropdown exists in the first place.
2595
  if (isset($form_state['values']['override']['dropdown'])) {
2596
    // #default_value is used to determine whether it was the default value or not.
2597
    // So the available options are: $display, 'default' and 'default_revert', not 'defaults'.
2598
    $was_defaulted = ($form['override']['dropdown']['#default_value'] === 'defaults');
2599
    $is_defaulted = ($form_state['values']['override']['dropdown'] === 'default');
2600
    $revert = ($form_state['values']['override']['dropdown'] === 'default_revert');
2601

    
2602
    if ($was_defaulted !== $is_defaulted && isset($form['#section'])) {
2603
      // We're changing which display these values apply to.
2604
      // Update the #section so it knows what to mark changed.
2605
      $form['#section'] = str_replace('default-', $form_state['display_id'] . '-', $form['#section']);
2606
    }
2607
  }
2608
  else {
2609
    // The user didn't get the dropdown for overriding the default display.
2610
    $was_defaulted = FALSE;
2611
    $is_defaulted = FALSE;
2612
    $revert = FALSE;
2613
  }
2614

    
2615
  return array($was_defaulted, $is_defaulted, $revert);
2616
}
2617

    
2618
/**
2619
 * Submit handler for cancel button
2620
 */
2621
function views_ui_standard_cancel($form, &$form_state) {
2622
  if (!empty($form_state['view']->changed) && isset($form_state['view']->form_cache)) {
2623
    unset($form_state['view']->form_cache);
2624
    views_ui_cache_set($form_state['view']);
2625
  }
2626

    
2627
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
2628
}
2629

    
2630
/**
2631
 * Add a <select> dropdown for a given section, allowing the user to
2632
 * change whether this info is stored on the default display or on
2633
 * the current display.
2634
 */
2635
function views_ui_standard_display_dropdown(&$form, &$form_state, $section) {
2636
  $view = &$form_state['view'];
2637
  $display_id = $form_state['display_id'];
2638
  $displays = $view->display;
2639
  $current_display = $view->display[$display_id];
2640

    
2641
  // Add the "2 of 3" progress indicator.
2642
  // @TODO: Move this to a separate function if it's needed on any forms that
2643
  // don't have the display dropdown.
2644
  if ($form_progress = views_ui_get_form_progress($view)) {
2645
    $form['progress']['#markup'] = '<div id="views-progress-indicator">' . t('@current of @total', array('@current' => $form_progress['current'], '@total' => $form_progress['total'])) . '</div>';
2646
    $form['progress']['#weight'] = -1001;
2647
  }
2648

    
2649
  if ($current_display->handler->is_default_display()) {
2650
    return;
2651
  }
2652

    
2653
  // Determine whether any other displays have overrides for this section.
2654
  $section_overrides = FALSE;
2655
  $section_defaulted = $current_display->handler->is_defaulted($section);
2656
  foreach ($displays as $id => $display) {
2657
    if ($id === 'default' || $id === $display_id) {
2658
      continue;
2659
    }
2660
    if ($display->handler && !$display->handler->is_defaulted($section)) {
2661
      $section_overrides = TRUE;
2662
    }
2663
  }
2664

    
2665
  $display_dropdown['default'] = ($section_overrides ? t('All displays (except overridden)') : t('All displays'));
2666
  $display_dropdown[$display_id] = t('This @display_type (override)', array('@display_type' => $current_display->display_plugin));
2667
  // Only display the revert option if we are in a overridden section.
2668
  if (!$section_defaulted) {
2669
    $display_dropdown['default_revert'] = t('Revert to default');
2670
  }
2671

    
2672
  $form['override'] = array(
2673
    '#prefix' => '<div class="views-override clearfix container-inline">',
2674
    '#suffix' => '</div>',
2675
    '#weight' => -1000,
2676
    '#tree' => TRUE,
2677
  );
2678
  $form['override']['dropdown'] = array(
2679
    '#type' => 'select',
2680
    '#title' => t('For'), // @TODO: Translators may need more context than this.
2681
    '#options' => $display_dropdown,
2682
  );
2683
  if ($current_display->handler->is_defaulted($section)) {
2684
    $form['override']['dropdown']['#default_value'] = 'defaults';
2685
  }
2686
  else {
2687
    $form['override']['dropdown']['#default_value'] = $display_id;
2688
  }
2689

    
2690
}
2691

    
2692
/**
2693
 * Get the user's current progress through the form stack.
2694
 *
2695
 * @param $view
2696
 *   The current view.
2697
 *
2698
 * @return
2699
 *   FALSE if the user is not currently in a multiple-form stack. Otherwise,
2700
 *   an associative array with the following keys:
2701
 *   - current: The number of the current form on the stack.
2702
 *   - total: The total number of forms originally on the stack.
2703
 */
2704
function views_ui_get_form_progress($view) {
2705
  $progress = FALSE;
2706
  if (!empty($view->stack)) {
2707
    $stack = $view->stack;
2708
    // The forms on the stack have integer keys that don't change as the forms
2709
    // are completed, so we can see which ones are still left.
2710
    $keys = array_keys($view->stack);
2711
    // Add 1 to the array keys for the benefit of humans, who start counting
2712
    // from 1 and not 0.
2713
    $current = reset($keys) + 1;
2714
    $total = end($keys) + 1;
2715
    if ($total > 1) {
2716
      $progress = array();
2717
      $progress['current'] = $current;
2718
      $progress['total'] = $total;
2719
    }
2720
  }
2721
  return $progress;
2722
}
2723

    
2724

    
2725
// --------------------------------------------------------------------------
2726
// Various subforms for editing the pieces of a view.
2727

    
2728
function views_ui_ajax_forms($key = NULL) {
2729
  $forms = array(
2730
    'display' => array(
2731
      'form_id' => 'views_ui_edit_display_form',
2732
      'args' => array('section'),
2733
    ),
2734
    'remove-display' => array(
2735
      'form_id' => 'views_ui_remove_display_form',
2736
      'args' => array(),
2737
    ),
2738
    'config-type' => array(
2739
      'form_id' => 'views_ui_config_type_form',
2740
      'args' => array('type'),
2741
    ),
2742
    'rearrange' => array(
2743
      'form_id' => 'views_ui_rearrange_form',
2744
      'args' => array('type'),
2745
    ),
2746
    'rearrange-filter' => array(
2747
      'form_id' => 'views_ui_rearrange_filter_form',
2748
      'args' => array('type'),
2749
    ),
2750
    'reorder-displays' => array(
2751
      'form_id' => 'views_ui_reorder_displays_form',
2752
      'args' => array(),
2753
    ),
2754
    'add-item' => array(
2755
      'form_id' => 'views_ui_add_item_form',
2756
      'args' => array('type'),
2757
    ),
2758
    'config-item' => array(
2759
      'form_id' => 'views_ui_config_item_form',
2760
      'args' => array('type', 'id'),
2761
    ),
2762
    'config-item-extra' => array(
2763
      'form_id' => 'views_ui_config_item_extra_form',
2764
      'args' => array('type', 'id'),
2765
    ),
2766
    'config-item-group' => array(
2767
      'form_id' => 'views_ui_config_item_group_form',
2768
      'args' => array('type', 'id'),
2769
    ),
2770
    'config-style' => array(
2771
      'form_id' => 'views_ui_config_style_form',
2772
      'args' => array('type', 'id'),
2773
    ),
2774
    'edit-details' => array(
2775
      'form_id' => 'views_ui_edit_details_form',
2776
      'args' => array(),
2777
    ),
2778
    'analyze' => array(
2779
      'form_id' => 'views_ui_analyze_view_form',
2780
      'args' => array(),
2781
    ),
2782
  );
2783

    
2784
  if ($key) {
2785
    return !empty($forms[$key]) ? $forms[$key] : NULL;
2786
  }
2787

    
2788
  return $forms;
2789
}
2790

    
2791
/**
2792
 * Build a form identifier that we can use to see if one form
2793
 * is the same as another. Since the arguments differ slightly
2794
 * we do a lot of spiffy concatenation here.
2795
 */
2796
function views_ui_build_identifier($key, $view, $display_id, $args) {
2797
  $form = views_ui_ajax_forms($key);
2798
  // Automatically remove the single-form cache if it exists and
2799
  // does not match the key.
2800
  $identifier = implode('-', array($key, $view->name, $display_id));
2801

    
2802
  foreach ($form['args'] as $id) {
2803
    $arg = (!empty($args)) ? array_shift($args) : NULL;
2804
    $identifier .= '-' . $arg;
2805
  }
2806
  return $identifier;
2807
}
2808

    
2809
/**
2810
 * Build up a $form_state object suitable for use with drupal_build_form
2811
 * based on known information about a form.
2812
 */
2813
function views_ui_build_form_state($js, $key, &$view, $display_id, $args) {
2814
  $form = views_ui_ajax_forms($key);
2815
  // Build up form state
2816
  $form_state = array(
2817
    'form_key' => $key,
2818
    'form_id' => $form['form_id'],
2819
    'view' => &$view,
2820
    'ajax' => $js,
2821
    'display_id' => $display_id,
2822
    'no_redirect' => TRUE,
2823
  );
2824

    
2825
  foreach ($form['args'] as $id) {
2826
    $form_state[$id] = (!empty($args)) ? array_shift($args) : NULL;
2827
  }
2828

    
2829
  return $form_state;
2830
}
2831

    
2832
/**
2833
 * Create the URL for one of our standard AJAX forms based upon known
2834
 * information about the form.
2835
 */
2836
function views_ui_build_form_url($form_state) {
2837
  $form = views_ui_ajax_forms($form_state['form_key']);
2838
  $ajax = empty($form_state['ajax']) ? 'nojs' : 'ajax';
2839
  $name = $form_state['view']->name;
2840
  $url = "admin/structure/views/$ajax/$form_state[form_key]/$name/$form_state[display_id]";
2841
  foreach ($form['args'] as $arg) {
2842
    $url .= '/' . $form_state[$arg];
2843
  }
2844
  return $url;
2845
}
2846

    
2847
/**
2848
 * Add another form to the stack; clicking 'apply' will go to this form
2849
 * rather than closing the ajax popup.
2850
 */
2851
function views_ui_add_form_to_stack($key, &$view, $display_id, $args, $top = FALSE, $rebuild_keys = FALSE) {
2852
  if (empty($view->stack)) {
2853
    $view->stack = array();
2854
  }
2855

    
2856
  $stack = array(views_ui_build_identifier($key, $view, $display_id, $args), $key, &$view, $display_id, $args);
2857
  // If we're being asked to add this form to the bottom of the stack, no
2858
  // special logic is required. Our work is equally easy if we were asked to add
2859
  // to the top of the stack, but there's nothing in it yet.
2860
  if (!$top || empty($view->stack)) {
2861
    $view->stack[] = $stack;
2862
  }
2863
  // If we're adding to the top of an existing stack, we have to maintain the
2864
  // existing integer keys, so they can be used for the "2 of 3" progress
2865
  // indicator (which will now read "2 of 4").
2866
  else {
2867
    $keys = array_keys($view->stack);
2868
    $first = current($keys);
2869
    $last = end($keys);
2870
    for ($i = $last; $i >= $first; $i--) {
2871
      if (!isset($view->stack[$i])) {
2872
        continue;
2873
      }
2874
      // Move form number $i to the next position in the stack.
2875
      $view->stack[$i + 1] = $view->stack[$i];
2876
      unset($view->stack[$i]);
2877
    }
2878
    // Now that the previously $first slot is free, move the new form into it.
2879
    $view->stack[$first] = $stack;
2880
    ksort($view->stack);
2881

    
2882
    // Start the keys from 0 again, if requested.
2883
    if ($rebuild_keys) {
2884
      $view->stack = array_values($view->stack);
2885
    }
2886
  }
2887
}
2888

    
2889
/**
2890
 * Generic entry point to handle forms.
2891
 *
2892
 * We do this for consistency and to make it easy to chain forms
2893
 * together.
2894
 */
2895
function views_ui_ajax_form($js, $key, $view, $display_id = '') {
2896
  // Reset the cache of IDs. Drupal rather aggressively prevents id duplication
2897
  // but this causes it to remember IDs that are no longer even being used.
2898
  if (isset($_POST['ajax_html_ids'])) {
2899
    unset($_POST['ajax_html_ids']);
2900
  }
2901

    
2902
  $form = views_ui_ajax_forms($key);
2903
  if (empty($form)) {
2904
    return MENU_NOT_FOUND;
2905
  }
2906

    
2907
  views_include('ajax');
2908
  $args = func_get_args();
2909
  // Remove the known args
2910
  array_splice($args, 0, 4);
2911

    
2912
  $form_state = views_ui_build_form_state($js, $key, $view, $display_id, $args);
2913
  // check to see if this is the top form of the stack. If it is, pop
2914
  // it off; if it isn't, the user clicked somewhere else and the stack is
2915
  // now irrelevant.
2916
  if (!empty($view->stack)) {
2917
    $identifier = views_ui_build_identifier($key, $view, $display_id, $args);
2918
    // Retrieve the first form from the stack without changing the integer keys,
2919
    // as they're being used for the "2 of 3" progress indicator.
2920
    reset($view->stack);
2921
    list($key, $top) = each($view->stack);
2922
    unset($view->stack[$key]);
2923

    
2924
    if (array_shift($top) != $identifier) {
2925
      $view->stack = array();
2926
    }
2927
  }
2928

    
2929
  // Automatically remove the form cache if it is set and the key does
2930
  // not match. This way navigating away from the form without hitting
2931
  // update will work.
2932
  if (isset($view->form_cache) && $view->form_cache['key'] != $key) {
2933
    unset($view->form_cache);
2934
  }
2935

    
2936
  // With the below logic, we may end up rendering a form twice (or two forms
2937
  // each sharing the same element ids), potentially resulting in
2938
  // drupal_add_js() being called twice to add the same setting. drupal_get_js()
2939
  // is ok with that, but until ajax_render() is (http://drupal.org/node/208611),
2940
  // reset the drupal_add_js() static before rendering the second time.
2941
  $drupal_add_js_original = drupal_add_js();
2942
  $drupal_add_js = &drupal_static('drupal_add_js');
2943
  $output = views_ajax_form_wrapper($form_state['form_id'], $form_state);
2944
  if ($form_state['submitted'] && empty($form_state['rerender'])) {
2945
    // Sometimes we need to re-generate the form for multi-step type operations.
2946
    $object = NULL;
2947
    if (!empty($view->stack)) {
2948
      $drupal_add_js = $drupal_add_js_original;
2949
      $stack = $view->stack;
2950
      $top = array_shift($stack);
2951
      $top[0] = $js;
2952
      $form_state = call_user_func_array('views_ui_build_form_state', $top);
2953
      $form_state['input'] = array();
2954
      $form_state['url'] = url(views_ui_build_form_url($form_state));
2955
      if (!$js) {
2956
        return drupal_goto(views_ui_build_form_url($form_state));
2957
      }
2958
      $output = views_ajax_form_wrapper($form_state['form_id'], $form_state);
2959
    }
2960
    elseif (!$js) {
2961
      // if nothing on the stack, non-js forms just go back to the main view editor.
2962
      return drupal_goto("admin/structure/views/view/$view->name/edit");
2963
    }
2964
    else {
2965
      $output = array();
2966
      $output[] = views_ajax_command_dismiss_form();
2967
      $output[] = views_ajax_command_show_buttons();
2968
      $output[] = views_ajax_command_trigger_preview();
2969
      if (!empty($form_state['#page_title'])) {
2970
        $output[] = views_ajax_command_replace_title($form_state['#page_title']);
2971
      }
2972
    }
2973
    // If this form was for view-wide changes, there's no need to regenerate
2974
    // the display section of the form.
2975
    if ($display_id !== '') {
2976
      views_ui_regenerate_tab($view, $output, $display_id);
2977
    }
2978
  }
2979

    
2980
  return $js ? array('#type' => 'ajax', '#commands' => $output) : $output;
2981
}
2982

    
2983
/**
2984
 * Submit handler to add a restore a removed display to a view.
2985
 */
2986
function views_ui_remove_display_form_restore($form, &$form_state) {
2987
  // Create the new display
2988
  $id = $form_state['display_id'];
2989
  $form_state['view']->display[$id]->deleted = FALSE;
2990

    
2991
  // Store in cache
2992
  views_ui_cache_set($form_state['view']);
2993
}
2994

    
2995
/**
2996
 * Form constructor callback to display analysis information on a view
2997
 */
2998
function views_ui_analyze_view_form($form, &$form_state) {
2999
  $view = &$form_state['view'];
3000

    
3001
  $form['#title'] = t('View analysis');
3002
  $form['#section'] = 'analyze';
3003

    
3004
  views_include('analyze');
3005
  $messages = views_analyze_view($view);
3006

    
3007
  $form['analysis'] = array(
3008
    '#prefix' => '<div class="form-item">',
3009
    '#suffix' => '</div>',
3010
    '#markup' => views_analyze_format_result($view, $messages),
3011
  );
3012

    
3013
  // Inform the standard button function that we want an OK button.
3014
  $form_state['ok_button'] = TRUE;
3015
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_analyze_view_form');
3016
  return $form;
3017
}
3018

    
3019
/**
3020
 * Submit handler for views_ui_analyze_view_form
3021
 */
3022
function views_ui_analyze_view_form_submit($form, &$form_state) {
3023
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
3024
}
3025

    
3026
/**
3027
 * Form constructor callback to reorder displays on a view
3028
 */
3029
function views_ui_reorder_displays_form($form, &$form_state) {
3030
  $view = &$form_state['view'];
3031
  $display_id = $form_state['display_id'];
3032

    
3033
  $form['view'] = array('#type' => 'value', '#value' => $view);
3034

    
3035
  $form['#tree'] = TRUE;
3036

    
3037
  $last_display = end($view->display);
3038

    
3039
  foreach ($view->display as $display) {
3040
    $form[$display->id] = array(
3041
      'title'  => array('#markup' => check_plain($display->display_title)),
3042
      'weight' => array(
3043
        '#type' => 'weight',
3044
        '#value' => $display->position,
3045
        '#delta' => $last_display->position,
3046
        '#title' => t('Weight for @display', array('@display' => $display->display_title)),
3047
        '#title_display' => 'invisible',
3048
      ),
3049
      '#tree' => TRUE,
3050
      '#display' => $display,
3051
      'removed' => array(
3052
        '#type' => 'checkbox',
3053
        '#id' => 'display-removed-' . $display->id,
3054
        '#attributes' => array('class' => array('views-remove-checkbox')),
3055
        '#default_value' => isset($display->deleted),
3056
      ),
3057
    );
3058

    
3059
    if (isset($display->deleted) && $display->deleted) {
3060
      $form[$display->id]['deleted'] = array('#type' => 'value', '#value' => TRUE);
3061
    }
3062
    if ($display->id === 'default') {
3063
      unset($form[$display->id]['weight']);
3064
      unset($form[$display->id]['removed']);
3065
    }
3066

    
3067
  }
3068

    
3069
  $form['#title'] = t('Displays Reorder');
3070
  $form['#section'] = 'reorder';
3071

    
3072
  // Add javascript settings that will be added via $.extend for tabledragging
3073
  $form['#js']['tableDrag']['reorder-displays']['weight'][0] = array(
3074
    'target' => 'weight',
3075
    'source' => NULL,
3076
    'relationship' => 'sibling',
3077
    'action' => 'order',
3078
    'hidden' => TRUE,
3079
    'limit' => 0,
3080
  );
3081

    
3082
  $form['#action'] = url('admin/structure/views/nojs/reorder-displays/' . $view->name . '/' . $display_id);
3083

    
3084
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_reorder_displays_form');
3085

    
3086
  return $form;
3087
}
3088

    
3089
/**
3090
 * Display position sorting function
3091
 */
3092
function _views_position_sort($display1, $display2) {
3093
  if ($display1->position != $display2->position) {
3094
    return $display1->position < $display2->position ? -1 : 1;
3095
  }
3096

    
3097
  return 0;
3098
}
3099

    
3100
/**
3101
 * Submit handler for rearranging display form
3102
 */
3103
function views_ui_reorder_displays_form_submit($form, &$form_state) {
3104
  foreach($form_state['input'] as $display => $info) {
3105
    // add each value that is a field with a weight to our list, but only if
3106
    // it has had its 'removed' checkbox checked.
3107
    if (is_array($info) && isset($info['weight']) && empty($info['removed'])) {
3108
      $order[$display] = $info['weight'];
3109
    }
3110
  }
3111

    
3112
  // Sort the order array
3113
  asort($order);
3114

    
3115
  // Fixing up positions
3116
  $position = 2;
3117

    
3118
  foreach(array_keys($order) as $display) {
3119
    $order[$display] = $position++;
3120
  }
3121

    
3122
  // Setting up position and removing deleted displays
3123
  $displays = $form_state['view']->display;
3124
  foreach($displays as $display_id => $display) {
3125
    // Don't touch the default !!!
3126
    if ($display_id === 'default') {
3127
      continue;
3128
    }
3129
    if (isset($order[$display_id])) {
3130
      $form_state['view']->display[$display_id]->position = $order[$display_id];
3131
    }
3132
    else {
3133
      $form_state['view']->display[$display_id]->deleted = TRUE;
3134
    }
3135
  }
3136

    
3137
  // Sorting back the display array as the position is not enough
3138
  uasort($form_state['view']->display, '_views_position_sort');
3139

    
3140
  // Store in cache
3141
  views_ui_cache_set($form_state['view']);
3142
  $form_state['redirect'] = array('admin/structure/views/view/' . $form_state['view']->name . '/edit', array('fragment' => 'views-tab-default'));
3143
}
3144

    
3145
/**
3146
 * Turn the reorder form into a proper table
3147
 */
3148
function theme_views_ui_reorder_displays_form($vars) {
3149
  $form = $vars['form'];
3150
  $rows = array();
3151
  foreach (element_children($form) as $key) {
3152
    if (isset($form[$key]['#display'])) {
3153
      $display = &$form[$key];
3154

    
3155
      $row = array();
3156
      $row[] = drupal_render($display['title']);
3157
      $form[$key]['weight']['#attributes']['class'] = array('weight');
3158
      $row[] = drupal_render($form[$key]['weight']);
3159
      if (isset($display['removed'])) {
3160
        $row[] = drupal_render($form[$key]['removed']) .
3161
          l('<span>' . t('Remove') . '</span>',
3162
            'javascript:void()',
3163
            array(
3164
              'attributes' => array(
3165
                'id' => 'display-remove-link-' . $key,
3166
                'class' => array('views-button-remove display-remove-link'),
3167
                'alt' => t('Remove this display'),
3168
                'title' => t('Remove this display')),
3169
              'html' => TRUE));
3170
      }
3171
      else {
3172
        $row[] = '';
3173
      }
3174
      $class = array();
3175
      $styles = array();
3176
      if (isset($form[$key]['weight']['#type'])) {
3177
        $class[] = 'draggable';
3178
      }
3179
      if (isset($form[$key]['deleted']['#value']) && $form[$key]['deleted']['#value']) {
3180
        $styles[] = 'display: none;';
3181
      }
3182
      $rows[] = array('data' => $row, 'class' => $class, 'id' => 'display-row-' . $key, 'style' => $styles);
3183
    }
3184
  }
3185

    
3186
  $header = array(t('Display'), t('Weight'), t('Remove'));
3187
  $output = '';
3188
  drupal_add_tabledrag('reorder-displays', 'order', 'sibling', 'weight');
3189

    
3190
  $output = drupal_render($form['override']);
3191
  $output .= '<div class="scroll">';
3192
  $output .= theme('table',
3193
    array('header' => $header,
3194
    'rows' => $rows,
3195
    'attributes' => array('id' => 'reorder-displays'),
3196
  ));
3197
  $output .= '</div>';
3198
  $output .= drupal_render_children($form);
3199

    
3200
  return $output;
3201
}
3202

    
3203
/**
3204
 * Form builder to edit details of a view.
3205
 */
3206
function views_ui_edit_details_form($form, &$form_state) {
3207
  $view = &$form_state['view'];
3208

    
3209
  $form['#title'] = t('View name and description');
3210
  $form['#section'] = 'details';
3211

    
3212
  $form['details'] = array(
3213
    '#theme_wrappers' => array('container'),
3214
    '#attributes' => array('class' => array('scroll')),
3215
  );
3216
  $form['details']['human_name'] = array(
3217
    '#type' => 'textfield',
3218
    '#title' => t('Human-readable name'),
3219
    '#description' => t('A descriptive human-readable name for this view. Spaces are allowed'),
3220
    '#default_value' => $view->get_human_name(),
3221
  );
3222
  $form['details']['tag'] = array(
3223
    '#type' => 'textfield',
3224
    '#title' => t('View tag'),
3225
    '#description' => t('Optionally, enter a comma delimited list of tags for this view to use in filtering and sorting views on the administrative page.'),
3226
    '#default_value' => $view->tag,
3227
    '#autocomplete_path' => 'admin/views/ajax/autocomplete/tag',
3228
  );
3229
  $form['details']['description'] = array(
3230
    '#type' => 'textfield',
3231
    '#title' => t('View description'),
3232
    '#description' => t('This description will appear on the Views administrative UI to tell you what the view is about.'),
3233
    '#default_value' => $view->description,
3234
  );
3235

    
3236
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_details_form');
3237
  return $form;
3238
}
3239

    
3240
/**
3241
 * Submit handler for views_ui_edit_details_form.
3242
 */
3243
function views_ui_edit_details_form_submit($form, &$form_state) {
3244
  $view = $form_state['view'];
3245
  foreach ($form_state['values'] as $key => $value) {
3246
    // Only save values onto the view if they're actual view properties
3247
    // (as opposed to 'op' or 'form_build_id').
3248
    if (isset($form['details'][$key])) {
3249
      $view->$key = $value;
3250
    }
3251
  }
3252
  $form_state['#page_title'] = views_ui_edit_page_title($view);
3253
  views_ui_cache_set($view);
3254
}
3255

    
3256
/**
3257
 * Form constructor callback to edit display of a view
3258
 */
3259
function views_ui_edit_display_form($form, &$form_state) {
3260
  $view = &$form_state['view'];
3261
  $display_id = $form_state['display_id'];
3262
  $section = $form_state['section'];
3263

    
3264
  if (!$view->set_display($display_id)) {
3265
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
3266
  }
3267
  $display = &$view->display[$display_id];
3268

    
3269
  // Get form from the handler.
3270
  $form['options'] = array(
3271
    '#theme_wrappers' => array('container'),
3272
    '#attributes' => array('class' => array('scroll')),
3273
  );
3274
  $display->handler->options_form($form['options'], $form_state);
3275

    
3276
  // The handler options form sets $form['#title'], which we need on the entire
3277
  // $form instead of just the ['options'] section.
3278
  $form['#title'] = $form['options']['#title'];
3279
  unset($form['options']['#title']);
3280

    
3281
  // Move the override dropdown out of the scrollable section of the form.
3282
  if (isset($form['options']['override'])) {
3283
    $form['override'] = $form['options']['override'];
3284
    unset($form['options']['override']);
3285
  }
3286

    
3287
  $name = NULL;
3288
  if (isset($form_state['update_name'])) {
3289
    $name = $form_state['update_name'];
3290
  }
3291

    
3292
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_display_form', $name);
3293
  return $form;
3294
}
3295

    
3296
/**
3297
 * Validate handler for views_ui_edit_display_form
3298
 */
3299
function views_ui_edit_display_form_validate($form, &$form_state) {
3300
  $display = &$form_state['view']->display[$form_state['display_id']];
3301
  $display->handler->options_validate($form['options'], $form_state);
3302

    
3303
  if (form_get_errors()) {
3304
    $form_state['rerender'] = TRUE;
3305
  }
3306
}
3307

    
3308
/**
3309
 * Submit handler for views_ui_edit_display_form
3310
 */
3311
function views_ui_edit_display_form_submit($form, &$form_state) {
3312
  $display = &$form_state['view']->display[$form_state['display_id']];
3313
  $display->handler->options_submit($form, $form_state);
3314

    
3315
  views_ui_cache_set($form_state['view']);
3316
}
3317

    
3318
/**
3319
 * Override handler for views_ui_edit_display_form
3320
 *
3321
 * @TODO: Not currently used. Remove unless we implement an override toggle.
3322
 */
3323
function views_ui_edit_display_form_override($form, &$form_state) {
3324
  $display = &$form_state['view']->display[$form_state['display_id']];
3325
  $display->handler->options_override($form, $form_state);
3326

    
3327
  views_ui_cache_set($form_state['view']);
3328
  $form_state['rerender'] = TRUE;
3329
  $form_state['rebuild'] = TRUE;
3330
}
3331

    
3332
/**
3333
 * Form to config items in the views UI.
3334
 */
3335
function views_ui_config_type_form($form, &$form_state) {
3336
  $view = &$form_state['view'];
3337
  $display_id = $form_state['display_id'];
3338
  $type = $form_state['type'];
3339

    
3340
  $types = views_object_types();
3341
  if (!$view->set_display($display_id)) {
3342
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
3343
  }
3344
  $display = &$view->display[$display_id];
3345
  $form['#title'] = t('Configure @type', array('@type' => $types[$type]['ltitle']));
3346
  $form['#section'] = $display_id . 'config-item';
3347

    
3348
  if ($display->handler->defaultable_sections($types[$type]['plural'])) {
3349
    $form_state['section'] = $types[$type]['plural'];
3350
    views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
3351
  }
3352

    
3353
  if (!empty($types[$type]['options']) && function_exists($types[$type]['options'])) {
3354
    $options = $type . '_options';
3355
    $form[$options] = array('#tree' => TRUE);
3356
    $types[$type]['options']($form, $form_state);
3357
  }
3358

    
3359
  $name = NULL;
3360
  if (isset($form_state['update_name'])) {
3361
    $name = $form_state['update_name'];
3362
  }
3363

    
3364
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_type_form', $name);
3365
  return $form;
3366
}
3367

    
3368
/**
3369
 * Submit handler for type configuration form
3370
 */
3371
function views_ui_config_type_form_submit($form, &$form_state) {
3372
  $types = views_object_types();
3373
  $display = &$form_state['view']->display[$form_state['display_id']];
3374

    
3375
  // Store in cache
3376
  views_ui_cache_set($form_state['view']);
3377
}
3378

    
3379
/**
3380
 * Form to rearrange items in the views UI.
3381
 */
3382
function views_ui_rearrange_form($form, &$form_state) {
3383
  $view = &$form_state['view'];
3384
  $display_id = $form_state['display_id'];
3385
  $type = $form_state['type'];
3386

    
3387
  $types = views_object_types();
3388
  if (!$view->set_display($display_id)) {
3389
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
3390
  }
3391
  $display = &$view->display[$display_id];
3392
  $form['#title'] = t('Rearrange @type', array('@type' => $types[$type]['ltitle']));
3393
  $form['#section'] = $display_id . 'rearrange-item';
3394

    
3395
  if ($display->handler->defaultable_sections($types[$type]['plural'])) {
3396
    $form_state['section'] = $types[$type]['plural'];
3397
    views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
3398
  }
3399

    
3400
  $count = 0;
3401

    
3402
  // Get relationship labels
3403
  $relationships = array();
3404
  foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
3405
    $relationships[$id] = $handler->label();
3406
    $handlers = $display->handler->get_option('relationships');
3407
    if ($handlers) {
3408
      foreach ($handlers as $id => $info) {
3409
        $handler = $display->handler->get_handler('relationship', $id);
3410
        $relationships[$id] = $handler->label();
3411
      }
3412
    }
3413
  }
3414

    
3415
  // Filters can now be grouped so we do a little bit extra:
3416
  $groups = array();
3417
  $grouping = FALSE;
3418
  if ($type == 'filter') {
3419
    $group_info = $view->display_handler->get_option('filter_groups');
3420
    if (!empty($group_info['groups']) && count($group_info['groups']) > 1) {
3421
      $grouping = TRUE;
3422
      $groups = array(0 => array());
3423
    }
3424
  }
3425

    
3426
  foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
3427
    $form['fields'][$id] = array('#tree' => TRUE);
3428
    $form['fields'][$id]['weight'] = array(
3429
      '#type' => 'textfield',
3430
      '#default_value' => ++$count,
3431
    );
3432
    $handler = $display->handler->get_handler($type, $id);
3433
    if ($handler) {
3434
      $name = $handler->ui_name() . ' ' . $handler->admin_summary();
3435
      if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
3436
        $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
3437
      }
3438

    
3439
      $form['fields'][$id]['name'] = array(
3440
        '#markup' => $name,
3441
      );
3442
    }
3443
    else {
3444
      $form['fields'][$id]['name'] = array('#markup' => t('Broken field @id', array('@id' => $id)));
3445
    }
3446
    $form['fields'][$id]['removed'] = array(
3447
      '#type' => 'checkbox',
3448
      '#id' => 'views-removed-' . $id,
3449
      '#attributes' => array('class' => array('views-remove-checkbox')),
3450
      '#default_value' => 0,
3451
    );
3452
  }
3453

    
3454
  // Add javascript settings that will be added via $.extend for tabledragging
3455
  $form['#js']['tableDrag']['arrange']['weight'][0] = array(
3456
    'target' => 'weight',
3457
    'source' => NULL,
3458
    'relationship' => 'sibling',
3459
    'action' => 'order',
3460
    'hidden' => TRUE,
3461
    'limit' => 0,
3462
  );
3463

    
3464
  $name = NULL;
3465
  if (isset($form_state['update_name'])) {
3466
    $name = $form_state['update_name'];
3467
  }
3468

    
3469
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_rearrange_form');
3470
  return $form;
3471
}
3472

    
3473
/**
3474
 * Turn the rearrange form into a proper table
3475
 */
3476
function theme_views_ui_rearrange_form($variables) {
3477
  $form = $variables['form'];
3478

    
3479
  $rows = array();
3480
  foreach (element_children($form['fields']) as $id) {
3481
    if (isset($form['fields'][$id]['name'])) {
3482
      $row = array();
3483
      $row[] = drupal_render($form['fields'][$id]['name']);
3484
      $form['fields'][$id]['weight']['#attributes']['class'] = array('weight');
3485
      $row[] = drupal_render($form['fields'][$id]['weight']);
3486
      $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));
3487
      $rows[] = array('data' => $row, 'class' => array('draggable'), 'id' => 'views-row-' . $id);
3488
    }
3489
  }
3490
  if (empty($rows)) {
3491
    $rows[] = array(array('data' => t('No fields available.'), 'colspan' => '2'));
3492
  }
3493

    
3494
  $header = array('', t('Weight'), t('Remove'));
3495
  $output = drupal_render($form['override']);
3496
  $output .= '<div class="scroll">';
3497
  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'arrange')));
3498
  $output .= '</div>';
3499
  $output .= drupal_render_children($form);
3500
  drupal_add_tabledrag('arrange', 'order', 'sibling', 'weight');
3501

    
3502
  return $output;
3503
}
3504

    
3505
/**
3506
 * Theme the expose filter form.
3507
 */
3508
function theme_views_ui_expose_filter_form($variables) {
3509
  $form = $variables['form'];
3510
  $more = drupal_render($form['more']);
3511

    
3512
  $output = drupal_render($form['form_description']);
3513
  $output .= drupal_render($form['expose_button']);
3514
  $output .= drupal_render($form['group_button']);
3515
  if (isset($form['required'])) {
3516
    $output .= drupal_render($form['required']);
3517
  }
3518
  $output .= drupal_render($form['label']);
3519
  $output .= drupal_render($form['description']);
3520

    
3521
  $output .= drupal_render($form['operator']);
3522
  $output .= drupal_render($form['value']);
3523

    
3524
  if (isset($form['use_operator'])) {
3525
    $output .= '<div class="views-left-40">';
3526
    $output .= drupal_render($form['use_operator']);
3527
    $output .= '</div>';
3528
  }
3529

    
3530
  // Only output the right column markup if there's a left column to begin with
3531
  if (!empty($form['operator']['#type'])) {
3532
    $output .= '<div class="views-right-60">';
3533
    $output .= drupal_render_children($form);
3534
    $output .= '</div>';
3535
  }
3536
  else {
3537
    $output .= drupal_render_children($form);
3538
  }
3539

    
3540
  $output .= $more;
3541

    
3542
  return $output;
3543
}
3544

    
3545
 /**
3546
 * Theme the build group filter form.
3547
 */
3548
function theme_views_ui_build_group_filter_form($variables) {
3549
  $form = $variables['form'];
3550
  $more = drupal_render($form['more']);
3551

    
3552
  $output = drupal_render($form['form_description']);
3553
  $output .= drupal_render($form['expose_button']);
3554
  $output .= drupal_render($form['group_button']);
3555
  if (isset($form['required'])) {
3556
    $output .= drupal_render($form['required']);
3557
  }
3558

    
3559
  $output .= drupal_render($form['operator']);
3560
  $output .= drupal_render($form['value']);
3561

    
3562
  $output .= '<div class="views-left-40">';
3563
  $output .= drupal_render($form['optional']);
3564
  $output .= drupal_render($form['remember']);
3565
  $output .= '</div>';
3566

    
3567
  $output .= '<div class="views-right-60">';
3568
  $output .= drupal_render($form['widget']);
3569
  $output .= drupal_render($form['label']);
3570
  $output .= drupal_render($form['description']);
3571
  $output .= '</div>';
3572

    
3573

    
3574
  $header = array(
3575
    t('Default'),
3576
    t('Weight'),
3577
    t('Label'),
3578
    t('Operator'),
3579
    t('Value'),
3580
    t('Operations'),
3581
  );
3582

    
3583
  $form['default_group'] = form_process_radios($form['default_group']);
3584
  $form['default_group_multiple'] = form_process_checkboxes($form['default_group_multiple']);
3585
  $form['default_group']['All']['#title'] = '';
3586

    
3587
  drupal_render($form['default_group_multiple']['All']); // Don't render
3588
  $rows[] = array(
3589
    drupal_render($form['default_group']['All']),
3590
    '',
3591
    array(
3592
      'data' => variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? t('&lt;Any&gt;') : t('- Any -'),
3593
      'colspan' => 4,
3594
      'class' => array('class' => 'any-default-radios-row'),
3595
    ),
3596
  );
3597

    
3598
  foreach (element_children($form['group_items']) as $group_id) {
3599
    $form['group_items'][$group_id]['value']['#title'] = '';
3600
    $data = array(
3601
      'default' => drupal_render($form['default_group'][$group_id]) . drupal_render($form['default_group_multiple'][$group_id]),
3602
      'weight' => drupal_render($form['group_items'][$group_id]['weight']),
3603
      'title' => drupal_render($form['group_items'][$group_id]['title']),
3604
      'operator' => drupal_render($form['group_items'][$group_id]['operator']),
3605
      'value' => drupal_render($form['group_items'][$group_id]['value']),
3606
      '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)),
3607
    );
3608
    $rows[] = array('data' => $data, 'id' => 'views-row-' . $group_id, 'class' => array('draggable'));
3609
  }
3610
  $table = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('views-filter-groups'), 'id' => 'views-filter-groups'))) . drupal_render($form['add_group']);
3611
  drupal_add_tabledrag('views-filter-groups', 'order', 'sibling', 'weight');
3612
  $render_form = drupal_render_children($form);
3613
  return $output . $render_form . $table . $more;
3614
}
3615

    
3616

    
3617
/**
3618
 * Submit handler for rearranging form
3619
 */
3620
function views_ui_rearrange_form_submit($form, &$form_state) {
3621
  $types = views_object_types();
3622
  $display = &$form_state['view']->display[$form_state['display_id']];
3623

    
3624
  $old_fields = $display->handler->get_option($types[$form_state['type']]['plural']);
3625
  $new_fields = $order = array();
3626

    
3627
  // Make an array with the weights
3628
  foreach ($form_state['values'] as $field => $info) {
3629
    // add each value that is a field with a weight to our list, but only if
3630
    // it has had its 'removed' checkbox checked.
3631
    if (is_array($info) && isset($info['weight']) && empty($info['removed'])) {
3632
      $order[$field] = $info['weight'];
3633
    }
3634
  }
3635

    
3636
  // Sort the array
3637
  asort($order);
3638

    
3639
  // Create a new list of fields in the new order.
3640
  foreach (array_keys($order) as $field) {
3641
    $new_fields[$field] = $old_fields[$field];
3642
  }
3643
  $display->handler->set_option($types[$form_state['type']]['plural'], $new_fields);
3644

    
3645
  // Store in cache
3646
  views_ui_cache_set($form_state['view']);
3647
}
3648

    
3649
/**
3650
 * Form to rearrange items in the views UI.
3651
 */
3652
function views_ui_rearrange_filter_form($form, &$form_state) {
3653
  $view = &$form_state['view'];
3654
  $display_id = $form_state['display_id'];
3655
  $type = $form_state['type'];
3656

    
3657
  $types = views_object_types();
3658
  if (!$view->set_display($display_id)) {
3659
    views_ajax_render(t('Invalid display id @display', array('@display' => $display_id)));
3660
  }
3661
  $display = &$view->display[$display_id];
3662
  $form['#title'] = check_plain($display->display_title) . ': ';
3663
  $form['#title'] .= t('Rearrange @type', array('@type' => $types[$type]['ltitle']));
3664
  $form['#section'] = $display_id . 'rearrange-item';
3665

    
3666
  if ($display->handler->defaultable_sections($types[$type]['plural'])) {
3667
    $form_state['section'] = $types[$type]['plural'];
3668
    views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
3669
  }
3670

    
3671
  if (!empty($view->form_cache)) {
3672
    $groups = $view->form_cache['groups'];
3673
    $handlers = $view->form_cache['handlers'];
3674
  }
3675
  else {
3676
    $groups = $display->handler->get_option('filter_groups');
3677
    $handlers = $display->handler->get_option($types[$type]['plural']);
3678
  }
3679
  $count = 0;
3680

    
3681
  // Get relationship labels
3682
  $relationships = array();
3683
  foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
3684
    $relationships[$id] = $handler->label();
3685
  }
3686

    
3687
  $group_options = array();
3688

    
3689
  /**
3690
   * Filter groups is an array that contains:
3691
   * array(
3692
   *   'operator' => 'and' || 'or',
3693
   *   'groups' => array(
3694
   *     $group_id => 'and' || 'or',
3695
   *   ),
3696
   * );
3697
   */
3698

    
3699
  $grouping = count(array_keys($groups['groups'])) > 1;
3700

    
3701
  $form['filter_groups']['#tree'] = TRUE;
3702
  $form['filter_groups']['operator'] = array(
3703
    '#type' => 'select',
3704
    '#options' => array (
3705
      'AND' => t('And'),
3706
      'OR' => t('Or'),
3707
    ),
3708
    '#default_value' => $groups['operator'],
3709
    '#attributes' => array(
3710
      'class' => array('warning-on-change'),
3711
    ),
3712
    '#title' => t('Operator to use on all groups'),
3713
    '#description' => t('Either "group 0 AND group 1 AND group 2" or "group 0 OR group 1 OR group 2", etc'),
3714
    '#access' => $grouping,
3715
  );
3716

    
3717
  $form['remove_groups']['#tree'] = TRUE;
3718

    
3719
  foreach ($groups['groups'] as $id => $group) {
3720
    $form['filter_groups']['groups'][$id] = array(
3721
      '#title' => t('Operator'),
3722
      '#type' => 'select',
3723
      '#options' => array(
3724
        'AND' => t('And'),
3725
        'OR' => t('Or'),
3726
      ),
3727
      '#default_value' => $group,
3728
      '#attributes' => array(
3729
        'class' => array('warning-on-change'),
3730
      ),
3731
    );
3732

    
3733
    $form['remove_groups'][$id] = array(); // to prevent a notice
3734
    if ($id != 1) {
3735
      $form['remove_groups'][$id] = array(
3736
        '#type' => 'submit',
3737
        '#value' => t('Remove group @group', array('@group' => $id)),
3738
        '#id' => "views-remove-group-$id",
3739
        '#attributes' => array(
3740
          'class' => array('views-remove-group'),
3741
        ),
3742
        '#group' => $id,
3743
      );
3744
    }
3745
    $group_options[$id] = $id == 1 ? t('Default group') : t('Group @group', array('@group' => $id));
3746
    $form['#group_renders'][$id] = array();
3747
  }
3748

    
3749
  $form['#group_options'] = $group_options;
3750
  $form['#groups'] = $groups;
3751
  // We don't use get_handlers() because we want items without handlers to
3752
  // appear and show up as 'broken' so that the user can see them.
3753
  $form['filters'] = array('#tree' => TRUE);
3754
  foreach ($handlers as $id => $field) {
3755
    // If the group does not exist, move the filters to the default group.
3756
    if (empty($field['group']) || empty($groups['groups'][$field['group']])) {
3757
      $field['group'] = 1;
3758
    }
3759

    
3760
    $handler = $display->handler->get_handler($type, $id);
3761
    if ($grouping && $handler && !$handler->can_group()) {
3762
      $field['group'] = 'ungroupable';
3763
    }
3764

    
3765
    // If not grouping and the handler is set ungroupable, move it back to
3766
    // the default group to prevent weird errors from having it be in its
3767
    // own group:
3768
    if (!$grouping && $field['group'] == 'ungroupable') {
3769
      $field['group'] = 1;
3770
    }
3771

    
3772
    // Place this item into the proper group for rendering.
3773
    $form['#group_renders'][$field['group']][] = $id;
3774

    
3775
    $form['filters'][$id]['weight'] = array(
3776
      '#type' => 'textfield',
3777
      '#default_value' => ++$count,
3778
      '#size' => 8,
3779
    );
3780
    $form['filters'][$id]['group'] = array(
3781
      '#type' => 'select',
3782
      '#options' => $group_options,
3783
      '#default_value' => $field['group'],
3784
      '#attributes' => array(
3785
        'class' => array('views-region-select', 'views-region-' . $id),
3786
      ),
3787
      '#access' => $field['group'] !== 'ungroupable',
3788
    );
3789

    
3790
    if ($handler) {
3791
      $name = $handler->ui_name() . ' ' . $handler->admin_summary();
3792
      if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
3793
        $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
3794
      }
3795

    
3796
      $form['filters'][$id]['name'] = array(
3797
        '#markup' => $name,
3798
      );
3799
    }
3800
    else {
3801
      $form['filters'][$id]['name'] = array('#markup' => t('Broken field @id', array('@id' => $id)));
3802
    }
3803
    $form['filters'][$id]['removed'] = array(
3804
      '#type' => 'checkbox',
3805
      '#id' => 'views-removed-' . $id,
3806
      '#attributes' => array('class' => array('views-remove-checkbox')),
3807
      '#default_value' => 0,
3808
    );
3809
  }
3810

    
3811
  if (isset($form_state['update_name'])) {
3812
    $name = $form_state['update_name'];
3813
  }
3814

    
3815
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_rearrange_filter_form');
3816
  $form['buttons']['add_group'] = array(
3817
    '#type' => 'submit',
3818
    '#value' => t('Create new filter group'),
3819
    '#id' => 'views-add-group',
3820
    '#group' => 'add',
3821
  );
3822

    
3823
  return $form;
3824
}
3825

    
3826
/**
3827
 * Turn the rearrange form into a proper table
3828
 */
3829
function theme_views_ui_rearrange_filter_form(&$vars) {
3830
  $form = $vars['form'];
3831
  $rows = $ungroupable_rows = array();
3832
  // Enable grouping only if > 1 group.
3833
  $grouping = count(array_keys($form['#group_options'])) > 1;
3834

    
3835
  foreach ($form['#group_renders'] as $group_id => $contents) {
3836
    // Header row for the group.
3837
    if ($group_id !== 'ungroupable') {
3838
      // Set up tabledrag so that it changes the group dropdown when rows are
3839
      // dragged between groups.
3840
      drupal_add_tabledrag('views-rearrange-filters', 'match', 'sibling', 'views-group-select', 'views-group-select-' . $group_id);
3841

    
3842
      // Title row, spanning all columns.
3843
      $row = array();
3844
      // Add a cell to the first row, containing the group operator.
3845
      $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)));
3846
      // Title.
3847
      $row[] = array('class' => array('group', 'group-title'), 'data' => '<span>' . $form['#group_options'][$group_id] . '</span>', 'colspan' => 4);
3848
      $rows[] = array('class' => array('views-group-title'), 'data' => $row, 'id' => 'views-group-title-' . $group_id);
3849

    
3850
      // Row which will only appear if the group has nothing in it.
3851
      $row = array();
3852
      $class = 'group-' . (count($contents) ? 'populated' : 'empty');
3853
      $instructions = '<span>' . t('No filters have been added.') . '</span> <span class="js-only">' . t('Drag to add filters.') . '</span>';
3854
      // When JavaScript is enabled, the button for removing the group (if it's
3855
      // present) should be hidden, since it will be replaced by a link on the
3856
      // client side.
3857
      if (!empty($form['remove_groups'][$group_id]['#type']) && $form['remove_groups'][$group_id]['#type'] == 'submit') {
3858
        $form['remove_groups'][$group_id]['#attributes']['class'][] = 'js-hide';
3859
      }
3860
      $row[] = array('colspan' => 5, 'data' => $instructions . drupal_render($form['remove_groups'][$group_id]));
3861
      $rows[] = array('class' => array("group-message", "group-$group_id-message", $class), 'data' => $row, 'id' => 'views-group-' . $group_id);
3862
    }
3863

    
3864
    foreach ($contents as $id) {
3865
      if (isset($form['filters'][$id]['name'])) {
3866
        $row = array();
3867
        $row[] = drupal_render($form['filters'][$id]['name']);
3868
        $form['filters'][$id]['weight']['#attributes']['class'] = array('weight');
3869
        $row[] = drupal_render($form['filters'][$id]['weight']);
3870
        $form['filters'][$id]['group']['#attributes']['class'] = array('views-group-select views-group-select-' . $group_id);
3871
        $row[] = drupal_render($form['filters'][$id]['group']);
3872
        $form['filters'][$id]['removed']['#attributes']['class'][] = 'js-hide';
3873
        $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));
3874

    
3875
        $row = array('data' => $row, 'class' => array('draggable'), 'id' => 'views-row-' . $id);
3876
        if ($group_id !== 'ungroupable') {
3877
          $rows[] = $row;
3878
        }
3879
        else {
3880
          $ungroupable_rows[] = $row;
3881
        }
3882
      }
3883
    }
3884
  }
3885
  if (empty($rows)) {
3886
    $rows[] = array(array('data' => t('No fields available.'), 'colspan' => '2'));
3887
  }
3888

    
3889
  $output = drupal_render($form['override']);
3890
  $output .= '<div class="scroll">';
3891
  if ($grouping) {
3892
    $output .= drupal_render($form['filter_groups']['operator']);
3893
  }
3894
  else {
3895
    $form['filter_groups']['groups'][0]['#title'] = t('Operator');
3896
    $output .= drupal_render($form['filter_groups']['groups'][0]);
3897
  }
3898

    
3899
  if (!empty($ungroupable_rows)) {
3900
    drupal_add_tabledrag('views-rearrange-filters-ungroupable', 'order', 'sibling', 'weight');
3901
    $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')));
3902
    $output .= theme('table', array('header' => $header, 'rows' => $ungroupable_rows, 'attributes' => array('id' => 'views-rearrange-filters-ungroupable', 'class' => array('arrange'))));
3903
  }
3904

    
3905
  // Set up tabledrag so that the weights are changed when rows are dragged.
3906
  drupal_add_tabledrag('views-rearrange-filters', 'order', 'sibling', 'weight');
3907
  $output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'views-rearrange-filters', 'class' => array('arrange'))));
3908
  $output .= '</div>';
3909

    
3910
  // When JavaScript is enabled, the button for adding a new group should be
3911
  // hidden, since it will be replaced by a link on the client side.
3912
  $form['buttons']['add_group']['#attributes']['class'][] = 'js-hide';
3913

    
3914
  // Render the rest of the form and return.
3915
  $output .= drupal_render_children($form);
3916
  return $output;
3917
}
3918

    
3919
/**
3920
 * Submit handler for rearranging form
3921
 */
3922
function views_ui_rearrange_filter_form_submit($form, &$form_state) {
3923
  $types = views_object_types();
3924
  $display = &$form_state['view']->display[$form_state['display_id']];
3925
  $remember_groups = array();
3926

    
3927
  if (!empty($form_state['view']->form_cache)) {
3928
    $old_fields = $form_state['view']->form_cache['handlers'];
3929
  }
3930
  else {
3931
    $old_fields = $display->handler->get_option($types[$form_state['type']]['plural']);
3932
  }
3933
  $count = 0;
3934

    
3935
  $groups = $form_state['values']['filter_groups'];
3936
  // Whatever button was clicked, re-calculate field information.
3937
  $new_fields = $order = array();
3938

    
3939
  // Make an array with the weights
3940
  foreach ($form_state['values']['filters'] as $field => $info) {
3941
    // add each value that is a field with a weight to our list, but only if
3942
    // it has had its 'removed' checkbox checked.
3943
    if (is_array($info) && empty($info['removed'])) {
3944
      if (isset($info['weight'])) {
3945
        $order[$field] = $info['weight'];
3946
      }
3947

    
3948
      if (isset($info['group'])) {
3949
        $old_fields[$field]['group'] = $info['group'];
3950
        $remember_groups[$info['group']][] = $field;
3951
      }
3952
    }
3953
  }
3954

    
3955
  // Sort the array
3956
  asort($order);
3957

    
3958
  // Create a new list of fields in the new order.
3959
  foreach (array_keys($order) as $field) {
3960
    $new_fields[$field] = $old_fields[$field];
3961
  }
3962

    
3963
  // If the #group property is set on the clicked button, that means we are
3964
  // either adding or removing a group, not actually updating the filters.
3965
  if (!empty($form_state['clicked_button']['#group'])) {
3966
    if ($form_state['clicked_button']['#group'] == 'add') {
3967
      // Add a new group
3968
      $groups['groups'][] = 'AND';
3969
    }
3970
    else {
3971
      // Renumber groups above the removed one down.
3972
      foreach (array_keys($groups['groups']) as $group_id) {
3973
        if ($group_id >= $form_state['clicked_button']['#group']) {
3974
          $old_group = $group_id + 1;
3975
          if (isset($groups['groups'][$old_group])) {
3976
            $groups['groups'][$group_id] = $groups['groups'][$old_group];
3977
            if (isset($remember_groups[$old_group])) {
3978
              foreach ($remember_groups[$old_group] as $id) {
3979
                $new_fields[$id]['group'] = $group_id;
3980
              }
3981
            }
3982
          }
3983
          else {
3984
            // If this is the last one, just unset it.
3985
            unset($groups['groups'][$group_id]);
3986
          }
3987
        }
3988
      }
3989
    }
3990
    // Update our cache with values so that cancel still works the way
3991
    // people expect.
3992
    $form_state['view']->form_cache = array(
3993
      'key' => 'rearrange-filter',
3994
      'groups' => $groups,
3995
      'handlers' => $new_fields,
3996
    );
3997

    
3998
    // Return to this form except on actual Update.
3999
    views_ui_add_form_to_stack('rearrange-filter', $form_state['view'], $form_state['display_id'], array($form_state['type']));
4000
  }
4001
  else {
4002
    // The actual update button was clicked. Remove the empty groups, and
4003
    // renumber them sequentially.
4004
    ksort($remember_groups);
4005
    $groups['groups'] = views_array_key_plus(array_values(array_intersect_key($groups['groups'], $remember_groups)));
4006
    // Change the 'group' key on each field to match. Here, $mapping is an
4007
    // array whose keys are the old group numbers and whose values are the new
4008
    // (sequentially numbered) ones.
4009
    $mapping = array_flip(views_array_key_plus(array_keys($remember_groups)));
4010
    foreach ($new_fields as &$new_field) {
4011
      $new_field['group'] = $mapping[$new_field['group']];
4012
    }
4013

    
4014
    // Write the changed handler values.
4015
    $display->handler->set_option($types[$form_state['type']]['plural'], $new_fields);
4016
    $display->handler->set_option('filter_groups', $groups);
4017
    if (isset($form_state['view']->form_cache)) {
4018
      unset($form_state['view']->form_cache);
4019
    }
4020
  }
4021

    
4022
  // Store in cache.
4023
  views_ui_cache_set($form_state['view']);
4024
}
4025

    
4026
/**
4027
 * Form to add_item items in the views UI.
4028
 */
4029
function views_ui_add_item_form($form, &$form_state) {
4030
  $view = &$form_state['view'];
4031
  $display_id = $form_state['display_id'];
4032
  $type = $form_state['type'];
4033

    
4034
  $form = array(
4035
    'options' => array(
4036
      '#theme_wrappers' => array('container'),
4037
      '#attributes' => array('class' => array('scroll')),
4038
    ),
4039
  );
4040

    
4041
  ctools_add_js('dependent');
4042

    
4043
  if (!$view->set_display($display_id)) {
4044
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4045
  }
4046
  $display = &$view->display[$display_id];
4047

    
4048
  $types = views_object_types();
4049
  $ltitle = $types[$type]['ltitle'];
4050
  $section = $types[$type]['plural'];
4051

    
4052
  if (!empty($types[$type]['type'])) {
4053
    $type = $types[$type]['type'];
4054
  }
4055

    
4056
  $form['#title'] = t('Add @type', array('@type' => $ltitle));
4057
  $form['#section'] = $display_id . 'add-item';
4058

    
4059

    
4060
  // Add the display override dropdown.
4061
  views_ui_standard_display_dropdown($form, $form_state, $section);
4062

    
4063
  // Figure out all the base tables allowed based upon what the relationships provide.
4064
  $base_tables = $view->get_base_tables();
4065
  $options = views_fetch_fields(array_keys($base_tables), $type, $display->handler->use_group_by());
4066

    
4067
  if (!empty($options)) {
4068
    $form['options']['controls'] = array(
4069
      '#theme_wrappers' => array('container'),
4070
      '#id' => 'views-filterable-options-controls',
4071
      '#attributes' => array('class' => array('container-inline')),
4072
    );
4073
    $form['options']['controls']['options_search'] = array(
4074
      '#type' => 'textfield',
4075
      '#title' => t('Search'),
4076
    );
4077

    
4078
    $groups = array('all' => t('- All -'));
4079
    $form['options']['controls']['group'] = array(
4080
      '#type' => 'select',
4081
      '#title' => t('Filter'),
4082
      '#options' => array(),
4083
      '#attributes' => array('class' => array('ctools-master-dependent')),
4084
    );
4085

    
4086
    $form['options']['name'] = array(
4087
      '#prefix' => '<div class="views-radio-box form-checkboxes views-filterable-options">',
4088
      '#suffix' => '</div>',
4089
      '#tree' => TRUE,
4090
      '#default_value' => 'all',
4091
    );
4092

    
4093
    // Group options first to simplify the DOM objects that Views
4094
    // dependent JS will act upon.
4095
    $grouped_options = array();
4096
    foreach ($options as $key => $option) {
4097
      $group = preg_replace('/[^a-z0-9]/', '-', strtolower($option['group']));
4098
      $groups[$group] = $option['group'];
4099
      $grouped_options[$group][$key] = $option;
4100
      if (!empty($option['aliases']) && is_array($option['aliases'])) {
4101
        foreach ($option['aliases'] as $id => $alias) {
4102
          if (empty($alias['base']) || !empty($base_tables[$alias['base']])) {
4103
            $copy = $option;
4104
            $copy['group'] = $alias['group'];
4105
            $copy['title'] = $alias['title'];
4106
            if (isset($alias['help'])) {
4107
              $copy['help'] = $alias['help'];
4108
            }
4109

    
4110
            $group = preg_replace('/[^a-z0-9]/', '-', strtolower($copy['group']));
4111
            $groups[$group] = $copy['group'];
4112
            $grouped_options[$group][$key . '$' . $id] = $copy;
4113
          }
4114
        }
4115
      }
4116
    }
4117

    
4118
    foreach ($grouped_options as $group => $group_options) {
4119
      $form['options']['name'][$group . '_start']['#markup'] = '<div class="ctools-dependent-all ctools-dependent-' . $group . '">';
4120
      $zebra = 0;
4121
      foreach ($group_options as $key => $option) {
4122
        $zebra_class = ($zebra % 2) ? 'odd' : 'even';
4123
        $form['options']['name'][$key] = array(
4124
          '#type' => 'checkbox',
4125
          '#title' => t('!group: !field', array('!group' => check_plain($option['group']), '!field' => check_plain($option['title']))),
4126
          '#description' => filter_xss_admin($option['help']),
4127
          '#return_value' => $key,
4128
          '#prefix' => "<div class='$zebra_class filterable-option'>",
4129
          '#suffix' => '</div>',
4130
        );
4131
        $zebra++;
4132
      }
4133
      $form['options']['name'][$group . '_end']['#markup'] = '</div>';
4134
    }
4135

    
4136
    $form['options']['controls']['group']['#options'] = $groups;
4137
  }
4138
  else {
4139
    $form['options']['markup'] = array(
4140
      '#markup' => '<div class="form-item">' . t('There are no @types available to add.', array('@types' =>  $ltitle)) . '</div>',
4141
    );
4142
  }
4143
  // Add a div to show the selected items
4144
  $form['selected'] = array(
4145
    '#type' => 'item',
4146
    '#markup' => '<div class="views-selected-options"></div>',
4147
    '#title' => t('Selected') . ':',
4148
    '#theme_wrappers' => array('form_element', 'views_container'),
4149
    '#attributes' => array('class' => array('container-inline', 'views-add-form-selected')),
4150
  );
4151
  ctools_include('dependent');
4152
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_add_item_form', t('Add and configure @types', array('@types' => $ltitle)));
4153

    
4154
  // Remove the default submit function.
4155
  $form['buttons']['submit']['#submit'] = array_diff($form['buttons']['submit']['#submit'], array('views_ui_standard_submit'));
4156
  $form['buttons']['submit']['#submit'][] = 'views_ui_add_item_form_submit';
4157

    
4158
  return $form;
4159
}
4160

    
4161
/**
4162
 * Submit handler for adding new item(s) to a view.
4163
 */
4164
function views_ui_add_item_form_submit($form, &$form_state) {
4165
  $type = $form_state['type'];
4166
  $types = views_object_types();
4167
  $section = $types[$type]['plural'];
4168

    
4169
  // Handle the override select.
4170
  list($was_defaulted, $is_defaulted) = views_ui_standard_override_values($form, $form_state);
4171
  if ($was_defaulted && !$is_defaulted) {
4172
    // We were using the default display's values, but we're now overriding
4173
    // the default display and saving values specific to this display.
4174
    $display = &$form_state['view']->display[$form_state['display_id']];
4175
    // set_override toggles the override of this section.
4176
    $display->handler->set_override($section);
4177
  }
4178
  elseif (!$was_defaulted && $is_defaulted) {
4179
    // We used to have an override for this display, but the user now wants
4180
    // to go back to the default display.
4181
    // Overwrite the default display with the current form values, and make
4182
    // the current display use the new default values.
4183
    $display = &$form_state['view']->display[$form_state['display_id']];
4184
    // options_override toggles the override of this section.
4185
    $display->handler->set_override($section);
4186
  }
4187

    
4188
  if (!empty($form_state['values']['name']) && is_array($form_state['values']['name'])) {
4189
    // Loop through each of the items that were checked and add them to the view.
4190
    foreach (array_keys(array_filter($form_state['values']['name'])) as $field) {
4191
      list($table, $field) = explode('.', $field, 2);
4192

    
4193
      if ($cut = strpos($field, '$')) {
4194
        $field = substr($field, 0, $cut);
4195
      }
4196
      $id = $form_state['view']->add_item($form_state['display_id'], $type, $table, $field);
4197

    
4198
      // check to see if we have group by settings
4199
      $key = $type;
4200
      // Footer,header and empty text have a different internal handler type(area).
4201
      if (isset($types[$type]['type'])) {
4202
        $key = $types[$type]['type'];
4203
      }
4204
      $handler = views_get_handler($table, $field, $key);
4205
      if ($form_state['view']->display_handler->use_group_by() && $handler->use_group_by()) {
4206
        views_ui_add_form_to_stack('config-item-group', $form_state['view'], $form_state['display_id'], array($type, $id));
4207
      }
4208

    
4209
      // check to see if this type has settings, if so add the settings form first
4210
      if ($handler && $handler->has_extra_options()) {
4211
        views_ui_add_form_to_stack('config-item-extra', $form_state['view'], $form_state['display_id'], array($type, $id));
4212
      }
4213
      // Then add the form to the stack
4214
      views_ui_add_form_to_stack('config-item', $form_state['view'], $form_state['display_id'], array($type, $id));
4215
    }
4216
  }
4217

    
4218
  if (isset($form_state['view']->form_cache)) {
4219
    unset($form_state['view']->form_cache);
4220
  }
4221

    
4222
  // Store in cache
4223
  views_ui_cache_set($form_state['view']);
4224
}
4225

    
4226
/**
4227
 * Override handler for views_ui_edit_display_form
4228
 */
4229
function views_ui_config_item_form_build_group($form, &$form_state) {
4230
  $item = &$form_state['handler']->options;
4231
  // flip. If the filter was a group, set back to a standard filter.
4232
  $item['is_grouped'] = empty($item['is_grouped']);
4233

    
4234
  // If necessary, set new defaults:
4235
  if ($item['is_grouped']) {
4236
    $form_state['handler']->build_group_options();
4237
  }
4238

    
4239
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4240

    
4241
  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);
4242

    
4243
  views_ui_cache_set($form_state['view']);
4244
  $form_state['rerender'] = TRUE;
4245
  $form_state['rebuild'] = TRUE;
4246
  $form_state['force_build_group_options'] = TRUE;
4247
}
4248

    
4249
/**
4250
 * Add a new group to the exposed filter groups.
4251
 */
4252
function views_ui_config_item_form_add_group($form, &$form_state) {
4253
  $item =& $form_state['handler']->options;
4254

    
4255
  // Add a new row.
4256
  $item['group_info']['group_items'][] = array();
4257

    
4258
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4259

    
4260
  views_ui_cache_set($form_state['view']);
4261
  $form_state['rerender'] = TRUE;
4262
  $form_state['rebuild'] = TRUE;
4263
  $form_state['force_build_group_options'] = TRUE;
4264
}
4265

    
4266
/**
4267
 * Form to config_item items in the views UI.
4268
 */
4269
function views_ui_config_item_form($form, &$form_state) {
4270
  $view = &$form_state['view'];
4271
  $display_id = $form_state['display_id'];
4272
  $type = $form_state['type'];
4273
  $id = $form_state['id'];
4274

    
4275
  $form = array(
4276
    'options' => array(
4277
      '#tree' => TRUE,
4278
      '#theme_wrappers' => array('container'),
4279
      '#attributes' => array('class' => array('scroll')),
4280
    ),
4281
  );
4282
  if (!$view->set_display($display_id)) {
4283
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4284
  }
4285
  $item = $view->get_item($display_id, $type, $id);
4286

    
4287
  if ($item) {
4288
    $handler = $view->display_handler->get_handler($type, $id);
4289
    if (empty($handler)) {
4290
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4291
    }
4292
    else {
4293
      $types = views_object_types();
4294

    
4295
      // If this item can come from the default display, show a dropdown
4296
      // that lets the user choose which display the changes should apply to.
4297
      if ($view->display_handler->defaultable_sections($types[$type]['plural'])) {
4298
        $form_state['section'] = $types[$type]['plural'];
4299
        views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
4300
      }
4301

    
4302
      // A whole bunch of code to figure out what relationships are valid for
4303
      // this item.
4304
      $relationships = $view->display_handler->get_option('relationships');
4305
      $relationship_options = array();
4306

    
4307
      foreach ($relationships as $relationship) {
4308
        // relationships can't link back to self. But also, due to ordering,
4309
        // relationships can only link to prior relationships.
4310
        if ($type == 'relationship' && $id == $relationship['id']) {
4311
          break;
4312
        }
4313
        $relationship_handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
4314
        // ignore invalid/broken relationships.
4315
        if (empty($relationship_handler)) {
4316
          continue;
4317
        }
4318

    
4319
        // If this relationship is valid for this type, add it to the list.
4320
        $data = views_fetch_data($relationship['table']);
4321
        $base = $data[$relationship['field']]['relationship']['base'];
4322
        $base_fields = views_fetch_fields($base, $form_state['type'], $view->display_handler->use_group_by());
4323
        if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
4324
          $relationship_handler->init($view, $relationship);
4325
          $relationship_options[$relationship['id']] = $relationship_handler->label();
4326
        }
4327
      }
4328

    
4329
      if (!empty($relationship_options)) {
4330
        // Make sure the existing relationship is even valid. If not, force
4331
        // it to none.
4332
        $base_fields = views_fetch_fields($view->base_table, $form_state['type'], $view->display_handler->use_group_by());
4333
        if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
4334
          $relationship_options = array_merge(array('none' => t('Do not use a relationship')), $relationship_options);
4335
        }
4336
        $rel = empty($item['relationship']) ? 'none' : $item['relationship'];
4337
        if (empty($relationship_options[$rel])) {
4338
          // Pick the first relationship.
4339
          $rel = key($relationship_options);
4340
          // We want this relationship option to get saved even if the user
4341
          // skips submitting the form.
4342
          $view->set_item_option($display_id, $type, $id, 'relationship', $rel);
4343
          $temp_view = $view->clone_view();
4344
          views_ui_cache_set($temp_view);
4345
        }
4346

    
4347
        $form['options']['relationship'] = array(
4348
          '#type' => 'select',
4349
          '#title' => t('Relationship'),
4350
          '#options' => $relationship_options,
4351
          '#default_value' => $rel,
4352
          '#weight' => -500,
4353
        );
4354
      }
4355
      else {
4356
        $form['options']['relationship'] = array(
4357
          '#type' => 'value',
4358
          '#value' => 'none',
4359
        );
4360
      }
4361

    
4362
      $form['#title'] = t('Configure @type: @item', array('@type' => $types[$type]['lstitle'], '@item' => $handler->ui_name()));
4363

    
4364
      if (!empty($handler->definition['help'])) {
4365
        $form['options']['form_description'] = array(
4366
          '#markup' => $handler->definition['help'],
4367
          '#theme_wrappers' => array('container'),
4368
          '#attributes' => array('class' => array('form-item description')),
4369
          '#weight' => -1000,
4370
        );
4371
      }
4372

    
4373
      $form['#section'] = $display_id . '-' . $type . '-' . $id;
4374

    
4375
      // Get form from the handler.
4376
      $handler->options_form($form['options'], $form_state);
4377
      $form_state['handler'] = &$handler;
4378
    }
4379

    
4380
    $name = NULL;
4381
    if (isset($form_state['update_name'])) {
4382
      $name = $form_state['update_name'];
4383
    }
4384

    
4385
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_form', $name, t('Remove'), 'remove');
4386
    // Only validate the override values, because this values are required for
4387
    // the override selection.
4388
    $form['buttons']['remove']['#limit_validation_errors'] = array(array('override'));
4389
  }
4390

    
4391
  return $form;
4392
}
4393

    
4394
/**
4395
 * Submit handler for configing new item(s) to a view.
4396
 */
4397
function views_ui_config_item_form_validate($form, &$form_state) {
4398
  $form_state['handler']->options_validate($form['options'], $form_state);
4399

    
4400
  if (form_get_errors()) {
4401
    $form_state['rerender'] = TRUE;
4402
  }
4403
}
4404

    
4405
/**
4406
 * A submit handler that is used for storing temporary items when using
4407
 * multi-step changes, such as ajax requests.
4408
 */
4409
function views_ui_config_item_form_submit_temporary($form, &$form_state) {
4410
  // Run it through the handler's submit function.
4411
  $form_state['handler']->options_submit($form['options'], $form_state);
4412
  $item = $form_state['handler']->options;
4413
  $types = views_object_types();
4414

    
4415
  // For footer/header $handler_type is area but $type is footer/header.
4416
  // For all other handle types it's the same.
4417
  $handler_type = $type = $form_state['type'];
4418
  if (!empty($types[$type]['type'])) {
4419
    $handler_type = $types[$type]['type'];
4420
  }
4421

    
4422
  $override = NULL;
4423
  if ($form_state['view']->display_handler->use_group_by() && !empty($item['group_type'])) {
4424
    if (empty($form_state['view']->query)) {
4425
      $form_state['view']->init_query();
4426
    }
4427
    $aggregate = $form_state['view']->query->get_aggregation_info();
4428
    if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
4429
      $override = $aggregate[$item['group_type']]['handler'][$type];
4430
    }
4431
  }
4432

    
4433
  // Create a new handler and unpack the options from the form onto it. We
4434
  // can use that for storage.
4435
  $handler = views_get_handler($item['table'], $item['field'], $handler_type, $override);
4436
  $handler->init($form_state['view'], $item);
4437

    
4438

    
4439
  // Add the incoming options to existing options because items using
4440
  // the extra form may not have everything in the form here.
4441
  $options = $form_state['values']['options'] + $form_state['handler']->options;
4442

    
4443
  // This unpacks only options that are in the definition, ensuring random
4444
  // extra stuff on the form is not sent through.
4445
  $handler->unpack_options($handler->options, $options, NULL, FALSE);
4446

    
4447
  // Store the item back on the view
4448
  $form_state['view']->temporary_options[$type][$form_state['id']] = $handler->options;
4449

    
4450
  // @todo: Figure out whether views_ui_ajax_form is perhaps the better place to fix the issue.
4451
  // views_ui_ajax_form() drops the current form from the stack, even if it's an #ajax.
4452
  // So add the item back to the top of the stack.
4453
  views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($type, $item['id']), TRUE);
4454

    
4455
  $form_state['rerender'] = TRUE;
4456
  $form_state['rebuild'] = TRUE;
4457
  // Write to cache
4458
  views_ui_cache_set($form_state['view']);
4459
}
4460

    
4461
/**
4462
 * Submit handler for configing new item(s) to a view.
4463
 */
4464
function views_ui_config_item_form_submit($form, &$form_state) {
4465
  // Run it through the handler's submit function.
4466
  $form_state['handler']->options_submit($form['options'], $form_state);
4467
  $item = $form_state['handler']->options;
4468
  $types = views_object_types();
4469

    
4470
  // For footer/header $handler_type is area but $type is footer/header.
4471
  // For all other handle types it's the same.
4472
  $handler_type = $type = $form_state['type'];
4473
  if (!empty($types[$type]['type'])) {
4474
    $handler_type = $types[$type]['type'];
4475
  }
4476

    
4477
  $override = NULL;
4478
  if ($form_state['view']->display_handler->use_group_by() && !empty($item['group_type'])) {
4479
    if (empty($form_state['view']->query)) {
4480
      $form_state['view']->init_query();
4481
    }
4482
    $aggregate = $form_state['view']->query->get_aggregation_info();
4483
    if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
4484
      $override = $aggregate[$item['group_type']]['handler'][$type];
4485
    }
4486
  }
4487

    
4488
  // Create a new handler and unpack the options from the form onto it. We
4489
  // can use that for storage.
4490
  $handler = views_get_handler($item['table'], $item['field'], $handler_type, $override);
4491
  $handler->init($form_state['view'], $item);
4492

    
4493

    
4494
  // Add the incoming options to existing options because items using
4495
  // the extra form may not have everything in the form here.
4496
  $options = $form_state['values']['options'] + $form_state['handler']->options;
4497

    
4498
  // This unpacks only options that are in the definition, ensuring random
4499
  // extra stuff on the form is not sent through.
4500
  $handler->unpack_options($handler->options, $options, NULL, FALSE);
4501

    
4502
  // Store the item back on the view
4503
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $handler->options);
4504

    
4505
  // Ensure any temporary options are removed.
4506
  if (isset($form_state['view']->temporary_options[$type][$form_state['id']])) {
4507
    unset($form_state['view']->temporary_options[$type][$form_state['id']]);
4508
  }
4509

    
4510
  // Write to cache
4511
  views_ui_cache_set($form_state['view']);
4512
}
4513

    
4514
/**
4515
 * Form to config_item items in the views UI.
4516
 */
4517
function views_ui_config_item_group_form($type, &$form_state) {
4518
  $view = &$form_state['view'];
4519
  $display_id = $form_state['display_id'];
4520
  $type = $form_state['type'];
4521
  $id = $form_state['id'];
4522

    
4523
  $form = array(
4524
    'options' => array(
4525
      '#tree' => TRUE,
4526
      '#theme_wrappers' => array('container'),
4527
      '#attributes' => array('class' => array('scroll')),
4528
    ),
4529
  );
4530
  if (!$view->set_display($display_id)) {
4531
    views_ajax_render(t('Invalid display id @display', array('@display' => $display_id)));
4532
  }
4533

    
4534
  $view->init_query();
4535

    
4536
  $item = $view->get_item($display_id, $type, $id);
4537

    
4538
  if ($item) {
4539
    $handler = $view->display_handler->get_handler($type, $id);
4540
    if (empty($handler)) {
4541
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4542
    }
4543
    else {
4544
      $handler->init($view, $item);
4545
      $types = views_object_types();
4546

    
4547
      $form['#title'] = t('Configure group settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
4548

    
4549
      $handler->groupby_form($form['options'], $form_state);
4550
      $form_state['handler'] = &$handler;
4551
    }
4552

    
4553
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_group_form');
4554
  }
4555
  return $form;
4556
}
4557

    
4558
/**
4559
 * Submit handler for configing group settings on a view.
4560
 */
4561
function views_ui_config_item_group_form_submit($form, &$form_state) {
4562
  $item =& $form_state['handler']->options;
4563
  $type = $form_state['type'];
4564
  $id = $form_state['id'];
4565

    
4566
  $handler = views_get_handler($item['table'], $item['field'], $type);
4567
  $handler->init($form_state['view'], $item);
4568

    
4569
  $handler->groupby_form_submit($form, $form_state);
4570

    
4571
  // Store the item back on the view
4572
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4573

    
4574
  // Write to cache
4575
  views_ui_cache_set($form_state['view']);
4576
}
4577

    
4578
/**
4579
 * Submit handler for removing an item from a view
4580
 */
4581
function views_ui_config_item_form_remove($form, &$form_state) {
4582
  // Store the item back on the view
4583
  list($was_defaulted, $is_defaulted) = views_ui_standard_override_values($form, $form_state);
4584
  // If the display selection was changed toggle the override value.
4585
  if ($was_defaulted != $is_defaulted) {
4586
    $display =& $form_state['view']->display[$form_state['display_id']];
4587
    $display->handler->options_override($form, $form_state);
4588
  }
4589
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], NULL);
4590

    
4591
  // Write to cache
4592
  views_ui_cache_set($form_state['view']);
4593
}
4594

    
4595
/**
4596
 * Override handler for views_ui_edit_display_form
4597
 */
4598
function views_ui_config_item_form_expose($form, &$form_state) {
4599
  $item = &$form_state['handler']->options;
4600
  // flip
4601
  $item['exposed'] = empty($item['exposed']);
4602

    
4603
  // If necessary, set new defaults:
4604
  if ($item['exposed']) {
4605
    $form_state['handler']->expose_options();
4606
  }
4607

    
4608
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4609

    
4610
  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);
4611

    
4612
  views_ui_cache_set($form_state['view']);
4613
  $form_state['rerender'] = TRUE;
4614
  $form_state['rebuild'] = TRUE;
4615
  $form_state['force_expose_options'] = TRUE;
4616
}
4617

    
4618
/**
4619
 * Form to config_item items in the views UI.
4620
 */
4621
function views_ui_config_item_extra_form($form, &$form_state) {
4622
  $view = &$form_state['view'];
4623
  $display_id = $form_state['display_id'];
4624
  $type = $form_state['type'];
4625
  $id = $form_state['id'];
4626

    
4627
  $form = array(
4628
    'options' => array(
4629
      '#tree' => TRUE,
4630
      '#theme_wrappers' => array('container'),
4631
      '#attributes' => array('class' => array('scroll')),
4632
    ),
4633
  );
4634
  if (!$view->set_display($display_id)) {
4635
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4636
  }
4637
  $item = $view->get_item($display_id, $type, $id);
4638

    
4639
  if ($item) {
4640
    $handler = $view->display_handler->get_handler($type, $id);
4641
    if (empty($handler)) {
4642
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4643
    }
4644
    else {
4645
      $handler->init($view, $item);
4646
      $types = views_object_types();
4647

    
4648
      $form['#title'] = t('Configure extra settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
4649

    
4650
      $form['#section'] = $display_id . '-' . $type . '-' . $id;
4651

    
4652
      // Get form from the handler.
4653
      $handler->extra_options_form($form['options'], $form_state);
4654
      $form_state['handler'] = &$handler;
4655
    }
4656

    
4657
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_extra_form');
4658
  }
4659
  return $form;
4660
}
4661

    
4662
/**
4663
 * Validation handler for configing new item(s) to a view.
4664
 */
4665
function views_ui_config_item_extra_form_validate($form, &$form_state) {
4666
  $form_state['handler']->extra_options_validate($form['options'], $form_state);
4667
}
4668

    
4669
/**
4670
 * Submit handler for configing new item(s) to a view.
4671
 */
4672
function views_ui_config_item_extra_form_submit($form, &$form_state) {
4673
  // Run it through the handler's submit function.
4674
  $form_state['handler']->extra_options_submit($form['options'], $form_state);
4675
  $item = $form_state['handler']->options;
4676

    
4677
  // Store the data we're given.
4678
  foreach ($form_state['values']['options'] as $key => $value) {
4679
    $item[$key] = $value;
4680
  }
4681

    
4682
  // Store the item back on the view
4683
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4684

    
4685
  // Write to cache
4686
  views_ui_cache_set($form_state['view']);
4687
}
4688

    
4689
/**
4690
 * Form to config_style items in the views UI.
4691
 */
4692
function views_ui_config_style_form($form, &$form_state) {
4693
  $view = &$form_state['view'];
4694
  $display_id = $form_state['display_id'];
4695
  $type = $form_state['type'];
4696
  $id = $form_state['id'];
4697

    
4698
  $form = array(
4699
    'options' => array(
4700
      '#tree' => TRUE,
4701
      '#theme_wrappers' => array('container'),
4702
      '#attributes' => array('class' => array('scroll')),
4703
    ),
4704
  );
4705
  if (!$view->set_display($display_id)) {
4706
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4707
  }
4708
  $item = $view->get_item($display_id, $type, $id);
4709

    
4710
  if ($item) {
4711
    $handler = views_get_handler($item['table'], $item['field'], $type);
4712
    if (empty($handler)) {
4713
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4714
    }
4715
    else {
4716
      $handler->init($view, $item);
4717
      $types = views_object_types();
4718

    
4719
      $form['#title'] = t('Configure summary style for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
4720

    
4721
      $form['#section'] = $display_id . '-' . $type . '-style-options';
4722

    
4723
      $plugin = views_get_plugin('style', $handler->options['style_plugin']);
4724
      if ($plugin) {
4725
        $form['style_options'] = array(
4726
          '#tree' => TRUE,
4727
        );
4728
        $plugin->init($view, $view->display[$display_id], $handler->options['style_options']);
4729

    
4730
        $plugin->options_form($form['style_options'], $form_state);
4731
      }
4732

    
4733
      $form_state['handler'] = &$handler;
4734
    }
4735

    
4736
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_style_form');
4737
  }
4738
  return $form;
4739
}
4740

    
4741
/**
4742
 * Submit handler for configing new item(s) to a view.
4743
 */
4744
function views_ui_config_style_form_submit($form, &$form_state) {
4745
  // Run it through the handler's submit function.
4746
  $form_state['handler']->options_submit($form['style_options'], $form_state);
4747
  $item = $form_state['handler']->options;
4748

    
4749
  // Store the data we're given.
4750
  $item['style_options'] = $form_state['values']['style_options'];
4751

    
4752
  // Store the item back on the view
4753
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4754

    
4755
  // Write to cache
4756
  views_ui_cache_set($form_state['view']);
4757
}
4758

    
4759
/**
4760
 * Get a list of roles in the system.
4761
 */
4762
function views_ui_get_roles() {
4763
  static $roles = NULL;
4764
  if (!isset($roles)) {
4765
    $roles = array();
4766
    $result = db_query("SELECT r.rid, r.name FROM {role} r ORDER BY r.name");
4767
    foreach ($result as $obj) {
4768
      $roles[$obj->rid] = $obj->name;
4769
    }
4770
  }
4771

    
4772
  return $roles;
4773
}
4774

    
4775
/**
4776
 * Form builder for the admin display defaults page.
4777
 */
4778
function views_ui_admin_settings_basic() {
4779
  $form = array();
4780
  $form['#attached']['css'] = views_ui_get_admin_css();
4781

    
4782
  $options = array();
4783
  foreach (list_themes() as $name => $theme) {
4784
    if ($theme->status) {
4785
      $options[$name] = $theme->info['name'];
4786
    }
4787
  }
4788

    
4789
  // This is not currently a fieldset but we may want it to be later,
4790
  // so this will make it easier to change if we do.
4791
  $form['basic'] = array();
4792

    
4793
  $form['basic']['views_ui_show_listing_filters'] = array(
4794
    '#type' => 'checkbox',
4795
    '#title' => t('Show filters on the list of views'),
4796
    '#default_value' => variable_get('views_ui_show_listing_filters', FALSE),
4797
  );
4798
  $form['basic']['views_ui_show_advanced_help_warning'] = array(
4799
    '#type' => 'checkbox',
4800
    '#title' => t('Show advanced help warning'),
4801
    '#default_value' => variable_get('views_ui_show_advanced_help_warning', TRUE),
4802
  );
4803

    
4804
  $form['basic']['views_ui_show_master_display'] = array(
4805
    '#type' => 'checkbox',
4806
    '#title' => t('Always show the master display'),
4807
    '#description' => t('Advanced users of views may choose to see the master (i.e. default) display.'),
4808
    '#default_value' => variable_get('views_ui_show_master_display', FALSE),
4809
  );
4810

    
4811
  $form['basic']['views_ui_show_advanced_column'] = array(
4812
    '#type' => 'checkbox',
4813
    '#title' => t('Always show advanced display settings'),
4814
    '#description' => t('Default to showing advanced display settings, such as relationships and contextual filters.'),
4815
    '#default_value' => variable_get('views_ui_show_advanced_column', FALSE),
4816
  );
4817

    
4818
  $form['basic']['views_ui_display_embed'] = array(
4819
    '#type' => 'checkbox',
4820
    '#title' => t('Show the embed display in the ui.'),
4821
    '#description' => t('Allow advanced user to use the embed view display. The plugin itself works if it\'s not visible in the ui'),
4822
    '#default_value' => variable_get('views_ui_display_embed', FALSE),
4823
  );
4824

    
4825
  $form['basic']['views_ui_custom_theme'] = array(
4826
    '#type' => 'select',
4827
    '#title' => t('Custom admin theme for the Views UI'),
4828
    '#options' => array('_default' => t('- Use default -')) + $options,
4829
    '#default_value' => variable_get('views_ui_custom_theme', '_default'),
4830
    '#description' => t('In some cases you might want to select a different admin theme for the Views UI.')
4831
  );
4832

    
4833
  $form['basic']['views_exposed_filter_any_label'] = array(
4834
    '#type' => 'select',
4835
    '#title' => t('Label for "Any" value on non-required single-select exposed filters'),
4836
    '#options' => array('old_any' => '<Any>', 'new_any' => t('- Any -')),
4837
    '#default_value' => variable_get('views_exposed_filter_any_label', 'new_any'),
4838
  );
4839

    
4840
  $form['live_preview'] = array(
4841
    '#type' => 'fieldset',
4842
    '#title' => t('Live preview settings'),
4843
  );
4844

    
4845
  $form['live_preview']['views_ui_always_live_preview'] = array(
4846
    '#type' => 'checkbox',
4847
    '#title' => t('Automatically update preview on changes'),
4848
    '#default_value' => variable_get('views_ui_always_live_preview', TRUE),
4849
  );
4850

    
4851
//  $form['live_preview']['views_ui_always_live_preview_button'] = array(
4852
//    '#type' => 'checkbox',
4853
//    '#title' => t('Always show the preview button, even when the automatically update option is checked'),
4854
//    '#default_value' => variable_get('views_ui_always_live_preview_button', FALSE),
4855
//  );
4856

    
4857
  $form['live_preview']['views_ui_show_preview_information'] = array(
4858
    '#type' => 'checkbox',
4859
    '#title' => t('Show information and statistics about the view during live preview'),
4860
    '#default_value' => variable_get('views_ui_show_preview_information', TRUE),
4861
  );
4862

    
4863
  $form['live_preview']['views_ui_show_sql_query_where'] = array(
4864
    '#type' => 'radios',
4865
    '#options' => array(
4866
      'above' => t('Above the preview'),
4867
      'below' => t('Below the preview'),
4868
    ),
4869
    '#id' => 'edit-show-sql',
4870
    '#default_value' => variable_get('views_ui_show_sql_query_where', 'above'),
4871
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4872
    '#prefix' => '<div id="edit-show-sql-wrapper" class="views-dependent">',
4873
    '#suffix' => '</div>',
4874
  );
4875

    
4876
  $form['live_preview']['views_ui_show_sql_query'] = array(
4877
    '#type' => 'checkbox',
4878
    '#title' => t('Show the SQL query'),
4879
    '#default_value' => variable_get('views_ui_show_sql_query', FALSE),
4880
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4881
  );
4882
  $form['live_preview']['views_ui_show_performance_statistics'] = array(
4883
    '#type' => 'checkbox',
4884
    '#title' => t('Show performance statistics'),
4885
    '#default_value' => variable_get('views_ui_show_performance_statistics', FALSE),
4886
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4887
  );
4888

    
4889
  $form['live_preview']['views_show_additional_queries'] = array(
4890
    '#type' => 'checkbox',
4891
    '#title' => t('Show other queries run during render during live preview'),
4892
    '#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."),
4893
    '#default_value' => variable_get('views_show_additional_queries', FALSE),
4894
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4895
  );
4896

    
4897
//  $form['live_preview']['views_ui_show_performance_statistics_where'] = array(
4898

    
4899
  return system_settings_form($form);
4900
}
4901

    
4902
/**
4903
 * Form builder for the advanced admin settings page.
4904
 */
4905
function views_ui_admin_settings_advanced() {
4906
  $form = array();
4907
  $form['#attached']['css'] = views_ui_get_admin_css();
4908

    
4909
  $form['cache'] = array(
4910
    '#type' => 'fieldset',
4911
    '#title' => t('Caching'),
4912
  );
4913

    
4914
  $form['cache']['views_skip_cache'] = array(
4915
    '#type' => 'checkbox',
4916
    '#title' => t('Disable views data caching'),
4917
    '#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."),
4918
    '#default_value' => variable_get('views_skip_cache', FALSE),
4919
  );
4920

    
4921
  $form['cache']['clear_cache'] = array(
4922
    '#type' => 'submit',
4923
    '#value' => t("Clear Views' cache"),
4924
    '#submit' => array('views_ui_tools_clear_cache'),
4925
  );
4926

    
4927
  $form['debug'] = array(
4928
    '#type' => 'fieldset',
4929
    '#title' => t('Debugging'),
4930
  );
4931

    
4932
  $form['debug']['views_sql_signature'] = array(
4933
    '#type' => 'checkbox',
4934
    '#title' => t('Add Views signature to all SQL queries'),
4935
    '#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."),
4936

    
4937
    '#default_value' => variable_get('views_sql_signature', FALSE),
4938
  );
4939

    
4940
  $form['debug']['views_no_javascript'] = array(
4941
    '#type' => 'checkbox',
4942
    '#title' => t('Disable JavaScript with Views'),
4943
    '#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."),
4944
    '#default_value' => variable_get('views_no_javascript', FALSE),
4945
  );
4946

    
4947
  $form['debug']['views_devel_output'] = array(
4948
    '#type' => 'checkbox',
4949
    '#title' => t('Enable views performance statistics/debug messages via the Devel module'),
4950
    '#description' => t("Check this to enable some Views query and performance statistics/debug messages <em>if Devel is installed</em>."),
4951
    '#default_value' => variable_get('views_devel_output', FALSE),
4952
  );
4953

    
4954
  $form['locale'] = array(
4955
    '#type' => 'fieldset',
4956
    '#title' => t('Localization'),
4957
  );
4958

    
4959
  $form['locale']['views_localization_plugin'] =  array(
4960
    '#type' => 'radios',
4961
    '#title' => t('Translation method'),
4962
    '#options' => views_fetch_plugin_names('localization', NULL, array()),
4963
    '#default_value' => views_get_localization_plugin(),
4964
    '#description' => t('Select a translation method to use for Views data like header, footer, and empty text.'),
4965
  );
4966

    
4967
  $regions = array();
4968
  $regions['watchdog'] = t('Watchdog');
4969
  if (module_exists('devel')) {
4970
    $regions['message'] = t('Devel message(dpm)');
4971
    $regions['drupal_debug'] = t('Devel logging (tmp://drupal_debug.txt)');
4972
  }
4973

    
4974
  $form['debug']['views_devel_region'] = array(
4975
    '#type' => 'select',
4976
    '#title' => t('Page region to output performance statistics/debug messages'),
4977
    '#default_value' => variable_get('views_devel_region', 'footer'),
4978
    '#options' => $regions,
4979
    '#dependency' => array('edit-views-devel-output' => array(1)),
4980
  );
4981

    
4982
  $options = views_fetch_plugin_names('display_extender');
4983
  if (!empty($options)) {
4984
    $form['extenders'] = array(
4985
      '#type' => 'fieldset',
4986
    );
4987
    ;
4988
    $form['extenders']['views_display_extenders'] = array(
4989
      '#title' => t('Display extenders'),
4990
      '#default_value' => views_get_enabled_display_extenders(),
4991
      '#options' => $options,
4992
      '#type' => 'checkboxes',
4993
      '#description' => t('Select extensions of the views interface.')
4994
    );
4995
  }
4996

    
4997
  return system_settings_form($form);
4998
}
4999

    
5000
/**
5001
 * Submit hook to clear the views cache.
5002
 */
5003
function views_ui_tools_clear_cache() {
5004
  views_invalidate_cache();
5005
  drupal_set_message(t('The cache has been cleared.'));
5006
}
5007

    
5008
/**
5009
 * Submit hook to clear Drupal's theme registry (thereby triggering
5010
 * a templates rescan).
5011
 */
5012
function views_ui_config_item_form_rescan($form, &$form_state) {
5013
  drupal_theme_rebuild();
5014

    
5015
  // The 'Theme: Information' page is about to be shown again. That page
5016
  // analyzes the output of theme_get_registry(). However, this latter
5017
  // function uses an internal cache (which was initialized before we
5018
  // called drupal_theme_rebuild()) so it won't reflect the
5019
  // current state of our theme registry. The only way to clear that cache
5020
  // is to re-initialize the theme system:
5021
  unset($GLOBALS['theme']);
5022
  drupal_theme_initialize();
5023

    
5024
  $form_state['rerender'] = TRUE;
5025
  $form_state['rebuild'] = TRUE;
5026
}
5027

    
5028
/**
5029
 * Override handler for views_ui_edit_display_form
5030
 */
5031
function views_ui_edit_display_form_change_theme($form, &$form_state) {
5032
  // This is just a temporary variable.
5033
  $form_state['view']->theme = $form_state['values']['theme'];
5034

    
5035
  views_ui_cache_set($form_state['view']);
5036
  $form_state['rerender'] = TRUE;
5037
  $form_state['rebuild'] = TRUE;
5038
}
5039

    
5040
/**
5041
 * Page callback for views tag autocomplete
5042
 */
5043
function views_ui_autocomplete_tag($string = '') {
5044
  $matches = array();
5045
  // get matches from default views:
5046
  views_include('view');
5047
  $views = views_get_all_views();
5048
  foreach ($views as $view) {
5049
    if (!empty($view->tag) && strpos($view->tag, $string) === 0) {
5050
      $matches[$view->tag] = check_plain($view->tag);
5051
      if (count($matches) >= 10) {
5052
        break;
5053
      }
5054
    }
5055
  }
5056

    
5057
  drupal_json_output($matches);
5058
}
5059

    
5060
// ------------------------------------------------------------------
5061
// Get information from the Views data
5062

    
5063
function _views_weight_sort($a, $b) {
5064
  if ($a['weight'] != $b['weight']) {
5065
    return $a['weight'] < $b['weight'] ? -1 : 1;
5066
  }
5067
  if ($a['title'] != $b['title']) {
5068
    return $a['title'] < $b['title'] ? -1 : 1;
5069
  }
5070

    
5071
  return 0;
5072
}
5073

    
5074
/**
5075
 * Fetch a list of all base tables available
5076
 *
5077
 * @return
5078
 *   A keyed array of in the form of 'base_table' => 'Description'.
5079
 */
5080
function views_fetch_base_tables() {
5081
  static $base_tables = array();
5082
  if (empty($base_tables)) {
5083
    $weights = array();
5084
    $tables = array();
5085
    $data = views_fetch_data();
5086
    foreach ($data as $table => $info) {
5087
      if (!empty($info['table']['base'])) {
5088
        $tables[$table] = array(
5089
          'title' => $info['table']['base']['title'],
5090
          'description' => !empty($info['table']['base']['help']) ? $info['table']['base']['help'] : '',
5091
          'weight' => !empty($info['table']['base']['weight']) ? $info['table']['base']['weight'] : 0,
5092
        );
5093
      }
5094
    }
5095
    uasort($tables, '_views_weight_sort');
5096
    $base_tables = $tables;
5097
  }
5098

    
5099
  return $base_tables;
5100
}
5101

    
5102
function _views_sort_types($a, $b) {
5103
  $a_group = drupal_strtolower($a['group']);
5104
  $b_group = drupal_strtolower($b['group']);
5105
  if ($a_group != $b_group) {
5106
    return $a_group < $b_group ? -1 : 1;
5107
  }
5108

    
5109
  $a_title = drupal_strtolower($a['title']);
5110
  $b_title = drupal_strtolower($b['title']);
5111
  if ($a_title != $b_title) {
5112
    return $a_title < $b_title ? -1 : 1;
5113
  }
5114

    
5115
  return 0;
5116
}
5117

    
5118
/**
5119
 * Fetch a list of all fields available for a given base type.
5120
 *
5121
 * @param (array|string) $base
5122
 *   A list or a single base_table, for example node.
5123
 * @param string $type
5124
 *   The handler type, for example field or filter.
5125
 * @param bool $grouping
5126
 *   Should the result grouping by its 'group' label.
5127
 *
5128
 * @return array
5129
 *   A keyed array of in the form of 'base_table' => 'Description'.
5130
 */
5131
function views_fetch_fields($base, $type, $grouping = FALSE) {
5132
  static $fields = array();
5133
  if (empty($fields)) {
5134
    $data = views_fetch_data();
5135
    $start = microtime(TRUE);
5136
    // This constructs this ginormous multi dimensional array to
5137
    // collect the important data about fields. In the end,
5138
    // the structure looks a bit like this (using nid as an example)
5139
    // $strings['nid']['filter']['title'] = 'string'.
5140
    //
5141
    // This is constructed this way because the above referenced strings
5142
    // can appear in different places in the actual data structure so that
5143
    // the data doesn't have to be repeated a lot. This essentially lets
5144
    // each field have a cheap kind of inheritance.
5145

    
5146
    foreach ($data as $table => $table_data) {
5147
      $bases = array();
5148
      $strings = array();
5149
      $skip_bases = array();
5150
      foreach ($table_data as $field => $info) {
5151
        // Collect table data from this table
5152
        if ($field == 'table') {
5153
          // calculate what tables this table can join to.
5154
          if (!empty($info['join'])) {
5155
            $bases = array_keys($info['join']);
5156
          }
5157
          // And it obviously joins to itself.
5158
          $bases[] = $table;
5159
          continue;
5160
        }
5161
        foreach (array('field', 'sort', 'filter', 'argument', 'relationship', 'area') as $key) {
5162
          if (!empty($info[$key])) {
5163
            if ($grouping && !empty($info[$key]['no group by'])) {
5164
              continue;
5165
            }
5166
            if (!empty($info[$key]['skip base'])) {
5167
              foreach ((array) $info[$key]['skip base'] as $base_name) {
5168
                $skip_bases[$field][$key][$base_name] = TRUE;
5169
              }
5170
            }
5171
            elseif (!empty($info['skip base'])) {
5172
              foreach ((array) $info['skip base'] as $base_name) {
5173
                $skip_bases[$field][$key][$base_name] = TRUE;
5174
              }
5175
            }
5176
            // Don't show old fields. The real field will be added right.
5177
            if (isset($info[$key]['moved to'])) {
5178
              continue;
5179
            }
5180
            foreach (array('title', 'group', 'help', 'base', 'aliases') as $string) {
5181
              // First, try the lowest possible level
5182
              if (!empty($info[$key][$string])) {
5183
                $strings[$field][$key][$string] = $info[$key][$string];
5184
              }
5185
              // Then try the field level
5186
              elseif (!empty($info[$string])) {
5187
                $strings[$field][$key][$string] = $info[$string];
5188
              }
5189
              // Finally, try the table level
5190
              elseif (!empty($table_data['table'][$string])) {
5191
                $strings[$field][$key][$string] = $table_data['table'][$string];
5192
              }
5193
              else {
5194
                if ($string != 'base' && $string != 'base') {
5195
                  $strings[$field][$key][$string] = t("Error: missing @component", array('@component' => $string));
5196
                }
5197
              }
5198
            }
5199
          }
5200
        }
5201
      }
5202
      foreach ($bases as $base_name) {
5203
        foreach ($strings as $field => $field_strings) {
5204
          foreach ($field_strings as $type_name => $type_strings) {
5205
            if (empty($skip_bases[$field][$type_name][$base_name])) {
5206
              $fields[$base_name][$type_name]["$table.$field"] = $type_strings;
5207
            }
5208
          }
5209
        }
5210
      }
5211
    }
5212
//    vsm('Views UI data build time: ' . (views_microtime() - $start) * 1000 . ' ms');
5213
  }
5214

    
5215
  // If we have an array of base tables available, go through them
5216
  // all and add them together. Duplicate keys will be lost and that's
5217
  // Just Fine.
5218
  if (is_array($base)) {
5219
    $strings = array();
5220
    foreach ($base as $base_table) {
5221
      if (isset($fields[$base_table][$type])) {
5222
        $strings += $fields[$base_table][$type];
5223
      }
5224
    }
5225
    uasort($strings, '_views_sort_types');
5226
    return $strings;
5227
  }
5228

    
5229
  if (isset($fields[$base][$type])) {
5230
    uasort($fields[$base][$type], '_views_sort_types');
5231
    return $fields[$base][$type];
5232
  }
5233
  return array();
5234
}
5235

    
5236

    
5237
/**
5238
 * Theme the form for the table style plugin
5239
 */
5240
function theme_views_ui_style_plugin_table($variables) {
5241
  $form = $variables['form'];
5242

    
5243
  $output = drupal_render($form['description_markup']);
5244

    
5245
  $header = array(
5246
    t('Field'),
5247
    t('Column'),
5248
    t('Align'),
5249
    t('Separator'),
5250
    array(
5251
      'data' => t('Sortable'),
5252
      'align' => 'center',
5253
    ),
5254
    array(
5255
      'data' => t('Default order'),
5256
      'align' => 'center',
5257
    ),
5258
    array(
5259
      'data' => t('Default sort'),
5260
      'align' => 'center',
5261
    ),
5262
    array(
5263
      'data' => t('Hide empty column'),
5264
      'align' => 'center',
5265
    ),
5266
  );
5267
  $rows = array();
5268
  foreach (element_children($form['columns']) as $id) {
5269
    $row = array();
5270
    $row[] = check_plain(drupal_render($form['info'][$id]['name']));
5271
    $row[] = drupal_render($form['columns'][$id]);
5272
    $row[] = drupal_render($form['info'][$id]['align']);
5273
    $row[] = drupal_render($form['info'][$id]['separator']);
5274
    if (!empty($form['info'][$id]['sortable'])) {
5275
      $row[] = array(
5276
        'data' => drupal_render($form['info'][$id]['sortable']),
5277
        'align' => 'center',
5278
      );
5279
      $row[] = array(
5280
        'data' => drupal_render($form['info'][$id]['default_sort_order']),
5281
        'align' => 'center',
5282
      );
5283
      $row[] = array(
5284
        'data' => drupal_render($form['default'][$id]),
5285
        'align' => 'center',
5286
      );
5287
    }
5288
    else {
5289
      $row[] = '';
5290
      $row[] = '';
5291
      $row[] = '';
5292
    }
5293
    $row[] = array(
5294
      'data' => drupal_render($form['info'][$id]['empty_column']),
5295
      'align' => 'center',
5296
    );
5297
    $rows[] = $row;
5298
  }
5299

    
5300
  // Add the special 'None' row.
5301
  $rows[] = array(t('None'), '', '', '', '', '', array('align' => 'center', 'data' => drupal_render($form['default'][-1])), '');
5302

    
5303
  $output .= theme('table', array('header' => $header, 'rows' => $rows));
5304
  $output .= drupal_render_children($form);
5305
  return $output;
5306
}
5307

    
5308
/**
5309
 * Placeholder function for overriding $display->display_title.
5310
 *
5311
 * @todo Remove this function once editing the display title is possible.
5312
 */
5313
function views_ui_get_display_label($view, $display_id, $check_changed = TRUE) {
5314
  $title = $display_id == 'default' ? t('Master') : $view->display[$display_id]->display_title;
5315
  $title = views_ui_truncate($title, 25);
5316

    
5317
  if ($check_changed && !empty($view->changed_display[$display_id])) {
5318
    $changed = '*';
5319
    $title = $title . $changed;
5320
  }
5321

    
5322
  return $title;
5323
}
5324

    
5325
function views_ui_add_template_page() {
5326
  $templates = views_get_all_templates();
5327

    
5328
  if (empty($templates)) {
5329
    return t('There are no templates available.');
5330
  }
5331

    
5332
  $header = array(
5333
    t('Name'),
5334
    t('Description'),
5335
    t('Operation'),
5336
  );
5337

    
5338
  $rows = array();
5339
  foreach ($templates as $name => $template) {
5340
    $rows[] = array(
5341
      array('data' => check_plain($template->get_human_name())),
5342
      array('data' => check_plain($template->description)),
5343
      array('data' => l('add', 'admin/structure/views/template/' . $template->name . '/add')),
5344
    );
5345
  }
5346

    
5347
  $output = theme('table', array('header' => $header, 'rows' => $rows));
5348
  return $output;
5349
}
5350

    
5351
/**
5352
 * #process callback for a button; determines if a button is the form's triggering element.
5353
 *
5354
 * The Form API has logic to determine the form's triggering element based on
5355
 * the data in $_POST. However, it only checks buttons based on a single #value
5356
 * per button. This function may be added to a button's #process callbacks to
5357
 * extend button click detection to support multiple #values per button. If the
5358
 * data in $_POST matches any value in the button's #values array, then the
5359
 * button is detected as having been clicked. This can be used when the value
5360
 * (label) of the same logical button may be different based on context (e.g.,
5361
 * "Apply" vs. "Apply and continue").
5362
 *
5363
 * @see _form_builder_handle_input_element()
5364
 * @see _form_button_was_clicked()
5365
 */
5366
function views_ui_form_button_was_clicked($element, &$form_state) {
5367
  $process_input = empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])));
5368
  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)) {
5369
    $form_state['triggering_element'] = $element;
5370
  }
5371
  return $element;
5372
}
5373

    
5374
/**
5375
 * #process callback for a button; makes implicit form submissions trigger as this button.
5376
 *
5377
 * @see Drupal.behaviors.viewsImplicitFormSubmission
5378
 */
5379
function views_ui_default_button($element, &$form_state, $form) {
5380
  $setting['viewsImplicitFormSubmission'][$form['#id']]['defaultButton'] = $element['#id'];
5381
  $element['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
5382
  return $element;
5383
}
5384

    
5385
/**
5386
 * List all instances of fields on any views.
5387
 *
5388
 * Therefore it builds up a table of each field which is used in any view.
5389
 *
5390
 * @see field_ui_fields_list()
5391
 */
5392
function views_ui_field_list() {
5393
  $views = views_get_all_views();
5394

    
5395
  // Fetch all fieldapi fields which are used in views
5396
  // Therefore search in all views, displays and handler-types.
5397
  $fields = array();
5398
  foreach ($views as $view) {
5399
    foreach ($view->display as $display_id => $display) {
5400
      if ($view->set_display($display_id)) {
5401
        foreach (views_object_types() as $type => $info) {
5402
          foreach ($view->get_items($type, $display_id) as $item) {
5403
            $data = views_fetch_data($item['table']);
5404
            if (isset($data[$item['field']]) && isset($data[$item['field']][$type])
5405
              && $data = $data[$item['field']][$type]) {
5406
              // The final check that we have a fieldapi field now.
5407
              if (isset($data['field_name'])) {
5408
                $fields[$data['field_name']][$view->name] = $view->name;
5409
              }
5410
            }
5411
          }
5412
        }
5413
      }
5414
    }
5415
  }
5416

    
5417
  $header = array(t('Field name'), t('Used in'));
5418
  $rows = array();
5419
  foreach ($fields as $field_name => $views) {
5420

    
5421
    $rows[$field_name]['data'][0] = check_plain($field_name);
5422
    foreach ($views as $view) {
5423
      $rows[$field_name]['data'][1][] = l($view, "admin/structure/views/view/$view");
5424
    }
5425
    $rows[$field_name]['data'][1] = implode(', ', $rows[$field_name]['data'][1]);
5426
  }
5427

    
5428
  // Sort rows by field name.
5429
  ksort($rows);
5430
  $output = array(
5431
    '#theme' => 'table',
5432
    '#header' => $header,
5433
    '#rows' => $rows,
5434
    '#empty' => t('No fields have been used in views yet.'),
5435
  );
5436

    
5437
  return $output;
5438
}
5439

    
5440
/**
5441
 * Lists all plugins and what enabled Views use them.
5442
 */
5443
function views_ui_plugin_list() {
5444
  $rows = views_plugin_list();
5445
  foreach ($rows as &$row) {
5446
    // Link each view name to the view itself.
5447
    foreach ($row['views'] as $row_name => $view) {
5448
      $row['views'][$row_name] = l($view, "admin/structure/views/view/$view");
5449
    }
5450
    $row['views'] = implode(', ', $row['views']);
5451
  }
5452

    
5453
  // Sort rows by field name.
5454
  ksort($rows);
5455
  return array(
5456
    '#theme' => 'table',
5457
    '#header' => array(t('Type'), t('Name'), t('Provided by'), t('Used in')),
5458
    '#rows' => $rows,
5459
    '#empty' => t('There are no enabled views.'),
5460
  );
5461
}