Projet

Général

Profil

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

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

1
<?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

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

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

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

    
901
    return $display_id;
902
  }
903
  elseif ($display_id) {
904
    return MENU_ACCESS_DENIED;
905
  }
906
  else {
907
    $display_id = NULL;
908
  }
909

    
910
  return $display_id;
911
}
912

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

    
927
  return $build;
928
}
929

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

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

    
940
  $form_state = array('build_info' => array('args' => array($view, $display_id)));
941
  $build['controls'] = drupal_build_form('views_ui_preview_form', $form_state);
942

    
943
  $args = array();
944
  if (!empty($form_state['values']['view_args'])) {
945
    $args = explode('/', $form_state['values']['view_args']);
946
  }
947

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

    
954
  return $build;
955
}
956

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

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

    
986
    $view->fix_missing_relationships();
987
  }
988

    
989
  ctools_include('dependent');
990
  $form['#attached']['js'][] = ctools_attach_js('dependent');
991
  $form['#attached']['js'][] = ctools_attach_js('collapsible-div');
992

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

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

    
1007
  $form['#attached']['css'] = views_ui_get_admin_css();
1008
  $module_path = drupal_get_path('module', 'views_ui');
1009

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

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

    
1028
  $form['#attributes']['class'] = array('form-edit');
1029

    
1030
  if (isset($view->locked) && is_object($view->locked)) {
1031
    $form['locked'] = array(
1032
      '#theme_wrappers' => array('container'),
1033
      '#attributes' => array('class' => array('view-locked', 'messages', 'warning')),
1034
      '#markup' => t('This view is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => theme('username', array('account' => user_load($view->locked->uid))), '!age' => format_interval(REQUEST_TIME - $view->locked->updated), '!break' => url('admin/structure/views/view/' . $view->name . '/break-lock'))),
1035
    );
1036
  }
1037
  if (isset($view->vid) && $view->vid == 'new') {
1038
    $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard the view.');
1039
  }
1040
  else {
1041
    $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard your changes.');
1042
  }
1043

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

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

    
1059
  $form['actions'] = array(
1060
    '#type' => 'actions',
1061
    '#weight' => 0,
1062
  );
1063

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

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

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

    
1090
  $form['displays']['top'] = views_ui_render_display_top($view, $display_id);
1091

    
1092
  // The rest requires a display to be selected.
1093
  if ($display_id) {
1094
    $form_state['display_id'] = $display_id;
1095

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

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

    
1158
  // If relationships had to be fixed, we want to get that into the cache
1159
  // so that edits work properly, and to try to get the user to save it
1160
  // so that it's not using weird fixed up relationships.
1161
  if (!empty($view->relationships_changed) && empty($_POST)) {
1162
    drupal_set_message(t('This view has been automatically updated to fix missing relationships. While this View should continue to work, you should verify that the automatic updates are correct and save this view.'));
1163
    views_ui_cache_set($view);
1164
  }
1165
  return $form;
1166
}
1167

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

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

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

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

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

    
1219
  return $form;
1220
}
1221

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

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

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

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

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

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

    
1310
  return $element;
1311
}
1312

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

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

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

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

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

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

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

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

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

    
1362
  // Mark the display for deletion.
1363
  $view->display[$display_id]->deleted = TRUE;
1364
  views_ui_cache_set($view);
1365

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

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

    
1379
  // Store in cache
1380
  views_ui_cache_set($form_state['view']);
1381

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

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

    
1394
  // Store in cache
1395
  views_ui_cache_set($form_state['view']);
1396

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

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

    
1408
  // Store in cache
1409
  views_ui_cache_set($form_state['view']);
1410

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

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

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

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

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

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

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

    
1503
  return $tabs;
1504
}
1505

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

    
1516
  return $advanced_mode || $additional_displays;
1517
}
1518

    
1519
/**
1520
 * Returns a renderable array representing the edit page for one display.
1521
 */
