Projet

Général

Profil

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

root / drupal7 / sites / all / modules / views / includes / admin.inc @ 4003efde

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
 * @return array
12
 *   An array of arrays. Each array represents a single file. The array format
13
 *   is:
14
 *   - file: The fully qualified name of the file to send to drupal_add_css
15
 *   - options: An array of options to pass to drupal_add_css.
16
 */
17
function views_ui_get_admin_css() {
18
  $module_path = drupal_get_path('module', 'views_ui');
19
  $list = array();
20
  $list[$module_path . '/css/views-admin.css'] = array();
21

    
22
  $list[$module_path . '/css/ie/views-admin.ie7.css'] = array(
23
    'browsers' => array(
24
      'IE' => 'lte IE 7',
25
      '!IE' => FALSE,
26
    ),
27
    'preprocess' => FALSE,
28
  );
29

    
30
  $list[$module_path . '/css/views-admin.theme.css'] = array();
31

    
32
  // Add in any theme specific CSS files we have.
33
  $themes = list_themes();
34
  $theme_key = $GLOBALS['theme'];
35
  while ($theme_key) {
36
    // Try to find the admin css file for non-core themes.
37
    if (!in_array($theme_key, array('garland', 'seven', 'bartik'))) {
38
      $theme_path = drupal_get_path('theme', $theme_key);
39
      // First search in the css directory, then in the root folder of the
40
      // theme.
41
      if (file_exists($theme_path . "/css/views-admin.$theme_key.css")) {
42
        $list[$theme_path . "/css/views-admin.$theme_key.css"] = array(
43
          'group' => CSS_THEME,
44
        );
45
      }
46
      elseif (file_exists($theme_path . "/views-admin.$theme_key.css")) {
47
        $list[$theme_path . "/views-admin.$theme_key.css"] = array(
48
          'group' => CSS_THEME,
49
        );
50
      }
51
    }
52
    else {
53
      $list[$module_path . "/css/views-admin.$theme_key.css"] = array(
54
        'group' => CSS_THEME,
55
      );
56
    }
57
    $theme_key = isset($themes[$theme_key]->base_theme) ? $themes[$theme_key]->base_theme : '';
58
  }
59
  // Views contains style overrides for the following modules.
60
  $module_list = array('contextual', 'advanced_help', 'ctools');
61
  foreach ($module_list as $module) {
62
    if (module_exists($module)) {
63
      $list[$module_path . '/css/views-admin.' . $module . '.css'] = array();
64
    }
65
  }
66

    
67
  return $list;
68
}
69

    
70
/**
71
 * Adds standard Views administration CSS to the current page.
72
 */
73
function views_ui_add_admin_css() {
74
  foreach (views_ui_get_admin_css() as $file => $options) {
75
    drupal_add_css($file, $options);
76
  }
77
}
78

    
79
/**
80
 * Check to see if the advanced help module is installed.
81
 *
82
 * If not display a message.
83
 *
84
 * Only call this function if the user is already in a position for this to be
85
 * useful.
86
 */
87
function views_ui_check_advanced_help() {
88
  if (!variable_get('views_ui_show_advanced_help_warning', TRUE)) {
89
    return;
90
  }
91

    
92
  if (!module_exists('advanced_help')) {
93
    $filename = db_query_range("SELECT filename FROM {system} WHERE type = 'module' AND name = 'advanced_help'", 0, 1)
94
      ->fetchField();
95
    if ($filename && file_exists($filename)) {
96
      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>',
97
        array(
98
          '@modules' => url('admin/modules'),
99
          '@hide' => url('admin/structure/views/settings'),
100
        )));
101
    }
102
    else {
103
      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>',
104
      array(
105
        '!href' => l('http://drupal.org/project/advanced_help', 'http://drupal.org/project/advanced_help'),
106
        '@hide' => url('admin/structure/views/settings'),
107
      )));
108
    }
109
  }
110
}
111

    
112
/**
113
 * Returns the results of the live preview.
114
 */
115
function views_ui_preview($view, $display_id, $args = array()) {
116
  // When this function is invoked as a page callback, each Views argument is
117
  // passed separately.
118
  if (!is_array($args)) {
119
    $args = array_slice(func_get_args(), 2);
120
  }
121

    
122
  // Save $_GET['q'] so it can be restored before returning from this function.
123
  $q = $_GET['q'];
124

    
125
  // Determine where the query and performance statistics should be output.
126
  $show_query = variable_get('views_ui_show_sql_query', FALSE);
127
  $show_info = variable_get('views_ui_show_preview_information', FALSE);
128
  $show_location = variable_get('views_ui_show_sql_query_where', 'above');
129

    
130
  $show_stats = variable_get('views_ui_show_performance_statistics', FALSE);
131
  if ($show_stats) {
132
    $show_stats = variable_get('views_ui_show_sql_query_where', 'above');
133
  }
134

    
135
  $combined = $show_query && $show_stats;
136

    
137
  $rows = array('query' => array(), 'statistics' => array());
138
  $output = '';
139

    
140
  $errors = $view->validate();
141
  if ($errors === TRUE) {
142
    $view->ajax = TRUE;
143
    $view->live_preview = TRUE;
144
    $view->views_ui_context = TRUE;
145

    
146
    // AJAX happens via $_POST but everything expects exposed data to be in
147
    // GET. Copy stuff but remove ajax-framework specific keys. If we're
148
    // clicking on links in a preview, though, we could actually still have
149
    // some in $_GET, so we use $_REQUEST to ensure we get it all.
150
    $exposed_input = $_REQUEST;
151
    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) {
152
      if (isset($exposed_input[$key])) {
153
        unset($exposed_input[$key]);
154
      }
155
    }
156

    
157
    $view->set_exposed_input($exposed_input);
158

    
159
    if (!$view->set_display($display_id)) {
160
      return t('Invalid display id @display', array('@display' => $display_id));
161
    }
162

    
163
    $view->set_arguments($args);
164

    
165
    // Store the current view URL for later use.
166
    if ($view->display_handler->get_option('path')) {
167
      $path = $view->get_url();
168
    }
169

    
170
    // Make view links come back to preview.
171
    $view->override_path = 'admin/structure/views/nojs/preview/' . $view->name . '/' . $display_id;
172

    
173
    // Also override $_GET['q'] so we get the pager.
174
    $original_path = current_path();
175
    $_GET['q'] = $view->override_path;
176
    if ($args) {
177
      $_GET['q'] .= '/' . implode('/', $args);
178
    }
179

    
180
    // Suppress contextual links of entities within the result set during a
181
    // Preview.
182
    // @todo We'll want to add contextual links specific to editing the View, so
183
    //   the suppression may need to be moved deeper into the Preview pipeline.
184
    views_ui_contextual_links_suppress_push();
185
    $preview = $view->preview($display_id, $args);
186
    views_ui_contextual_links_suppress_pop();
187

    
188
    // Reset variables.
189
    unset($view->override_path);
190
    $_GET['q'] = $original_path;
191

    
192
    // Prepare the query information and statistics to show either above or
193
    // below the view preview.
194
    if ($show_info || $show_query || $show_stats) {
195
      // Get information from the preview for display.
196
      if (!empty($view->build_info['query'])) {
197
        if ($show_query) {
198
          $query = $view->build_info['query'];
199
          // Only the SQL default class has a method getArguments.
200
          $quoted = array();
201

    
202
          if (get_class($view->query) == 'views_plugin_query_default') {
203
            $quoted = $query->getArguments();
204
            $connection = Database::getConnection();
205
            foreach ($quoted as $key => $val) {
206
              if (is_array($val)) {
207
                $quoted[$key] = implode(', ', array_map(array($connection, 'quote'), $val));
208
              }
209
              else {
210
                $quoted[$key] = $connection->quote($val);
211
              }
212
            }
213
          }
214
          $rows['query'][] = array('<strong>' . t('Query') . '</strong>', '<pre>' . check_plain(strtr($query, $quoted)) . '</pre>');
215
          if (!empty($view->additional_queries)) {
216
            $queries = '<strong>' . t('These queries were run during view rendering:') . '</strong>';
217
            foreach ($view->additional_queries as $query) {
218
              if ($queries) {
219
                $queries .= "\n";
220
              }
221
              $queries .= t('[@time ms]', array('@time' => intval($query[1] * 100000) / 100)) . ' ' . $query[0];
222
            }
223

    
224
            $rows['query'][] = array('<strong>' . t('Other queries') . '</strong>', '<pre>' . $queries . '</pre>');
225
          }
226
        }
227
        if ($show_info) {
228
          $rows['query'][] = array('<strong>' . t('Title') . '</strong>', filter_xss_admin($view->get_title()));
229
          if (isset($path)) {
230
            $path = l($path, $path);
231
          }
232
          else {
233
            $path = t('This display has no path.');
234
          }
235
          $rows['query'][] = array('<strong>' . t('Path') . '</strong>', $path);
236
        }
237

    
238
        if ($show_stats) {
239
          $rows['statistics'][] = array('<strong>' . t('Query build time') . '</strong>', t('@time ms', array('@time' => intval($view->build_time * 100000) / 100)));
240
          $rows['statistics'][] = array('<strong>' . t('Query execute time') . '</strong>', t('@time ms', array('@time' => intval($view->execute_time * 100000) / 100)));
241
          $rows['statistics'][] = array('<strong>' . t('View render time') . '</strong>', t('@time ms', array('@time' => intval($view->render_time * 100000) / 100)));
242

    
243
        }
244
        drupal_alter('views_preview_info', $rows, $view);
245
      }
246
      else {
247
        // No query was run. Display that information in place of either the
248
        // query or the performance statistics, whichever comes first.
249
        if ($combined || ($show_location === 'above')) {
250
          $rows['query'] = array(array('<strong>' . t('Query') . '</strong>', t('No query was run')));
251
        }
252
        else {
253
          $rows['statistics'] = array(array('<strong>' . t('Query') . '</strong>', t('No query was run')));
254
        }
255
      }
256
    }
257
  }
258
  else {
259
    foreach ($errors as $error) {
260
      drupal_set_message($error, 'error');
261
    }
262
    $preview = t('Unable to preview due to validation errors.');
263
  }
264

    
265
  // Assemble the preview, the query info, and the query statistics in the
266
  // requested order.
267
  if ($show_location === 'above') {
268
    if ($combined) {
269
      $output .= '<div class="views-query-info">' . theme('table', array('rows' => array_merge($rows['query'], $rows['statistics']))) . '</div>';
270
    }
271
    else {
272
      $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['query'])) . '</div>';
273
    }
274
  }
275
  elseif ($show_stats === 'above') {
276
    $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['statistics'])) . '</div>';
277
  }
278

    
279
  $output .= $preview;
280

    
281
  if ($show_location === 'below') {
282
    if ($combined) {
283
      $output .= '<div class="views-query-info">' . theme('table', array('rows' => array_merge($rows['query'], $rows['statistics']))) . '</div>';
284
    }
285
    else {
286
      $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['query'])) . '</div>';
287
    }
288
  }
289
  elseif ($show_stats === 'below') {
290
    $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['statistics'])) . '</div>';
291
  }
292

    
293
  $_GET['q'] = $q;
294
  return $output;
295
}
296

    
297
/**
298
 * Page callback to add a new view.
299
 */
300
function views_ui_add_page() {
301
  views_ui_add_admin_css();
302
  drupal_set_title(t('Add new view'));
303
  return drupal_get_form('views_ui_add_form');
304
}
305

    
306
/**
307
 * Form builder for the "add new view" page.
308
 */
309
function views_ui_add_form($form, &$form_state) {
310
  ctools_include('dependent');
311
  $form['#attached']['js'][] = drupal_get_path('module', 'views_ui') . '/js/views-admin.js';
312
  $form['#attributes']['class'] = array('views-admin');
313

    
314
  $form['human_name'] = array(
315
    '#type' => 'textfield',
316
    '#title' => t('View name'),
317
    '#required' => TRUE,
318
    '#size' => 32,
319
    '#default_value' => !empty($form_state['view']) ? $form_state['view']->human_name : '',
320
    '#maxlength' => 255,
321
  );
322
  $form['name'] = array(
323
    '#type' => 'machine_name',
324
    '#maxlength' => 128,
325
    '#machine_name' => array(
326
      'exists' => 'views_get_view',
327
      'source' => array('human_name'),
328
    ),
329
    '#description' => t('A unique machine-readable name for this View. It must only contain lowercase letters, numbers, and underscores.'),
330
  );
331

    
332
  $form['description_enable'] = array(
333
    '#type' => 'checkbox',
334
    '#title' => t('Description'),
335
  );
336
  $form['description'] = array(
337
    '#type' => 'textfield',
338
    '#title' => t('Provide description'),
339
    '#title_display' => 'invisible',
340
    '#size' => 64,
341
    '#default_value' => !empty($form_state['view']) ? $form_state['view']->description : '',
342
    '#dependency' => array(
343
      'edit-description-enable' => array(1),
344
    ),
345
  );
346

    
347
  // Create a wrapper for the entire dynamic portion of the form. Everything
348
  // that can be updated by AJAX goes somewhere inside here. For example, this
349
  // is needed by "Show" dropdown (below); it changes the base table of the
350
  // view and therefore potentially requires all options on the form to be
351
  // dynamically updated.
352
  $form['displays'] = array();
353

    
354
  // Create the part of the form that allows the user to select the basic
355
  // properties of what the view will display.
356
  $form['displays']['show'] = array(
357
    '#type' => 'fieldset',
358
    '#tree' => TRUE,
359
    '#attributes' => array('class' => array('container-inline')),
360
  );
361

    
362
  // Create the "Show" dropdown, which allows the base table of the view to be
363
  // selected.
364
  $wizard_plugins = views_ui_get_wizards();
365
  $options = array();
366
  foreach ($wizard_plugins as $key => $wizard) {
367
    $options[$key] = $wizard['title'];
368
  }
369
  $form['displays']['show']['wizard_key'] = array(
370
    '#type' => 'select',
371
    '#title' => t('Show'),
372
    '#options' => $options,
373
  );
374
  $show_form = &$form['displays']['show'];
375
  $show_form['wizard_key']['#default_value'] = views_ui_get_selected($form_state, array('show', 'wizard_key'), 'node', $show_form['wizard_key']);
376
  // Changing this dropdown updates the entire content of $form['displays'] via
377
  // AJAX.
378
  views_ui_add_ajax_trigger($show_form, 'wizard_key', array('displays'));
379

    
380
  // Build the rest of the form based on the currently selected wizard plugin.
381
  $wizard_key = $show_form['wizard_key']['#default_value'];
382
  $get_instance = $wizard_plugins[$wizard_key]['get_instance'];
383
  $wizard_instance = $get_instance($wizard_plugins[$wizard_key]);
384
  $form = $wizard_instance->build_form($form, $form_state);
385

    
386
  $form['save'] = array(
387
    '#type' => 'submit',
388
    '#value' => t('Save & exit'),
389
    '#validate' => array('views_ui_wizard_form_validate'),
390
    '#submit' => array('views_ui_add_form_save_submit'),
391
  );
392
  $form['continue'] = array(
393
    '#type' => 'submit',
394
    '#value' => t('Continue & edit'),
395
    '#validate' => array('views_ui_wizard_form_validate'),
396
    '#submit' => array('views_ui_add_form_store_edit_submit'),
397
    '#process' => array_merge(array('views_ui_default_button'), element_info_property('submit', '#process', array())),
398
  );
399
  $form['cancel'] = array(
400
    '#type' => 'submit',
401
    '#value' => t('Cancel'),
402
    '#submit' => array('views_ui_add_form_cancel_submit'),
403
    '#limit_validation_errors' => array(),
404
  );
405

    
406
  return $form;
407
}
408

    
409
/**
410
 * Helper form element validator: integer.
411
 *
412
 * The problem with this is that the function is private so it's not guaranteed
413
 * that it might not be renamed/changed. In the future field.module or something
414
 * else should provide a public validate function.
415
 *
416
 * @see _element_validate_integer_positive()
417
 */
418
function views_element_validate_integer($element, &$form_state) {
419
  $value = $element['#value'];
420
  if ($value !== '' && (!is_numeric($value) || intval($value) != $value || abs($value) != $value)) {
421
    form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title'])));
422
  }
423
}
424

    
425
/**
426
 * Gets the current value of a #select element, from within a form constructor.
427
 *
428
 * This function is intended for use in highly dynamic forms (in particular the
429
 * add view wizard) which are rebuilt in different ways depending on which
430
 * triggering element (AJAX or otherwise) was most recently fired. For example,
431
 * sometimes it is necessary to decide how to build one dynamic form element
432
 * based on the value of a different dynamic form element that may not have
433
 * even been present on the form the last time it was submitted. This function
434
 * takes care of resolving those conflicts and gives you the proper current
435
 * value of the requested #select element.
436
 *
437
 * By necessity, this function sometimes uses non-validated user input from
438
 * $form_state['input'] in making its determination. Although it performs some
439
 * minor validation of its own, it is not complete. The intention is that the
440
 * return value of this function should only be used to help decide how to
441
 * build the current form the next time it is reloaded, not to be saved as if
442
 * it had gone through the normal, final form validation process. Do NOT use
443
 * the results of this function for any other purpose besides deciding how to
444
 * build the next version of the form.
445
 *
446
 * @param array $form_state
447
 *   The standard associative array containing the current state of the form.
448
 * @param array $parents
449
 *   An array of parent keys that point to the part of the submitted form
450
 *   values that are expected to contain the element's value (in the case where
451
 *   this form element was actually submitted). In a simple case (assuming
452
 *   #tree is TRUE throughout the form), if the select element is located in
453
 *   $form['wrapper']['select'], so that the submitted form values would
454
 *   normally be found in $form_state['values']['wrapper']['select'], you would
455
 *   pass array('wrapper', 'select') for this parameter.
456
 * @param mixed $default_value
457
 *   The default value to return if the #select element does not currently have
458
 *   a proper value set based on the submitted input.
459
 * @param array $element
460
 *   An array representing the current version of the #select element within
461
 *   the form.
462
 *
463
 * @return array
464
 *   The current value of the #select element. A common use for this is to feed
465
 *   it back into $element['#default_value'] so that the form will be rendered
466
 *   with the correct value selected.
467
 */
468
function views_ui_get_selected($form_state, $parents, $default_value, $element) {
469
  // For now, don't trust this to work on anything but a #select element.
470
  if (!isset($element['#type']) || $element['#type'] != 'select' || !isset($element['#options'])) {
471
    return $default_value;
472
  }
473

    
474
  // If there is a user-submitted value for this element that matches one of
475
  // the currently available options attached to it, use that. We need to check
476
  // $form_state['input'] rather than $form_state['values'] here because the
477
  // triggering element often has the #limit_validation_errors property set to
478
  // prevent unwanted errors elsewhere on the form. This means that the
479
  // $form_state['values'] array won't be complete. We could make it complete
480
  // by adding each required part of the form to the #limit_validation_errors
481
  // property individually as the form is being built, but this is difficult to
482
  // do for a highly dynamic and extensible form. This method is much simpler.
483
  if (!empty($form_state['input'])) {
484
    $key_exists = NULL;
485
    $submitted = drupal_array_get_nested_value($form_state['input'], $parents, $key_exists);
486
    // Check that the user-submitted value is one of the allowed options before
487
    // returning it. This is not a substitute for actual form validation;
488
    // rather it is necessary because, for example, the same select element
489
    // might have #options A, B, and C under one set of conditions but #options
490
    // D, E, F under a different set of conditions. So the form submission
491
    // might have occurred with option A selected, but when the form is rebuilt
492
    // option A is no longer one of the choices. In that case, we don't want to
493
    // use the value that was submitted anymore but rather fall back to the
494
    // default value.
495
    if ($key_exists && in_array($submitted, array_keys($element['#options']))) {
496
      return $submitted;
497
    }
498
  }
499

    
500
  // Fall back on returning the default value if nothing was returned above.
501
  return $default_value;
502
}
503

    
504
/**
505
 * Converts a form element in the add view wizard to be AJAX-enabled.
506
 *
507
 * This function takes a form element and adds AJAX behaviors to it such that
508
 * changing it triggers another part of the form to update automatically. It
509
 * also adds a submit button to the form that appears next to the triggering
510
 * element and that duplicates its functionality for users who do not have
511
 * JavaScript enabled (the button is automatically hidden for users who do have
512
 * JavaScript).
513
 *
514
 * To use this function, call it directly from your form builder function
515
 * immediately after you have defined the form element that will serve as the
516
 * JavaScript trigger. Calling it elsewhere (such as in hook_form_alter()) may
517
 * mean that the non-JavaScript fallback button does not appear in the correct
518
 * place in the form.
519
 *
520
 * @param array $wrapping_element
521
 *   The element whose child will server as the AJAX trigger. For example, if
522
 *   $form['some_wrapper']['triggering_element'] represents the element which
523
 *   will trigger the AJAX behavior, you would pass $form['some_wrapper'] for
524
 *   this parameter.
525
 * @param string $trigger_key
526
 *   The key within the wrapping element that identifies which of its children
527
 *   serves as the AJAX trigger. In the above example, you would pass
528
 *   'triggering_element' for this parameter.
529
 * @param array $refresh_parents
530
 *   An array of parent keys that point to the part of the form that will be
531
 *   refreshed by AJAX. For example, if triggering the AJAX behavior should
532
 *   cause $form['dynamic_content']['section'] to be refreshed, you would pass
533
 *   array('dynamic_content', 'section') for this parameter.
534
 */
535
function views_ui_add_ajax_trigger(&$wrapping_element, $trigger_key, $refresh_parents) {
536
  $seen_ids = &drupal_static(__FUNCTION__ . ':seen_ids', array());
537
  $seen_buttons = &drupal_static(__FUNCTION__ . ':seen_buttons', array());
538

    
539
  // Add the AJAX behavior to the triggering element.
540
  $triggering_element = &$wrapping_element[$trigger_key];
541
  $triggering_element['#ajax']['callback'] = 'views_ui_ajax_update_form';
542
  // We do not use drupal_html_id() to get an ID for the AJAX wrapper, because
543
  // it remembers IDs across AJAX requests (and won't reuse them), but in our
544
  // case we need to use the same ID from request to request so that the
545
  // wrapper can be recognized by the AJAX system and its content can be
546
  // dynamically updated. So instead, we will keep track of duplicate IDs
547
  // (within a single request) on our own, later in this function.
548
  $triggering_element['#ajax']['wrapper'] = 'edit-view-' . implode('-', $refresh_parents) . '-wrapper';
549

    
550
  // Add a submit button for users who do not have JavaScript enabled. It
551
  // should be displayed next to the triggering element on the form.
552
  $button_key = $trigger_key . '_trigger_update';
553
  $wrapping_element[$button_key] = array(
554
    '#type' => 'submit',
555
    // Hide this button when JavaScript is enabled.
556
    '#attributes' => array('class' => array('js-hide')),
557
    '#submit' => array('views_ui_nojs_submit'),
558
    // Add a process function to limit this button's validation errors to the
559
    // triggering element only. We have to do this in #process since until the
560
    // form API has added the #parents property to the triggering element for
561
    // us, we don't have any (easy) way to find out where its submitted values
562
    // will eventually appear in $form_state['values'].
563
    '#process' => array_merge(array('views_ui_add_limited_validation'), element_info_property('submit', '#process', array())),
564
    // Add an after-build function that inserts a wrapper around the region of
565
    // the form that needs to be refreshed by AJAX (so that the AJAX system can
566
    // detect and dynamically update it). This is done in #after_build because
567
    // it's a convenient place where we have automatic access to the complete
568
    // form array, but also to minimize the chance that the HTML we add will
569
    // get clobbered by code that runs after we have added it.
570
    '#after_build' => array_merge(element_info_property('submit', '#after_build', array()), array('views_ui_add_ajax_wrapper')),
571
  );
572
  // Copy #weight and #access from the triggering element to the button, so
573
  // that the two elements will be displayed together.
574
  foreach (array('#weight', '#access') as $property) {
575
    if (isset($triggering_element[$property])) {
576
      $wrapping_element[$button_key][$property] = $triggering_element[$property];
577
    }
578
  }
579
  // For easiest integration with the form API and the testing framework, we
580
  // always give the button a unique #value, rather than playing around with
581
  // #name.
582
  $button_title = !empty($triggering_element['#title']) ? $triggering_element['#title'] : $trigger_key;
583
  if (empty($seen_buttons[$button_title])) {
584
    $wrapping_element[$button_key]['#value'] = t('Update "@title" choice', array(
585
      '@title' => $button_title,
586
    ));
587
    $seen_buttons[$button_title] = 1;
588
  }
589
  else {
590
    $wrapping_element[$button_key]['#value'] = t('Update "@title" choice (@number)', array(
591
      '@title' => $button_title,
592
      '@number' => ++$seen_buttons[$button_title],
593
    ));
594
  }
595

    
596
  // Attach custom data to the triggering element and submit button, so we can
597
  // use it in both the process function and AJAX callback.
598
  $ajax_data = array(
599
    'wrapper' => $triggering_element['#ajax']['wrapper'],
600
    'trigger_key' => $trigger_key,
601
    'refresh_parents' => $refresh_parents,
602
    // Keep track of duplicate wrappers so we don't add the same wrapper to the
603
    // page more than once.
604
    'duplicate_wrapper' => !empty($seen_ids[$triggering_element['#ajax']['wrapper']]),
605
  );
606
  $seen_ids[$triggering_element['#ajax']['wrapper']] = TRUE;
607
  $triggering_element['#views_ui_ajax_data'] = $ajax_data;
608
  $wrapping_element[$button_key]['#views_ui_ajax_data'] = $ajax_data;
609
}
610

    
611
/**
612
 * Processes a non-JS fallback submit button to limit its validation errors.
613
 */
614
function views_ui_add_limited_validation($element, &$form_state) {
615
  // Retrieve the AJAX triggering element so we can determine its parents. (We
616
  // know it's at the same level of the complete form array as the submit
617
  // button, so all we have to do to find it is swap out the submit button's
618
  // last array parent.)
619
  $array_parents = $element['#array_parents'];
620
  array_pop($array_parents);
621
  $array_parents[] = $element['#views_ui_ajax_data']['trigger_key'];
622
  $ajax_triggering_element = drupal_array_get_nested_value($form_state['complete form'], $array_parents);
623

    
624
  // Limit this button's validation to the AJAX triggering element, so it can
625
  // update the form for that change without requiring that the rest of the
626
  // form be filled out properly yet.
627
  $element['#limit_validation_errors'] = array($ajax_triggering_element['#parents']);
628

    
629
  // If we are in the process of a form submission and this is the button that
630
  // was clicked, the form API workflow in form_builder() will have already
631
  // copied it to $form_state['triggering_element'] before our #process
632
  // function is run. So we need to make the same modifications in $form_state
633
  // as we did to the element itself, to ensure that #limit_validation_errors
634
  // will actually be set in the correct place.
635
  if (!empty($form_state['triggering_element'])) {
636
    $clicked_button = &$form_state['triggering_element'];
637
    if ($clicked_button['#name'] == $element['#name'] && $clicked_button['#value'] == $element['#value']) {
638
      $clicked_button['#limit_validation_errors'] = $element['#limit_validation_errors'];
639
    }
640
  }
641

    
642
  return $element;
643
}
644

    
645
/**
646
 * After-build function that adds a wrapper to a form region (AJAX refreshes).
647
 *
648
 * This function inserts a wrapper around the region of the form that needs to
649
 * be refreshed by AJAX, based on information stored in the corresponding submit
650
 * button form element.
651
 */