1522
function views_ui_get_display_tab($view, $display_id) {
1523
  $build = array();
1524
  $display = $view->display[$display_id];
1525
  // If the plugin doesn't exist, display an error message instead of an edit
1526
  // page.
1527
  if (empty($display->handler)) {
1528
    $title = isset($display->display_title) ? $display->display_title : t('Invalid');
1529
    // @TODO: Improved UX for the case where a plugin is missing.
1530
    $build['#markup'] = t("Error: Display @display refers to a plugin named '@plugin', but that plugin is not available.", array('@display' => $display->id, '@plugin' => $display->display_plugin));
1531
  }
1532
  // Build the content of the edit page.
1533
  else {
1534
    $build['details'] = views_ui_get_display_tab_details($view, $display);
1535
  }
1536
  // In AJAX context, views_ui_regenerate_tab() returns this outside of form
1537
  // context, so hook_form_views_ui_edit_form_alter() is insufficient.
1538
  drupal_alter('views_ui_display_tab', $build, $view, $display_id);
1539
  return $build;
1540
}
1541

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

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

    
1569
  if (!$isDisplayDeleted && $isDeletable && !$isDefault) {
1570
    $prefix = '<div class="ctools-no-js ctools-button ctools-dropbutton"><div class="ctools-link"><a href="#" class="ctools-twisty ctools-text">open</a></div><div class="ctools-content"><ul class="horizontal right actions">';
1571
    $suffix = '</ul></div></div>';
1572
    $itemElement = 'li';
1573
  }
1574
  else {
1575
    $prefix = '<div class="ctools-button"><div class="ctools-content"><ul class="horizontal right actions">';
1576
    $suffix = '</ul></div></div>';
1577
    $itemElement = 'li';
1578
  }
1579

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

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

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

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

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

    
1670
  $build['columns']['first']['#theme_wrappers'] = array('container');
1671
  $build['columns']['first']['#attributes'] = array('class' => array('views-display-column', 'first'));
1672

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

    
1676
  $build['columns']['second']['settings'] = array();
1677
  $build['columns']['second']['header'] = array();
1678
  $build['columns']['second']['footer'] = array();
1679
  $build['columns']['second']['pager'] = array();
1680

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

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

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

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

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

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

    
1749
  return $build;
1750
}
1751

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

    
1762
  $option_build['#description'] = $option['title'];
1763

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

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

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

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

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

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

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

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

    
1818
function template_preprocess_views_ui_display_tab_bucket(&$variables) {
1819
  $element = $variables['element'];
1820

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

    
1833
  $variables['content'] = $element['#children'];
1834
  $variables['title'] = $element['#title'];
1835
  $variables['actions'] = !empty($element['#actions']) ? $element['#actions'] : '';
1836
}
1837

    
1838
function template_preprocess_views_ui_display_tab_column(&$variables) {
1839
  $element = $variables['element'];
1840

    
1841
  $variables['content'] = $element['#children'];
1842
  $variables['column'] = $element['#column'];
1843
}
1844

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

    
1867
  return $form;
1868
}
1869

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

    
1890
  return $form;
1891
}
1892

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

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

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

    
1958
/**
1959
 * Import a view from cut & paste.
1960
 */
1961
function views_ui_import_page($form, &$form_state) {
1962
  $form['name'] = array(
1963
    '#type' => 'textfield',
1964
    '#title' => t('View name'),
1965
    '#description' => t('Enter the name to use for this view if it is different from the source view. Leave blank to use the name of the view.'),
1966
  );
1967

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

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

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

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

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

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

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

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

    
2027
  if ($form_state['values']['name']) {
2028
    $view->name = $form_state['values']['name'];
2029
  }
2030

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

    
2043
  // Make sure base table gets set properly if it got moved.
2044
  $view->update();
2045

    
2046
  $view->init_display();
2047

    
2048
  $broken = FALSE;
2049

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

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

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

    
2091
  if ($broken) {
2092
    form_set_error('', t('Unable to import view.'));
2093
  }
2094

    
2095
  $form_state['view'] = &$view;
2096
}
2097

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

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

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

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

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

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

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

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

    
2182
function views_ui_edit_view_form_delete($form, &$form_state) {
2183
  unset($_REQUEST['destination']);
2184
  // Redirect to the delete confirm page
2185
  $form_state['redirect'] = array('admin/structure/views/view/' . $form_state['view']->name . '/delete', array('query' => drupal_get_destination() + array('cancel' => 'admin/structure/views/view/' . $form_state['view']->name . '/edit')));
2186
}
2187

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

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

    
2207
  $build['#name'] = $build['#title'] = $types[$type]['title'];
2208

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

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

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

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

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

    
2275
  // If there's an options form for the bucket, link to it.
2276
  if (!empty($types[$type]['options'])) {
2277
    $build['#title'] = l($build['#title'], "admin/structure/views/nojs/config-type/$view->name/$display->id/$type", array('attributes' => array('class' => array('views-ajax-link'), 'id' => 'views-title-' . $type)));
2278
  }
2279

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

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

    
2308
  $build['fields'] = array();
2309

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

    
2315
    $handler = $display->handler->get_handler($type, $id);
2316
    if (empty($handler)) {
2317
      $build['fields'][$id]['#class'][] = 'broken';
2318
      $field_name = t('Broken/missing handler: @table > @field', array('@table' => $field['table'], '@field' => $field['field']));
2319
      $build['fields'][$id]['#link'] = l($field_name, "admin/structure/views/nojs/config-item/$view->name/$display->id/$type/$id", array('attributes' => array('class' => array('views-ajax-link')), 'html' => TRUE));
2320
      continue;
2321
    }
2322

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

    
2328
    $description = filter_xss_admin($handler->admin_summary());
2329
    $link_text = $field_name . (empty($description) ? '' : " ($description)");
2330
    $link_attributes = array('class' => array('views-ajax-link'));
2331
    if (!empty($field['exclude'])) {
2332
      $link_attributes['class'][] = 'views-field-excluded';
2333
    }
2334
    $build['fields'][$id]['#link'] = l($link_text, "admin/structure/views/nojs/config-item/$view->name/$display->id/$type/$id", array('attributes' => $link_attributes, 'html' => TRUE));
2335
    $build['fields'][$id]['#class'][] = drupal_clean_css_identifier($display->id . '-' . $type . '-' . $id);
2336
    if (!empty($view->changed_sections[$display->id . '-' . $type . '-' . $id])) {
2337
      // @TODO: #changed is no longer being used?
2338
      $build['fields'][$id]['#changed'] = TRUE;
2339
    }
2340

    
2341
    if ($display->handler->use_group_by() && $handler->use_group_by()) {
2342
      $build['fields'][$id]['#settings_links'][] = l('<span class="label">' . t('Aggregation settings') . '</span>', "admin/structure/views/nojs/config-item-group/$view->name/$display->id/$type/$id", array('attributes' => array('class' => 'views-button-configure views-ajax-link', 'title' => t('Aggregation settings')), 'html' => true));
2343
    }
2344

    
2345
    if ($handler->has_extra_options()) {
2346
      $build['fields'][$id]['#settings_links'][] = l('<span class="label">' . t('Settings') . '</span>', "admin/structure/views/nojs/config-item-extra/$view->name/$display->id/$type/$id", array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => t('Settings')), 'html' => true));
2347
    }
2348

    
2349
    if ($grouping) {
2350
      $gid = $handler->options['group'];
2351

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

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

    
2385
  return $build;
2386
}
2387

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2539
  list($was_defaulted, $is_defaulted, $revert) = views_ui_standard_override_values($form, $form_state);
2540

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

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

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

    
2581
  $submit_handler = $form['#form_id'] . '_submit';
2582
  if (function_exists($submit_handler)) {
2583
    $submit_handler($form, $form_state);
2584
  }
2585
}
2586

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

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

    
2612
  return array($was_defaulted, $is_defaulted, $revert);
2613
}
2614

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

    
2624
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
2625
}
2626

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

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

    
2646
  if ($current_display->handler->is_default_display()) {
2647
    return;
2648
  }
2649

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

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

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

    
2687
}
2688

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

    
2721

    
2722
// --------------------------------------------------------------------------
2723
// Various subforms for editing the pieces of a view.
2724

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

    
2781
  if ($key) {
2782
    return !empty($forms[$key]) ? $forms[$key] : NULL;
2783
  }