652
function views_ui_add_ajax_wrapper($element, &$form_state) {
653
  // Don't add the wrapper <div> if the same one was already inserted on this
654
  // form.
655
  if (empty($element['#views_ui_ajax_data']['duplicate_wrapper'])) {
656
    // Find the region of the complete form that needs to be refreshed by AJAX.
657
    // This was earlier stored in a property on the element.
658
    $complete_form = &$form_state['complete form'];
659
    $refresh_parents = $element['#views_ui_ajax_data']['refresh_parents'];
660
    $refresh_element = drupal_array_get_nested_value($complete_form, $refresh_parents);
661

    
662
    // The HTML ID that AJAX expects was also stored in a property on the
663
    // element, so use that information to insert the wrapper <div> here.
664
    $id = $element['#views_ui_ajax_data']['wrapper'];
665
    $refresh_element += array(
666
      '#prefix' => '',
667
      '#suffix' => '',
668
    );
669
    $refresh_element['#prefix'] = '<div id="' . $id . '" class="views-ui-ajax-wrapper">' . $refresh_element['#prefix'];
670
    $refresh_element['#suffix'] .= '</div>';
671

    
672
    // Copy the element that needs to be refreshed back into the form, with our
673
    // modifications to it.
674
    drupal_array_set_nested_value($complete_form, $refresh_parents, $refresh_element);
675
  }
676

    
677
  return $element;
678
}
679

    
680
/**
681
 * Updates a part of the add view form via AJAX.
682
 *
683
 * @return
684
 *   The part of the form that has changed.
685
 */
686
function views_ui_ajax_update_form($form, $form_state) {
687
  // The region that needs to be updated was stored in a property of the
688
  // triggering element by views_ui_add_ajax_trigger(), so all we have to do is
689
  // retrieve that here.
690
  return drupal_array_get_nested_value($form, $form_state['triggering_element']['#views_ui_ajax_data']['refresh_parents']);
691
}
692

    
693
/**
694
 * Non-JavaScript fallback for updating the add view form.
695
 */
696
function views_ui_nojs_submit($form, &$form_state) {
697
  $form_state['rebuild'] = TRUE;
698
}
699

    
700
/**
701
 * Validate the add view form.
702
 */
703
function views_ui_wizard_form_validate($form, &$form_state) {
704
  $wizard = views_ui_get_wizard($form_state['values']['show']['wizard_key']);
705
  $form_state['wizard'] = $wizard;
706
  $get_instance = $wizard['get_instance'];
707
  $form_state['wizard_instance'] = $get_instance($wizard);
708
  $errors = $form_state['wizard_instance']->validate($form, $form_state);
709
  foreach ($errors as $name => $message) {
710
    form_set_error($name, $message);
711
  }
712
}
713

    
714
/**
715
 * Process the add view form, 'save'.
716
 */
717
function views_ui_add_form_save_submit($form, &$form_state) {
718
  try {
719
    $view = $form_state['wizard_instance']->create_view($form, $form_state);
720
  }
721
  catch (ViewsWizardException $e) {
722
    drupal_set_message($e->getMessage(), 'error');
723
    $form_state['redirect'] = 'admin/structure/views';
724
  }
725
  $view->save();
726

    
727
  $form_state['redirect'] = 'admin/structure/views';
728
  if (!empty($view->display['page'])) {
729
    $display = $view->display['page'];
730
    if ($display->handler->has_path()) {
731
      $one_path = $display->handler->get_option('path');
732
      if (strpos($one_path, '%') === FALSE) {
733
        $form_state['redirect'] = $one_path;
734
        // PATH TO THE VIEW IF IT HAS ONE.
735
        return;
736
      }
737
    }
738
  }
739
  drupal_set_message(t('Your view was saved. You may edit it from the list below.'));
740
}
741

    
742
/**
743
 * Process the add view form, 'continue'.
744
 */
745
function views_ui_add_form_store_edit_submit($form, &$form_state) {
746
  try {
747
    $view = $form_state['wizard_instance']->create_view($form, $form_state);
748
  }
749
  catch (ViewsWizardException $e) {
750
    drupal_set_message($e->getMessage(), 'error');
751
    $form_state['redirect'] = 'admin/structure/views';
752
  }
753
  // Just cache it temporarily to edit it.
754
  views_ui_cache_set($view);
755

    
756
  // If there is a destination query, ensure we still redirect the user to the
757
  // edit view page, and then redirect the user to the destination.
758
  $destination = array();
759
  if (isset($_GET['destination'])) {
760
    $destination = drupal_get_destination();
761
    unset($_GET['destination']);
762
  }
763
  $form_state['redirect'] = array('admin/structure/views/view/' . $view->name, array('query' => $destination));
764
}
765

    
766
/**
767
 * Cancel the add view form.
768
 */
769
function views_ui_add_form_cancel_submit($form, &$form_state) {
770
  $form_state['redirect'] = 'admin/structure/views';
771
}
772

    
773
/**
774
 * Form element validation handler for a taxonomy autocomplete field.
775
 *
776
 * This allows a taxonomy autocomplete field to be validated outside the
777
 * standard Field API workflow, without passing in a complete field widget.
778
 * Instead, all that is required is that $element['#field_name'] contain the
779
 * name of the taxonomy autocomplete field that is being validated.
780
 *
781
 * This function is currently not used for validation directly, although it
782
 * could be. Instead, it is only used to store the term IDs and vocabulary name
783
 * in the element value, based on the tags that the user typed in.
784
 *
785
 * @see taxonomy_autocomplete_validate()
786
 */
787
function views_ui_taxonomy_autocomplete_validate($element, &$form_state) {
788
  $value = array();
789
  if ($tags = $element['#value']) {
790
    // Get the machine names of the vocabularies we will search, keyed by the
791
    // vocabulary IDs.
792
    $field = field_info_field($element['#field_name']);
793
    $vocabularies = array();
794
    if (!empty($field['settings']['allowed_values'])) {
795
      foreach ($field['settings']['allowed_values'] as $tree) {
796
        if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
797
          $vocabularies[$vocabulary->vid] = $tree['vocabulary'];
798
        }
799
      }
800
    }
801
    // Store the term ID of each (valid) tag that the user typed.
802
    $typed_terms = drupal_explode_tags($tags);
803
    foreach ($typed_terms as $typed_term) {
804
      if ($terms = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => array_keys($vocabularies)))) {
805
        $term = array_pop($terms);
806
        $value['tids'][] = $term->tid;
807
      }
808
    }
809
    // Store the term IDs along with the name of the vocabulary. Currently
810
    // Views (as well as the Field UI) assumes that there will only be one
811
    // vocabulary, although technically the API allows there to be more than
812
    // one.
813
    if (!empty($value['tids'])) {
814
      $value['tids'] = array_unique($value['tids']);
815
      $value['vocabulary'] = array_pop($vocabularies);
816
    }
817
  }
818
  form_set_value($element, $value, $form_state);
819
}
820

    
821
/**
822
 * Theme function; returns basic administrative information about a view.
823
 *
824
 * TODO: template + preprocess.
825
 */
826
function theme_views_ui_view_info($variables) {
827
  $view = $variables['view'];
828
  $title = $view->get_human_name();
829

    
830
  $displays = _views_ui_get_displays_list($view);
831
  $displays = empty($displays) ? t('None') : format_plural(count($displays), 'Display', 'Displays') . ': ' . '<em>' . implode(', ', $displays) . '</em>';
832

    
833
  switch ($view->type) {
834
    case t('Default'):
835
    default:
836
      $type = t('In code');
837
      break;
838

    
839
    case t('Normal'):
840
      $type = t('In database');
841
      break;
842

    
843
    case t('Overridden'):
844
      $type = t('Database overriding code');
845
      break;
846
  }
847

    
848
  $output = '';
849
  $output .= '<div class="views-ui-view-title">' . check_plain($title) . "</div>\n";
850
  $output .= '<div class="views-ui-view-displays">' . $displays . "</div>\n";
851
  $output .= '<div class="views-ui-view-storage">' . $type . "</div>\n";
852
  $output .= '<div class="views-ui-view-base">' . t('Type') . ': ' . check_plain($variables['base']) . "</div>\n";
853
  return $output;
854
}
855

    
856
/**
857
 * Page to delete a view.
858
 */
859
function views_ui_break_lock_confirm($form, &$form_state, $view) {
860
  $form_state['view'] = &$view;
861
  $form = array();
862

    
863
  if (empty($view->locked)) {
864
    $form['message']['#markup'] = t('There is no lock on view %name to break.', array('%name' => $view->name));
865
    return $form;
866
  }
867

    
868
  $cancel = 'admin/structure/views/view/' . $view->name . '/edit';
869

    
870
  $account = user_load($view->locked->uid);
871
  return confirm_form($form,
872
                  t('Are you sure you want to break the lock on view %name?',
873
                  array('%name' => $view->name)),
874
                  $cancel,
875
                  t('By breaking this lock, any unsaved changes made by !user will be lost!', array('!user' => theme('username', array('account' => $account)))),
876
                  t('Break lock'),
877
                  t('Cancel'));
878
}
879

    
880
/**
881
 * Submit handler to break_lock a view.
882
 */
883
function views_ui_break_lock_confirm_submit(&$form, &$form_state) {
884
  ctools_object_cache_clear_all('view', $form_state['view']->name);
885
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
886
  drupal_set_message(t('The lock has been broken and you may now edit this view.'));
887
}
888

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

    
912
    return $display_id;
913
  }
914
  elseif ($display_id) {
915
    return MENU_ACCESS_DENIED;
916
  }
917
  else {
918
    $display_id = NULL;
919
  }
920

    
921
  return $display_id;
922
}
923

    
924
/**
925
 * Page callback for the Edit View page.
926
 */
927
function views_ui_edit_page($view, $display_id = NULL) {
928
  $display_id = views_ui_edit_page_display($view, $display_id);
929
  if (!in_array($display_id, array(MENU_ACCESS_DENIED, MENU_NOT_FOUND))) {
930
    $build = array();
931
    $build['edit_form'] = drupal_get_form('views_ui_edit_form', $view, $display_id);
932
    $build['preview'] = views_ui_build_preview($view, $display_id, FALSE);
933
  }
934
  else {
935
    $build = $display_id;
936
  }
937

    
938
  return $build;
939
}
940

    
941
/**
942
 *
943
 */
944
function views_ui_build_preview($view, $display_id, $render = TRUE) {
945
  if (isset($_POST['ajax_html_ids'])) {
946
    unset($_POST['ajax_html_ids']);
947
  }
948

    
949
  $build = array(
950
    '#theme_wrappers' => array('container'),
951
    '#attributes' => array('id' => 'views-preview-wrapper', 'class' => 'views-admin clearfix'),
952
  );
953

    
954
  $form_state = array('build_info' => array('args' => array($view, $display_id)));
955
  $build['controls'] = drupal_build_form('views_ui_preview_form', $form_state);
956

    
957
  $args = array();
958
  if (!empty($form_state['values']['view_args'])) {
959
    $args = explode('/', $form_state['values']['view_args']);
960
  }
961

    
962
  $build['preview'] = array(
963
    '#theme_wrappers' => array('container'),
964
    '#attributes' => array('id' => 'views-live-preview'),
965
    '#markup' => $render ? views_ui_preview($view->clone_view(), $display_id, $args) : '',
966
  );
967

    
968
  return $build;
969
}
970

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

    
994
  if ($display_id) {
995
    if (!$view->set_display($display_id)) {
996
      $form['#markup'] = t('Invalid display id @display', array('@display' => $display_id));
997
      return $form;
998
    }
999

    
1000
    $view->fix_missing_relationships();
1001
  }
1002

    
1003
  ctools_include('dependent');
1004
  $form['#attached']['js'][] = ctools_attach_js('dependent');
1005
  $form['#attached']['js'][] = ctools_attach_js('collapsible-div');
1006

    
1007
  $form['#tree'] = TRUE;
1008
  // @todo When more functionality is added to this form, cloning here may be
1009
  //   too soon. But some of what we do with $view later in this function
1010
  //   results in making it unserializable due to PDO limitations.
1011
  $form_state['view'] = clone $view;
1012

    
1013
  $form['#attached']['library'][] = array('system', 'ui.tabs');
1014
  $form['#attached']['library'][] = array('system', 'ui.dialog');
1015
  $form['#attached']['library'][] = array('system', 'drupal.ajax');
1016
  $form['#attached']['library'][] = array('system', 'jquery.form');
1017
  // @todo This should be getting added to the page when an ajax popup calls
1018
  // for it, instead of having to add it manually here.
1019
  $form['#attached']['js'][] = 'misc/tabledrag.js';
1020

    
1021
  $form['#attached']['css'] = views_ui_get_admin_css();
1022
  $module_path = drupal_get_path('module', 'views_ui');
1023

    
1024
  $form['#attached']['js'][] = $module_path . '/js/views-admin.js';
1025
  $form['#attached']['js'][] = array(
1026
    'data' => array(
1027
      'views' => array(
1028
        'ajax' => array(
1029
          'id' => '#views-ajax-body',
1030
          'title' => '#views-ajax-title',
1031
          'popup' => '#views-ajax-popup',
1032
          'defaultForm' => views_ui_get_default_ajax_message(),
1033
        ),
1034
      ),
1035
    ),
1036
    'type' => 'setting',
1037
  );
1038

    
1039
  $form += array(
1040
    '#prefix' => '',
1041
    '#suffix' => '',
1042
  );
1043
  $form['#prefix'] = $form['#prefix'] . '<div class="views-edit-view views-admin clearfix">';
1044
  $form['#suffix'] = '</div>' . $form['#suffix'];
1045

    
1046
  $form['#attributes']['class'] = array('form-edit');
1047

    
1048
  if (isset($view->locked) && is_object($view->locked)) {
1049
    $form['locked'] = array(
1050
      '#theme_wrappers' => array('container'),
1051
      '#attributes' => array(
1052
        'class' => array('view-locked', 'messages', 'warning'),
1053
      ),
1054
      '#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'))),
1055
    );
1056
  }
1057
  if (isset($view->vid) && $view->vid == 'new') {
1058
    $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard the view.');
1059
  }
1060
  else {
1061
    $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard your changes.');
1062
  }
1063

    
1064
  $form['changed'] = array(
1065
    '#theme_wrappers' => array('container'),
1066
    '#attributes' => array(
1067
      'class' => array('view-changed', 'messages', 'warning'),
1068
    ),
1069
    '#markup' => $message,
1070
  );
1071
  if (empty($view->changed)) {
1072
    $form['changed']['#attributes']['class'][] = 'js-hide';
1073
  }
1074

    
1075
  $form['help_text'] = array(
1076
    '#prefix' => '<div>',
1077
    '#suffix' => '</div>',
1078
    '#markup' => t('Modify the display(s) of your view below or add new displays.'),
1079
  );
1080

    
1081
  $form['actions'] = array(
1082
    '#type' => 'actions',
1083
    '#weight' => 0,
1084
  );
1085

    
1086
  if (empty($view->changed)) {
1087
    $form['actions']['#attributes'] = array(
1088
      'class' => array(
1089
        'js-hide',
1090
      ),
1091
    );
1092
  }
1093

    
1094
  $form['actions']['save'] = array(
1095
    '#type' => 'submit',
1096
    '#value' => t('Save'),
1097
    // Taken from the "old" UI. @todo: Review and rename.
1098
    '#validate' => array('views_ui_edit_view_form_validate'),
1099
    '#submit' => array('views_ui_edit_view_form_submit'),
1100
  );
1101
  $form['actions']['cancel'] = array(
1102
    '#type' => 'submit',
1103
    '#value' => t('Cancel'),
1104
    '#submit' => array('views_ui_edit_view_form_cancel'),
1105
  );
1106

    
1107
  $form['displays'] = array(
1108
    '#prefix' => '<h1 class="unit-title clearfix">' . t('Displays') . "</h1>\n"
1109
      . '<div class="views-displays">',
1110
    '#suffix' => '</div>',
1111
  );
1112

    
1113
  $form['displays']['top'] = views_ui_render_display_top($view, $display_id);
1114

    
1115
  // The rest requires a display to be selected.
1116
  if ($display_id) {
1117
    $form_state['display_id'] = $display_id;
1118

    
1119
    // The part of the page where editing will take place. This element is the
1120
    // CTools collapsible-div container for the display edit elements.
1121
    $form['displays']['settings'] = array(
1122
      '#theme_wrappers' => array('container'),
1123
      '#attributes' => array(
1124
        'class' => array(
1125
          'views-display-settings',
1126
          'box-margin',
1127
          'ctools-collapsible-container',
1128
        ),
1129
      ),
1130
      '#id' => 'edit-display-settings',
1131
    );
1132
    $display_title = views_ui_get_display_label($view, $display_id, FALSE);
1133
    // Add a handle for the ctools collapsible-div. The handle is the title of
1134
    // the display.
1135
    $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>';
1136
    // Add a text that the display is disabled.
1137
    if (!empty($view->display[$display_id]->handler)) {
1138
      $enabled = $view->display[$display_id]->handler->get_option('enabled');
1139
      if (empty($enabled)) {
1140
        $form['displays']['settings']['disabled']['#markup'] = t('This display is disabled.');
1141
      }
1142
    }
1143
    // The ctools collapsible-div content.
1144
    $form['displays']['settings']['settings_content'] = array(
1145
      '#theme_wrappers' => array('container'),
1146
      '#id' => 'edit-display-settings-content',
1147
      '#attributes' => array(
1148
        'class' => array(
1149
          'ctools-collapsible-content',
1150
        ),
1151
      ),
1152
    );
1153
    // Add the edit display content.
1154
    $form['displays']['settings']['settings_content']['tab_content'] = views_ui_get_display_tab($view, $display_id);
1155
    $form['displays']['settings']['settings_content']['tab_content']['#theme_wrappers'] = array('container');
1156
    $form['displays']['settings']['settings_content']['tab_content']['#attributes'] = array('class' => array('views-display-tab'));
1157
    $form['displays']['settings']['settings_content']['tab_content']['#id'] = 'views-tab-' . $display_id;
1158
    // Mark deleted displays as such.
1159
    if (!empty($view->display[$display_id]->deleted)) {
1160
      $form['displays']['settings']['settings_content']['tab_content']['#attributes']['class'][] = 'views-display-deleted';
1161
    }
1162
    // Mark disabled displays as such.
1163
    if (empty($enabled)) {
1164
      $form['displays']['settings']['settings_content']['tab_content']['#attributes']['class'][] = 'views-display-disabled';
1165
    }
1166

    
1167
    // The content of the popup dialog.
1168
    $form['ajax-area'] = array(
1169
      '#theme_wrappers' => array('container'),
1170
      '#id' => 'views-ajax-popup',
1171
    );
1172
    $form['ajax-area']['ajax-title'] = array(
1173
      '#markup' => '<h2 id="views-ajax-title"></h2>',
1174
    );
1175
    $form['ajax-area']['ajax-body'] = array(
1176
      '#theme_wrappers' => array('container'),
1177
      '#id' => 'views-ajax-body',
1178
      '#markup' => views_ui_get_default_ajax_message(),
1179
    );
1180
  }
1181

    
1182
  // If relationships had to be fixed, we want to get that into the cache
1183
  // so that edits work properly, and to try to get the user to save it
1184
  // so that it's not using weird fixed up relationships.
1185
  if (!empty($view->relationships_changed) && empty($_POST)) {
1186
    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.'));
1187
    views_ui_cache_set($view);
1188
  }
1189
  return $form;
1190
}
1191

    
1192
/**
1193
 * Provide the preview formulas and the preview output, too.
1194
 */
1195
function views_ui_preview_form($form, &$form_state, $view, $display_id = 'default') {
1196
  $form_state['no_cache'] = TRUE;
1197
  $form_state['view'] = $view;
1198

    
1199
  $form['#attributes'] = array('class' => array('clearfix'));
1200

    
1201
  // Add a checkbox controlling whether or not this display auto-previews.
1202
  $form['live_preview'] = array(
1203
    '#type' => 'checkbox',
1204
    '#id' => 'edit-displays-live-preview',
1205
    '#title' => t('Auto preview'),
1206
    '#default_value' => variable_get('views_ui_always_live_preview', TRUE),
1207
  );
1208

    
1209
  // Add the arguments textfield.
1210
  $form['view_args'] = array(
1211
    '#type' => 'textfield',
1212
    '#title' => t('Preview with contextual filters:'),
1213
    '#description' => t('Separate contextual filter values with a "/". For example, %example.', array('%example' => '40/12/10')),
1214
    '#id' => 'preview-args',
1215
    // '#attributes' => array('class' => array('ctools-auto-submit')),
1216
  );
1217

    
1218
  // Add the preview button.
1219
  $form['button'] = array(
1220
    '#type' => 'submit',
1221
    '#value' => t('Update preview'),
1222
    '#attributes' => array('class' => array('arguments-preview', 'ctools-auto-submit-click')),
1223
    '#pre_render' => array('ctools_dependent_pre_render'),
1224
    '#prefix' => '<div id="preview-submit-wrapper">',
1225
    '#suffix' => '</div>',
1226
    '#id' => 'preview-submit',
1227
    '#submit' => array('views_ui_edit_form_submit_preview'),
1228
    '#ajax' => array(
1229
      'path' => 'admin/structure/views/view/' . $view->name . '/preview/' . $display_id . '/ajax',
1230
      'wrapper' => 'views-preview-wrapper',
1231
      'event' => 'click',
1232
      'progress' => array('type' => 'throbber'),
1233
      'method' => 'replace',
1234
    ),
1235
    // Make ENTER in arguments textfield (and other controls) submit the form
1236
    // as this button, not the Save button.
1237
    // @todo This only works for JS users. To make this work for nojs users,
1238
    // we may need to split Preview into a separate form.
1239
    '#process' => array_merge(array('views_ui_default_button'), element_info_property('submit', '#process', array())),
1240
  );
1241
  $form['#action'] = url('admin/structure/views/view/' . $view->name . '/preview/' . $display_id);
1242

    
1243
  return $form;
1244
}
1245

    
1246
/**
1247
 * Render the top of the display so it can be updated during ajax operations.
1248
 */