2784

    
2785
  return $forms;
2786
}
2787

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

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

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

    
2822
  foreach ($form['args'] as $id) {
2823
    $form_state[$id] = (!empty($args)) ? array_shift($args) : NULL;
2824
  }
2825

    
2826
  return $form_state;
2827
}
2828

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

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

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

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

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

    
2899
  $form = views_ui_ajax_forms($key);
2900
  if (empty($form)) {
2901
    return MENU_NOT_FOUND;
2902
  }
2903

    
2904
  views_include('ajax');
2905
  $args = func_get_args();
2906
  // Remove the known args
2907
  array_splice($args, 0, 4);
2908

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

    
2921
    if (array_shift($top) != $identifier) {
2922
      $view->stack = array();
2923
    }
2924
  }
2925

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

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

    
2977
  return $js ? array('#type' => 'ajax', '#commands' => $output) : $output;
2978
}
2979

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

    
2988
  // Store in cache
2989
  views_ui_cache_set($form_state['view']);
2990
}
2991

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

    
2998
  $form['#title'] = t('View analysis');
2999
  $form['#section'] = 'analyze';
3000

    
3001
  views_include('analyze');
3002
  $messages = views_analyze_view($view);
3003

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

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

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

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

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

    
3032
  $form['#tree'] = TRUE;
3033

    
3034
  $last_display = end($view->display);
3035

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

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

    
3064
  }
3065

    
3066
  $form['#title'] = t('Displays Reorder');
3067
  $form['#section'] = 'reorder';
3068

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

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

    
3081
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_reorder_displays_form');
3082

    
3083
  return $form;
3084
}
3085

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

    
3094
  return 0;
3095
}
3096

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

    
3109
  // Sort the order array
3110
  asort($order);
3111

    
3112
  // Fixing up positions
3113
  $position = 2;
3114

    
3115
  foreach(array_keys($order) as $display) {
3116
    $order[$display] = $position++;
3117
  }
3118

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

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

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

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

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

    
3183
  $header = array(t('Display'), t('Weight'), t('Remove'));
3184
  $output = '';
3185
  drupal_add_tabledrag('reorder-displays', 'order', 'sibling', 'weight');
3186

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

    
3197
  return $output;
3198
}
3199

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

    
3206
  $form['#title'] = t('View name and description');
3207
  $form['#section'] = 'details';
3208

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

    
3233
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_details_form');
3234
  return $form;
3235
}
3236

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

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

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

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

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

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

    
3284
  $name = NULL;
3285
  if (isset($form_state['update_name'])) {
3286
    $name = $form_state['update_name'];
3287
  }
3288

    
3289
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_display_form', $name);
3290
  return $form;
3291
}
3292

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

    
3300
  if (form_get_errors()) {
3301
    $form_state['rerender'] = TRUE;
3302
  }
3303
}
3304

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

    
3312
  views_ui_cache_set($form_state['view']);
3313
}
3314

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

    
3324
  views_ui_cache_set($form_state['view']);
3325
  $form_state['rerender'] = TRUE;
3326
  $form_state['rebuild'] = TRUE;
3327
}
3328

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

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

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

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

    
3356
  $name = NULL;
3357
  if (isset($form_state['update_name'])) {
3358
    $name = $form_state['update_name'];
3359
  }
3360

    
3361
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_type_form', $name);
3362
  return $form;
3363
}
3364

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

    
3372
  // Store in cache
3373
  views_ui_cache_set($form_state['view']);
3374
}
3375

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

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

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

    
3397
  $count = 0;
3398

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

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

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

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

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

    
3461
  $name = NULL;
3462
  if (isset($form_state['update_name'])) {
3463
    $name = $form_state['update_name'];
3464
  }
3465

    
3466
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_rearrange_form');
3467
  return $form;
3468
}
3469

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

    
3476
  $rows = array();
3477
  foreach (element_children($form['fields']) as $id) {
3478
    if (isset($form['fields'][$id]['name'])) {
3479
      $row = array();
3480
      $row[] = drupal_render($form['fields'][$id]['name']);
3481
      $form['fields'][$id]['weight']['#attributes']['class'] = array('weight');
3482
      $row[] = drupal_render($form['fields'][$id]['weight']);
3483
      $row[] = drupal_render($form['fields'][$id]['removed']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $id, 'class' => array('views-hidden', 'views-button-remove', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => TRUE));
3484
      $rows[] = array('data' => $row, 'class' => array('draggable'), 'id' => 'views-row-' . $id);
3485
    }
3486
  }
3487
  if (empty($rows)) {
3488
    $rows[] = array(array('data' => t('No fields available.'), 'colspan' => '2'));
3489
  }
3490

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

    
3499
  return $output;
3500
}
3501

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

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

    
3518
  $output .= drupal_render($form['operator']);
3519
  $output .= drupal_render($form['value']);
3520

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

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

    
3537
  $output .= $more;
3538

    
3539
  return $output;
3540
}
3541

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

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

    
3556
  $output .= drupal_render($form['operator']);
3557
  $output .= drupal_render($form['value']);
3558

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

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

    
3570

    
3571
  $header = array(
3572
    t('Default'),
3573
    t('Weight'),
3574
    t('Label'),
3575
    t('Operator'),
3576
    t('Value'),
3577
    t('Operations'),
3578
  );
3579

    
3580
  $form['default_group'] = form_process_radios($form['default_group']);
3581
  $form['default_group_multiple'] = form_process_checkboxes($form['default_group_multiple']);
3582
  $form['default_group']['All']['#title'] = '';
3583

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

    
3595
  foreach (element_children($form['group_items']) as $group_id) {
3596
    $form['group_items'][$group_id]['value']['#title'] = '';
3597
    $data = array(
3598
      'default' => drupal_render($form['default_group'][$group_id]) . drupal_render($form['default_group_multiple'][$group_id]),
3599
      'weight' => drupal_render($form['group_items'][$group_id]['weight']),
3600
      'title' => drupal_render($form['group_items'][$group_id]['title']),
3601
      'operator' => drupal_render($form['group_items'][$group_id]['operator']),
3602
      'value' => drupal_render($form['group_items'][$group_id]['value']),
3603
      'remove' => drupal_render($form['group_items'][$group_id]['remove']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $group_id, 'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => true)),
3604
    );
3605
    $rows[] = array('data' => $data, 'id' => 'views-row-' . $group_id, 'class' => array('draggable'));
3606
  }
3607
  $table = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('views-filter-groups'), 'id' => 'views-filter-groups'))) . drupal_render($form['add_group']);
3608
  drupal_add_tabledrag('views-filter-groups', 'order', 'sibling', 'weight');
3609
  $render_form = drupal_render_children($form);
3610
  return $output . $render_form . $table . $more;
3611
}
3612

    
3613

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

    
3621
  $old_fields = $display->handler->get_option($types[$form_state['type']]['plural']);
3622
  $new_fields = $order = array();
3623

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

    
3633
  // Sort the array
3634
  asort($order);
3635

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

    
3642
  // Store in cache
3643
  views_ui_cache_set($form_state['view']);
3644
}
3645

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

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

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

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

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

    
3684
  $group_options = array();
3685

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

    
3696
  $grouping = count(array_keys($groups['groups'])) > 1;
3697

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

    
3714
  $form['remove_groups']['#tree'] = TRUE;
3715

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

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

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

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

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

    
3769
    // Place this item into the proper group for rendering.
3770
    $form['#group_renders'][$field['group']][] = $id;
3771

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

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

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

    
3808
  if (isset($form_state['update_name'])) {
3809
    $name = $form_state['update_name'];
3810
  }
3811

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

    
3820
  return $form;
3821
}
3822

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

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

    
3839
      // Title row, spanning all columns.
3840
      $row = array();
3841
      // Add a cell to the first row, containing the group operator.
3842
      $row[] = array('class' => array('group', 'group-operator', 'container-inline'), 'data' => drupal_render($form['filter_groups']['groups'][$group_id]), 'rowspan' => max(array(2, count($contents) + 1)));