1249
function views_ui_render_display_top($view, $display_id) {
1250
  $element['#theme_wrappers'] = array('views_container');
1251
  $element['#attributes']['class'] = array('views-display-top', 'clearfix');
1252
  $element['#attributes']['id'] = array('views-display-top');
1253

    
1254
  // Extra actions for the display.
1255
  $element['extra_actions'] = array(
1256
    '#theme' => 'links__ctools_dropbutton',
1257
    '#attributes' => array(
1258
      'id' => 'views-display-extra-actions',
1259
      'class' => array(
1260
        'horizontal', 'right', 'links', 'actions',
1261
      ),
1262
    ),
1263
    '#links' => array(
1264
      'edit-details' => array(
1265
        'title' => t('edit view name/description'),
1266
        'href' => "admin/structure/views/nojs/edit-details/$view->name",
1267
        'attributes' => array('class' => array('views-ajax-link')),
1268
      ),
1269
      'analyze' => array(
1270
        'title' => t('analyze view'),
1271
        'href' => "admin/structure/views/nojs/analyze/$view->name/$display_id",
1272
        'attributes' => array('class' => array('views-ajax-link')),
1273
      ),
1274
      'clone' => array(
1275
        'title' => t('clone view'),
1276
        'href' => "admin/structure/views/view/$view->name/clone",
1277
      ),
1278
      'export' => array(
1279
        'title' => t('export view'),
1280
        'href' => "admin/structure/views/view/$view->name/export",
1281
      ),
1282
      'reorder' => array(
1283
        'title' => t('reorder displays'),
1284
        'href' => "admin/structure/views/nojs/reorder-displays/$view->name/$display_id",
1285
        'attributes' => array('class' => array('views-ajax-link')),
1286
      ),
1287
    ),
1288
  );
1289

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

    
1293
  if (isset($view->type) && $view->type != t('Default')) {
1294
    if ($view->type == t('Overridden')) {
1295
      $element['extra_actions']['#links']['revert'] = array(
1296
        'title' => t('revert view'),
1297
        'href' => "admin/structure/views/view/$view->name/revert",
1298
        'query' => array('destination' => "admin/structure/views/view/$view->name"),
1299
      );
1300
    }
1301
    else {
1302
      $element['extra_actions']['#links']['delete'] = array(
1303
        'title' => t('delete view'),
1304
        'href' => "admin/structure/views/view/$view->name/delete",
1305
      );
1306
    }
1307
  }
1308

    
1309
  // Determine the displays available for editing.
1310
  if ($tabs = views_ui_edit_page_display_tabs($view, $display_id)) {
1311
    if ($display_id) {
1312
      $tabs[$display_id]['#active'] = TRUE;
1313
    }
1314
    $tabs['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2><ul id = "views-display-menu-tabs" class="tabs secondary">';
1315
    $tabs['#suffix'] = '</ul>';
1316
    $element['tabs'] = $tabs;
1317
  }
1318

    
1319
  // Buttons for adding a new display.
1320
  foreach (views_fetch_plugin_names('display', NULL, array($view->base_table)) as $type => $label) {
1321
    $element['add_display'][$type] = array(
1322
      '#type' => 'submit',
1323
      '#value' => t('Add !display', array('!display' => $label)),
1324
      '#limit_validation_errors' => array(),
1325
      '#submit' => array('views_ui_edit_form_submit_add_display', 'views_ui_edit_form_submit_delay_destination'),
1326
      '#attributes' => array('class' => array('add-display')),
1327
      // Allow JavaScript to remove the 'Add ' prefix from the button label when
1328
      // placing the button in a "Add" dropdown menu.
1329
      '#process' => array_merge(array('views_ui_form_button_was_clicked'), element_info_property('submit', '#process', array())),
1330
      '#values' => array(t('Add !display', array('!display' => $label)), $label),
1331
    );
1332
  }
1333

    
1334
  return $element;
1335
}
1336

    
1337
/**
1338
 *
1339
 */
1340
function views_ui_get_default_ajax_message() {
1341
  return '<div class="message">' . t("Click on an item to edit that item's details.") . '</div>';
1342
}
1343

    
1344
/**
1345
 * Submit handler to add a display to a view.
1346
 */
1347
function views_ui_edit_form_submit_add_display($form, &$form_state) {
1348
  $view = $form_state['view'];
1349

    
1350
  // Create the new display.
1351
  $parents = $form_state['triggering_element']['#parents'];
1352
  $display_type = array_pop($parents);
1353
  $display_id = $view->add_display($display_type);
1354
  views_ui_cache_set($view);
1355

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

    
1360
/**
1361
 * Submit handler to duplicate a display for a view.
1362
 */
1363
function views_ui_edit_form_submit_duplicate_display($form, &$form_state) {
1364
  $view = $form_state['view'];
1365
  $display_id = $form_state['display_id'];
1366

    
1367
  // Create the new display.
1368
  $display = $view->display[$display_id];
1369
  $new_display_id = $view->add_display($display->display_plugin);
1370
  $view->display[$new_display_id] = clone $display;
1371
  $view->display[$new_display_id]->id = $new_display_id;
1372

    
1373
  // By setting the current display the changed marker will appear on the new
1374
  // display.
1375
  $view->current_display = $new_display_id;
1376
  views_ui_cache_set($view);
1377

    
1378
  // Redirect to the new display's edit page.
1379
  $form_state['redirect'] = 'admin/structure/views/view/' . $view->name . '/edit/' . $new_display_id;
1380
}
1381

    
1382
/**
1383
 * Submit handler to delete a display from a view.
1384
 */
1385
function views_ui_edit_form_submit_delete_display($form, &$form_state) {
1386
  $view = $form_state['view'];
1387
  $display_id = $form_state['display_id'];
1388

    
1389
  // Mark the display for deletion.
1390
  $view->display[$display_id]->deleted = TRUE;
1391
  views_ui_cache_set($view);
1392

    
1393
  // Redirect to the top-level edit page. The first remaining display will
1394
  // become the active display.
1395
  $form_state['redirect'] = 'admin/structure/views/view/' . $view->name;
1396
}
1397

    
1398
/**
1399
 * Submit handler to add a restore a removed display to a view.
1400
 */
1401
function views_ui_edit_form_submit_undo_delete_display($form, &$form_state) {
1402
  // Create the new display.
1403
  $id = $form_state['display_id'];
1404
  $form_state['view']->display[$id]->deleted = FALSE;
1405

    
1406
  // Store in cache.
1407
  views_ui_cache_set($form_state['view']);
1408

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

    
1413
/**
1414
 * Submit handler to enable a disabled display.
1415
 */
1416
function views_ui_edit_form_submit_enable_display($form, &$form_state) {
1417
  $id = $form_state['display_id'];
1418
  // set_option doesn't work because this would might affect upper displays.
1419
  $form_state['view']->display[$id]->handler->set_option('enabled', TRUE);
1420

    
1421
  // Store in cache.
1422
  views_ui_cache_set($form_state['view']);
1423

    
1424
  // Redirect to the top-level edit page.
1425
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
1426
}
1427

    
1428
/**
1429
 * Submit handler to disable display.
1430
 */
1431
function views_ui_edit_form_submit_disable_display($form, &$form_state) {
1432
  $id = $form_state['display_id'];
1433
  $form_state['view']->display[$id]->handler->set_option('enabled', FALSE);
1434

    
1435
  // Store in cache.
1436
  views_ui_cache_set($form_state['view']);
1437

    
1438
  // Redirect to the top-level edit page.
1439
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
1440
}
1441

    
1442
/**
1443
 * Submit handler when Preview button is clicked.
1444
 */
1445
function views_ui_edit_form_submit_preview($form, &$form_state) {
1446
  // Rebuild the form with a pristine $view object.
1447
  $form_state['build_info']['args'][0] = views_ui_cache_load($form_state['view']->name);
1448
  $form_state['show_preview'] = TRUE;
1449
  $form_state['rebuild'] = TRUE;
1450
}
1451

    
1452
/**
1453
 * Submit handler for form buttons that do not complete a form workflow.
1454
 *
1455
 * The Edit View form is a multistep form workflow, but with state managed by
1456
 * the CTools object cache rather than $form_state['rebuild']. Without this
1457
 * submit handler, buttons that add or remove displays would redirect to the
1458
 * destination parameter (e.g., when the Edit View form is linked to from a
1459
 * contextual link). This handler can be added to buttons whose form submission
1460
 * should not yet redirect to the destination.
1461
 */
1462
function views_ui_edit_form_submit_delay_destination($form, &$form_state) {
1463
  if (isset($_GET['destination']) && $form_state['redirect'] !== FALSE) {
1464
    if (!isset($form_state['redirect'])) {
1465
      $form_state['redirect'] = $_GET['q'];
1466
    }
1467
    if (is_string($form_state['redirect'])) {
1468
      $form_state['redirect'] = array($form_state['redirect']);
1469
    }
1470
    $options = isset($form_state['redirect'][1]) ? $form_state['redirect'][1] : array();
1471
    if (!isset($options['query']['destination'])) {
1472
      $options['query']['destination'] = $_GET['destination'];
1473
    }
1474
    $form_state['redirect'][1] = $options;
1475
    unset($_GET['destination']);
1476
  }
1477
}
1478

    
1479
/**
1480
 * Adds tabs for navigating across Displays when editing a View.
1481
 *
1482
 * This function can be called from hook_menu_local_tasks_alter() to implement
1483
 * these tabs as secondary local tasks, or it can be called from elsewhere if
1484
 * having them as secondary local tasks isn't desired. The caller is responsible
1485
 * for setting the active tab's #active property to TRUE.
1486
 *
1487
 * @param view $view
1488
 *    The view which will be edited.
1489
 * @param string $display_id
1490
 *    The display_id which is edited on the current request.
1491
 */
1492
function views_ui_edit_page_display_tabs(view $view, $display_id = NULL) {
1493
  $tabs = array();
1494

    
1495
  // Create a tab for each display.
1496
  foreach ($view->display as $id => $display) {
1497
    $tabs[$id] = array(
1498
      '#theme' => 'menu_local_task',
1499
      '#link' => array(
1500
        'title' => views_ui_get_display_label($view, $id),
1501
        'href' => 'admin/structure/views/view/' . $view->name . '/edit/' . $id,
1502
        'localized_options' => array(),
1503
      ),
1504
    );
1505
    if (!empty($display->deleted)) {
1506
      $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-deleted-link';
1507
    }
1508
    if (isset($display->display_options['enabled']) && !$display->display_options['enabled']) {
1509
      $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-disabled-link';
1510
    }
1511
  }
1512

    
1513
  // If the default display isn't supposed to be shown, don't display its tab,
1514
  // unless it's the only display.
1515
  if ((!views_ui_show_default_display($view) && $display_id != 'default') && count($tabs) > 1) {
1516
    $tabs['default']['#access'] = FALSE;
1517
  }
1518

    
1519
  // Mark the display tab as red to show validation errors.
1520
  $view->validate();
1521
  foreach ($view->display as $id => $display) {
1522
    if (!empty($view->display_errors[$id])) {
1523
      // Always show the tab.
1524
      $tabs[$id]['#access'] = TRUE;
1525
      // Add a class to mark the error and a title to make a hover tip.
1526
      $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'error';
1527
      $tabs[$id]['#link']['localized_options']['attributes']['title'] = t('This display has one or more validation errors; please review it.');
1528
    }
1529
  }
1530

    
1531
  return $tabs;
1532
}
1533

    
1534
/**
1535
 * Controls whether or not the default display should have its own tab on edit.
1536
 */
1537
function views_ui_show_default_display($view) {
1538
  // Always show the default display for advanced users who prefer that mode.
1539
  $advanced_mode = variable_get('views_ui_show_master_display', FALSE);
1540
  // For other users, show the default display only if there are no others, and
1541
  // hide it if there's at least one "real" display.
1542
  $additional_displays = (count($view->display) == 1);
1543

    
1544
  return $advanced_mode || $additional_displays;
1545
}
1546

    
1547
/**
1548
 * Returns a renderable array representing the edit page for one display.
1549
 */
1550
function views_ui_get_display_tab($view, $display_id) {
1551
  $build = array();
1552
  $display = $view->display[$display_id];
1553
  // If the plugin doesn't exist, display an error message instead of an edit
1554
  // page.
1555
  if (empty($display->handler)) {
1556
    $title = isset($display->display_title) ? $display->display_title : t('Invalid');
1557
    // @todo: Improved UX for the case where a plugin is missing.
1558
    $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));
1559
  }
1560
  // Build the content of the edit page.
1561
  else {
1562
    $build['details'] = views_ui_get_display_tab_details($view, $display);
1563
  }
1564
  // In AJAX context, views_ui_regenerate_tab() returns this outside of form
1565
  // context, so hook_form_views_ui_edit_form_alter() is insufficient.
1566
  drupal_alter('views_ui_display_tab', $build, $view, $display_id);
1567
  return $build;
1568
}
1569

    
1570
/**
1571
 * Helper function to get the display details section of the edit UI.
1572
 *
1573
 * @param view $view
1574
 *   The full view object.
1575
 * @param object $display
1576
 *   The display object to work with.
1577
 *
1578
 * @return array
1579
 *   A renderable page build array.
1580
 */
1581
function views_ui_get_display_tab_details($view, $display) {
1582
  $display_title = views_ui_get_display_label($view, $display->id, FALSE);
1583
  $build = array(
1584
    '#theme_wrappers' => array('container'),
1585
    '#attributes' => array('id' => 'edit-display-settings-details'),
1586
  );
1587

    
1588
  $plugin = views_fetch_plugin_data('display', $view->display[$display->id]->display_plugin);
1589
  // The following is for display purposes only. We need to determine if there
1590
  // is more than one button and wrap the buttons in a .ctools-dropbutton class
1591
  // if more than one is present. Otherwise, we'll just wrap the actions in the
1592
  // .ctools-button class.
1593
  $is_display_deleted = !empty($display->deleted);
1594
  $is_deletable = empty($plugin['no remove']);
1595
  // The master display cannot be cloned.
1596
  $is_default = $display->id == 'default';
1597
  // @todo: Figure out why get_option doesn't work here.
1598
  $is_enabled = $display->handler->get_option('enabled');
1599

    
1600
  if (!$is_display_deleted && $is_deletable && !$is_default) {
1601
    $prefix = '<div class="ctools-no-js ctools-button ctools-dropbutton"><div class="ctools-link"><a href="#" class="ctools-twisty ctools-text"><span class="element-invisible">open</span></a></div><div class="ctools-content"><ul class="horizontal right actions">';
1602
    $suffix = '</ul></div></div>';
1603
    $item_element = 'li';
1604
  }
1605
  else {
1606
    $prefix = '<div class="ctools-button"><div class="ctools-content"><ul class="horizontal right actions">';
1607
    $suffix = '</ul></div></div>';
1608
    $item_element = 'li';
1609
  }
1610

    
1611
  if ($display->id != 'default') {
1612
    $build['top']['#theme_wrappers'] = array('container');
1613
    $build['top']['#attributes']['id'] = 'edit-display-settings-top';
1614
    $build['top']['#attributes']['class'] = array(
1615
      'views-ui-display-tab-actions',
1616
      'views-ui-display-tab-bucket',
1617
      'clearfix',
1618
    );
1619

    
1620
    // The Delete, Duplicate and Undo Delete buttons.
1621
    $build['top']['actions'] = array(
1622
      '#prefix' => $prefix,
1623
      '#suffix' => $suffix,
1624
    );
1625

    
1626
    if (!$is_display_deleted) {
1627
      if (!$is_enabled) {
1628
        $build['top']['actions']['enable'] = array(
1629
          '#type' => 'submit',
1630
          '#value' => t('enable @display_title', array('@display_title' => $display_title)),
1631
          '#limit_validation_errors' => array(),
1632
          '#submit' => array(
1633
            'views_ui_edit_form_submit_enable_display',
1634
            'views_ui_edit_form_submit_delay_destination',
1635
          ),
1636
          '#prefix' => '<' . $item_element . ' class="enable">',
1637
          "#suffix" => '</' . $item_element . '>',
1638
        );
1639
      }
1640
      // Add a link to view the page.
1641
      elseif ($display->handler->has_path()) {
1642
        $path = $display->handler->get_path();
1643
        if (strpos($path, '%') === FALSE) {
1644
          $build['top']['actions']['path'] = array(
1645
            '#type' => 'link',
1646
            '#title' => t('view @display', array('@display' => $display->display_title)),
1647
            '#options' => array(
1648
              'alt' => array(t('Go to the real page for this display')),
1649
            ),
1650
            '#href' => $path,
1651
            '#prefix' => '<' . $item_element . ' class="view">',
1652
            "#suffix" => '</' . $item_element . '>',
1653
          );
1654
        }
1655
      }
1656
      if (!$is_default) {
1657
        $build['top']['actions']['duplicate'] = array(
1658
          '#type' => 'submit',
1659
          '#value' => t('clone @display_title', array('@display_title' => $display_title)),
1660
          '#limit_validation_errors' => array(),
1661
          '#submit' => array(
1662
            'views_ui_edit_form_submit_duplicate_display',
1663
            'views_ui_edit_form_submit_delay_destination',
1664
          ),
1665
          '#prefix' => '<' . $item_element . ' class="duplicate">',
1666
          "#suffix" => '</' . $item_element . '>',
1667
        );
1668
      }
1669
      if ($is_deletable) {
1670
        $build['top']['actions']['delete'] = array(
1671
          '#type' => 'submit',
1672
          '#value' => t('delete @display_title', array('@display_title' => $display_title)),
1673
          '#limit_validation_errors' => array(),
1674
          '#submit' => array(
1675
            'views_ui_edit_form_submit_delete_display',
1676
            'views_ui_edit_form_submit_delay_destination',
1677
          ),
1678
          '#prefix' => '<' . $item_element . ' class="delete">',
1679
          "#suffix" => '</' . $item_element . '>',
1680
        );
1681
      }
1682
      if ($is_enabled) {
1683
        $build['top']['actions']['disable'] = array(
1684
          '#type' => 'submit',
1685
          '#value' => t('disable @display_title', array('@display_title' => $display_title)),
1686
          '#limit_validation_errors' => array(),
1687
          '#submit' => array(
1688
            'views_ui_edit_form_submit_disable_display',
1689
            'views_ui_edit_form_submit_delay_destination',
1690
          ),
1691
          '#prefix' => '<' . $item_element . ' class="disable">',
1692
          "#suffix" => '</' . $item_element . '>',
1693
        );
1694
      }
1695
    }
1696
    else {
1697
      $build['top']['actions']['undo_delete'] = array(
1698
        '#type' => 'submit',
1699
        '#value' => t('undo delete of @display_title', array('@display_title' => $display_title)),
1700
        '#limit_validation_errors' => array(),
1701
        '#submit' => array(
1702
          'views_ui_edit_form_submit_undo_delete_display',
1703
          'views_ui_edit_form_submit_delay_destination',
1704
        ),
1705
        '#prefix' => '<' . $item_element . ' class="undo-delete">',
1706
        "#suffix" => '</' . $item_element . '>',
1707
      );
1708
    }
1709

    
1710
    // The area above the three columns.
1711
    $build['top']['display_title'] = array(
1712
      '#theme' => 'views_ui_display_tab_setting',
1713
      '#description' => t('Display name'),
1714
      '#link' => $display->handler->option_link(check_plain($display_title), 'display_title'),
1715
    );
1716
  }
1717

    
1718
  $build['columns'] = array();
1719
  $build['columns']['#theme_wrappers'] = array('container');
1720
  $build['columns']['#attributes'] = array(
1721
    'id' => 'edit-display-settings-main',
1722
    'class' => array('clearfix', 'views-display-columns'),
1723
  );
1724

    
1725
  $build['columns']['first']['#theme_wrappers'] = array('container');
1726
  $build['columns']['first']['#attributes'] = array(
1727
    'class' => array('views-display-column', 'first'),
1728
  );
1729

    
1730
  $build['columns']['second']['#theme_wrappers'] = array('container');
1731
  $build['columns']['second']['#attributes'] = array(
1732
    'class' => array('views-display-column', 'second'),
1733
  );
1734

    
1735
  $build['columns']['second']['settings'] = array();
1736
  $build['columns']['second']['header'] = array();
1737
  $build['columns']['second']['footer'] = array();
1738
  $build['columns']['second']['pager'] = array();
1739

    
1740
  // The third column buckets are wrapped in a CTools collapsible div.
1741
  $build['columns']['third']['#theme_wrappers'] = array('container');
1742
  $build['columns']['third']['#attributes'] = array(
1743
    'class' => array(
1744
      'views-display-column',
1745
      'third',
1746
      'ctools-collapsible-container',
1747
      'ctools-collapsible-remember',
1748
    ),
1749
  );
1750

    
1751
  // Specify an id that won't change after AJAX requests, so ctools can keep
1752
  // track of the user's preferred collapsible state. Use the same id across
1753
  // different displays of the same view, so changing displays doesn't
1754
  // recollapse the column.
1755
  $build['columns']['third']['#attributes']['id'] = 'views-ui-advanced-column-' . $view->name;
1756
  // Collapse the div by default.
1757
  if (!variable_get('views_ui_show_advanced_column', FALSE)) {
1758
    $build['columns']['third']['#attributes']['class'][] = 'ctools-collapsed';
1759
  }
1760
  $build['columns']['third']['advanced'] = array('#markup' => '<h3 class="ctools-collapsible-handle"><a href="">' . t('Advanced') . '</a></h3>');
1761
  $build['columns']['third']['collapse']['#theme_wrappers'] = array('container');
1762
  $build['columns']['third']['collapse']['#attributes'] = array(
1763
    'class' => array('ctools-collapsible-content'),
1764
  );
1765

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

    
1770
  // Fetch options from the display plugin, with a list of buckets they go into.
1771
  $options = array();
1772
  $display->handler->options_summary($buckets, $options);
1773

    
1774
  // Place each option into its bucket.
1775
  foreach ($options as $id => $option) {
1776
    // Each option self-identifies as belonging in a particular bucket.
1777
    $buckets[$option['category']]['build'][$id] = views_ui_edit_form_get_build_from_option($id, $option, $view, $display);
1778
  }
1779

    
1780
  // Place each bucket into the proper column.
1781
  foreach ($buckets as $id => $bucket) {
1782
    // Let buckets identify themselves as belonging in a column.
1783
    if (isset($bucket['column']) && isset($build['columns'][$bucket['column']])) {
1784
      $column = $bucket['column'];
1785
    }
1786
    // If a bucket doesn't pick one of our predefined columns to belong to, put
1787
    // it in the last one.
1788
    else {
1789
      $column = 'third';
1790
    }
1791
    if (isset($bucket['build']) && is_array($bucket['build'])) {
1792
      // The third column is a CTools collapsible div, so
1793
      // the structure of the form is a little different for this column.
1794
      if ($column === 'third') {
1795
        $build['columns'][$column]['collapse'][$id] = $bucket['build'];
1796
        $build['columns'][$column]['collapse'][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
1797
        $build['columns'][$column]['collapse'][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
1798
        $build['columns'][$column]['collapse'][$id]['#name'] = !empty($bucket['title']) ? $bucket['title'] : $id;
1799
      }
1800
      else {
1801
        $build['columns'][$column][$id] = $bucket['build'];
1802
        $build['columns'][$column][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
1803
        $build['columns'][$column][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
1804
        $build['columns'][$column][$id]['#name'] = !empty($bucket['title']) ? $bucket['title'] : $id;
1805
      }
1806
    }
1807
  }
1808

    
1809
  $build['columns']['first']['fields'] = views_ui_edit_form_get_bucket('field', $view, $display);
1810
  $build['columns']['first']['filters'] = views_ui_edit_form_get_bucket('filter', $view, $display);
1811
  $build['columns']['first']['sorts'] = views_ui_edit_form_get_bucket('sort', $view, $display);
1812
  $build['columns']['second']['header'] = views_ui_edit_form_get_bucket('header', $view, $display);
1813
  $build['columns']['second']['footer'] = views_ui_edit_form_get_bucket('footer', $view, $display);
1814
  $build['columns']['third']['collapse']['arguments'] = views_ui_edit_form_get_bucket('argument', $view, $display);
1815
  $build['columns']['third']['collapse']['relationships'] = views_ui_edit_form_get_bucket('relationship', $view, $display);
1816
  $build['columns']['third']['collapse']['empty'] = views_ui_edit_form_get_bucket('empty', $view, $display);
1817

    
1818
  return $build;
1819
}
1820

    
1821
/**
1822
 * Build a renderable array representing one option on the edit form.
1823
 *
1824
 * This function might be more logical as a method on an object, if a suitable
1825
 * object emerges out of refactoring.
1826
 */
1827
function views_ui_edit_form_get_build_from_option($id, $option, $view, $display) {
1828
  $option_build = array();
1829
  $option_build['#theme'] = 'views_ui_display_tab_setting';
1830

    
1831
  $option_build['#description'] = $option['title'];
1832

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

    
1835
  $option_build['#links'] = array();
1836
  if (!empty($option['links']) && is_array($option['links'])) {
1837
    foreach ($option['links'] as $link_id => $link_value) {
1838
      $option_build['#settings_links'][] = $display->handler->option_link($option['setting'], $link_id, 'views-button-configure', $link_value);
1839
    }
1840
  }
1841

    
1842
  if (!empty($display->handler->options['defaults'][$id])) {
1843
    $display_id = 'default';
1844
    $option_build['#defaulted'] = TRUE;
1845
  }
1846
  else {
1847
    $display_id = $display->id;
1848
    if (!$display->handler->is_default_display()) {
1849
      if ($display->handler->defaultable_sections($id)) {
1850
        $option_build['#overridden'] = TRUE;
1851
      }
1852
    }
1853
  }
1854
  $option_build['#attributes']['class'][] = drupal_clean_css_identifier($display_id . '-' . $id);
1855
  if (!empty($view->changed_sections[$display_id . '-' . $id])) {
1856
    $option_build['#changed'] = TRUE;
1857
  }
1858
  return $option_build;
1859
}
1860

    
1861
/**
1862
 * 
1863
 */
1864
function template_preprocess_views_ui_display_tab_setting(&$variables) {
1865
  static $zebra = 0;
1866
  $variables['zebra'] = ($zebra % 2 === 0 ? 'odd' : 'even');
1867
  $zebra++;
1868

    
1869
  // Put the main link to the left side.
1870
  array_unshift($variables['settings_links'], $variables['link']);
1871
  $variables['settings_links'] = implode('<span class="label">&nbsp;|&nbsp;</span>', $variables['settings_links']);
1872

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

    
1876
  if (!empty($variables['defaulted'])) {
1877
    $variables['classes_array'][] = 'defaulted';
1878
  }
1879
  if (!empty($variables['overridden'])) {
1880
    $variables['classes_array'][] = 'overridden';
1881
    $variables['attributes_array']['title'][] = t('Overridden');
1882
  }
1883

    
1884
  // Append a colon to the description, if requested.
1885
  if ($variables['description'] && $variables['description_separator']) {
1886
    $variables['description'] .= t(':');
1887
  }
1888
}
1889

    
1890
/**
1891
 * 
1892
 */
1893
function template_preprocess_views_ui_display_tab_bucket(&$variables) {
1894
  $element = $variables['element'];
1895

    
1896
  $variables['item_help_icon'] = '';
1897
  if (!empty($element['#item_help_icon'])) {
1898
    $variables['item_help_icon'] = render($element['#item_help_icon']);
1899
  }
1900
  if (!empty($element['#name'])) {
1901
    $variables['classes_array'][] = drupal_clean_css_identifier(strtolower($element['#name']));
1902
  }
1903
  if (!empty($element['#overridden'])) {
1904
    $variables['classes_array'][] = 'overridden';
1905
    $variables['attributes_array']['title'][] = t('Overridden');
1906
  }
1907

    
1908
  $variables['content'] = $element['#children'];
1909
  $variables['title'] = $element['#title'];
1910
  $variables['actions'] = !empty($element['#actions']) ? $element['#actions'] : '';
1911
}
1912

    
1913
/**
1914
 *
1915
 */
1916
function template_preprocess_views_ui_display_tab_column(&$variables) {
1917
  $element = $variables['element'];
1918

    
1919
  $variables['content'] = $element['#children'];
1920
  $variables['column'] = $element['#column'];
1921
}
1922

    
1923
/**
1924
 * Move form elements into fieldsets for presentation purposes.
1925
 *
1926
 * Many views forms use #tree = TRUE to keep their values in a hierarchy for
1927
 * easier storage. Moving the form elements into fieldsets during form building
1928
 * would break up that hierarchy. Therefore, we wait until the pre_render stage,
1929
 * where any changes we make affect presentation only and aren't reflected in
1930
 * $form_state['values'].
1931
 */
1932
function views_ui_pre_render_add_fieldset_markup($form) {
1933
  foreach (element_children($form) as $key) {
1934
    $element = $form[$key];
1935
    // In our form builder functions, we added an arbitrary #fieldset property
1936
    // to any element that belongs in a fieldset. If this form element has that
1937
    // property, move it into its fieldset.
1938
    if (isset($element['#fieldset']) && isset($form[$element['#fieldset']])) {
1939
      $form[$element['#fieldset']][$key] = $element;
1940
      // Remove the original element this duplicates.
1941
      unset($form[$key]);
1942
    }
1943
  }
1944

    
1945
  return $form;
1946
}
1947

    
1948
/**
1949
 * Flattens the structure of an element containing the #flatten property.
1950
 *
1951
 * If a form element has #flatten = TRUE, then all of it's children
1952
 * get moved to the same level as the element itself.
1953
 * So $form['to_be_flattened'][$key] becomes $form[$key], and
1954
 * $form['to_be_flattened'] gets unset.
1955
 */
1956
function views_ui_pre_render_flatten_data($form) {
1957
  foreach (element_children($form) as $key) {
1958
    $element = $form[$key];
1959
    if (!empty($element['#flatten'])) {
1960
      foreach (element_children($element) as $child_key) {
1961
        $form[$child_key] = $form[$key][$child_key];
1962
      }
1963
      // All done, remove the now-empty parent.
1964
      unset($form[$key]);
1965
    }
1966
  }
1967

    
1968
  return $form;
1969
}
1970

    
1971
/**
1972
 * Moves argument options into their place.
1973
 *
1974
 * When configuring the default argument behavior, almost each of the radio
1975
 * buttons has its own fieldset shown bellow it when the radio button is
1976
 * clicked. That fieldset is created through a custom form process callback.
1977
 * Each element that has #argument_option defined and pointing to a default
1978
 * behavior gets moved to the appropriate fieldset.
1979
 * So if #argument_option is specified as 'default', the element is moved
1980
 * to the 'default_options' fieldset.
1981
 */
1982
function views_ui_pre_render_move_argument_options($form) {
1983
  foreach (element_children($form) as $key) {
1984
    $element = $form[$key];
1985
    if (!empty($element['#argument_option'])) {
1986
      $container_name = $element['#argument_option'] . '_options';
1987
      if (isset($form['no_argument']['default_action'][$container_name])) {
1988
        $form['no_argument']['default_action'][$container_name][$key] = $element;
1989
      }
1990
      // Remove the original element this duplicates.
1991
      unset($form[$key]);
1992
    }
1993
  }
1994
  return $form;
1995
}
1996

    
1997
/**
1998
 * Custom form radios process function.
1999
 *
2000
 * Roll out a single radios element to a list of radios,
2001
 * using the options array as index.
2002
 * While doing that, create a container element underneath each option, which
2003
 * contains the settings related to that option.
2004
 *
2005
 * @see form_process_radios()
2006
 */
2007
function views_ui_process_container_radios($element) {
2008
  if (count($element['#options']) > 0) {
2009
    foreach ($element['#options'] as $key => $choice) {
2010
      $element += array($key => array());
2011
      // Generate the parents as the autogenerator does, so we will have a
2012
      // unique id for each radio button.
2013
      $parents_for_id = array_merge($element['#parents'], array($key));
2014

    
2015
      $element[$key] += array(
2016
        '#type' => 'radio',
2017
        '#title' => $choice,
2018
        // The key is sanitized in drupal_attributes() during output from the
2019
        // theme function.
2020
        '#return_value' => $key,
2021
        '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
2022
        '#attributes' => $element['#attributes'],
2023
        '#parents' => $element['#parents'],
2024
        '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
2025
        '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
2026
      );
2027
      $element[$key . '_options'] = array(
2028
        '#type' => 'container',
2029
        '#attributes' => array('class' => array('views-admin-dependent')),
2030
      );
2031
    }
2032
  }
2033
  return $element;
2034
}
2035

    
2036
/**
2037
 * Import a view from cut & paste.
2038
 */
2039
function views_ui_import_page($form, &$form_state) {
2040
  $form['name'] = array(
2041
    '#type' => 'textfield',
2042
    '#title' => t('View name'),
2043
    '#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.'),
2044
  );
2045

    
2046
  $form['name_override'] = array(
2047
    '#type' => 'checkbox',
2048
    '#title' => t('Replace an existing view if one exists with the same name'),
2049
  );
2050

    
2051
  $form['bypass_validation'] = array(
2052
    '#type' => 'checkbox',
2053
    '#title' => t('Bypass view validation'),
2054
    '#description' => t('Bypass the validation of plugins and handlers when importing this view.'),
2055
  );
2056

    
2057
  $form['view'] = array(
2058
    '#type' => 'textarea',
2059
    '#title' => t('Paste view code here'),
2060
    '#required' => TRUE,
2061
  );
2062

    
2063
  $form['submit'] = array(
2064
    '#type' => 'submit',
2065
    '#value' => t('Import'),
2066
    '#submit' => array('views_ui_import_submit'),
2067
    '#validate' => array('views_ui_import_validate'),
2068
  );
2069
  return $form;
2070
}
2071

    
2072
/**
2073
 * Validate handler to import a view.
2074
 */
2075
function views_ui_import_validate($form, &$form_state) {
2076
  $view = '';
2077
  views_include('view');
2078
  // Be forgiving if someone pastes views code that starts with '<?php'.
2079
  if (substr($form_state['values']['view'], 0, 5) == '<?php') {
2080
    $form_state['values']['view'] = substr($form_state['values']['view'], 5);
2081
  }
2082
  ob_start();
2083
  eval($form_state['values']['view']);
2084
  ob_end_clean();
2085

    
2086
  if (!is_object($view)) {
2087
    return form_error($form['view'], t('Unable to interpret view code.'));
2088
  }
2089

    
2090
  if (empty($view->api_version) || $view->api_version < 2) {
2091
    form_error($form['view'], t('That view is not compatible with this version of Views.
2092
      If you have a view from views1 you have to go to a drupal6 installation and import it there.'));
2093
  }
2094
  elseif (version_compare($view->api_version, views_api_version(), '>')) {
2095
    form_error($form['view'], t('That view is created for the version @import_version of views, but you only have @api_version', array(
2096
      '@import_version' => $view->api_version,
2097
      '@api_version' => views_api_version(),
2098
    )));
2099
  }
2100

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

    
2106
  if ($form_state['values']['name']) {
2107
    $view->name = $form_state['values']['name'];
2108
  }
2109

    
2110
  $test = views_get_view($view->name);
2111
  if (!$form_state['values']['name_override']) {
2112
    if ($test && $test->type != t('Default')) {
2113
      form_set_error('', t('A view by that name already exists; please choose a different name'));
2114
    }
2115
  }
2116
  else {
2117
    if ($test->vid) {
2118
      $view->vid = $test->vid;
2119
    }
2120
  }
2121

    
2122
  // Make sure base table gets set properly if it got moved.
2123
  $view->update();
2124

    
2125
  $view->init_display();
2126

    
2127
  $broken = FALSE;
2128

    
2129
  // Bypass the validation of view pluigns/handlers if option is checked.
2130
  if (!$form_state['values']['bypass_validation']) {
2131
    // Make sure that all plugins and handlers needed by this view actually
2132
    // exist.
2133
    foreach ($view->display as $id => $display) {
2134
      if (empty($display->handler) || !empty($display->handler->broken)) {
2135
        drupal_set_message(t('Display plugin @plugin is not available.', array(
2136
            '@plugin' => $display->display_plugin,
2137
          )), 'error');
2138
        $broken = TRUE;
2139
        continue;
2140
      }
2141

    
2142
      $plugin = views_get_plugin('style', $display->handler->get_option('style_plugin'));
2143
      if (!$plugin) {
2144
        drupal_set_message(t('Style plugin @plugin is not available.', array(
2145
            '@plugin' => $display->handler->get_option('style_plugin'),
2146
          )), 'error');
2147
        $broken = TRUE;
2148
      }
2149
      elseif ($plugin->uses_row_plugin()) {
2150
        $plugin = views_get_plugin('row', $display->handler->get_option('row_plugin'));
2151
        if (!$plugin) {
2152
          drupal_set_message(t('Row plugin @plugin is not available.', array(
2153
              '@plugin' => $display->handler->get_option('row_plugin'),
2154
            )), 'error');
2155
          $broken = TRUE;
2156
        }
2157
      }
2158

    
2159
      foreach (views_object_types() as $type => $info) {
2160
        $handlers = $display->handler->get_handlers($type);
2161
        if ($handlers) {
2162
          foreach ($handlers as $id => $handler) {
2163
            if ($handler->broken()) {
2164
              drupal_set_message(t('@type handler @table.@field is not available.', array(
2165
                  '@type' => $info['stitle'],
2166
                  '@table' => $handler->table,
2167
                  '@field' => $handler->field,
2168
                )), 'error');
2169
              $broken = TRUE;
2170
            }
2171
          }
2172
        }
2173
      }
2174
    }
2175
  }
2176

    
2177
  if ($broken) {
2178
    form_set_error('', t('Unable to import view.'));
2179
  }
2180

    
2181
  $form_state['view'] = &$view;
2182
}
2183

    
2184
/**
2185
 * Submit handler for view import.
2186
 */
2187
function views_ui_import_submit($form, &$form_state) {
2188
  // Store in cache and then go to edit.
2189
  views_ui_cache_set($form_state['view']);
2190
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
2191
}
2192

    
2193
/**
2194
 * Validate that a view is complete and whole.
2195
 */
2196
function views_ui_edit_view_form_validate($form, &$form_state) {
2197
  // Do not validate cancel or delete or revert.
2198
  if (empty($form_state['clicked_button']['#value']) || $form_state['clicked_button']['#value'] != t('Save')) {
2199
    return;
2200
  }
2201

    
2202
  $errors = $form_state['view']->validate();
2203
  if ($errors !== TRUE) {
2204
    foreach ($errors as $error) {
2205
      form_set_error('', $error);
2206
    }
2207
  }
2208
}
2209

    
2210
/**
2211
 * Submit handler for the edit view form.
2212
 */
2213
function views_ui_edit_view_form_submit($form, &$form_state) {
2214
  // Go through and remove displayed scheduled for removal.
2215
  foreach ($form_state['view']->display as $id => $display) {
2216
    if (!empty($display->deleted)) {
2217
      unset($form_state['view']->display[$id]);
2218
    }
2219
  }
2220
  // Rename display ids if needed.
2221
  foreach ($form_state['view']->display as $id => $display) {
2222
    if (!empty($display->new_id)) {
2223
      $form_state['view']->display[$id]->id = $display->new_id;
2224
      // Redirect the user to the renamed display to be sure that the page
2225
      // itself exists and doesn't throw errors.
2226
      $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $display->new_id;
2227
    }
2228
  }
2229

    
2230
  // Direct the user to the right url, if the path of the display has changed.
2231
  if (!empty($_GET['destination'])) {
2232
    $destination = $_GET['destination'];
2233
    // Find out the first display which has a changed path and redirect to this
2234
    // URL.
2235
    $old_view = views_get_view($form_state['view']->name);
2236
    foreach ($old_view->display as $id => $display) {
2237
      // Only check for displays with a path.
2238
      if (!isset($display->display_options['path'])) {
2239
        continue;
2240
      }
2241
      $old_path = $display->display_options['path'];
2242
      if (($display->display_plugin == 'page') && ($old_path == $destination) && ($old_path != $form_state['view']->display[$id]->display_options['path'])) {
2243
        $destination = $form_state['view']->display[$id]->display_options['path'];
2244
        unset($_GET['destination']);
2245
      }
2246
    }
2247
    $form_state['redirect'] = $destination;
2248
  }
2249

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

    
2253
  // Remove this view from cache so we can edit it properly.
2254
  ctools_object_cache_clear('view', $form_state['view']->name);
2255
}
2256

    
2257
/**
2258
 * Submit handler for the edit view form.
2259
 */
2260
function views_ui_edit_view_form_cancel($form, &$form_state) {
2261
  // Remove this view from cache so edits will be lost.
2262
  ctools_object_cache_clear('view', $form_state['view']->name);
2263
  if (empty($form['view']->vid)) {
2264
    // I seem to have to drupal_goto here because I can't get fapi to
2265
    // honor the redirect target. Not sure what I screwed up here.
2266
    drupal_goto('admin/structure/views');
2267
  }
2268
}
2269

    
2270
/**
2271
 *
2272
 */
2273
function views_ui_edit_view_form_delete($form, &$form_state) {
2274
  unset($_REQUEST['destination']);
2275
  // Redirect to the delete confirm page.
2276
  $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')));
2277
}
2278

    
2279
/**
2280
 * Add information about a section to a display.
2281
 */
2282
function views_ui_edit_form_get_bucket($type, $view, $display) {
2283
  $build = array(
2284
    '#theme_wrappers' => array('views_ui_display_tab_bucket'),
2285
  );
2286
  $types = views_object_types();
2287

    
2288
  $build['#overridden'] = FALSE;
2289
  $build['#defaulted'] = FALSE;
2290
  if (module_exists('advanced_help')) {
2291
    $build['#item_help_icon'] = array(
2292
      '#theme' => 'advanced_help_topic',
2293
      '#module' => 'views',
2294
      '#topic' => $type,
2295
    );
2296
  }
2297

    
2298
  $build['#name'] = $build['#title'] = $types[$type]['title'];
2299

    
2300
  // Different types now have different rearrange forms, so we use this switch
2301
  // to get the right one.
2302
  switch ($type) {
2303
    case 'filter':
2304
      $rearrange_url = "admin/structure/views/nojs/rearrange-$type/$view->name/$display->id/$type";
2305
      $rearrange_text = t('And/Or, Rearrange');
2306
      // @todo Add another class to have another symbol for filter rearrange.
2307
      $class = 'icon compact rearrange';
2308
      break;
2309

    
2310
    case 'field':
2311
      // Fetch the style plugin info so we know whether to list fields or not.
2312
      $style_plugin = $display->handler->get_plugin();
2313
      $uses_fields = $style_plugin && $style_plugin->uses_fields();
2314
      if (!$uses_fields) {
2315
        $build['fields'][] = array(
2316
          '#markup' => t('The selected style or row format does not utilize fields.'),
2317
          '#theme_wrappers' => array('views_container'),
2318
          '#attributes' => array('class' => array('views-display-setting')),
2319
        );
2320
        return $build;
2321
      }
2322

    
2323
    default:
2324
      $rearrange_url = "admin/structure/views/nojs/rearrange/$view->name/$display->id/$type";
2325
      $rearrange_text = t('Rearrange');
2326
      $class = 'icon compact rearrange';
2327
  }
2328

    
2329
  // Create an array of actions to pass to theme_links
2330
  $actions = array();
2331
  $count_handlers = count($display->handler->get_handlers($type));
2332
  $actions['add'] = array(
2333
    'title' => t('Add'),
2334
    'href' => "admin/structure/views/nojs/add-item/$view->name/$display->id/$type",
2335
    'attributes' => array('class' => array('icon compact add', 'views-ajax-link'), 'title' => t('Add'), 'id' => 'views-add-' . $type),
2336
    'html' => TRUE,
2337
  );
2338
  if ($count_handlers > 0) {
2339
    $actions['rearrange'] = array(
2340
      'title' => $rearrange_text,
2341
      'href' => $rearrange_url,
2342
      'attributes' => array('class' => array($class, 'views-ajax-link'), 'title' => t('Rearrange'), 'id' => 'views-rearrange-' . $type),
2343
      'html' => TRUE,
2344
    );
2345
  }
2346

    
2347
  // Render the array of links.
2348
  $build['#actions'] = theme('links__ctools_dropbutton',
2349
    array(
2350
      'links' => $actions,
2351
      'attributes' => array(
2352
        'class' => array('inline', 'links', 'actions', 'horizontal', 'right'),
2353
      ),
2354
      'class' => array('views-ui-settings-bucket-operations'),
2355
    )
2356
  );
2357

    
2358
  if (!$display->handler->is_default_display()) {
2359
    if (!$display->handler->is_defaulted($types[$type]['plural'])) {
2360
      $build['#overridden'] = TRUE;
2361
    }
2362
    else {
2363
      $build['#defaulted'] = TRUE;
2364
    }
2365
  }
2366

    
2367
  // If there's an options form for the bucket, link to it.
2368
  if (!empty($types[$type]['options'])) {
2369
    $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)));
2370
  }
2371

    
2372
  static $relationships = NULL;
2373
  if (!isset($relationships)) {
2374
    // Get relationship labels.
2375
    $relationships = array();
2376
    // @todo: get_handlers()
2377
    $handlers = $display->handler->get_option('relationships');
2378
    if ($handlers) {
2379
      foreach ($handlers as $id => $info) {
2380
        $handler = $display->handler->get_handler('relationship', $id);
2381
        $relationships[$id] = $handler->label();
2382
      }
2383
    }
2384
  }
2385

    
2386
  // Filters can now be grouped so we do a little bit extra.
2387
  $groups = array();
2388
  $grouping = FALSE;
2389
  if ($type == 'filter') {
2390
    $group_info = $view->display_handler->get_option('filter_groups');
2391
    // If there is only one group but it is using the "OR" filter, we still
2392
    // treat it as a group for display purposes, since we want to display the
2393
    // "OR" label next to items within the group.
2394
    if (!empty($group_info['groups']) && (count($group_info['groups']) > 1 || current($group_info['groups']) == 'OR')) {
2395
      $grouping = TRUE;
2396
      $groups = array(0 => array());
2397
    }
2398
  }
2399

    
2400
  $build['fields'] = array();
2401

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

    
2407
    $handler = $display->handler->get_handler($type, $id);
2408
    if (empty($handler)) {
2409
      $build['fields'][$id]['#class'][] = 'broken';
2410
      $field_name = t('Broken/missing handler: @table > @field', array('@table' => $field['table'], '@field' => $field['field']));
2411
      $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));
2412
      continue;
2413
    }
2414

    
2415
    $field_name = check_plain($handler->ui_name(TRUE));
2416
    if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
2417
      $field_name = '(' . $relationships[$field['relationship']] . ') ' . $field_name;
2418
    }
2419

    
2420
    $description = filter_xss_admin($handler->admin_summary());
2421
    $link_text = $field_name . (empty($description) ? '' : " ($description)");
2422
    $link_attributes = array('class' => array('views-ajax-link'));
2423
    if (!empty($field['exclude'])) {
2424
      $link_attributes['class'][] = 'views-field-excluded';
2425
    }
2426
    $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));
2427
    $build['fields'][$id]['#class'][] = drupal_clean_css_identifier($display->id . '-' . $type . '-' . $id);
2428
    if (!empty($view->changed_sections[$display->id . '-' . $type . '-' . $id])) {
2429
      // @todo: #changed is no longer being used?
2430
      $build['fields'][$id]['#changed'] = TRUE;
2431
    }
2432

    
2433
    if ($display->handler->use_group_by() && $handler->use_group_by()) {
2434
      $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));
2435
    }
2436

    
2437
    if ($handler->has_extra_options()) {
2438
      $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));
2439
    }
2440

    
2441
    if ($grouping) {
2442
      $gid = $handler->options['group'];
2443

    
2444
      // Show in default group if the group does not exist.
2445
      if (empty($group_info['groups'][$gid])) {
2446
        $gid = 0;
2447
      }
2448
      $groups[$gid][] = $id;
2449
    }
2450
  }
2451

    
2452
  // If using grouping, re-order fields so that they show up properly in the
2453
  // list.
2454
  if ($type == 'filter' && $grouping) {
2455
    $store = $build['fields'];
2456
    $build['fields'] = array();
2457
    foreach ($groups as $gid => $contents) {
2458
      // Display an operator between each group.
2459
      if (!empty($build['fields'])) {
2460
        $build['fields'][] = array(
2461
          '#theme' => 'views_ui_display_tab_setting',
2462
          '#class' => array('views-group-text'),
2463
          '#link' => ($group_info['operator'] == 'OR' ? t('OR') : t('AND')),
2464
        );
2465
      }
2466
      // Display an operator between each pair of filters within the group.
2467
      $keys = array_keys($contents);
2468
      $last = end($keys);
2469
      foreach ($contents as $key => $pid) {
2470
        if ($key != $last) {
2471
          $store[$pid]['#link'] .= '&nbsp;&nbsp;' . ($group_info['groups'][$gid] == 'OR' ? t('OR') : t('AND'));
2472
        }
2473
        $build['fields'][$pid] = $store[$pid];
2474
      }
2475
    }
2476
  }
2477

    
2478
  return $build;
2479
}
2480

    
2481
/**
2482
 * Regenerate the current tab for AJAX updates.
2483
 */
2484
function views_ui_regenerate_tab(&$view, &$output, $display_id) {
2485
  if (!$view->set_display('default')) {
2486
    return;
2487
  }
2488

    
2489
  // Regenerate the main display area.
2490
  $build = views_ui_get_display_tab($view, $display_id);
2491
  views_ui_add_microweights($build);
2492
  $output[] = ajax_command_html('#views-tab-' . $display_id, drupal_render($build));
2493

    
2494
  // Regenerate the top area so changes to display names and order will appear.
2495
  $build = views_ui_render_display_top($view, $display_id);
2496
  views_ui_add_microweights($build);
2497
  $output[] = ajax_command_replace('#views-display-top', drupal_render($build));
2498
}
2499

    
2500
/**
2501
 * Recursively adds microweights to a render array.
2502
 *
2503
 * Similar to what form_builder() does for forms.
2504
 *
2505
 * @todo Submit a core patch to fix drupal_render() to do this, so that all
2506
 *   render arrays automatically preserve array insertion order, as forms do.
2507
 */
2508
function views_ui_add_microweights(&$build) {
2509
  $count = 0;
2510
  foreach (element_children($build) as $key) {
2511
    if (!isset($build[$key]['#weight'])) {
2512
      $build[$key]['#weight'] = $count / 1000;
2513
    }
2514
    views_ui_add_microweights($build[$key]);
2515
    $count++;
2516
  }
2517
}
2518

    
2519
/**
2520
 * Provide a standard set of Apply/Cancel/OK buttons for the forms. Also provide
2521
 * a hidden op operator because the forms plugin doesn't seem to properly
2522
 * provide which button was clicked.
2523
 *
2524
 * TODO: Is the hidden op operator still here somewhere, or is that part of the
2525
 * docblock outdated?
2526
 */
2527
function views_ui_standard_form_buttons(&$form, &$form_state, $form_id, $name = NULL, $third = NULL, $submit = NULL) {
2528
  $form['buttons'] = array(
2529
    '#prefix' => '<div class="clearfix"><div class="form-buttons">',
2530
    '#suffix' => '</div></div>',
2531
  );
2532

    
2533
  if (empty($name)) {
2534
    $name = t('Apply');
2535
    $view = $form_state['view'];
2536
    if (!empty($view->stack) && count($view->stack) > 1) {
2537
      $name = t('Apply and continue');
2538
    }
2539
    $names = array(t('Apply'), t('Apply and continue'));
2540
  }
2541

    
2542
  // Forms that are purely informational set an ok_button flag, so we know not
2543
  // to create an "Apply" button for them.
2544
  if (empty($form_state['ok_button'])) {
2545
    $form['buttons']['submit'] = array(
2546
      '#type' => 'submit',
2547
      '#value' => $name,
2548
      // The regular submit handler ($form_id . '_submit') does not apply if
2549
      // we're updating the default display. It does apply if we're updating
2550
      // the current display. Since we have no way of knowing at this point
2551
      // which display the user wants to update, views_ui_standard_submit will
2552
      // take care of running the regular submit handler as appropriate.
2553
      '#submit' => array('views_ui_standard_submit'),
2554
    );
2555
    // Form API button click detection requires the button's #value to be the
2556
    // same between the form build of the initial page request, and the initial
2557
    // form build of the request processing the form submission. Ideally, the
2558
    // button's #value shouldn't change until the form rebuild step. However,
2559
    // views_ui_ajax_form() implements a different multistep form workflow than
2560
    // the Form API does, and adjusts $view->stack prior to form processing, so
2561
    // we compensate by extending button click detection code to support any of
2562
    // the possible button labels.
2563
    if (isset($names)) {
2564
      $form['buttons']['submit']['#values'] = $names;
2565
      $form['buttons']['submit']['#process'] = array_merge(array('views_ui_form_button_was_clicked'), element_info_property($form['buttons']['submit']['#type'], '#process', array()));
2566
    }
2567
    // If a validation handler exists for the form, assign it to this button.
2568
    if (function_exists($form_id . '_validate')) {
2569
      $form['buttons']['submit']['#validate'][] = $form_id . '_validate';
2570
    }
2571
  }
2572

    
2573
  // Create a "Cancel" button. For purely informational forms, label it "OK".
2574
  $cancel_submit = function_exists($form_id . '_cancel') ? $form_id . '_cancel' : 'views_ui_standard_cancel';
2575
  $form['buttons']['cancel'] = array(
2576
    '#type' => 'submit',
2577
    '#value' => empty($form_state['ok_button']) ? t('Cancel') : t('Ok'),
2578
    '#submit' => array($cancel_submit),
2579
    '#validate' => array(),
2580
  );
2581

    
2582
  // Some forms specify a third button, with a name and submit handler.
2583
  if ($third) {
2584
    if (empty($submit)) {
2585
      $submit = 'third';
2586
    }
2587
    $third_submit = function_exists($form_id . '_' . $submit) ? $form_id . '_' . $submit : 'views_ui_standard_cancel';
2588

    
2589
    $form['buttons'][$submit] = array(
2590
      '#type' => 'submit',
2591
      '#value' => $third,
2592
      '#validate' => array(),
2593
      '#submit' => array($third_submit),
2594
    );
2595
  }
2596

    
2597
  // Compatibility, to be removed later: // @todo When is "later"? We used to
2598
  // set these items on the form, but now we want them on the $form_state.
2599
  if (isset($form['#title'])) {
2600
    $form_state['title'] = $form['#title'];
2601
  }
2602
  if (isset($form['#help_topic'])) {
2603
    $form_state['help_topic'] = $form['#help_topic'];
2604
  }
2605
  if (isset($form['#help_module'])) {
2606
    $form_state['help_module'] = $form['#help_module'];
2607
  }
2608
  if (isset($form['#url'])) {
2609
    $form_state['url'] = $form['#url'];
2610
  }
2611
  if (isset($form['#section'])) {
2612
    $form_state['#section'] = $form['#section'];
2613
  }
2614
  // Finally, we never want these cached -- our object cache does that for us.
2615
  $form['#no_cache'] = TRUE;
2616

    
2617
  // If this isn't an ajaxy form, then we want to set the title.
2618
  if (!empty($form['#title'])) {
2619
    drupal_set_title($form['#title']);
2620
  }
2621
}
2622

    
2623
/**
2624
 * Basic submit handler applicable to all 'standard' forms.
2625
 *
2626
 * This submit handler determines whether the user wants the submitted changes
2627
 * to apply to the default display or to the current display, and dispatches
2628
 * control appropriately.
2629
 */
2630
function views_ui_standard_submit($form, &$form_state) {
2631
  // Determine whether the values the user entered are intended to apply to
2632
  // the current display or the default display.
2633
  list($was_defaulted, $is_defaulted, $revert) = views_ui_standard_override_values($form, $form_state);
2634

    
2635
  // Mark the changed section of the view as changed.
2636
  // @todo Document why we are doing this, and see if we still need it.
2637
  if (!empty($form['#section'])) {
2638
    $form_state['view']->changed_sections[$form['#section']] = TRUE;
2639
  }
2640

    
2641
  // Based on the user's choice in the display dropdown, determine which display
2642
  // these changes apply to.
2643
  if ($revert) {
2644
    // If it's revert just change the override and return.
2645
    $display = &$form_state['view']->display[$form_state['display_id']];
2646
    $display->handler->options_override($form, $form_state);
2647

    
2648
    // Don't execute the normal submit handling but still store the changed
2649
    // view into cache.
2650
    views_ui_cache_set($form_state['view']);
2651
    return;
2652
  }
2653
  elseif ($was_defaulted === $is_defaulted) {
2654
    // We're not changing which display these form values apply to.
2655
    // Run the regular submit handler for this form.
2656
  }
2657
  elseif ($was_defaulted && !$is_defaulted) {
2658
    // We were using the default display's values, but we're now overriding
2659
    // the default display and saving values specific to this display.
2660
    $display = &$form_state['view']->display[$form_state['display_id']];
2661
    // options_override toggles the override of this section.
2662
    $display->handler->options_override($form, $form_state);
2663
    $display->handler->options_submit($form, $form_state);
2664
  }
2665
  elseif (!$was_defaulted && $is_defaulted) {
2666
    // We used to have an override for this display, but the user now wants
2667
    // to go back to the default display.
2668
    // Overwrite the default display with the current form values, and make
2669
    // the current display use the new default values.
2670
    $display = &$form_state['view']->display[$form_state['display_id']];
2671
    // options_override toggles the override of this section.
2672
    $display->handler->options_override($form, $form_state);
2673
    $display->handler->options_submit($form, $form_state);
2674
  }
2675

    
2676
  $submit_handler = $form['#form_id'] . '_submit';
2677
  if (function_exists($submit_handler)) {
2678
    $submit_handler($form, $form_state);
2679
  }
2680
}
2681

    
2682
/**
2683
 * Return the was_defaulted, is_defaulted and revert state of a form.
2684
 */