3843
      // Title.
3844
      $row[] = array('class' => array('group', 'group-title'), 'data' => '<span>' . $form['#group_options'][$group_id] . '</span>', 'colspan' => 4);
3845
      $rows[] = array('class' => array('views-group-title'), 'data' => $row, 'id' => 'views-group-title-' . $group_id);
3846

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

    
3861
    foreach ($contents as $id) {
3862
      if (isset($form['filters'][$id]['name'])) {
3863
        $row = array();
3864
        $row[] = drupal_render($form['filters'][$id]['name']);
3865
        $form['filters'][$id]['weight']['#attributes']['class'] = array('weight');
3866
        $row[] = drupal_render($form['filters'][$id]['weight']);
3867
        $form['filters'][$id]['group']['#attributes']['class'] = array('views-group-select views-group-select-' . $group_id);
3868
        $row[] = drupal_render($form['filters'][$id]['group']);
3869
        $form['filters'][$id]['removed']['#attributes']['class'][] = 'js-hide';
3870
        $row[] = drupal_render($form['filters'][$id]['removed']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $id, 'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => true));
3871

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

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

    
3896
  if (!empty($ungroupable_rows)) {
3897
    drupal_add_tabledrag('views-rearrange-filters-ungroupable', 'order', 'sibling', 'weight');
3898
    $header = array(t('Ungroupable filters'), t('Weight'), array('class' => array('views-hide-label'), 'data' => t('Group')), array('class' => array('views-hide-label'), 'data' => t('Remove')));
3899
    $output .= theme('table', array('header' => $header, 'rows' => $ungroupable_rows, 'attributes' => array('id' => 'views-rearrange-filters-ungroupable', 'class' => array('arrange'))));
3900
  }
3901

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

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

    
3911
  // Render the rest of the form and return.
3912
  $output .= drupal_render_children($form);
3913
  return $output;
3914
}
3915

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

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

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

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

    
3945
      if (isset($info['group'])) {
3946
        $old_fields[$field]['group'] = $info['group'];
3947
        $remember_groups[$info['group']][] = $field;
3948
      }
3949
    }
3950
  }
3951

    
3952
  // Sort the array
3953
  asort($order);
3954

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

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

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

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

    
4019
  // Store in cache.
4020
  views_ui_cache_set($form_state['view']);
4021
}
4022

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

    
4031
  $form = array(
4032
    'options' => array(
4033
      '#theme_wrappers' => array('container'),
4034
      '#attributes' => array('class' => array('scroll')),
4035
    ),
4036
  );
4037

    
4038
  ctools_add_js('dependent');
4039

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

    
4045
  $types = views_object_types();
4046
  $ltitle = $types[$type]['ltitle'];
4047
  $section = $types[$type]['plural'];
4048

    
4049
  if (!empty($types[$type]['type'])) {
4050
    $type = $types[$type]['type'];
4051
  }
4052

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

    
4056

    
4057
  // Add the display override dropdown.
4058
  views_ui_standard_display_dropdown($form, $form_state, $section);
4059

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

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

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

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

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

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

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

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

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

    
4155
  return $form;
4156
}
4157

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

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

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

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

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

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

    
4215
  if (isset($form_state['view']->form_cache)) {
4216
    unset($form_state['view']->form_cache);
4217
  }
4218

    
4219
  // Store in cache
4220
  views_ui_cache_set($form_state['view']);
4221
}
4222

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

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

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

    
4238
  views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($form_state['type'], $form_state['id']), TRUE, TRUE);
4239

    
4240
  views_ui_cache_set($form_state['view']);
4241
  $form_state['rerender'] = TRUE;
4242
  $form_state['rebuild'] = TRUE;
4243
  $form_state['force_build_group_options'] = TRUE;
4244
}
4245

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

    
4252
  // Add a new row.
4253
  $item['group_info']['group_items'][] = array();
4254

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

    
4257
  views_ui_cache_set($form_state['view']);
4258
  $form_state['rerender'] = TRUE;
4259
  $form_state['rebuild'] = TRUE;