2685
function views_ui_standard_override_values($form, $form_state) {
2686
  // Make sure the dropdown exists in the first place.
2687
  if (isset($form_state['values']['override']['dropdown'])) {
2688
    // #default_value is used to determine whether it was the default value or
2689
    // not. So the available options are: $display, 'default' and
2690
    // 'default_revert', not 'defaults'.
2691
    $was_defaulted = ($form['override']['dropdown']['#default_value'] === 'defaults');
2692
    $is_defaulted = ($form_state['values']['override']['dropdown'] === 'default');
2693
    $revert = ($form_state['values']['override']['dropdown'] === 'default_revert');
2694

    
2695
    if ($was_defaulted !== $is_defaulted && isset($form['#section'])) {
2696
      // We're changing which display these values apply to.
2697
      // Update the #section so it knows what to mark changed.
2698
      $form['#section'] = str_replace('default-', $form_state['display_id'] . '-', $form['#section']);
2699
    }
2700
  }
2701
  else {
2702
    // The user didn't get the dropdown for overriding the default display.
2703
    $was_defaulted = FALSE;
2704
    $is_defaulted = FALSE;
2705
    $revert = FALSE;
2706
  }
2707

    
2708
  return array($was_defaulted, $is_defaulted, $revert);
2709
}
2710

    
2711
/**
2712
 * Submit handler for cancel button.
2713
 */
2714
function views_ui_standard_cancel($form, &$form_state) {
2715
  if (!empty($form_state['view']->changed) && isset($form_state['view']->form_cache)) {
2716
    unset($form_state['view']->form_cache);
2717
    views_ui_cache_set($form_state['view']);
2718
  }
2719

    
2720
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
2721
}
2722

    
2723
/**
2724
 * Add a <select> dropdown for a given section.
2725
 *
2726
 * Allows the user to change whether this info is stored on the default display
2727
 * or on the current display.
2728
 */
2729
function views_ui_standard_display_dropdown(&$form, &$form_state, $section) {
2730
  $view = &$form_state['view'];
2731
  $display_id = $form_state['display_id'];
2732
  $displays = $view->display;
2733
  $current_display = $view->display[$display_id];
2734

    
2735
  // Add the "2 of 3" progress indicator.
2736
  // @todo: Move this to a separate function if it's needed on any forms that
2737
  // don't have the display dropdown.
2738
  if ($form_progress = views_ui_get_form_progress($view)) {
2739
    $form['progress']['#markup'] = '<div id="views-progress-indicator">' . t('@current of @total', array('@current' => $form_progress['current'], '@total' => $form_progress['total'])) . '</div>';
2740
    $form['progress']['#weight'] = -1001;
2741
  }
2742

    
2743
  if ($current_display->handler->is_default_display()) {
2744
    return;
2745
  }
2746

    
2747
  // Determine whether any other displays have overrides for this section.
2748
  $section_overrides = FALSE;
2749
  $section_defaulted = $current_display->handler->is_defaulted($section);
2750
  foreach ($displays as $id => $display) {
2751
    if ($id === 'default' || $id === $display_id) {
2752
      continue;
2753
    }
2754
    if ($display->handler && !$display->handler->is_defaulted($section)) {
2755
      $section_overrides = TRUE;
2756
    }
2757
  }
2758

    
2759
  $display_dropdown['default'] = ($section_overrides ? t('All displays (except overridden)') : t('All displays'));
2760
  $display_dropdown[$display_id] = t('This @display_type (override)', array('@display_type' => $current_display->display_plugin));
2761
  // Only display the revert option if we are in a overridden section.
2762
  if (!$section_defaulted) {
2763
    $display_dropdown['default_revert'] = t('Revert to default');
2764
  }
2765

    
2766
  $form['override'] = array(
2767
    '#prefix' => '<div class="views-override clearfix container-inline">',
2768
    '#suffix' => '</div>',
2769
    '#weight' => -1000,
2770
    '#tree' => TRUE,
2771
  );
2772
  $form['override']['dropdown'] = array(
2773
    '#type' => 'select',
2774
    '#title' => t('For'),
2775
  // @todo: Translators may need more context than this.
2776
    '#options' => $display_dropdown,
2777
  );
2778
  if ($current_display->handler->is_defaulted($section)) {
2779
    $form['override']['dropdown']['#default_value'] = 'defaults';
2780
  }
2781
  else {
2782
    $form['override']['dropdown']['#default_value'] = $display_id;
2783
  }
2784

    
2785
}
2786

    
2787
/**
2788
 * Get the user's current progress through the form stack.
2789
 *
2790
 * @param view $view
2791
 *   The current view.
2792
 *
2793
 * @return
2794
 *   FALSE if the user is not currently in a multiple-form stack. Otherwise,
2795
 *   an associative array with the following keys:
2796
 *   - current: The number of the current form on the stack.
2797
 *   - total: The total number of forms originally on the stack.
2798
 */
2799
function views_ui_get_form_progress($view) {
2800
  $progress = FALSE;
2801
  if (!empty($view->stack)) {
2802
    $stack = $view->stack;
2803
    // The forms on the stack have integer keys that don't change as the forms
2804
    // are completed, so we can see which ones are still left.
2805
    $keys = array_keys($view->stack);
2806
    // Add 1 to the array keys for the benefit of humans, who start counting
2807
    // from 1 and not 0.
2808
    $current = reset($keys) + 1;
2809
    $total = end($keys) + 1;
2810
    if ($total > 1) {
2811
      $progress = array();
2812
      $progress['current'] = $current;
2813
      $progress['total'] = $total;
2814
    }
2815
  }
2816
  return $progress;
2817
}
2818

    
2819

    
2820
// --------------------------------------------------------------------------
2821
// Various subforms for editing the pieces of a view.
2822
function views_ui_ajax_forms($key = NULL) {
2823
  $forms = array(
2824
    'display' => array(
2825
      'form_id' => 'views_ui_edit_display_form',
2826
      'args' => array('section'),
2827
    ),
2828
    'remove-display' => array(
2829
      'form_id' => 'views_ui_remove_display_form',
2830
      'args' => array(),
2831
    ),
2832
    'config-type' => array(
2833
      'form_id' => 'views_ui_config_type_form',
2834
      'args' => array('type'),
2835
    ),
2836
    'rearrange' => array(
2837
      'form_id' => 'views_ui_rearrange_form',
2838
      'args' => array('type'),
2839
    ),
2840
    'rearrange-filter' => array(
2841
      'form_id' => 'views_ui_rearrange_filter_form',
2842
      'args' => array('type'),
2843
    ),
2844
    'reorder-displays' => array(
2845
      'form_id' => 'views_ui_reorder_displays_form',
2846
      'args' => array(),
2847
    ),
2848
    'add-item' => array(
2849
      'form_id' => 'views_ui_add_item_form',
2850
      'args' => array('type'),
2851
    ),
2852
    'config-item' => array(
2853
      'form_id' => 'views_ui_config_item_form',
2854
      'args' => array('type', 'id'),
2855
    ),
2856
    'config-item-extra' => array(
2857
      'form_id' => 'views_ui_config_item_extra_form',
2858
      'args' => array('type', 'id'),
2859
    ),
2860
    'config-item-group' => array(
2861
      'form_id' => 'views_ui_config_item_group_form',
2862
      'args' => array('type', 'id'),
2863
    ),
2864
    'config-style' => array(
2865
      'form_id' => 'views_ui_config_style_form',
2866
      'args' => array('type', 'id'),
2867
    ),
2868
    'edit-details' => array(
2869
      'form_id' => 'views_ui_edit_details_form',
2870
      'args' => array(),
2871
    ),
2872
    'analyze' => array(
2873
      'form_id' => 'views_ui_analyze_view_form',
2874
      'args' => array(),
2875
    ),
2876
  );
2877

    
2878
  if ($key) {
2879
    return !empty($forms[$key]) ? $forms[$key] : NULL;
2880
  }
2881

    
2882
  return $forms;
2883
}
2884

    
2885
/**
2886
 * Build a form identifier that we can use to see if one form
2887
 * is the same as another. Since the arguments differ slightly
2888
 * we do a lot of spiffy concatenation here.
2889
 */
2890
function views_ui_build_identifier($key, $view, $display_id, $args) {
2891
  $form = views_ui_ajax_forms($key);
2892
  // Automatically remove the single-form cache if it exists and
2893
  // does not match the key.
2894
  $identifier = implode('-', array($key, $view->name, $display_id));
2895

    
2896
  foreach ($form['args'] as $id) {
2897
    $arg = (!empty($args)) ? array_shift($args) : NULL;
2898
    $identifier .= '-' . $arg;
2899
  }
2900
  return $identifier;
2901
}
2902

    
2903
/**
2904
 * Build up a $form_state object suitable for use with drupal_build_form
2905
 * based on known information about a form.
2906
 */
2907
function views_ui_build_form_state($js, $key, &$view, $display_id, $args) {
2908
  $form = views_ui_ajax_forms($key);
2909
  // Build up form state.
2910
  $form_state = array(
2911
    'form_key' => $key,
2912
    'form_id' => $form['form_id'],
2913
    'view' => &$view,
2914
    'ajax' => $js,
2915
    'display_id' => $display_id,
2916
    'no_redirect' => TRUE,
2917
  );
2918

    
2919
  foreach ($form['args'] as $id) {
2920
    $form_state[$id] = (!empty($args)) ? array_shift($args) : NULL;
2921
  }
2922

    
2923
  return $form_state;
2924
}
2925

    
2926
/**
2927
 * Create the URL for one of our standard AJAX forms based upon known
2928
 * information about the form.
2929
 */
2930
function views_ui_build_form_url($form_state) {
2931
  $form = views_ui_ajax_forms($form_state['form_key']);
2932
  $ajax = empty($form_state['ajax']) ? 'nojs' : 'ajax';
2933
  $name = $form_state['view']->name;
2934
  $url = "admin/structure/views/$ajax/$form_state[form_key]/$name/$form_state[display_id]";
2935
  foreach ($form['args'] as $arg) {
2936
    $url .= '/' . $form_state[$arg];
2937
  }
2938
  return $url;
2939
}
2940

    
2941
/**
2942
 * Add another form to the stack; clicking 'apply' will go to this form
2943
 * rather than closing the ajax popup.
2944
 */
2945
function views_ui_add_form_to_stack($key, &$view, $display_id, $args, $top = FALSE, $rebuild_keys = FALSE) {
2946
  if (empty($view->stack)) {
2947
    $view->stack = array();
2948
  }
2949

    
2950
  $stack = array(views_ui_build_identifier($key, $view, $display_id, $args), $key, &$view, $display_id, $args);
2951
  // If we're being asked to add this form to the bottom of the stack, no
2952
  // special logic is required. Our work is equally easy if we were asked to add
2953
  // to the top of the stack, but there's nothing in it yet.
2954
  if (!$top || empty($view->stack)) {
2955
    $view->stack[] = $stack;
2956
  }
2957
  // If we're adding to the top of an existing stack, we have to maintain the
2958
  // existing integer keys, so they can be used for the "2 of 3" progress
2959
  // indicator (which will now read "2 of 4").
2960
  else {
2961
    $keys = array_keys($view->stack);
2962
    $first = current($keys);
2963
    $last = end($keys);
2964
    for ($i = $last; $i >= $first; $i--) {
2965
      if (!isset($view->stack[$i])) {
2966
        continue;
2967
      }
2968
      // Move form number $i to the next position in the stack.
2969
      $view->stack[$i + 1] = $view->stack[$i];
2970
      unset($view->stack[$i]);
2971
    }
2972
    // Now that the previously $first slot is free, move the new form into it.
2973
    $view->stack[$first] = $stack;
2974
    ksort($view->stack);
2975

    
2976
    // Start the keys from 0 again, if requested.
2977
    if ($rebuild_keys) {
2978
      $view->stack = array_values($view->stack);
2979
    }
2980
  }
2981
}
2982

    
2983
/**
2984
 * Generic entry point to handle forms.
2985
 *
2986
 * We do this for consistency and to make it easy to chain forms
2987
 * together.
2988
 */
2989
function views_ui_ajax_form($js, $key, $view, $display_id = '') {
2990
  $args = func_get_args();
2991
  // Remove the known args.
2992
  array_splice($args, 0, 4);
2993

    
2994
  // Reset the cache of IDs. Drupal rather aggressively prevents id duplication
2995
  // but this causes it to remember IDs that are no longer even being used.
2996
  if (isset($_POST['ajax_html_ids'])) {
2997
    unset($_POST['ajax_html_ids']);
2998
  }
2999

    
3000
  $form = views_ui_ajax_forms($key);
3001
  if (empty($form)) {
3002
    return MENU_NOT_FOUND;
3003
  }
3004

    
3005
  views_include('ajax');
3006

    
3007
  $form_state = views_ui_build_form_state($js, $key, $view, $display_id, $args);
3008
  // check to see if this is the top form of the stack. If it is, pop
3009
  // it off; if it isn't, the user clicked somewhere else and the stack is
3010
  // now irrelevant.
3011
  if (!empty($view->stack)) {
3012
    $identifier = views_ui_build_identifier($key, $view, $display_id, $args);
3013
    // Retrieve the first form from the stack without changing the integer keys,
3014
    // as they're being used for the "2 of 3" progress indicator.
3015
    reset($view->stack);
3016
    $key = key($view->stack);
3017
    $top = current($view->stack);
3018
    unset($view->stack[$key]);
3019
    if (array_shift($top) != $identifier) {
3020
      $view->stack = array();
3021
    }
3022
  }
3023

    
3024
  // Automatically remove the form cache if it is set and the key does
3025
  // not match. This way navigating away from the form without hitting
3026
  // update will work.
3027
  if (isset($view->form_cache) && $view->form_cache['key'] != $key) {
3028
    unset($view->form_cache);
3029
  }
3030

    
3031
  // With the below logic, we may end up rendering a form twice (or two forms
3032
  // each sharing the same element ids), potentially resulting in
3033
  // drupal_add_js() being called twice to add the same setting. drupal_get_js()
3034
  // is ok with that, but until ajax_render() is, reset the drupal_add_js()
3035
  // static before rendering the second time.
3036
  // @see http://drupal.org/node/208611
3037
  $drupal_add_js_original = drupal_add_js();
3038
  $drupal_add_js = &drupal_static('drupal_add_js');
3039
  $output = views_ajax_form_wrapper($form_state['form_id'], $form_state);
3040
  if ($form_state['submitted'] && empty($form_state['rerender'])) {
3041
    // Sometimes we need to re-generate the form for multi-step type operations.
3042
    $object = NULL;
3043
    if (!empty($view->stack)) {
3044
      $drupal_add_js = $drupal_add_js_original;
3045
      $stack = $view->stack;
3046
      $top = array_shift($stack);
3047
      $top[0] = $js;
3048

    
3049
      // Change view into a reference.
3050
      $stepview = $top[2];
3051
      $top[2] = &$stepview;
3052

    
3053
      $form_state = call_user_func_array('views_ui_build_form_state', $top);
3054
      $form_state['input'] = array();
3055
      $form_state['url'] = url(views_ui_build_form_url($form_state));
3056
      if (!$js) {
3057
        return drupal_goto(views_ui_build_form_url($form_state));
3058
      }
3059
      $output = views_ajax_form_wrapper($form_state['form_id'], $form_state);
3060
    }
3061
    elseif (!$js) {
3062
      // If nothing on the stack, non-js forms just go back to the main view
3063
      // editor.
3064
      return drupal_goto("admin/structure/views/view/$view->name/edit");
3065
    }
3066
    else {
3067
      $output = array();
3068
      $output[] = views_ajax_command_dismiss_form();
3069
      $output[] = views_ajax_command_show_buttons(!empty($view->changed));
3070
      $output[] = views_ajax_command_trigger_preview();
3071
      if (!empty($form_state['#page_title'])) {
3072
        $output[] = views_ajax_command_replace_title($form_state['#page_title']);
3073
      }
3074
    }
3075
    // If this form was for view-wide changes, there's no need to regenerate
3076
    // the display section of the form.
3077
    if ($display_id !== '') {
3078
      views_ui_regenerate_tab($view, $output, $display_id);
3079
    }
3080
  }
3081

    
3082
  return $js ? array('#type' => 'ajax', '#commands' => $output) : $output;
3083
}
3084

    
3085
/**
3086
 * Submit handler to add a restore a removed display to a view.
3087
 */
3088
function views_ui_remove_display_form_restore($form, &$form_state) {
3089
  // Create the new display.
3090
  $id = $form_state['display_id'];
3091
  $form_state['view']->display[$id]->deleted = FALSE;
3092

    
3093
  // Store in cache.
3094
  views_ui_cache_set($form_state['view']);
3095
}
3096

    
3097
/**
3098
 * Form constructor callback to display analysis information on a view.
3099
 */
3100
function views_ui_analyze_view_form($form, &$form_state) {
3101
  $view = &$form_state['view'];
3102

    
3103
  $form['#title'] = t('View analysis');
3104
  $form['#section'] = 'analyze';
3105

    
3106
  views_include('analyze');
3107
  $messages = views_analyze_view($view);
3108

    
3109
  $form['analysis'] = array(
3110
    '#prefix' => '<div class="form-item">',
3111
    '#suffix' => '</div>',
3112
    '#markup' => views_analyze_format_result($view, $messages),
3113
  );
3114

    
3115
  // Inform the standard button function that we want an OK button.
3116
  $form_state['ok_button'] = TRUE;
3117
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_analyze_view_form');
3118
  return $form;
3119
}
3120

    
3121
/**
3122
 * Submit handler for views_ui_analyze_view_form.
3123
 */
3124
function views_ui_analyze_view_form_submit($form, &$form_state) {
3125
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
3126
}
3127

    
3128
/**
3129
 * Form constructor callback to reorder displays on a view.
3130
 */
3131
function views_ui_reorder_displays_form($form, &$form_state) {
3132
  $view = &$form_state['view'];
3133
  $display_id = $form_state['display_id'];
3134

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

    
3137
  $form['#tree'] = TRUE;
3138

    
3139
  $last_display = end($view->display);
3140

    
3141
  foreach ($view->display as $display) {
3142
    $form[$display->id] = array(
3143
      'title'  => array('#markup' => check_plain($display->display_title)),
3144
      'weight' => array(
3145
        '#type' => 'weight',
3146
        '#value' => $display->position,
3147
        '#delta' => $last_display->position,
3148
        '#title' => t('Weight for @display', array('@display' => $display->display_title)),
3149
        '#title_display' => 'invisible',
3150
      ),
3151
      '#tree' => TRUE,
3152
      '#display' => $display,
3153
      'removed' => array(
3154
        '#type' => 'checkbox',
3155
        '#id' => 'display-removed-' . $display->id,
3156
        '#attributes' => array('class' => array('views-remove-checkbox')),
3157
        '#default_value' => isset($display->deleted),
3158
      ),
3159
    );
3160

    
3161
    if (isset($display->deleted) && $display->deleted) {
3162
      $form[$display->id]['deleted'] = array('#type' => 'value', '#value' => TRUE);
3163
    }
3164
    if ($display->id === 'default') {
3165
      unset($form[$display->id]['weight']);
3166
      unset($form[$display->id]['removed']);
3167
    }
3168

    
3169
  }
3170

    
3171
  $form['#title'] = t('Displays Reorder');
3172
  $form['#section'] = 'reorder';
3173

    
3174
  // Add JavaScript settings that will be added via $.extend for tabledragging.
3175
  $form['#js']['tableDrag']['reorder-displays']['weight'][0] = array(
3176
    'target' => 'weight',
3177
    'source' => NULL,
3178
    'relationship' => 'sibling',
3179
    'action' => 'order',
3180
    'hidden' => TRUE,
3181
    'limit' => 0,
3182
  );
3183

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

    
3186
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_reorder_displays_form');
3187

    
3188
  return $form;
3189
}
3190

    
3191
/**
3192
 * Display position sorting function.
3193
 */
3194
function _views_position_sort($display1, $display2) {
3195
  if ($display1->position != $display2->position) {
3196
    return $display1->position < $display2->position ? -1 : 1;
3197
  }
3198

    
3199
  return 0;
3200
}
3201

    
3202
/**
3203
 * Submit handler for rearranging display form.
3204
 */
3205
function views_ui_reorder_displays_form_submit($form, &$form_state) {
3206
  foreach ($form_state['input'] as $display => $info) {
3207
    // add each value that is a field with a weight to our list, but only if
3208
    // it has had its 'removed' checkbox checked.
3209
    if (is_array($info) && isset($info['weight']) && empty($info['removed'])) {
3210
      $order[$display] = $info['weight'];
3211
    }
3212
  }
3213

    
3214
  // Sort the order array.
3215
  asort($order);
3216

    
3217
  // Fixing up positions.
3218
  $position = 2;
3219

    
3220
  foreach (array_keys($order) as $display) {
3221
    $order[$display] = $position++;
3222
  }
3223

    
3224
  // Setting up position and removing deleted displays.
3225
  $displays = $form_state['view']->display;
3226
  foreach ($displays as $display_id => $display) {
3227
    // Don't touch the default !!!
3228
    if ($display_id === 'default') {
3229
      continue;
3230
    }
3231
    if (isset($order[$display_id])) {
3232
      $form_state['view']->display[$display_id]->position = $order[$display_id];
3233
    }
3234
    else {
3235
      $form_state['view']->display[$display_id]->deleted = TRUE;
3236
    }
3237
  }
3238

    
3239
  // Sorting back the display array as the position is not enough.
3240
  uasort($form_state['view']->display, '_views_position_sort');
3241

    
3242
  // Store in cache.
3243
  views_ui_cache_set($form_state['view']);
3244
  $form_state['redirect'] = array('admin/structure/views/view/' . $form_state['view']->name . '/edit', array('fragment' => 'views-tab-default'));
3245
}
3246

    
3247
/**
3248
 * Turn the reorder form into a proper table.
3249
 */
3250
function theme_views_ui_reorder_displays_form($vars) {
3251
  $form = $vars['form'];
3252
  $rows = array();
3253
  foreach (element_children($form) as $key) {
3254
    if (isset($form[$key]['#display'])) {
3255
      $display = &$form[$key];
3256

    
3257
      $row = array();
3258
      $row[] = drupal_render($display['title']);
3259
      $form[$key]['weight']['#attributes']['class'] = array('weight');
3260
      $row[] = drupal_render($form[$key]['weight']);
3261
      if (isset($display['removed'])) {
3262
        $row[] = drupal_render($form[$key]['removed']) .
3263
          l('<span>' . t('Remove') . '</span>',
3264
            'javascript:void()',
3265
            array(
3266
              'attributes' => array(
3267
                'id' => 'display-remove-link-' . $key,
3268
                'class' => array('views-button-remove display-remove-link'),
3269
                'alt' => t('Remove this display'),
3270
                'title' => t('Remove this display')),
3271
              'html' => TRUE));
3272
      }
3273
      else {
3274
        $row[] = '';
3275
      }
3276
      $class = array();
3277
      $styles = array();
3278
      if (isset($form[$key]['weight']['#type'])) {
3279
        $class[] = 'draggable';
3280
      }
3281
      if (isset($form[$key]['deleted']['#value']) && $form[$key]['deleted']['#value']) {
3282
        $styles[] = 'display: none;';
3283
      }
3284
      $rows[] = array('data' => $row, 'class' => $class, 'id' => 'display-row-' . $key, 'style' => $styles);
3285
    }
3286
  }
3287

    
3288
  $header = array(t('Display'), t('Weight'), t('Remove'));
3289
  $output = '';
3290
  drupal_add_tabledrag('reorder-displays', 'order', 'sibling', 'weight');
3291

    
3292
  $output = drupal_render($form['override']);
3293
  $output .= '<div class="scroll">';
3294
  $output .= theme('table',
3295
    array(
3296
      'header' => $header,
3297
      'rows' => $rows,
3298
      'attributes' => array('id' => 'reorder-displays'),
3299
    ));
3300
  $output .= '</div>';
3301
  $output .= drupal_render_children($form);
3302

    
3303
  return $output;
3304
}
3305

    
3306
/**
3307
 * Form builder to edit details of a view.
3308
 */
3309
function views_ui_edit_details_form($form, &$form_state) {
3310
  $view = &$form_state['view'];
3311

    
3312
  $form['#title'] = t('View name and description');
3313
  $form['#section'] = 'details';
3314

    
3315
  $form['details'] = array(
3316
    '#theme_wrappers' => array('container'),
3317
    '#attributes' => array('class' => array('scroll')),
3318
  );
3319
  $form['details']['human_name'] = array(
3320
    '#type' => 'textfield',
3321
    '#title' => t('Human-readable name'),
3322
    '#description' => t('A descriptive human-readable name for this view. Spaces are allowed'),
3323
    '#default_value' => $view->get_human_name(),
3324
  );
3325
  $form['details']['tag'] = array(
3326
    '#type' => 'textfield',
3327
    '#title' => t('View tag'),
3328
    '#description' => t('Optionally, enter a comma delimited list of tags for this view to use in filtering and sorting views on the administrative page.'),
3329
    '#default_value' => $view->tag,
3330
    '#autocomplete_path' => 'admin/views/ajax/autocomplete/tag',
3331
  );
3332
  $form['details']['description'] = array(
3333
    '#type' => 'textfield',
3334
    '#title' => t('View description'),
3335
    '#description' => t('This description will appear on the Views administrative UI to tell you what the view is about.'),
3336
    '#default_value' => $view->description,
3337
  );
3338

    
3339
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_details_form');
3340
  return $form;
3341
}
3342

    
3343
/**
3344
 * Submit handler for views_ui_edit_details_form.
3345
 */
3346
function views_ui_edit_details_form_submit($form, &$form_state) {
3347
  $view = $form_state['view'];
3348
  foreach ($form_state['values'] as $key => $value) {
3349
    // Only save values onto the view if they're actual view properties
3350
    // (as opposed to 'op' or 'form_build_id').
3351
    if (isset($form['details'][$key])) {
3352
      $view->$key = $value;
3353
    }
3354
  }
3355
  $form_state['#page_title'] = views_ui_edit_page_title($view);
3356
  views_ui_cache_set($view);
3357
}
3358

    
3359
/**
3360
 * Form constructor callback to edit display of a view.
3361
 */
3362
function views_ui_edit_display_form($form, &$form_state) {
3363
  $view = &$form_state['view'];
3364
  $display_id = $form_state['display_id'];
3365
  $section = $form_state['section'];
3366

    
3367
  if (!$view->set_display($display_id)) {
3368
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
3369
  }
3370
  $display = &$view->display[$display_id];
3371

    
3372
  // Get form from the handler.
3373
  $form['options'] = array(
3374
    '#theme_wrappers' => array('container'),
3375
    '#attributes' => array('class' => array('scroll')),
3376
  );
3377
  $display->handler->options_form($form['options'], $form_state);
3378

    
3379
  // The handler options form sets $form['#title'], which we need on the entire
3380
  // $form instead of just the ['options'] section.
3381
  $form['#title'] = $form['options']['#title'];
3382
  unset($form['options']['#title']);
3383

    
3384
  // Move the override dropdown out of the scrollable section of the form.
3385
  if (isset($form['options']['override'])) {
3386
    $form['override'] = $form['options']['override'];
3387
    unset($form['options']['override']);
3388
  }
3389

    
3390
  $name = NULL;
3391
  if (isset($form_state['update_name'])) {
3392
    $name = $form_state['update_name'];
3393
  }
3394

    
3395
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_display_form', $name);
3396
  return $form;
3397
}
3398

    
3399
/**
3400
 * Validate handler for views_ui_edit_display_form.
3401
 */
3402
function views_ui_edit_display_form_validate($form, &$form_state) {
3403
  $display = &$form_state['view']->display[$form_state['display_id']];
3404
  $display->handler->options_validate($form['options'], $form_state);
3405

    
3406
  if (form_get_errors()) {
3407
    $form_state['rerender'] = TRUE;
3408
  }
3409
}
3410

    
3411
/**
3412
 * Submit handler for views_ui_edit_display_form.
3413
 */
3414
function views_ui_edit_display_form_submit($form, &$form_state) {
3415
  $display = &$form_state['view']->display[$form_state['display_id']];
3416
  $display->handler->options_submit($form, $form_state);
3417

    
3418
  views_ui_cache_set($form_state['view']);
3419
}
3420

    
3421
/**
3422
 * Override handler for views_ui_edit_display_form.
3423
 *
3424
 * @todo: Not currently used. Remove unless we implement an override toggle.
3425
 */
3426
function views_ui_edit_display_form_override($form, &$form_state) {
3427
  $display = &$form_state['view']->display[$form_state['display_id']];
3428
  $display->handler->options_override($form, $form_state);
3429

    
3430
  views_ui_cache_set($form_state['view']);
3431
  $form_state['rerender'] = TRUE;
3432
  $form_state['rebuild'] = TRUE;
3433
}
3434

    
3435
/**
3436
 * Form to config items in the views UI.
3437
 */
3438
function views_ui_config_type_form($form, &$form_state) {
3439
  $view = &$form_state['view'];
3440
  $display_id = $form_state['display_id'];
3441
  $type = $form_state['type'];
3442

    
3443
  $types = views_object_types();
3444
  if (!$view->set_display($display_id)) {
3445
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
3446
  }
3447
  $display = &$view->display[$display_id];
3448
  $form['#title'] = t('Configure @type', array('@type' => $types[$type]['ltitle']));
3449
  $form['#section'] = $display_id . 'config-item';
3450

    
3451
  if ($display->handler->defaultable_sections($types[$type]['plural'])) {
3452
    $form_state['section'] = $types[$type]['plural'];
3453
    views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
3454
  }
3455

    
3456
  if (!empty($types[$type]['options']) && function_exists($types[$type]['options'])) {
3457
    $options = $type . '_options';
3458
    $form[$options] = array('#tree' => TRUE);
3459
    $types[$type]['options']($form, $form_state);
3460
  }
3461

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

    
3467
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_type_form', $name);
3468
  return $form;
3469
}
3470

    
3471
/**
3472
 * Submit handler for type configuration form.
3473
 */
3474
function views_ui_config_type_form_submit($form, &$form_state) {
3475
  $types = views_object_types();
3476
  $display = &$form_state['view']->display[$form_state['display_id']];
3477

    
3478
  // Store in cache.
3479
  views_ui_cache_set($form_state['view']);
3480
}
3481

    
3482
/**
3483
 * Form to rearrange items in the views UI.
3484
 */
3485
function views_ui_rearrange_form($form, &$form_state) {
3486
  $view = &$form_state['view'];
3487
  $display_id = $form_state['display_id'];
3488
  $type = $form_state['type'];
3489

    
3490
  $types = views_object_types();
3491
  if (!$view->set_display($display_id)) {
3492
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
3493
  }
3494
  $display = &$view->display[$display_id];
3495
  $form['#title'] = t('Rearrange @type', array('@type' => $types[$type]['ltitle']));
3496
  $form['#section'] = $display_id . 'rearrange-item';
3497

    
3498
  if ($display->handler->defaultable_sections($types[$type]['plural'])) {
3499
    $form_state['section'] = $types[$type]['plural'];
3500
    views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
3501
  }
3502

    
3503
  $count = 0;
3504

    
3505
  // Get relationship labels.
3506
  $relationships = array();
3507
  foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
3508
    $relationships[$id] = $handler->label();
3509
    $handlers = $display->handler->get_option('relationships');
3510
    if ($handlers) {
3511
      foreach ($handlers as $id => $info) {
3512
        $handler = $display->handler->get_handler('relationship', $id);
3513
        $relationships[$id] = $handler->label();
3514
      }
3515
    }
3516
  }
3517

    
3518
  // Filters can now be grouped so we do a little bit extra.
3519
  $groups = array();
3520
  $grouping = FALSE;
3521
  if ($type == 'filter') {
3522
    $group_info = $view->display_handler->get_option('filter_groups');
3523
    if (!empty($group_info['groups']) && count($group_info['groups']) > 1) {
3524
      $grouping = TRUE;
3525
      $groups = array(0 => array());
3526
    }
3527
  }
3528

    
3529
  foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
3530
    $form['fields'][$id] = array('#tree' => TRUE);
3531
    $form['fields'][$id]['weight'] = array(
3532
      '#type' => 'textfield',
3533
      '#default_value' => ++$count,
3534
    );
3535
    $handler = $display->handler->get_handler($type, $id);
3536
    if ($handler) {
3537
      $name = $handler->ui_name() . ' ' . $handler->admin_summary();
3538
      if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
3539
        $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
3540
      }
3541

    
3542
      $form['fields'][$id]['name'] = array(
3543
        '#markup' => $name,
3544
      );
3545
    }
3546
    else {
3547
      $form['fields'][$id]['name'] = array('#markup' => t('Broken field @id', array('@id' => $id)));
3548
    }
3549
    $form['fields'][$id]['removed'] = array(
3550
      '#type' => 'checkbox',
3551
      '#id' => 'views-removed-' . $id,
3552
      '#attributes' => array('class' => array('views-remove-checkbox')),
3553
      '#default_value' => 0,
3554
    );
3555
  }
3556

    
3557
  // Add JavaScript settings that will be added via $.extend for tabledragging.
3558
  $form['#js']['tableDrag']['arrange']['weight'][0] = array(
3559
    'target' => 'weight',
3560
    'source' => NULL,
3561
    'relationship' => 'sibling',
3562
    'action' => 'order',
3563
    'hidden' => TRUE,
3564
    'limit' => 0,
3565
  );
3566

    
3567
  $name = NULL;
3568
  if (isset($form_state['update_name'])) {
3569
    $name = $form_state['update_name'];
3570
  }
3571

    
3572
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_rearrange_form');
3573
  return $form;
3574
}
3575

    
3576
/**
3577
 * Turn the rearrange form into a proper table.
3578
 */
3579
function theme_views_ui_rearrange_form($variables) {
3580
  $form = $variables['form'];
3581

    
3582
  $rows = array();
3583
  foreach (element_children($form['fields']) as $id) {
3584
    if (isset($form['fields'][$id]['name'])) {
3585
      $row = array();
3586
      $row[] = drupal_render($form['fields'][$id]['name']);
3587
      $form['fields'][$id]['weight']['#attributes']['class'] = array('weight');
3588
      $row[] = drupal_render($form['fields'][$id]['weight']);
3589
      $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));
3590
      $rows[] = array('data' => $row, 'class' => array('draggable'), 'id' => 'views-row-' . $id);
3591
    }
3592
  }
3593
  if (empty($rows)) {
3594
    $rows[] = array(array('data' => t('No fields available.'), 'colspan' => '2'));
3595
  }
3596

    
3597
  $header = array('', t('Weight'), t('Remove'));
3598
  $output = drupal_render($form['override']);
3599
  $output .= '<div class="scroll">';
3600
  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'arrange')));
3601
  $output .= '</div>';
3602
  $output .= drupal_render_children($form);
3603
  drupal_add_tabledrag('arrange', 'order', 'sibling', 'weight');
3604

    
3605
  return $output;
3606
}
3607

    
3608
/**
3609
 * Theme the expose filter form.
3610
 */
3611
function theme_views_ui_expose_filter_form($variables) {
3612
  $form = $variables['form'];
3613
  $more = drupal_render($form['more']);
3614

    
3615
  $output = drupal_render($form['form_description']);
3616
  $output .= drupal_render($form['expose_button']);
3617
  $output .= drupal_render($form['group_button']);
3618
  if (isset($form['required'])) {
3619
    $output .= drupal_render($form['required']);
3620
  }
3621
  $output .= drupal_render($form['label']);
3622
  $output .= drupal_render($form['description']);
3623

    
3624
  $output .= drupal_render($form['operator']);
3625
  $output .= drupal_render($form['value']);
3626

    
3627
  if (isset($form['use_operator'])) {
3628
    $output .= '<div class="views-left-40">';
3629
    $output .= drupal_render($form['use_operator']);
3630
    $output .= '</div>';
3631
  }
3632

    
3633
  // Only output the right column markup if there's a left column to begin with.
3634
  if (!empty($form['operator']['#type'])) {
3635
    $output .= '<div class="views-right-60">';
3636
    $output .= drupal_render_children($form);
3637
    $output .= '</div>';
3638
  }
3639
  else {
3640
    $output .= drupal_render_children($form);
3641
  }
3642

    
3643
  $output .= $more;
3644

    
3645
  return $output;
3646
}
3647

    
3648
/**
3649
 * Theme the build group filter form.
3650
 */
3651
function theme_views_ui_build_group_filter_form($variables) {
3652
  $form = $variables['form'];
3653
  $more = drupal_render($form['more']);
3654

    
3655
  $output = drupal_render($form['form_description']);
3656
  $output .= drupal_render($form['expose_button']);
3657
  $output .= drupal_render($form['group_button']);
3658
  if (isset($form['required'])) {
3659
    $output .= drupal_render($form['required']);
3660
  }
3661

    
3662
  $output .= drupal_render($form['operator']);
3663
  $output .= drupal_render($form['value']);
3664

    
3665
  $output .= '<div class="views-left-40">';
3666
  $output .= drupal_render($form['optional']);
3667
  $output .= drupal_render($form['remember']);
3668
  $output .= '</div>';
3669

    
3670
  $output .= '<div class="views-right-60">';
3671
  $output .= drupal_render($form['widget']);
3672
  $output .= drupal_render($form['label']);
3673
  $output .= drupal_render($form['description']);
3674
  $output .= '</div>';
3675

    
3676
  $header = array(
3677
    t('Default'),
3678
    t('Weight'),
3679
    t('Label'),
3680
    t('Operator'),
3681
    t('Value'),
3682
    t('Operations'),
3683
  );
3684

    
3685
  $form['default_group'] = form_process_radios($form['default_group']);
3686
  $form['default_group_multiple'] = form_process_checkboxes($form['default_group_multiple']);
3687
  $form['default_group']['All']['#title'] = '';
3688

    
3689
  drupal_render($form['default_group_multiple']['All']);
3690
  // Don't render.
3691
  $rows[] = array(
3692
    drupal_render($form['default_group']['All']),
3693
    '',
3694
    array(
3695
      'data' => variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? t('&lt;Any&gt;') : t('- Any -'),
3696
      'colspan' => 4,
3697
      'class' => array('class' => 'any-default-radios-row'),
3698
    ),
3699
  );
3700

    
3701
  foreach (element_children($form['group_items']) as $group_id) {
3702
    $form['group_items'][$group_id]['value']['#title'] = '';
3703
    $data = array(
3704
      'default' => drupal_render($form['default_group'][$group_id]) . drupal_render($form['default_group_multiple'][$group_id]),
3705
      'weight' => drupal_render($form['group_items'][$group_id]['weight']),
3706
      'title' => drupal_render($form['group_items'][$group_id]['title']),
3707
      'operator' => drupal_render($form['group_items'][$group_id]['operator']),
3708
      'value' => drupal_render($form['group_items'][$group_id]['value']),
3709
      '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)),
3710
    );
3711
    $rows[] = array('data' => $data, 'id' => 'views-row-' . $group_id, 'class' => array('draggable'));
3712
  }
3713
  $table = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('views-filter-groups'), 'id' => 'views-filter-groups'))) . drupal_render($form['add_group']);
3714
  drupal_add_tabledrag('views-filter-groups', 'order', 'sibling', 'weight');
3715
  $render_form = drupal_render_children($form);
3716
  return $output . $render_form . $table . $more;
3717
}
3718

    
3719

    
3720
/**
3721
 * Submit handler for rearranging form.
3722
 */
3723
function views_ui_rearrange_form_submit($form, &$form_state) {
3724
  $types = views_object_types();
3725
  $display = &$form_state['view']->display[$form_state['display_id']];
3726

    
3727
  $old_fields = $display->handler->get_option($types[$form_state['type']]['plural']);
3728
  $new_fields = $order = array();
3729

    
3730
  // Make an array with the weights.
3731
  foreach ($form_state['values'] as $field => $info) {
3732
    // add each value that is a field with a weight to our list, but only if
3733
    // it has had its 'removed' checkbox checked.
3734
    if (is_array($info) && isset($info['weight']) && empty($info['removed'])) {
3735
      $order[$field] = $info['weight'];
3736
    }
3737
  }
3738

    
3739
  // Sort the array.
3740
  asort($order);
3741

    
3742
  // Create a new list of fields in the new order.
3743
  foreach (array_keys($order) as $field) {
3744
    $new_fields[$field] = $old_fields[$field];
3745
  }
3746
  $display->handler->set_option($types[$form_state['type']]['plural'], $new_fields);
3747

    
3748
  // Store in cache.
3749
  views_ui_cache_set($form_state['view']);
3750
}
3751

    
3752
/**
3753
 * Form to rearrange items in the views UI.
3754
 */
3755
function views_ui_rearrange_filter_form($form, &$form_state) {
3756
  $view = &$form_state['view'];
3757
  $display_id = $form_state['display_id'];
3758
  $type = $form_state['type'];
3759

    
3760
  $types = views_object_types();
3761
  if (!$view->set_display($display_id)) {
3762
    views_ajax_render(t('Invalid display id @display', array('@display' => $display_id)));
3763
  }
3764
  $display = &$view->display[$display_id];
3765
  $form['#title'] = check_plain($display->display_title) . ': ';
3766
  $form['#title'] .= t('Rearrange @type', array('@type' => $types[$type]['ltitle']));
3767
  $form['#section'] = $display_id . 'rearrange-item';
3768

    
3769
  if ($display->handler->defaultable_sections($types[$type]['plural'])) {
3770
    $form_state['section'] = $types[$type]['plural'];
3771
    views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
3772
  }
3773

    
3774
  if (!empty($view->form_cache)) {
3775
    $groups = $view->form_cache['groups'];
3776
    $handlers = $view->form_cache['handlers'];
3777
  }
3778
  else {
3779
    $groups = $display->handler->get_option('filter_groups');
3780
    $handlers = $display->handler->get_option($types[$type]['plural']);
3781
  }
3782
  $count = 0;
3783

    
3784
  // Get relationship labels.
3785
  $relationships = array();
3786
  foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
3787
    $relationships[$id] = $handler->label();
3788
  }
3789

    
3790
  $group_options = array();
3791

    
3792
  /**
3793
   * Filter groups is an array that contains:
3794
   * array(
3795
   *   'operator' => 'and' || 'or',
3796
   *   'groups' => array(
3797
   *     $group_id => 'and' || 'or',
3798
   *   ),
3799
   * );
3800
   */
3801

    
3802
  $grouping = count(array_keys($groups['groups'])) > 1;
3803

    
3804
  $form['filter_groups']['#tree'] = TRUE;
3805
  $form['filter_groups']['operator'] = array(
3806
    '#type' => 'select',
3807
    '#options' => array(
3808
      'AND' => t('And'),
3809
      'OR' => t('Or'),
3810
    ),
3811
    '#default_value' => $groups['operator'],
3812
    '#attributes' => array(
3813
      'class' => array('warning-on-change'),
3814
    ),
3815
    '#title' => t('Operator to use on all groups'),
3816
    '#description' => t('Either "group 0 AND group 1 AND group 2" or "group 0 OR group 1 OR group 2", etc'),
3817
    '#access' => $grouping,
3818
  );
3819

    
3820
  $form['remove_groups']['#tree'] = TRUE;
3821

    
3822
  foreach ($groups['groups'] as $id => $group) {
3823
    $form['filter_groups']['groups'][$id] = array(
3824
      '#title' => t('Operator'),
3825
      '#type' => 'select',
3826
      '#options' => array(
3827
        'AND' => t('And'),
3828
        'OR' => t('Or'),
3829
      ),
3830
      '#default_value' => $group,
3831
      '#attributes' => array(
3832
        'class' => array('warning-on-change'),
3833
      ),
3834
    );
3835

    
3836
    $form['remove_groups'][$id] = array();
3837
    // to prevent a notice.
3838
    if ($id != 1) {
3839
      $form['remove_groups'][$id] = array(
3840
        '#type' => 'submit',
3841
        '#value' => t('Remove group @group', array('@group' => $id)),
3842
        '#id' => "views-remove-group-$id",
3843
        '#attributes' => array(
3844
          'class' => array('views-remove-group'),
3845
        ),
3846
        '#group' => $id,
3847
      );
3848
    }
3849
    $group_options[$id] = $id == 1 ? t('Default group') : t('Group @group', array('@group' => $id));
3850
    $form['#group_renders'][$id] = array();
3851
  }
3852

    
3853
  $form['#group_options'] = $group_options;
3854
  $form['#groups'] = $groups;
3855
  // We don't use get_handlers() because we want items without handlers to
3856
  // appear and show up as 'broken' so that the user can see them.
3857
  $form['filters'] = array('#tree' => TRUE);
3858
  foreach ($handlers as $id => $field) {
3859
    // If the group does not exist, move the filters to the default group.
3860
    if (empty($field['group']) || empty($groups['groups'][$field['group']])) {
3861
      $field['group'] = 1;
3862
    }
3863

    
3864
    $handler = $display->handler->get_handler($type, $id);
3865
    if ($grouping && $handler && !$handler->can_group()) {
3866
      $field['group'] = 'ungroupable';
3867
    }
3868

    
3869
    // If not grouping and the handler is set ungroupable, move it back to the
3870
    // default group to prevent weird errors from having it be in its own group.
3871
    if (!$grouping && $field['group'] == 'ungroupable') {
3872
      $field['group'] = 1;
3873
    }
3874

    
3875
    // Place this item into the proper group for rendering.
3876
    $form['#group_renders'][$field['group']][] = $id;
3877

    
3878
    $form['filters'][$id]['weight'] = array(
3879
      '#type' => 'textfield',
3880
      '#default_value' => ++$count,
3881
      '#size' => 8,
3882
    );
3883
    $form['filters'][$id]['group'] = array(
3884
      '#type' => 'select',
3885
      '#options' => $group_options,
3886
      '#default_value' => $field['group'],
3887
      '#attributes' => array(
3888
        'class' => array('views-region-select', 'views-region-' . $id),
3889
      ),
3890
      '#access' => $field['group'] !== 'ungroupable',
3891
    );
3892

    
3893
    if ($handler) {
3894
      $name = $handler->ui_name() . ' ' . $handler->admin_summary();
3895
      if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
3896
        $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
3897
      }
3898

    
3899
      $form['filters'][$id]['name'] = array(
3900
        '#markup' => $name,
3901
      );
3902
    }
3903
    else {
3904
      $form['filters'][$id]['name'] = array('#markup' => t('Broken field @id', array('@id' => $id)));
3905
    }
3906
    $form['filters'][$id]['removed'] = array(
3907
      '#type' => 'checkbox',
3908
      '#id' => 'views-removed-' . $id,
3909
      '#attributes' => array('class' => array('views-remove-checkbox')),
3910
      '#default_value' => 0,
3911
    );
3912
  }
3913

    
3914
  if (isset($form_state['update_name'])) {
3915
    $name = $form_state['update_name'];
3916
  }
3917

    
3918
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_rearrange_filter_form');
3919
  $form['buttons']['add_group'] = array(
3920
    '#type' => 'submit',
3921
    '#value' => t('Create new filter group'),
3922
    '#id' => 'views-add-group',
3923
    '#group' => 'add',
3924
  );
3925

    
3926
  return $form;
3927
}
3928

    
3929
/**
3930
 * Turn the rearrange form into a proper table.
3931
 */
3932
function theme_views_ui_rearrange_filter_form(&$vars) {
3933
  $form = $vars['form'];
3934
  $rows = $ungroupable_rows = array();
3935
  // Enable grouping only if > 1 group.
3936
  $grouping = count(array_keys($form['#group_options'])) > 1;
3937

    
3938
  foreach ($form['#group_renders'] as $group_id => $contents) {
3939
    // Header row for the group.
3940
    if ($group_id !== 'ungroupable') {
3941
      // Set up tabledrag so that it changes the group dropdown when rows are
3942
      // dragged between groups.
3943
      drupal_add_tabledrag('views-rearrange-filters', 'match', 'sibling', 'views-group-select', 'views-group-select-' . $group_id);
3944

    
3945
      // Title row, spanning all columns.
3946
      $row = array();
3947
      // Add a cell to the first row, containing the group operator.
3948
      $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)));
3949
      // Title.
3950
      $row[] = array('class' => array('group', 'group-title'), 'data' => '<span>' . $form['#group_options'][$group_id] . '</span>', 'colspan' => 4);
3951
      $rows[] = array('class' => array('views-group-title'), 'data' => $row, 'id' => 'views-group-title-' . $group_id);
3952

    
3953
      // Row which will only appear if the group has nothing in it.
3954
      $row = array();
3955
      $class = 'group-' . (count($contents) ? 'populated' : 'empty');
3956
      $instructions = '<span>' . t('No filters have been added.') . '</span> <span class="js-only">' . t('Drag to add filters.') . '</span>';
3957
      // When JavaScript is enabled, the button for removing the group (if it's
3958
      // present) should be hidden, since it will be replaced by a link on the
3959
      // client side.
3960
      if (!empty($form['remove_groups'][$group_id]['#type']) && $form['remove_groups'][$group_id]['#type'] == 'submit') {
3961
        $form['remove_groups'][$group_id]['#attributes']['class'][] = 'js-hide';
3962
      }
3963
      $row[] = array('colspan' => 5, 'data' => $instructions . drupal_render($form['remove_groups'][$group_id]));
3964
      $rows[] = array('class' => array("group-message", "group-$group_id-message", $class), 'data' => $row, 'id' => 'views-group-' . $group_id);
3965
    }
3966

    
3967
    foreach ($contents as $id) {
3968
      if (isset($form['filters'][$id]['name'])) {
3969
        $row = array();
3970
        $row[] = drupal_render($form['filters'][$id]['name']);
3971
        $form['filters'][$id]['weight']['#attributes']['class'] = array('weight');
3972
        $row[] = drupal_render($form['filters'][$id]['weight']);
3973
        $form['filters'][$id]['group']['#attributes']['class'] = array('views-group-select views-group-select-' . $group_id);
3974
        $row[] = drupal_render($form['filters'][$id]['group']);
3975
        $form['filters'][$id]['removed']['#attributes']['class'][] = 'js-hide';
3976
        $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));
3977

    
3978
        $row = array('data' => $row, 'class' => array('draggable'), 'id' => 'views-row-' . $id);
3979
        if ($group_id !== 'ungroupable') {
3980
          $rows[] = $row;
3981
        }
3982
        else {
3983
          $ungroupable_rows[] = $row;
3984
        }
3985
      }
3986
    }
3987
  }
3988
  if (empty($rows)) {
3989
    $rows[] = array(array('data' => t('No fields available.'), 'colspan' => '2'));
3990
  }
3991

    
3992
  $output = drupal_render($form['override']);
3993
  $output .= '<div class="scroll">';
3994
  if ($grouping) {
3995
    $output .= drupal_render($form['filter_groups']['operator']);
3996
  }
3997
  else {
3998
    $form['filter_groups']['groups'][0]['#title'] = t('Operator');
3999
    $output .= drupal_render($form['filter_groups']['groups'][0]);
4000
  }
4001

    
4002
  if (!empty($ungroupable_rows)) {
4003
    drupal_add_tabledrag('views-rearrange-filters-ungroupable', 'order', 'sibling', 'weight');
4004
    $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')));
4005
    $output .= theme('table', array('header' => $header, 'rows' => $ungroupable_rows, 'attributes' => array('id' => 'views-rearrange-filters-ungroupable', 'class' => array('arrange'))));
4006
  }
4007

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

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

    
4017
  // Render the rest of the form and return.
4018
  $output .= drupal_render_children($form);
4019
  return $output;
4020
}
4021

    
4022
/**
4023
 * Submit handler for rearranging form.
4024
 */
4025
function views_ui_rearrange_filter_form_submit($form, &$form_state) {
4026
  $types = views_object_types();
4027
  $display = &$form_state['view']->display[$form_state['display_id']];
4028
  $remember_groups = array();
4029

    
4030
  if (!empty($form_state['view']->form_cache)) {
4031
    $old_fields = $form_state['view']->form_cache['handlers'];
4032
  }
4033
  else {
4034
    $old_fields = $display->handler->get_option($types[$form_state['type']]['plural']);
4035
  }
4036
  $count = 0;
4037

    
4038
  $groups = $form_state['values']['filter_groups'];
4039
  // Whatever button was clicked, re-calculate field information.
4040
  $new_fields = $order = array();
4041

    
4042
  // Make an array with the weights.
4043
  foreach ($form_state['values']['filters'] as $field => $info) {
4044
    // add each value that is a field with a weight to our list, but only if
4045
    // it has had its 'removed' checkbox checked.
4046
    if (is_array($info) && empty($info['removed'])) {
4047
      if (isset($info['weight'])) {
4048
        $order[$field] = $info['weight'];
4049
      }
4050

    
4051
      if (isset($info['group'])) {
4052
        $old_fields[$field]['group'] = $info['group'];
4053
        $remember_groups[$info['group']][] = $field;
4054
      }
4055
    }
4056
  }
4057

    
4058
  // Sort the array.
4059
  asort($order);
4060

    
4061
  // Create a new list of fields in the new order.
4062
  foreach (array_keys($order) as $field) {
4063
    $new_fields[$field] = $old_fields[$field];
4064
  }
4065

    
4066
  // If the #group property is set on the clicked button, that means we are
4067
  // either adding or removing a group, not actually updating the filters.
4068
  if (!empty($form_state['clicked_button']['#group'])) {
4069
    if ($form_state['clicked_button']['#group'] == 'add') {
4070
      // Add a new group.
4071
      $groups['groups'][] = 'AND';
4072
    }
4073
    else {
4074
      // Renumber groups above the removed one down.
4075
      foreach (array_keys($groups['groups']) as $group_id) {
4076
        if ($group_id >= $form_state['clicked_button']['#group']) {
4077
          $old_group = $group_id + 1;
4078
          if (isset($groups['groups'][$old_group])) {
4079
            $groups['groups'][$group_id] = $groups['groups'][$old_group];
4080
            if (isset($remember_groups[$old_group])) {
4081
              foreach ($remember_groups[$old_group] as $id) {
4082
                $new_fields[$id]['group'] = $group_id;
4083
              }
4084
            }
4085
          }
4086
          else {
4087
            // If this is the last one, just unset it.
4088
            unset($groups['groups'][$group_id]);
4089
          }
4090
        }
4091
      }
4092
    }
4093
    // Update our cache with values so that cancel still works the way
4094
    // people expect.
4095
    $form_state['view']->form_cache = array(
4096
      'key' => 'rearrange-filter',
4097
      'groups' => $groups,
4098
      'handlers' => $new_fields,
4099
    );
4100

    
4101
    // Return to this form except on actual Update.
4102
    views_ui_add_form_to_stack('rearrange-filter', $form_state['view'], $form_state['display_id'], array($form_state['type']));
4103
  }
4104
  else {
4105
    // The actual update button was clicked. Remove the empty groups, and
4106
    // renumber them sequentially.
4107
    ksort($remember_groups);
4108
    $groups['groups'] = views_array_key_plus(array_values(array_intersect_key($groups['groups'], $remember_groups)));
4109
    // Change the 'group' key on each field to match. Here, $mapping is an
4110
    // array whose keys are the old group numbers and whose values are the new
4111
    // (sequentially numbered) ones.
4112
    $mapping = array_flip(views_array_key_plus(array_keys($remember_groups)));
4113
    foreach ($new_fields as &$new_field) {
4114
      $new_field['group'] = $mapping[$new_field['group']];
4115
    }
4116

    
4117
    // Write the changed handler values.
4118
    $display->handler->set_option($types[$form_state['type']]['plural'], $new_fields);
4119
    $display->handler->set_option('filter_groups', $groups);
4120
    if (isset($form_state['view']->form_cache)) {
4121
      unset($form_state['view']->form_cache);
4122
    }
4123
  }
4124

    
4125
  // Store in cache.
4126
  views_ui_cache_set($form_state['view']);
4127
}
4128

    
4129
/**
4130
 * Form to add_item items in the views UI.
4131
 */