4260
  $form_state['force_build_group_options'] = TRUE;
4261
}
4262

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

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

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

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

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

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

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

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

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

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

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

    
4370
      $form['#section'] = $display_id . '-' . $type . '-' . $id;
4371

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

    
4377
    $name = NULL;
4378
    if (isset($form_state['update_name'])) {
4379
      $name = $form_state['update_name'];
4380
    }
4381

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

    
4388
  return $form;
4389
}
4390

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

    
4397
  if (form_get_errors()) {
4398
    $form_state['rerender'] = TRUE;
4399
  }
4400
}
4401

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

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

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

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

    
4435

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

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

    
4444
  // Store the item back on the view
4445
  $form_state['view']->temporary_options[$type][$form_state['id']] = $handler->options;
4446

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

    
4452
  $form_state['rerender'] = TRUE;
4453
  $form_state['rebuild'] = TRUE;
4454
  // Write to cache
4455
  views_ui_cache_set($form_state['view']);
4456
}
4457

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

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

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

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

    
4490

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

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

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

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

    
4507
  // Write to cache
4508
  views_ui_cache_set($form_state['view']);
4509
}
4510

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

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

    
4531
  $view->init_query();
4532

    
4533
  $item = $view->get_item($display_id, $type, $id);
4534

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

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

    
4546
      $handler->groupby_form($form['options'], $form_state);
4547
      $form_state['handler'] = &$handler;
4548
    }
4549

    
4550
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_group_form');
4551
  }
4552
  return $form;
4553
}
4554

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

    
4563
  $handler = views_get_handler($item['table'], $item['field'], $type);
4564
  $handler->init($form_state['view'], $item);
4565

    
4566
  $handler->groupby_form_submit($form, $form_state);
4567

    
4568
  // Store the item back on the view
4569
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4570

    
4571
  // Write to cache
4572
  views_ui_cache_set($form_state['view']);
4573
}
4574

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

    
4588
  // Write to cache
4589
  views_ui_cache_set($form_state['view']);
4590
}
4591

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

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

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

    
4607
  views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($form_state['type'], $form_state['id']), TRUE, TRUE);
4608

    
4609
  views_ui_cache_set($form_state['view']);
4610
  $form_state['rerender'] = TRUE;
4611
  $form_state['rebuild'] = TRUE;
4612
  $form_state['force_expose_options'] = TRUE;
4613
}
4614

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

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

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

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

    
4647
      $form['#section'] = $display_id . '-' . $type . '-' . $id;
4648

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

    
4654
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_extra_form');
4655
  }
4656
  return $form;
4657
}
4658

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

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

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

    
4679
  // Store the item back on the view
4680
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4681

    
4682
  // Write to cache
4683
  views_ui_cache_set($form_state['view']);
4684
}
4685

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

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

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

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

    
4718
      $form['#section'] = $display_id . '-' . $type . '-style-options';
4719

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

    
4727
        $plugin->options_form($form['style_options'], $form_state);
4728
      }
4729

    
4730
      $form_state['handler'] = &$handler;
4731
    }
4732

    
4733
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_style_form');
4734
  }
4735
  return $form;
4736
}
4737

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

    
4746
  // Store the data we're given.
4747
  $item['style_options'] = $form_state['values']['style_options'];
4748

    
4749
  // Store the item back on the view
4750
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4751

    
4752
  // Write to cache
4753
  views_ui_cache_set($form_state['view']);
4754
}
4755

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

    
4769
  return $roles;
4770
}
4771

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

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

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

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

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

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

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

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

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

    
4837
  $form['live_preview'] = array(
4838
    '#type' => 'fieldset',
4839
    '#title' => t('Live preview settings'),
4840
  );
4841

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

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

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

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

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

    
4886
  $form['live_preview']['views_show_additional_queries'] = array(
4887
    '#type' => 'checkbox',
4888
    '#title' => t('Show other queries run during render during live preview'),
4889
    '#description' => t("Drupal has the potential to run many queries while a view is being rendered. Checking this box will display every query run during view render as part of the live preview."),
4890
    '#default_value' => variable_get('views_show_additional_queries', FALSE),
4891
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4892
  );