4132
function views_ui_add_item_form($form, &$form_state) {
4133
  $view = &$form_state['view'];
4134
  $display_id = $form_state['display_id'];
4135
  $type = $form_state['type'];
4136

    
4137
  $form = array(
4138
    'options' => array(
4139
      '#theme_wrappers' => array('container'),
4140
      '#attributes' => array('class' => array('scroll')),
4141
    ),
4142
  );
4143

    
4144
  ctools_add_js('dependent');
4145

    
4146
  if (!$view->set_display($display_id)) {
4147
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4148
  }
4149
  $display = &$view->display[$display_id];
4150

    
4151
  $types = views_object_types();
4152
  $ltitle = $types[$type]['ltitle'];
4153
  $section = $types[$type]['plural'];
4154

    
4155
  if (!empty($types[$type]['type'])) {
4156
    $type = $types[$type]['type'];
4157
  }
4158

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

    
4162
  // Add the display override dropdown.
4163
  views_ui_standard_display_dropdown($form, $form_state, $section);
4164

    
4165
  // Figure out all the base tables allowed based upon what the relationships
4166
  // provide.
4167
  $base_tables = $view->get_base_tables();
4168
  $options = views_fetch_fields(array_keys($base_tables), $type, $display->handler->use_group_by());
4169

    
4170
  if (!empty($options)) {
4171
    $form['options']['controls'] = array(
4172
      '#theme_wrappers' => array('container'),
4173
      '#id' => 'views-filterable-options-controls',
4174
      '#attributes' => array('class' => array('container-inline')),
4175
    );
4176
    $form['options']['controls']['options_search'] = array(
4177
      '#type' => 'textfield',
4178
      '#title' => t('Search'),
4179
    );
4180

    
4181
    $groups = array('all' => t('- All -'));
4182
    $form['options']['controls']['group'] = array(
4183
      '#type' => 'select',
4184
      '#title' => t('Filter'),
4185
      '#options' => array(),
4186
      '#attributes' => array('class' => array('ctools-master-dependent')),
4187
    );
4188

    
4189
    $form['options']['name'] = array(
4190
      '#prefix' => '<div class="views-radio-box form-checkboxes views-filterable-options">',
4191
      '#suffix' => '</div>',
4192
      '#tree' => TRUE,
4193
      '#default_value' => 'all',
4194
    );
4195

    
4196
    // Group options first to simplify the DOM objects that Views
4197
    // dependent JS will act upon.
4198
    $grouped_options = array();
4199
    foreach ($options as $key => $option) {
4200
      $group = preg_replace('/[^a-z0-9]/', '-', strtolower($option['group']));
4201
      $groups[$group] = $option['group'];
4202
      $grouped_options[$group][$key] = $option;
4203
      if (!empty($option['aliases']) && is_array($option['aliases'])) {
4204
        foreach ($option['aliases'] as $id => $alias) {
4205
          if (empty($alias['base']) || !empty($base_tables[$alias['base']])) {
4206
            $copy = $option;
4207
            $copy['group'] = $alias['group'];
4208
            $copy['title'] = $alias['title'];
4209
            if (isset($alias['help'])) {
4210
              $copy['help'] = $alias['help'];
4211
            }
4212

    
4213
            $group = preg_replace('/[^a-z0-9]/', '-', strtolower($copy['group']));
4214
            $groups[$group] = $copy['group'];
4215
            $grouped_options[$group][$key . '$' . $id] = $copy;
4216
          }
4217
        }
4218
      }
4219
    }
4220

    
4221
    foreach ($grouped_options as $group => $group_options) {
4222
      $form['options']['name'][$group . '_start']['#markup'] = '<div class="ctools-dependent-all ctools-dependent-' . $group . '">';
4223
      $zebra = 0;
4224
      foreach ($group_options as $key => $option) {
4225
        $zebra_class = ($zebra % 2) ? 'odd' : 'even';
4226
        $form['options']['name'][$key] = array(
4227
          '#type' => 'checkbox',
4228
          '#title' => t('!group: !field', array('!group' => check_plain($option['group']), '!field' => check_plain($option['title']))),
4229
          '#description' => filter_xss_admin($option['help']),
4230
          '#return_value' => $key,
4231
          '#prefix' => "<div class='$zebra_class filterable-option'>",
4232
          '#suffix' => '</div>',
4233
        );
4234
        $zebra++;
4235
      }
4236
      $form['options']['name'][$group . '_end']['#markup'] = '</div>';
4237
    }
4238

    
4239
    $form['options']['controls']['group']['#options'] = $groups;
4240
  }
4241
  else {
4242
    $form['options']['markup'] = array(
4243
      '#markup' => '<div class="form-item">' . t('There are no @types available to add.', array('@types' => $ltitle)) . '</div>',
4244
    );
4245
  }
4246
  // Add a div to show the selected items.
4247
  $form['selected'] = array(
4248
    '#type' => 'item',
4249
    '#markup' => '<div class="views-selected-options"></div>',
4250
    '#title' => t('Selected') . ':',
4251
    '#theme_wrappers' => array('form_element', 'views_container'),
4252
    '#attributes' => array('class' => array('container-inline', 'views-add-form-selected')),
4253
  );
4254
  ctools_include('dependent');
4255
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_add_item_form', t('Add and configure @types', array('@types' => $ltitle)));
4256

    
4257
  // Remove the default submit function.
4258
  $form['buttons']['submit']['#submit'] = array_diff($form['buttons']['submit']['#submit'], array('views_ui_standard_submit'));
4259
  $form['buttons']['submit']['#submit'][] = 'views_ui_add_item_form_submit';
4260

    
4261
  return $form;
4262
}
4263

    
4264
/**
4265
 * Submit handler for adding new item(s) to a view.
4266
 */
4267
function views_ui_add_item_form_submit($form, &$form_state) {
4268
  $type = $form_state['type'];
4269
  $types = views_object_types();
4270
  $section = $types[$type]['plural'];
4271

    
4272
  // Handle the override select.
4273
  list($was_defaulted, $is_defaulted) = views_ui_standard_override_values($form, $form_state);
4274
  if ($was_defaulted && !$is_defaulted) {
4275
    // We were using the default display's values, but we're now overriding
4276
    // the default display and saving values specific to this display.
4277
    $display = &$form_state['view']->display[$form_state['display_id']];
4278
    // set_override toggles the override of this section.
4279
    $display->handler->set_override($section);
4280
  }
4281
  elseif (!$was_defaulted && $is_defaulted) {
4282
    // We used to have an override for this display, but the user now wants
4283
    // to go back to the default display.
4284
    // Overwrite the default display with the current form values, and make
4285
    // the current display use the new default values.
4286
    $display = &$form_state['view']->display[$form_state['display_id']];
4287
    // options_override toggles the override of this section.
4288
    $display->handler->set_override($section);
4289
  }
4290

    
4291
  if (!empty($form_state['values']['name']) && is_array($form_state['values']['name'])) {
4292
    // Loop through each of the items that were checked and add them to the
4293
    // view.
4294
    foreach (array_keys(array_filter($form_state['values']['name'])) as $field) {
4295
      list($table, $field) = explode('.', $field, 2);
4296

    
4297
      if ($cut = strpos($field, '$')) {
4298
        $field = substr($field, 0, $cut);
4299
      }
4300
      $id = $form_state['view']->add_item($form_state['display_id'], $type, $table, $field);
4301

    
4302
      // Check to see if we have group by settings.
4303
      $key = $type;
4304
      // Footer,header and empty text have a different internal handler type
4305
      // (area).
4306
      if (isset($types[$type]['type'])) {
4307
        $key = $types[$type]['type'];
4308
      }
4309
      $handler = views_get_handler($table, $field, $key);
4310
      if ($form_state['view']->display_handler->use_group_by() && $handler->use_group_by()) {
4311
        views_ui_add_form_to_stack('config-item-group', $form_state['view'], $form_state['display_id'], array($type, $id));
4312
      }
4313

    
4314
      // Check to see if this type has settings, if so add the settings form
4315
      // first.
4316
      if ($handler && $handler->has_extra_options()) {
4317
        views_ui_add_form_to_stack('config-item-extra', $form_state['view'], $form_state['display_id'], array($type, $id));
4318
      }
4319
      // Then add the form to the stack.
4320
      views_ui_add_form_to_stack('config-item', $form_state['view'], $form_state['display_id'], array($type, $id));
4321
    }
4322
  }
4323

    
4324
  if (isset($form_state['view']->form_cache)) {
4325
    unset($form_state['view']->form_cache);
4326
  }
4327

    
4328
  // Store in cache.
4329
  views_ui_cache_set($form_state['view']);
4330
}
4331

    
4332
/**
4333
 * Override handler for views_ui_edit_display_form.
4334
 */
4335
function views_ui_config_item_form_build_group($form, &$form_state) {
4336
  $item = &$form_state['handler']->options;
4337
  // flip. If the filter was a group, set back to a standard filter.
4338
  $item['is_grouped'] = empty($item['is_grouped']);
4339

    
4340
  // If necessary, set new defaults.
4341
  if ($item['is_grouped']) {
4342
    $form_state['handler']->build_group_options();
4343
  }
4344

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

    
4347
  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);
4348

    
4349
  views_ui_cache_set($form_state['view']);
4350
  $form_state['rerender'] = TRUE;
4351
  $form_state['rebuild'] = TRUE;
4352
  $form_state['force_build_group_options'] = TRUE;
4353
}
4354

    
4355
/**
4356
 * Add a new group to the exposed filter groups.
4357
 */
4358
function views_ui_config_item_form_add_group($form, &$form_state) {
4359
  $item =& $form_state['handler']->options;
4360

    
4361
  // Add a new row.
4362
  $item['group_info']['group_items'][] = array();
4363

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

    
4366
  views_ui_cache_set($form_state['view']);
4367
  $form_state['rerender'] = TRUE;
4368
  $form_state['rebuild'] = TRUE;
4369
  $form_state['force_build_group_options'] = TRUE;
4370
}
4371

    
4372
/**
4373
 * Form to config_item items in the views UI.
4374
 */
4375
function views_ui_config_item_form($form, &$form_state) {
4376
  $view = &$form_state['view'];
4377
  $display_id = $form_state['display_id'];
4378
  $type = $form_state['type'];
4379
  $id = $form_state['id'];
4380

    
4381
  $form = array(
4382
    'options' => array(
4383
      '#tree' => TRUE,
4384
      '#theme_wrappers' => array('container'),
4385
      '#attributes' => array('class' => array('scroll')),
4386
    ),
4387
  );
4388
  if (!$view->set_display($display_id)) {
4389
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4390
  }
4391
  $item = $view->get_item($display_id, $type, $id);
4392

    
4393
  if ($item) {
4394
    $handler = $view->display_handler->get_handler($type, $id);
4395
    if (empty($handler)) {
4396
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4397
    }
4398
    else {
4399
      $types = views_object_types();
4400

    
4401
      // If this item can come from the default display, show a dropdown
4402
      // that lets the user choose which display the changes should apply to.
4403
      if ($view->display_handler->defaultable_sections($types[$type]['plural'])) {
4404
        $form_state['section'] = $types[$type]['plural'];
4405
        views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
4406
      }
4407

    
4408
      // A whole bunch of code to figure out what relationships are valid for
4409
      // this item.
4410
      $relationships = $view->display_handler->get_option('relationships');
4411
      $relationship_options = array();
4412

    
4413
      foreach ($relationships as $relationship) {
4414
        // Relationships can't link back to self. But also, due to ordering,
4415
        // relationships can only link to prior relationships.
4416
        if ($type == 'relationship' && $id == $relationship['id']) {
4417
          break;
4418
        }
4419
        $relationship_handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
4420
        // Ignore invalid/broken relationships.
4421
        if (empty($relationship_handler)) {
4422
          continue;
4423
        }
4424

    
4425
        // If this relationship is valid for this type, add it to the list.
4426
        $data = views_fetch_data($relationship['table']);
4427
        $base = $data[$relationship['field']]['relationship']['base'];
4428
        $base_fields = views_fetch_fields($base, $form_state['type'], $view->display_handler->use_group_by());
4429
        if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
4430
          $relationship_handler->init($view, $relationship);
4431
          $relationship_options[$relationship['id']] = $relationship_handler->label();
4432
        }
4433
      }
4434

    
4435
      if (!empty($relationship_options)) {
4436
        // Make sure the existing relationship is even valid. If not, force
4437
        // it to none.
4438
        $base_fields = views_fetch_fields($view->base_table, $form_state['type'], $view->display_handler->use_group_by());
4439
        if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
4440
          $relationship_options = array_merge(array('none' => t('Do not use a relationship')), $relationship_options);
4441
        }
4442
        $rel = empty($item['relationship']) ? 'none' : $item['relationship'];
4443
        if (empty($relationship_options[$rel])) {
4444
          // Pick the first relationship.
4445
          $rel = key($relationship_options);
4446
          // We want this relationship option to get saved even if the user
4447
          // skips submitting the form.
4448
          $view->set_item_option($display_id, $type, $id, 'relationship', $rel);
4449
          $temp_view = $view->clone_view();
4450
          views_ui_cache_set($temp_view);
4451
        }
4452

    
4453
        $form['options']['relationship'] = array(
4454
          '#type' => 'select',
4455
          '#title' => t('Relationship'),
4456
          '#options' => $relationship_options,
4457
          '#default_value' => $rel,
4458
          '#weight' => -500,
4459
        );
4460
      }
4461
      else {
4462
        $form['options']['relationship'] = array(
4463
          '#type' => 'value',
4464
          '#value' => 'none',
4465
        );
4466
      }
4467

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

    
4470
      if (!empty($handler->definition['help'])) {
4471
        $form['options']['form_description'] = array(
4472
          '#markup' => $handler->definition['help'],
4473
          '#theme_wrappers' => array('container'),
4474
          '#attributes' => array('class' => array('form-item description')),
4475
          '#weight' => -1000,
4476
        );
4477
      }
4478

    
4479
      $form['#section'] = $display_id . '-' . $type . '-' . $id;
4480

    
4481
      // Get form from the handler.
4482
      $handler->options_form($form['options'], $form_state);
4483
      $form_state['handler'] = &$handler;
4484
    }
4485

    
4486
    $name = NULL;
4487
    if (isset($form_state['update_name'])) {
4488
      $name = $form_state['update_name'];
4489
    }
4490

    
4491
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_form', $name, t('Remove'), 'remove');
4492
    // Only validate the override values, because this values are required for
4493
    // the override selection.
4494
    $form['buttons']['remove']['#limit_validation_errors'] = array(array('override'));
4495
  }
4496

    
4497
  return $form;
4498
}
4499

    
4500
/**
4501
 * Submit handler for configing new item(s) to a view.
4502
 */
4503
function views_ui_config_item_form_validate($form, &$form_state) {
4504
  $form_state['handler']->options_validate($form['options'], $form_state);
4505

    
4506
  if (form_get_errors()) {
4507
    $form_state['rerender'] = TRUE;
4508
  }
4509
}
4510

    
4511
/**
4512
 * A submit handler that is used for storing temporary items when using
4513
 * multi-step changes, such as ajax requests.
4514
 */
4515
function views_ui_config_item_form_submit_temporary($form, &$form_state) {
4516
  // Run it through the handler's submit function.
4517
  $form_state['handler']->options_submit($form['options'], $form_state);
4518
  $item = $form_state['handler']->options;
4519
  $types = views_object_types();
4520

    
4521
  // For footer/header $handler_type is area but $type is footer/header.
4522
  // For all other handle types it's the same.
4523
  $handler_type = $type = $form_state['type'];
4524
  if (!empty($types[$type]['type'])) {
4525
    $handler_type = $types[$type]['type'];
4526
  }
4527

    
4528
  $override = NULL;
4529
  if ($form_state['view']->display_handler->use_group_by() && !empty($item['group_type'])) {
4530
    if (empty($form_state['view']->query)) {
4531
      $form_state['view']->init_query();
4532
    }
4533
    $aggregate = $form_state['view']->query->get_aggregation_info();
4534
    if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
4535
      $override = $aggregate[$item['group_type']]['handler'][$type];
4536
    }
4537
  }
4538

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

    
4544
  // Add the incoming options to existing options because items using
4545
  // the extra form may not have everything in the form here.
4546
  $options = $form_state['values']['options'] + $form_state['handler']->options;
4547

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

    
4552
  // Store the item back on the view.
4553
  $form_state['view']->temporary_options[$type][$form_state['id']] = $handler->options;
4554

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

    
4560
  $form_state['rerender'] = TRUE;
4561
  $form_state['rebuild'] = TRUE;
4562
  // Write to cache.
4563
  views_ui_cache_set($form_state['view']);
4564
}
4565

    
4566
/**
4567
 * Submit handler for configing new item(s) to a view.
4568
 */
4569
function views_ui_config_item_form_submit($form, &$form_state) {
4570
  // Run it through the handler's submit function.
4571
  $form_state['handler']->options_submit($form['options'], $form_state);
4572
  $item = $form_state['handler']->options;
4573
  $types = views_object_types();
4574

    
4575
  // For footer/header $handler_type is area but $type is footer/header.
4576
  // For all other handle types it's the same.
4577
  $handler_type = $type = $form_state['type'];
4578
  if (!empty($types[$type]['type'])) {
4579
    $handler_type = $types[$type]['type'];
4580
  }
4581

    
4582
  $override = NULL;
4583
  if ($form_state['view']->display_handler->use_group_by() && !empty($item['group_type'])) {
4584
    if (empty($form_state['view']->query)) {
4585
      $form_state['view']->init_query();
4586
    }
4587
    $aggregate = $form_state['view']->query->get_aggregation_info();
4588
    if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
4589
      $override = $aggregate[$item['group_type']]['handler'][$type];
4590
    }
4591
  }
4592

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

    
4598
  // Add the incoming options to existing options because items using
4599
  // the extra form may not have everything in the form here.
4600
  $options = $form_state['values']['options'] + $form_state['handler']->options;
4601

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

    
4606
  // Store the item back on the view.
4607
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $handler->options);
4608

    
4609
  // Ensure any temporary options are removed.
4610
  if (isset($form_state['view']->temporary_options[$type][$form_state['id']])) {
4611
    unset($form_state['view']->temporary_options[$type][$form_state['id']]);
4612
  }
4613

    
4614
  // Write to cache.
4615
  views_ui_cache_set($form_state['view']);
4616
}
4617

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

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

    
4638
  $view->init_query();
4639

    
4640
  $item = $view->get_item($display_id, $type, $id);
4641

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

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

    
4653
      $handler->groupby_form($form['options'], $form_state);
4654
      $form_state['handler'] = &$handler;
4655
    }
4656

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

    
4662
/**
4663
 * Submit handler for configing group settings on a view.
4664
 */
4665
function views_ui_config_item_group_form_submit($form, &$form_state) {
4666
  $item =& $form_state['handler']->options;
4667
  $type = $form_state['type'];
4668
  $id = $form_state['id'];
4669

    
4670
  $handler = views_get_handler($item['table'], $item['field'], $type);
4671
  $handler->init($form_state['view'], $item);
4672

    
4673
  $handler->groupby_form_submit($form, $form_state);
4674

    
4675
  // Store the item back on the view.
4676
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4677

    
4678
  // Write to cache.
4679
  views_ui_cache_set($form_state['view']);
4680
}
4681

    
4682
/**
4683
 * Submit handler for removing an item from a view.
4684
 */
4685
function views_ui_config_item_form_remove($form, &$form_state) {
4686
  // Store the item back on the view.
4687
  list($was_defaulted, $is_defaulted) = views_ui_standard_override_values($form, $form_state);
4688
  // If the display selection was changed toggle the override value.
4689
  if ($was_defaulted != $is_defaulted) {
4690
    $display =& $form_state['view']->display[$form_state['display_id']];
4691
    $display->handler->options_override($form, $form_state);
4692
  }
4693
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], NULL);
4694

    
4695
  // Write to cache.
4696
  views_ui_cache_set($form_state['view']);
4697
}
4698

    
4699
/**
4700
 * Override handler for views_ui_edit_display_form.
4701
 */
4702
function views_ui_config_item_form_expose($form, &$form_state) {
4703
  $item = &$form_state['handler']->options;
4704
  // flip.
4705
  $item['exposed'] = empty($item['exposed']);
4706

    
4707
  // If necessary, set new defaults.
4708
  if ($item['exposed']) {
4709
    $form_state['handler']->expose_options();
4710
  }
4711

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

    
4714
  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);
4715

    
4716
  views_ui_cache_set($form_state['view']);
4717
  $form_state['rerender'] = TRUE;
4718
  $form_state['rebuild'] = TRUE;
4719
  $form_state['force_expose_options'] = TRUE;
4720
}
4721

    
4722
/**
4723
 * Form to config_item items in the views UI.
4724
 */
4725
function views_ui_config_item_extra_form($form, &$form_state) {
4726
  $view = &$form_state['view'];
4727
  $display_id = $form_state['display_id'];
4728
  $type = $form_state['type'];
4729
  $id = $form_state['id'];
4730

    
4731
  $form = array(
4732
    'options' => array(
4733
      '#tree' => TRUE,
4734
      '#theme_wrappers' => array('container'),
4735
      '#attributes' => array('class' => array('scroll')),
4736
    ),
4737
  );
4738
  if (!$view->set_display($display_id)) {
4739
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4740
  }
4741
  $item = $view->get_item($display_id, $type, $id);
4742

    
4743
  if ($item) {
4744
    $handler = $view->display_handler->get_handler($type, $id);
4745
    if (empty($handler)) {
4746
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4747
    }
4748
    else {
4749
      $handler->init($view, $item);
4750
      $types = views_object_types();
4751

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

    
4754
      $form['#section'] = $display_id . '-' . $type . '-' . $id;
4755

    
4756
      // Get form from the handler.
4757
      $handler->extra_options_form($form['options'], $form_state);
4758
      $form_state['handler'] = &$handler;
4759
    }
4760

    
4761
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_extra_form');
4762
  }
4763
  return $form;
4764
}
4765

    
4766
/**
4767
 * Validation handler for configing new item(s) to a view.
4768
 */
4769
function views_ui_config_item_extra_form_validate($form, &$form_state) {
4770
  $form_state['handler']->extra_options_validate($form['options'], $form_state);
4771
}
4772

    
4773
/**
4774
 * Submit handler for configing new item(s) to a view.
4775
 */
4776
function views_ui_config_item_extra_form_submit($form, &$form_state) {
4777
  // Run it through the handler's submit function.
4778
  $form_state['handler']->extra_options_submit($form['options'], $form_state);
4779
  $item = $form_state['handler']->options;
4780

    
4781
  // Store the data we're given.
4782
  foreach ($form_state['values']['options'] as $key => $value) {
4783
    $item[$key] = $value;
4784
  }
4785

    
4786
  // Store the item back on the view.
4787
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4788

    
4789
  // Write to cache.
4790
  views_ui_cache_set($form_state['view']);
4791
}
4792

    
4793
/**
4794
 * Form to config_style items in the views UI.
4795
 */
4796
function views_ui_config_style_form($form, &$form_state) {
4797
  $view = &$form_state['view'];
4798
  $display_id = $form_state['display_id'];
4799
  $type = $form_state['type'];
4800
  $id = $form_state['id'];
4801

    
4802
  $form = array(
4803
    'options' => array(
4804
      '#tree' => TRUE,
4805
      '#theme_wrappers' => array('container'),
4806
      '#attributes' => array('class' => array('scroll')),
4807
    ),
4808
  );
4809
  if (!$view->set_display($display_id)) {
4810
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4811
  }
4812
  $item = $view->get_item($display_id, $type, $id);
4813

    
4814
  if ($item) {
4815
    $handler = views_get_handler($item['table'], $item['field'], $type);
4816
    if (empty($handler)) {
4817
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4818
    }
4819
    else {
4820
      $handler->init($view, $item);
4821
      $types = views_object_types();
4822

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

    
4825
      $form['#section'] = $display_id . '-' . $type . '-style-options';
4826

    
4827
      $plugin = views_get_plugin('style', $handler->options['style_plugin']);
4828
      if ($plugin) {
4829
        $form['style_options'] = array(
4830
          '#tree' => TRUE,
4831
        );
4832
        $plugin->init($view, $view->display[$display_id], $handler->options['style_options']);
4833

    
4834
        $plugin->options_form($form['style_options'], $form_state);
4835
      }
4836

    
4837
      $form_state['handler'] = &$handler;
4838
    }
4839

    
4840
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_style_form');
4841
  }
4842
  return $form;
4843
}
4844

    
4845
/**
4846
 * Submit handler for configing new item(s) to a view.
4847
 */
4848
function views_ui_config_style_form_submit($form, &$form_state) {
4849
  // Run it through the handler's submit function.
4850
  $form_state['handler']->options_submit($form['style_options'], $form_state);
4851
  $item = $form_state['handler']->options;
4852

    
4853
  // Store the data we're given.
4854
  $item['style_options'] = $form_state['values']['style_options'];
4855

    
4856
  // Store the item back on the view.
4857
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4858

    
4859
  // Write to cache.
4860
  views_ui_cache_set($form_state['view']);
4861
}
4862

    
4863
/**
4864
 * Get a list of roles in the system.
4865
 */
4866
function views_ui_get_roles() {
4867
  static $roles = NULL;
4868
  if (!isset($roles)) {
4869
    $roles = array();
4870
    $result = db_query("SELECT r.rid, r.name FROM {role} r ORDER BY r.name");
4871
    foreach ($result as $obj) {
4872
      $roles[$obj->rid] = $obj->name;
4873
    }
4874
  }
4875

    
4876
  return $roles;
4877
}
4878

    
4879
/**
4880
 * Form builder for the admin display defaults page.
4881
 */
4882
function views_ui_admin_settings_basic() {
4883
  $form = array();
4884
  $form['#attached']['css'] = views_ui_get_admin_css();
4885

    
4886
  $options = array();
4887
  foreach (list_themes() as $name => $theme) {
4888
    if ($theme->status) {
4889
      $options[$name] = $theme->info['name'];
4890
    }
4891
  }
4892

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

    
4897
  $form['basic']['views_ui_show_listing_filters'] = array(
4898
    '#type' => 'checkbox',
4899
    '#title' => t('Show filters on the list of views'),
4900
    '#default_value' => variable_get('views_ui_show_listing_filters', FALSE),
4901
  );
4902
  $form['basic']['views_ui_show_advanced_help_warning'] = array(
4903
    '#type' => 'checkbox',
4904
    '#title' => t('Show advanced help warning'),
4905
    '#default_value' => variable_get('views_ui_show_advanced_help_warning', TRUE),
4906
  );
4907

    
4908
  $form['basic']['views_ui_show_master_display'] = array(
4909
    '#type' => 'checkbox',
4910
    '#title' => t('Always show the master display'),
4911
    '#description' => t('Advanced users of views may choose to see the master (i.e. default) display.'),
4912
    '#default_value' => variable_get('views_ui_show_master_display', FALSE),
4913
  );
4914

    
4915
  $form['basic']['views_ui_show_advanced_column'] = array(
4916
    '#type' => 'checkbox',
4917
    '#title' => t('Always show advanced display settings'),
4918
    '#description' => t('Default to showing advanced display settings, such as relationships and contextual filters.'),
4919
    '#default_value' => variable_get('views_ui_show_advanced_column', FALSE),
4920
  );
4921

    
4922
  $form['basic']['views_ui_display_embed'] = array(
4923
    '#type' => 'checkbox',
4924
    '#title' => t('Show the embed display in the ui.'),
4925
    '#description' => t('Allow advanced user to use the embed view display. The plugin itself works if it\'s not visible in the ui'),
4926
    '#default_value' => variable_get('views_ui_display_embed', FALSE),
4927
  );
4928

    
4929
  $form['basic']['views_ui_custom_theme'] = array(
4930
    '#type' => 'select',
4931
    '#title' => t('Custom admin theme for the Views UI'),
4932
    '#options' => array('_default' => t('- Use default -')) + $options,
4933
    '#default_value' => variable_get('views_ui_custom_theme', '_default'),
4934
    '#description' => t('In some cases you might want to select a different admin theme for the Views UI.'),
4935
  );
4936

    
4937
  $form['basic']['views_exposed_filter_any_label'] = array(
4938
    '#type' => 'select',
4939
    '#title' => t('Label for "Any" value on non-required single-select exposed filters'),
4940
    '#options' => array('old_any' => '<Any>', 'new_any' => t('- Any -')),
4941
    '#default_value' => variable_get('views_exposed_filter_any_label', 'new_any'),
4942
  );
4943

    
4944
  $form['live_preview'] = array(
4945
    '#type' => 'fieldset',
4946
    '#title' => t('Live preview settings'),
4947
  );
4948

    
4949
  $form['live_preview']['views_ui_always_live_preview'] = array(
4950
    '#type' => 'checkbox',
4951
    '#title' => t('Automatically update preview on changes'),
4952
    '#default_value' => variable_get('views_ui_always_live_preview', TRUE),
4953
  );
4954

    
4955
  // $form['live_preview']['views_ui_always_live_preview_button'] = array(
4956
  //   '#type' => 'checkbox',
4957
  //   '#title' => t('Always show the preview button, even when the automatically update option is checked'),
4958
  //   '#default_value' => variable_get('views_ui_always_live_preview_button', FALSE),
4959
  // );
4960
  $form['live_preview']['views_ui_show_preview_information'] = array(
4961
    '#type' => 'checkbox',
4962
    '#title' => t('Show information and statistics about the view during live preview'),
4963
    '#default_value' => variable_get('views_ui_show_preview_information', TRUE),
4964
  );
4965

    
4966
  $form['live_preview']['views_ui_show_sql_query_where'] = array(
4967
    '#type' => 'radios',
4968
    '#options' => array(
4969
      'above' => t('Above the preview'),
4970
      'below' => t('Below the preview'),
4971
    ),
4972
    '#id' => 'edit-show-sql',
4973
    '#default_value' => variable_get('views_ui_show_sql_query_where', 'above'),
4974
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4975
    '#prefix' => '<div id="edit-show-sql-wrapper" class="views-dependent">',
4976
    '#suffix' => '</div>',
4977
  );
4978

    
4979
  $form['live_preview']['views_ui_show_sql_query'] = array(
4980
    '#type' => 'checkbox',
4981
    '#title' => t('Show the SQL query'),
4982
    '#default_value' => variable_get('views_ui_show_sql_query', FALSE),
4983
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4984
  );
4985
  $form['live_preview']['views_ui_show_performance_statistics'] = array(
4986
    '#type' => 'checkbox',
4987
    '#title' => t('Show performance statistics'),
4988
    '#default_value' => variable_get('views_ui_show_performance_statistics', FALSE),
4989
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4990
  );
4991

    
4992
  $form['live_preview']['views_show_additional_queries'] = array(
4993
    '#type' => 'checkbox',
4994
    '#title' => t('Show other queries run during render during live preview'),
4995
    '#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."),
4996
    '#default_value' => variable_get('views_show_additional_queries', FALSE),
4997
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4998
  );
4999

    
5000
  // $form['live_preview']['views_ui_show_performance_statistics_where'] = array(
5001
  return system_settings_form($form);
5002
}
5003

    
5004
/**
5005
 * Form builder for the advanced admin settings page.
5006
 */
5007
function views_ui_admin_settings_advanced() {
5008
  $form = array();
5009
  $form['#attached']['css'] = views_ui_get_admin_css();
5010

    
5011
  $form['cache'] = array(
5012
    '#type' => 'fieldset',
5013
    '#title' => t('Caching'),
5014
  );
5015

    
5016
  $form['cache']['views_skip_cache'] = array(
5017
    '#type' => 'checkbox',
5018
    '#title' => t('Disable views data caching'),
5019
    '#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."),
5020
    '#default_value' => variable_get('views_skip_cache', FALSE),
5021
  );
5022

    
5023
  $form['cache']['clear_cache'] = array(
5024
    '#type' => 'submit',
5025
    '#value' => t("Clear Views' cache"),
5026
    '#submit' => array('views_ui_tools_clear_cache'),
5027
  );
5028

    
5029
  $form['debug'] = array(
5030
    '#type' => 'fieldset',
5031
    '#title' => t('Debugging'),
5032
  );
5033

    
5034
  $form['debug']['views_sql_signature'] = array(
5035
    '#type' => 'checkbox',
5036
    '#title' => t('Add Views signature to all SQL queries'),
5037
    '#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."),
5038

    
5039
    '#default_value' => variable_get('views_sql_signature', FALSE),
5040
  );
5041

    
5042
  $form['debug']['views_no_javascript'] = array(
5043
    '#type' => 'checkbox',
5044
    '#title' => t('Disable JavaScript with Views'),
5045
    '#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."),
5046
    '#default_value' => variable_get('views_no_javascript', FALSE),
5047
  );
5048

    
5049
  $form['debug']['views_devel_output'] = array(
5050
    '#type' => 'checkbox',
5051
    '#title' => t('Enable views performance statistics/debug messages via the Devel module'),
5052
    '#description' => t("Check this to enable some Views query and performance statistics/debug messages <em>if Devel is installed</em>."),
5053
    '#default_value' => variable_get('views_devel_output', FALSE),
5054
  );
5055

    
5056
  $form['locale'] = array(
5057
    '#type' => 'fieldset',
5058
    '#title' => t('Localization'),
5059
  );
5060

    
5061
  $form['locale']['views_localization_plugin'] = array(
5062
    '#type' => 'radios',
5063
    '#title' => t('Translation method'),
5064
    '#options' => views_fetch_plugin_names('localization', NULL, array()),
5065
    '#default_value' => views_get_localization_plugin(),
5066
    '#description' => t('Select a translation method to use for Views data like header, footer, and empty text.'),
5067
  );
5068

    
5069
  $form['locale']['views_localize_all'] = array(
5070
    '#type' => 'checkbox',
5071
    '#title' => t('Use same translation method for exported views'),
5072
    '#description' => t('Exported views will use Core translation by default. Enable this to always use the configured translation method.'),
5073
    '#default_value' => variable_get('views_localize_all', FALSE),
5074
  );
5075

    
5076
  $regions = array();
5077
  $regions['watchdog'] = t('Watchdog');
5078
  if (module_exists('devel')) {
5079
    $regions['message'] = t('Devel message(dpm)');
5080
    $regions['drupal_debug'] = t('Devel logging (tmp://drupal_debug.txt)');
5081
  }
5082

    
5083
  $form['debug']['views_devel_region'] = array(
5084
    '#type' => 'select',
5085
    '#title' => t('Page region to output performance statistics/debug messages'),
5086
    '#default_value' => variable_get('views_devel_region', 'footer'),
5087
    '#options' => $regions,
5088
    '#dependency' => array('edit-views-devel-output' => array(1)),
5089
  );
5090

    
5091
  $options = views_fetch_plugin_names('display_extender');
5092
  if (!empty($options)) {
5093
    $form['extenders'] = array(
5094
      '#type' => 'fieldset',
5095
    );
5096
    ;
5097
    $form['extenders']['views_display_extenders'] = array(
5098
      '#title' => t('Display extenders'),
5099
      '#default_value' => views_get_enabled_display_extenders(),
5100
      '#options' => $options,
5101
      '#type' => 'checkboxes',
5102
      '#description' => t('Select extensions of the views interface.'),
5103
    );
5104
  }
5105

    
5106
  return system_settings_form($form);
5107
}
5108

    
5109
/**
5110
 * Submit hook to clear the views cache.
5111
 */
5112
function views_ui_tools_clear_cache() {
5113
  views_invalidate_cache();
5114
  drupal_set_message(t('The cache has been cleared.'));
5115
}
5116

    
5117
/**
5118
 * Submit hook to clear Drupal's theme registry (thereby triggering
5119
 * a templates rescan).
5120
 */
5121
function views_ui_config_item_form_rescan($form, &$form_state) {
5122
  drupal_theme_rebuild();
5123

    
5124
  // The 'Theme: Information' page is about to be shown again. That page
5125
  // analyzes the output of theme_get_registry(). However, this latter
5126
  // function uses an internal cache (which was initialized before we
5127
  // called drupal_theme_rebuild()) so it won't reflect the
5128
  // current state of our theme registry. The only way to clear that cache
5129
  // is to re-initialize the theme system.
5130
  unset($GLOBALS['theme']);
5131
  drupal_theme_initialize();
5132

    
5133
  $form_state['rerender'] = TRUE;
5134
  $form_state['rebuild'] = TRUE;
5135
}
5136

    
5137
/**
5138
 * Override handler for views_ui_edit_display_form.
5139
 */
5140
function views_ui_edit_display_form_change_theme($form, &$form_state) {
5141
  // This is just a temporary variable.
5142
  $form_state['view']->theme = $form_state['values']['theme'];
5143

    
5144
  views_ui_cache_set($form_state['view']);
5145
  $form_state['rerender'] = TRUE;
5146
  $form_state['rebuild'] = TRUE;
5147
}
5148

    
5149
/**
5150
 * Page callback for views tag autocomplete.
5151
 */
5152
function views_ui_autocomplete_tag($string = '') {
5153
  $matches = array();
5154
  // Get matches from default views.
5155
  views_include('view');
5156
  $views = views_get_all_views();
5157
  foreach ($views as $view) {
5158
    if (!empty($view->tag) && strpos($view->tag, $string) === 0) {
5159
      $matches[$view->tag] = check_plain($view->tag);
5160
      if (count($matches) >= 10) {
5161
        break;
5162
      }
5163
    }
5164
  }
5165

    
5166
  drupal_json_output($matches);
5167
}
5168

    
5169
// ------------------------------------------------------------------
5170
// Get information from the Views data.
5171
function _views_weight_sort($a, $b) {
5172
  if ($a['weight'] != $b['weight']) {
5173
    return $a['weight'] < $b['weight'] ? -1 : 1;
5174
  }
5175
  if ($a['title'] != $b['title']) {
5176
    return $a['title'] < $b['title'] ? -1 : 1;
5177
  }
5178

    
5179
  return 0;
5180
}
5181

    
5182
/**
5183
 * Fetch a list of all base tables available.
5184
 *
5185
 * @return
5186
 *   A keyed array of in the form of 'base_table' => 'Description'.
5187
 */
5188
function views_fetch_base_tables() {
5189
  static $base_tables = array();
5190
  if (empty($base_tables)) {
5191
    $weights = array();
5192
    $tables = array();
5193
    $data = views_fetch_data();
5194
    foreach ($data as $table => $info) {
5195
      if (!empty($info['table']['base'])) {
5196
        $tables[$table] = array(
5197
          'title' => $info['table']['base']['title'],
5198
          'description' => !empty($info['table']['base']['help']) ? $info['table']['base']['help'] : '',
5199
          'weight' => !empty($info['table']['base']['weight']) ? $info['table']['base']['weight'] : 0,
5200
        );
5201
      }
5202
    }
5203
    uasort($tables, '_views_weight_sort');
5204
    $base_tables = $tables;
5205
  }
5206

    
5207
  return $base_tables;
5208
}
5209

    
5210
/**
5211
 *
5212
 */
5213
function _views_sort_types($a, $b) {
5214
  $a_group = drupal_strtolower($a['group']);
5215
  $b_group = drupal_strtolower($b['group']);
5216
  if ($a_group != $b_group) {
5217
    return $a_group < $b_group ? -1 : 1;
5218
  }
5219

    
5220
  $a_title = drupal_strtolower($a['title']);
5221
  $b_title = drupal_strtolower($b['title']);
5222
  if ($a_title != $b_title) {
5223
    return $a_title < $b_title ? -1 : 1;
5224
  }
5225

    
5226
  return 0;
5227
}
5228

    
5229
/**
5230
 * Fetch a list of all fields available for a given base type.
5231
 *
5232
 * @param (array|string) $base
5233
 *   A list or a single base_table, for example node.
5234
 * @param string $type
5235
 *   The handler type, for example field or filter.
5236
 * @param bool $grouping
5237
 *   Should the result grouping by its 'group' label.
5238
 *
5239
 * @return array
5240
 *   A keyed array of in the form of 'base_table' => 'Description'.
5241
 */
5242
function views_fetch_fields($base, $type, $grouping = FALSE) {
5243
  static $fields = array();
5244
  if (empty($fields)) {
5245
    $data = views_fetch_data();
5246
    // This constructs this ginormous multi dimensional array to
5247
    // collect the important data about fields. In the end,
5248
    // the structure looks a bit like this (using nid as an example)
5249
    // $strings['nid']['filter']['title'] = 'string'.
5250
    //
5251
    // This is constructed this way because the above referenced strings
5252
    // can appear in different places in the actual data structure so that
5253
    // the data doesn't have to be repeated a lot. This essentially lets
5254
    // each field have a cheap kind of inheritance.
5255
    $components = array('title', 'group', 'help', 'base', 'aliases');
5256
    $components_errors = array();
5257
    // Fixed number of calls to t() to produce error components.
5258
    foreach ($components as $string) {
5259
      $components_errors[$string] = t("Error: missing @component", array('@component' => $string));
5260
    }
5261
    foreach ($data as $table => $table_data) {
5262
      $bases = array();
5263
      $strings = array();
5264
      $skip_bases = array();
5265
      foreach ($table_data as $field => $info) {
5266
        // Collect table data from this table.
5267
        if ($field == 'table') {
5268
          // calculate what tables this table can join to.
5269
          if (!empty($info['join'])) {
5270
            $bases = array_keys($info['join']);
5271
          }
5272
          // And it obviously joins to itself.
5273
          $bases[] = $table;
5274
          continue;
5275
        }
5276
        foreach (array('field', 'sort', 'filter', 'argument', 'relationship', 'area') as $key) {
5277
          if (!empty($info[$key])) {
5278
            if ($grouping && !empty($info[$key]['no group by'])) {
5279
              continue;
5280
            }
5281
            if (!empty($info[$key]['skip base'])) {
5282
              foreach ((array) $info[$key]['skip base'] as $base_name) {
5283
                $skip_bases[$field][$key][$base_name] = TRUE;
5284
              }
5285
            }
5286
            elseif (!empty($info['skip base'])) {
5287
              foreach ((array) $info['skip base'] as $base_name) {
5288
                $skip_bases[$field][$key][$base_name] = TRUE;
5289
              }
5290
            }
5291
            // Don't show old fields. The real field will be added right.
5292
            if (isset($info[$key]['moved to'])) {
5293
              continue;
5294
            }
5295
            foreach ($components as $string) {
5296
              // First, try the lowest possible level.
5297
              if (!empty($info[$key][$string])) {
5298
                $strings[$field][$key][$string] = $info[$key][$string];
5299
              }
5300
              // Then try the field level.
5301
              elseif (!empty($info[$string])) {
5302
                $strings[$field][$key][$string] = $info[$string];
5303
              }
5304
              // Finally, try the table level.
5305
              elseif (!empty($table_data['table'][$string])) {
5306
                $strings[$field][$key][$string] = $table_data['table'][$string];
5307
              }
5308
              else {
5309
                if ($string != 'base') {
5310
                  $strings[$field][$key][$string] = $components_errors[$string];
5311
                }
5312
              }
5313
            }
5314
          }
5315
        }
5316
      }
5317
      foreach ($bases as $base_name) {
5318
        foreach ($strings as $field => $field_strings) {
5319
          foreach ($field_strings as $type_name => $type_strings) {
5320
            if (empty($skip_bases[$field][$type_name][$base_name])) {
5321
              $fields[$base_name][$type_name]["$table.$field"] = $type_strings;
5322
            }
5323
          }
5324
        }
5325
      }
5326
    }
5327
  }
5328

    
5329
  // If we have an array of base tables available, go through them all and add
5330
  // them together. Duplicate keys will be lost and that's Just Fine.
5331
  if (is_array($base)) {
5332
    $strings = array();
5333
    foreach ($base as $base_table) {
5334
      if (isset($fields[$base_table][$type])) {
5335
        $strings += $fields[$base_table][$type];
5336
      }
5337
    }
5338
    uasort($strings, '_views_sort_types');
5339
    return $strings;
5340
  }
5341

    
5342
  if (isset($fields[$base][$type])) {
5343
    uasort($fields[$base][$type], '_views_sort_types');
5344
    return $fields[$base][$type];
5345
  }
5346
  return array();
5347
}
5348

    
5349

    
5350
/**
5351
 * Theme the form for the table style plugin.
5352
 */
5353
function theme_views_ui_style_plugin_table($variables) {
5354
  $form = $variables['form'];
5355

    
5356
  $output = drupal_render($form['description_markup']);
5357

    
5358
  $header = array(
5359
    t('Field'),
5360
    t('Column'),
5361
    t('Align'),
5362
    t('Separator'),
5363
    array(
5364
      'data' => t('Sortable'),
5365
      'align' => 'center',
5366
    ),
5367
    array(
5368
      'data' => t('Default order'),
5369
      'align' => 'center',
5370
    ),
5371
    array(
5372
      'data' => t('Default sort'),
5373
      'align' => 'center',
5374
    ),
5375
    array(
5376
      'data' => t('Hide empty column'),
5377
      'align' => 'center',
5378
    ),
5379
  );
5380
  $rows = array();
5381
  foreach (element_children($form['columns']) as $id) {
5382
    $row = array();
5383
    $row[] = check_plain(drupal_render($form['info'][$id]['name']));
5384
    $row[] = drupal_render($form['columns'][$id]);
5385
    $row[] = drupal_render($form['info'][$id]['align']);
5386
    $row[] = drupal_render($form['info'][$id]['separator']);
5387
    if (!empty($form['info'][$id]['sortable'])) {
5388
      $row[] = array(
5389
        'data' => drupal_render($form['info'][$id]['sortable']),
5390
        'align' => 'center',
5391
      );
5392
      $row[] = array(
5393
        'data' => drupal_render($form['info'][$id]['default_sort_order']),
5394
        'align' => 'center',
5395
      );
5396
      $row[] = array(
5397
        'data' => drupal_render($form['default'][$id]),
5398
        'align' => 'center',
5399
      );
5400
    }
5401
    else {
5402
      $row[] = '';
5403
      $row[] = '';
5404
      $row[] = '';
5405
    }
5406
    $row[] = array(
5407
      'data' => drupal_render($form['info'][$id]['empty_column']),
5408
      'align' => 'center',
5409
    );
5410
    $rows[] = $row;
5411
  }
5412

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

    
5416
  $output .= theme('table', array('header' => $header, 'rows' => $rows));
5417
  $output .= drupal_render_children($form);
5418
  return $output;
5419
}
5420

    
5421
/**
5422
 * Placeholder function for overriding $display->display_title.
5423
 *
5424
 * @todo Remove this function once editing the display title is possible.
5425
 */
5426
function views_ui_get_display_label($view, $display_id, $check_changed = TRUE) {
5427
  $title = $display_id == 'default' ? t('Master') : $view->display[$display_id]->display_title;
5428
  $title = views_ui_truncate($title, 25);
5429

    
5430
  if ($check_changed && !empty($view->changed_display[$display_id])) {
5431
    $changed = '*';
5432
    $title = $title . $changed;
5433
  }
5434

    
5435
  return $title;
5436
}
5437

    
5438
/**
5439
 *
5440
 */
5441
function views_ui_add_template_page() {
5442
  $templates = views_get_all_templates();
5443

    
5444
  if (empty($templates)) {
5445
    return t('There are no templates available.');
5446
  }
5447

    
5448
  $header = array(
5449
    t('Name'),
5450
    t('Description'),
5451
    t('Operation'),
5452
  );
5453

    
5454
  $rows = array();
5455
  foreach ($templates as $name => $template) {
5456
    $rows[] = array(
5457
      array('data' => check_plain($template->get_human_name())),
5458
      array('data' => check_plain($template->description)),
5459
      array('data' => l('add', 'admin/structure/views/template/' . $template->name . '/add')),
5460
    );
5461
  }
5462

    
5463
  $output = theme('table', array('header' => $header, 'rows' => $rows));
5464
  return $output;
5465
}
5466

    
5467
/**
5468
 * #process callback for a button.
5469
 *
5470
 * Determines if a button is the form's triggering element.
5471
 *
5472
 * The Form API has logic to determine the form's triggering element based on
5473
 * the data in $_POST. However, it only checks buttons based on a single #value
5474
 * per button. This function may be added to a button's #process callbacks to
5475
 * extend button click detection to support multiple #values per button. If the
5476
 * data in $_POST matches any value in the button's #values array, then the
5477
 * button is detected as having been clicked. This can be used when the value
5478
 * (label) of the same logical button may be different based on context (e.g.,
5479
 * "Apply" vs. "Apply and continue").
5480
 *
5481
 * @see _form_builder_handle_input_element()
5482
 * @see _form_button_was_clicked()
5483
 */
5484
function views_ui_form_button_was_clicked($element, &$form_state) {
5485
  $process_input = empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])));
5486
  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)) {
5487
    $form_state['triggering_element'] = $element;
5488
  }
5489
  return $element;
5490
}
5491

    
5492
/**
5493
 * #process callback for a button.
5494
 *
5495
 * Makes implicit form submissions trigger as this button.
5496
 *
5497
 * @see Drupal.behaviors.viewsImplicitFormSubmission
5498
 */
5499
function views_ui_default_button($element, &$form_state, $form) {
5500
  $setting['viewsImplicitFormSubmission'][$form['#id']]['defaultButton'] = $element['#id'];
5501
  $element['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
5502
  return $element;
5503
}
5504

    
5505
/**
5506
 * List all instances of fields on any views.
5507
 *
5508
 * Therefore it builds up a table of each field which is used in any view.
5509
 *
5510
 * @see field_ui_fields_list()
5511
 */
5512
function views_ui_field_list() {
5513
  $views = views_get_all_views();
5514

    
5515
  // Fetch all fieldapi fields which are used in views
5516
  // Therefore search in all views, displays and handler-types.
5517
  $fields = array();
5518
  foreach ($views as $view) {
5519
    foreach ($view->display as $display_id => $display) {
5520
      if ($view->set_display($display_id)) {
5521
        foreach (views_object_types() as $type => $info) {
5522
          foreach ($view->get_items($type, $display_id) as $item) {
5523
            $data = views_fetch_data($item['table']);
5524
            if (isset($data[$item['field']]) && isset($data[$item['field']][$type])
5525
              && $data = $data[$item['field']][$type]) {
5526
              // The final check that we have a fieldapi field now.
5527
              if (isset($data['field_name'])) {
5528
                $fields[$data['field_name']][$view->name] = $view->name;
5529
              }
5530
            }
5531
          }
5532
        }
5533
      }
5534
    }
5535
  }
5536

    
5537
  $header = array(t('Field name'), t('Used in'));
5538
  $rows = array();
5539
  foreach ($fields as $field_name => $views) {
5540

    
5541
    $rows[$field_name]['data'][0] = check_plain($field_name);
5542
    foreach ($views as $view) {
5543
      $rows[$field_name]['data'][1][] = l($view, "admin/structure/views/view/$view");
5544
    }
5545
    $rows[$field_name]['data'][1] = implode(', ', $rows[$field_name]['data'][1]);
5546
  }
5547

    
5548
  // Sort rows by field name.
5549
  ksort($rows);
5550
  $output = array(
5551
    '#theme' => 'table',
5552
    '#header' => $header,
5553
    '#rows' => $rows,
5554
    '#empty' => t('No fields have been used in views yet.'),
5555
  );
5556

    
5557
  return $output;
5558
}
5559

    
5560
/**
5561
 * Lists all plugins and what enabled Views use them.
5562
 */
5563
function views_ui_plugin_list() {
5564
  $rows = views_plugin_list();
5565
  foreach ($rows as &$row) {
5566
    // Link each view name to the view itself.
5567
    foreach ($row['views'] as $row_name => $view) {
5568
      $row['views'][$row_name] = l($view, "admin/structure/views/view/$view");
5569
    }
5570
    $row['views'] = implode(', ', $row['views']);
5571
  }
5572

    
5573
  // Sort rows by field name.
5574
  ksort($rows);
5575
  return array(
5576
    '#theme' => 'table',
5577
    '#header' => array(t('Type'), t('Name'), t('Provided by'), t('Used in')),
5578
    '#rows' => $rows,
5579
    '#empty' => t('There are no enabled views.'),
5580
  );
5581
}