4893

    
4894
//  $form['live_preview']['views_ui_show_performance_statistics_where'] = array(
4895

    
4896
  return system_settings_form($form);
4897
}
4898

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

    
4906
  $form['cache'] = array(
4907
    '#type' => 'fieldset',
4908
    '#title' => t('Caching'),
4909
  );
4910

    
4911
  $form['cache']['views_skip_cache'] = array(
4912
    '#type' => 'checkbox',
4913
    '#title' => t('Disable views data caching'),
4914
    '#description' => t("Views caches data about tables, modules and views available, to increase performance. By checking this box, Views will skip this cache and always rebuild this data when needed. This can have a serious performance impact on your site."),
4915
    '#default_value' => variable_get('views_skip_cache', FALSE),
4916
  );
4917

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

    
4924
  $form['debug'] = array(
4925
    '#type' => 'fieldset',
4926
    '#title' => t('Debugging'),
4927
  );
4928

    
4929
  $form['debug']['views_sql_signature'] = array(
4930
    '#type' => 'checkbox',
4931
    '#title' => t('Add Views signature to all SQL queries'),
4932
    '#description' => t("All Views-generated queries will include the name of the views and display 'view-name:display-name' as a string  at the end of the SELECT clause. This makes identifying Views queries in database server logs simpler, but should only be used when troubleshooting."),
4933

    
4934
    '#default_value' => variable_get('views_sql_signature', FALSE),
4935
  );
4936

    
4937
  $form['debug']['views_no_javascript'] = array(
4938
    '#type' => 'checkbox',
4939
    '#title' => t('Disable JavaScript with Views'),
4940
    '#description' => t("If you are having problems with the JavaScript, you can disable it here. The Views UI should degrade and still be usable without javascript; it's just not as good."),
4941
    '#default_value' => variable_get('views_no_javascript', FALSE),
4942
  );
4943

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

    
4951
  $form['locale'] = array(
4952
    '#type' => 'fieldset',
4953
    '#title' => t('Localization'),
4954
  );
4955

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

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

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

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

    
4994
  return system_settings_form($form);
4995
}
4996

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

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

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

    
5021
  $form_state['rerender'] = TRUE;
5022
  $form_state['rebuild'] = TRUE;
5023
}
5024

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

    
5032
  views_ui_cache_set($form_state['view']);
5033
  $form_state['rerender'] = TRUE;
5034
  $form_state['rebuild'] = TRUE;
5035
}
5036

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

    
5054
  drupal_json_output($matches);
5055
}
5056

    
5057
// ------------------------------------------------------------------
5058
// Get information from the Views data
5059

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

    
5068
  return 0;
5069
}
5070

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

    
5096
  return $base_tables;
5097
}
5098

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

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

    
5112
  return 0;
5113
}
5114

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

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

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

    
5226
  if (isset($fields[$base][$type])) {
5227
    uasort($fields[$base][$type], '_views_sort_types');
5228
    return $fields[$base][$type];
5229
  }
5230
  return array();
5231
}
5232

    
5233

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

    
5240
  $output = drupal_render($form['description_markup']);
5241

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

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

    
5300
  $output .= theme('table', array('header' => $header, 'rows' => $rows));
5301
  $output .= drupal_render_children($form);
5302
  return $output;
5303
}
5304

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

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

    
5319
  return $title;
5320
}
5321

    
5322
function views_ui_add_template_page() {
5323
  $templates = views_get_all_templates();
5324

    
5325
  if (empty($templates)) {
5326
    return t('There are no templates available.');
5327
  }
5328

    
5329
  $header = array(
5330
    t('Name'),
5331
    t('Description'),
5332
    t('Operation'),
5333
  );
5334

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

    
5344
  $output = theme('table', array('header' => $header, 'rows' => $rows));
5345
  return $output;
5346
}
5347

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

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

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

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

    
5414
  $header = array(t('Field name'), t('Used in'));
5415
  $rows = array();
5416
  foreach ($fields as $field_name => $views) {
5417

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

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

    
5434
  return $output;
5435
}
5436

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

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