Newer
Older
<?php
/**
* @file admin.inc
* Provides the Views' administrative interface.
*/
/**
* Create an array of Views admin CSS for adding or attaching.
*
* This returns an array of arrays. Each array represents a single
* file. The array format is:
* - file: The fully qualified name of the file to send to drupal_add_css
* - options: An array of options to pass to drupal_add_css.
function views_ui_get_admin_css() {
Jesse Beach
committed
$module_path = drupal_get_path('module', 'views_ui');
$list = array();
$list[$module_path . '/css/views-admin.css'] = array();
$list[$module_path . '/css/ie/views-admin.ie7.css'] = array(
'browsers' => array(
'IE' => 'lte IE 7',
'!IE' => FALSE
),
'preprocess' => FALSE,
Jesse Beach
committed
$list[$module_path . '/css/views-admin.theme.css'] = array();
// Add in any theme specific CSS files we have
$themes = list_themes();
$theme_key = $GLOBALS['theme'];
while ($theme_key) {
$list[$module_path . "/css/views-admin.$theme_key.css"] = array(
'group' => CSS_THEME,
);
$theme_key = isset($themes[$theme_key]->base_theme) ? $themes[$theme_key]->base_theme : '';
}
// Views contains style overrides for the following modules
$module_list = array('contextual', 'advanced_help', 'ctools');
foreach ($module_list as $module) {
if (module_exists($module)) {
$list[$module_path . '/css/views-admin.' . $module . '.css'] = array();
}
return $list;
}
/**
* Adds standard Views administration CSS to the current page.
*/
function views_ui_add_admin_css() {
foreach (views_ui_get_admin_css() as $file => $options) {
drupal_add_css($file, $options);
Jesse Beach
committed
}
}
/**
* Check to see if the advanced help module is installed, and if not put up
* a message.
*
* Only call this function if the user is already in a position for this to
* be useful.
*/
function views_ui_check_advanced_help() {
if (!variable_get('views_ui_show_advanced_help_warning', TRUE)) {
return;
}
if (!module_exists('advanced_help')) {
$filename = db_query_range("SELECT filename FROM {system} WHERE type = 'module' AND name = 'advanced_help'", 0, 1)
->fetchField();
if ($filename && file_exists($filename)) {
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">Hide this message.</a>', array('@modules' => url('admin/modules'),'@hide' => url('admin/structure/views/settings'))));
drupal_set_message(t('If you install the advanced help module from !href, Views will provide more and better help. <a href="@hide">Hide this message.</a>', array('!href' => l('http://drupal.org/project/advanced_help', 'http://drupal.org/project/advanced_help'), '@hide' => url('admin/structure/views/settings'))));
* #pre_render callback for the live preview element.
*
* Delaying the preview generation until the rendering phase ensures that time
* isn't wasted generating a preview if it won't be displayed (for example, if
* after processing the Edit form, a redirect is issued).
function views_ui_pre_render_preview($element) {
Alex Bronstein
committed
$preview = views_ui_preview($element['#view'], $element['#display_id'], $element['#preview_args']);
// @todo HTML does not support a FORM inside a FORM. Remove this hack after
// fixing Views UI to display the preview outside of the edit form, so that
// we don't have to mess with the exposed widget form in this way.
$preview = preg_replace('@</?form.*?>@', '', $preview);
$element['#markup'] = $preview;
* Returns the results of the live preview.
function views_ui_preview($view, $display_id, $args = array()) {
// When this function is invoked as a page callback, each Views argument is
// passed separately.
if (!is_array($args)) {
$args = array_slice(func_get_args(), 2);
Alex Bronstein
committed
// Save $_GET['q'] so it can be restored before returning from this function.
$q = $_GET['q'];
Katherine Senzee
committed
// Determine where the query and performance statistics should be output.
$show_query = variable_get('views_ui_show_sql_query', FALSE);
$show_info = variable_get('views_ui_show_preview_information', FALSE);
$show_location = variable_get('views_ui_show_sql_query_where', 'above');
Katherine Senzee
committed
$show_stats = variable_get('views_ui_show_performance_statistics', FALSE);
if ($show_stats) {
$show_stats = variable_get('views_ui_show_sql_query_where', 'above');
Katherine Senzee
committed
$combined = ($show_query === $show_stats);
Katherine Senzee
committed
$rows = array('query' => array(), 'statistics' => array());
$output = '';
$errors = $view->validate();
if ($errors === TRUE) {
// AJAX happens via $_POST but everything expects exposed data to
// be in GET. Copy stuff but remove ajax-framework specific keys.
$exposed_input = $_POST;
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') as $key) {
if (isset($exposed_input[$key])) {
unset($exposed_input[$key]);
}
}
Alex Bronstein
committed
// @todo Remove this hack after fixing Views UI to display the preview
// outside of the edit form, so that the edit form doesn't conflict with
// the exposed widget form.
$exposed_input['form_id'] = 'views_exposed_form';
unset($exposed_input['form_build_id'], $exposed_input['form_token']);
// Store the current view URL for later use:
$view->set_arguments($args);
if ($view->display_handler->get_option('path')) {
$path = $view->get_url();
}
// Make view links come back to preview.
$view->override_path = 'admin/structure/views/nojs/preview/' . $view->name . '/' . $display_id;
// Also override $_GET['q'] so we get the pager.
$_GET['q'] = $view->override_path;
if ($args) {
$_GET['q'] .= '/' . implode('/', $args);
// Suppress contextual links of entities within the result set during a
// Preview.
// @todo We'll want to add contextual links specific to editing the View, so
// the suppression may need to be moved deeper into the Preview pipeline.
Alex Bronstein
committed
views_ui_contextual_links_suppress_push();
try {
$preview = $view->preview($display_id, $args);
}
catch (Exception $e) {
drupal_set_message($e->getMessage(), 'error');
}
Alex Bronstein
committed
views_ui_contextual_links_suppress_pop();
Katherine Senzee
committed
// Prepare the query information and statistics to show either above or
// below the view preview.
if ($show_info || $show_query || $show_stats) {
// Get information from the preview for display.
if (!empty($view->build_info['query'])) {
Katherine Senzee
committed
if ($show_query) {
$query = $view->build_info['query'];
// Only the sql default class has a method getArguments.
$quoted = array();
if (get_class($view->query) == 'views_plugin_query_default') {
$arguments = $query->getArguments();
$connection = Database::getConnection();
foreach ((array)$query->arguments() as $key => $val) {
$quoted[$key] = $connection->quote($val);
Katherine Senzee
committed
$rows['query'][] = array('<strong>' . t('Query') . '</strong>', '<pre>' . check_plain(strtr($query, $quoted)) . '</pre>');
if (!empty($view->additional_queries)) {
$queries = '<strong>' . t('These queries were run during view rendering:') . '</strong>';
foreach ($view->additional_queries as $query) {
if ($queries) {
$queries .= "\n";
}
$queries .= t('[@time ms]', array('@time' => intval($query[1] * 100000) / 100)) . ' ' . $query[0];
}
Katherine Senzee
committed
$rows['query'][] = array('<strong>' . t('Other queries') . '</strong>', '<pre>' . $queries . '</pre>');
}
if ($show_info) {
Katherine Senzee
committed
$rows['query'][] = array('<strong>' . t('Title') . '</strong>', filter_xss_admin($view->get_title()));
if (isset($path)) {
$path = l($path, $path);
}
else {
$path = t('This display has no path.');
}
$rows['query'][] = array('<strong>' . t('Path') . '</strong>', $path);
Katherine Senzee
committed
if ($show_stats) {
$rows['statistics'][] = array('<strong>' . t('Query build time') . '</strong>', t('@time ms', array('@time' => intval($view->build_time * 100000) / 100)));
$rows['statistics'][] = array('<strong>' . t('Query execute time') . '</strong>', t('@time ms', array('@time' => intval($view->execute_time * 100000) / 100)));
$rows['statistics'][] = array('<strong>' . t('View render time') . '</strong>', t('@time ms', array('@time' => intval($view->render_time * 100000) / 100)));
Katherine Senzee
committed
}
drupal_alter('views_preview_info', $rows, $view);
Katherine Senzee
committed
// No query was run. Display that information in place of either the
// query or the performance statistics, whichever comes first.
if ($combined || ($show_location === 'above')) {
Katherine Senzee
committed
$rows['query'] = array(array('<strong>' . t('Query') . '</strong>', t('No query was run')));
}
else {
$rows['statistics'] = array(array('<strong>' . t('Query') . '</strong>', t('No query was run')));
}
}
}
}
else {
foreach ($errors as $error) {
drupal_set_message($error, 'error');
}
$preview = t('Unable to preview due to validation errors.');
}
Katherine Senzee
committed
// Assemble the preview, the query info, and the query statistics in the
// requested order.
if ($show_location === 'above') {
Katherine Senzee
committed
if ($combined) {
$output .= '<div class="views-query-info">' . theme('table', array('rows' => array_merge($rows['query'], $rows['statistics']))) . '</div>';
}
else {
$output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['query'])) . '</div>';
}
Katherine Senzee
committed
elseif ($show_stats === 'above') {
$output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['statistics'])) . '</div>';
Katherine Senzee
committed
$output .= $preview;
if ($show_location === 'below') {
Katherine Senzee
committed
if ($combined) {
$output .= '<div class="views-query-info">' . theme('table', array('rows' => array_merge($rows['query'], $rows['statistics']))) . '</div>';
Katherine Senzee
committed
else {
$output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['query'])) . '</div>';
Katherine Senzee
committed
elseif ($show_stats === 'below') {
$output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['statistics'])) . '</div>';
Alex Bronstein
committed
$_GET['q'] = $q;
* AJAX callback that returns the preview section of the edit form.
function views_ui_preview_callback($form, $form_state) {
return $form['displays']['preview'];
}
/**
* Page callback to add a new view.
*/
function views_ui_add_page() {
views_ui_add_admin_css();
David Rothstein
committed
drupal_set_title(t('Add new view'));
return drupal_get_form('views_ui_add_form');
* Form builder for the "add new view" page.
*/
function views_ui_add_form($form, &$form_state) {
ctools_include('dependent');
$form['#attached']['js'][] = drupal_get_path('module', 'views_ui') . '/js/views-admin.js';
$form['#attributes']['class'] = array('views-admin');
$form['human_name'] = array(
'#type' => 'textfield',
'#title' => t('View name'),
'#required' => TRUE,
'#size' => 32,
David Rothstein
committed
'#default_value' => !empty($form_state['view']) ? $form_state['view']->human_name : '',
'#maxlength' => 255,
$form['name'] = array(
'#type' => 'machine_name',
'#maxlength' => 32,
'#machine_name' => array(
'exists' => 'views_get_view',
'source' => array('human_name'),
),
'#description' => t('A unique machine-readable name for this View. It must only contain lowercase letters, numbers, and underscores.'),
);
$form['description_enable'] = array(
'#type' => 'checkbox',
'#title' => t('Description'),
);
$form['description'] = array(
'#type' => 'textfield',
'#title' => t('Provide description'),
'#title_display' => 'invisible',
'#size' => 64,
'#default_value' => !empty($form_state['view']) ? $form_state['view']->description : '',
'#dependency' => array(
'edit-description-enable' => array(1),
David Rothstein
committed
// Create a wrapper for the entire dynamic portion of the form. Everything
// that can be updated by AJAX goes somewhere inside here. For example, this
// is needed by "Show" dropdown (below); it changes the base table of the
// view and therefore potentially requires all options on the form to be
// dynamically updated.
$form['displays'] = array();
// Create the part of the form that allows the user to select the basic
// properties of what the view will display.
David Rothstein
committed
$form['displays']['show'] = array(
'#type' => 'fieldset',
'#tree' => TRUE,
David Rothstein
committed
'#attributes' => array('class' => array('container-inline')),
David Rothstein
committed
// Create the "Show" dropdown, which allows the base table of the view to be
// selected.
$wizard_plugins = views_ui_get_wizards();
$options = array();
foreach ($wizard_plugins as $key => $w) {
$options[$key] = $w['title'];
David Rothstein
committed
$form['displays']['show']['wizard_key'] = array(
'#type' => 'select',
'#title' => t('Show'),
'#options' => $options,
David Rothstein
committed
$show_form = &$form['displays']['show'];
$show_form['wizard_key']['#default_value'] = views_ui_get_selected($form_state, array('show', 'wizard_key'), 'node', $show_form['wizard_key']);
// Changing this dropdown updates the entire content of $form['displays'] via
// AJAX.
views_ui_add_ajax_trigger($show_form, 'wizard_key', array('displays'));
// Build the rest of the form based on the currently selected wizard plugin.
$wizard_key = $show_form['wizard_key']['#default_value'];
$get_instance = $wizard_plugins[$wizard_key]['get_instance'];
$wizard_instance = $get_instance($wizard_plugins[$wizard_key]);
$form = $wizard_instance->build_form($form, $form_state);
'#value' => t('Save & exit'),
'#validate' => array('views_ui_wizard_form_validate'),
'#submit' => array('views_ui_add_form_save_submit'),
);
$form['continue'] = array(
'#value' => t('Continue & edit'),
'#validate' => array('views_ui_wizard_form_validate'),
'#submit' => array('views_ui_add_form_store_edit_submit'),
'#process' => array_merge(array('views_ui_default_button'), element_info_property('submit', '#process', array())),
$form['cancel'] = array(
'#type' => 'submit',
'#value' => t('Cancel'),
'#submit' => array('views_ui_add_form_cancel_submit'),
'#limit_validation_errors' => array(),
David Rothstein
committed
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
* Gets the current value of a #select element, from within a form constructor function.
*
* This function is intended for use in highly dynamic forms (in particular the
* add view wizard) which are rebuilt in different ways depending on which
* triggering element (AJAX or otherwise) was most recently fired. For example,
* sometimes it is necessary to decide how to build one dynamic form element
* based on the value of a different dynamic form element that may not have
* even been present on the form the last time it was submitted. This function
* takes care of resolving those conflicts and gives you the proper current
* value of the requested #select element.
*
* By necessity, this function sometimes uses non-validated user input from
* $form_state['input'] in making its determination. Although it performs some
* minor validation of its own, it is not complete. The intention is that the
* return value of this function should only be used to help decide how to
* build the current form the next time it is reloaded, not to be saved as if
* it had gone through the normal, final form validation process. Do NOT use
* the results of this function for any other purpose besides deciding how to
* build the next version of the form.
*
* @param $form_state
* The standard associative array containing the current state of the form.
* @param $parents
* An array of parent keys that point to the part of the submitted form
* values that are expected to contain the element's value (in the case where
* this form element was actually submitted). In a simple case (assuming
* #tree is TRUE throughout the form), if the select element is located in
* $form['wrapper']['select'], so that the submitted form values would
* normally be found in $form_state['values']['wrapper']['select'], you would
* pass array('wrapper', 'select') for this parameter.
* @param $default_value
* The default value to return if the #select element does not currently have
* a proper value set based on the submitted input.
* @param $element
* An array representing the current version of the #select element within
* the form.
*
* @return
* The current value of the #select element. A common use for this is to feed
* it back into $element['#default_value'] so that the form will be rendered
* with the correct value selected.
David Rothstein
committed
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
function views_ui_get_selected($form_state, $parents, $default_value, $element) {
// For now, don't trust this to work on anything but a #select element.
if (!isset($element['#type']) || $element['#type'] != 'select' || !isset($element['#options'])) {
return $default_value;
}
// If there is a user-submitted value for this element that matches one of
// the currently available options attached to it, use that. We need to check
// $form_state['input'] rather than $form_state['values'] here because the
// triggering element often has the #limit_validation_errors property set to
// prevent unwanted errors elsewhere on the form. This means that the
// $form_state['values'] array won't be complete. We could make it complete
// by adding each required part of the form to the #limit_validation_errors
// property individually as the form is being built, but this is difficult to
// do for a highly dynamic and extensible form. This method is much simpler.
if (!empty($form_state['input'])) {
$key_exists = NULL;
$submitted = drupal_array_get_nested_value($form_state['input'], $parents, $key_exists);
// Check that the user-submitted value is one of the allowed options before
// returning it. This is not a substitute for actual form validation;
// rather it is necessary because, for example, the same select element
// might have #options A, B, and C under one set of conditions but #options
// D, E, F under a different set of conditions. So the form submission
// might have occurred with option A selected, but when the form is rebuilt
// option A is no longer one of the choices. In that case, we don't want to
// use the value that was submitted anymore but rather fall back to the
// default value.
if ($key_exists && in_array($submitted, array_keys($element['#options']))) {
return $submitted;
}
David Rothstein
committed
// Fall back on returning the default value if nothing was returned above.
return $default_value;
David Rothstein
committed
* Converts a form element in the add view wizard to be AJAX-enabled.
David Rothstein
committed
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
* This function takes a form element and adds AJAX behaviors to it such that
* changing it triggers another part of the form to update automatically. It
* also adds a submit button to the form that appears next to the triggering
* element and that duplicates its functionality for users who do not have
* JavaScript enabled (the button is automatically hidden for users who do have
* JavaScript).
*
* To use this function, call it directly from your form builder function
* immediately after you have defined the form element that will serve as the
* JavaScript trigger. Calling it elsewhere (such as in hook_form_alter()) may
* mean that the non-JavaScript fallback button does not appear in the correct
* place in the form.
*
* @param $wrapping_element
* The element whose child will server as the AJAX trigger. For example, if
* $form['some_wrapper']['triggering_element'] represents the element which
* will trigger the AJAX behavior, you would pass $form['some_wrapper'] for
* this parameter.
* @param $trigger_key
* The key within the wrapping element that identifies which of its children
* serves as the AJAX trigger. In the above example, you would pass
* 'triggering_element' for this parameter.
* @param $refresh_parents
* An array of parent keys that point to the part of the form that will be
* refreshed by AJAX. For example, if triggering the AJAX behavior should
* cause $form['dynamic_content']['section'] to be refreshed, you would pass
* array('dynamic_content', 'section') for this parameter.
David Rothstein
committed
function views_ui_add_ajax_trigger(&$wrapping_element, $trigger_key, $refresh_parents) {
David Rothstein
committed
$seen_ids = &drupal_static(__FUNCTION__ . ':seen_ids', array());
$seen_buttons = &drupal_static(__FUNCTION__ . ':seen_buttons', array());
David Rothstein
committed
// Add the AJAX behavior to the triggering element.
$triggering_element = &$wrapping_element[$trigger_key];
$triggering_element['#ajax']['callback'] = 'views_ui_ajax_update_form';
// We do not use drupal_html_id() to get an ID for the AJAX wrapper, because
// it remembers IDs across AJAX requests (and won't reuse them), but in our
// case we need to use the same ID from request to request so that the
// wrapper can be recognized by the AJAX system and its content can be
// dynamically updated. So instead, we will keep track of duplicate IDs
// (within a single request) on our own, later in this function.
$triggering_element['#ajax']['wrapper'] = 'edit-view-' . implode('-', $refresh_parents) . '-wrapper';
// Add a submit button for users who do not have JavaScript enabled. It
// should be displayed next to the triggering element on the form.
$button_key = $trigger_key . '_trigger_update';
$wrapping_element[$button_key] = array(
David Rothstein
committed
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
// Hide this button when JavaScript is enabled.
'#attributes' => array('class' => array('js-hide')),
'#submit' => array('views_ui_nojs_submit'),
// Add a process function to limit this button's validation errors to the
// triggering element only. We have to do this in #process since until the
// form API has added the #parents property to the triggering element for
// us, we don't have any (easy) way to find out where its submitted values
// will eventually appear in $form_state['values'].
'#process' => array_merge(array('views_ui_add_limited_validation'), element_info_property('submit', '#process', array())),
// Add an after-build function that inserts a wrapper around the region of
// the form that needs to be refreshed by AJAX (so that the AJAX system can
// detect and dynamically update it). This is done in #after_build because
// it's a convenient place where we have automatic access to the complete
// form array, but also to minimize the chance that the HTML we add will
// get clobbered by code that runs after we have added it.
'#after_build' => array_merge(element_info_property('submit', '#after_build', array()), array('views_ui_add_ajax_wrapper')),
);
// Copy #weight and #access from the triggering element to the button, so
// that the two elements will be displayed together.
foreach (array('#weight', '#access') as $property) {
if (isset($triggering_element[$property])) {
$wrapping_element[$button_key][$property] = $triggering_element[$property];
}
}
// For easiest integration with the form API and the testing framework, we
// always give the button a unique #value, rather than playing around with
// #name.
$button_title = !empty($triggering_element['#title']) ? $triggering_element['#title'] : $trigger_key;
if (empty($seen_buttons[$button_title])) {
$wrapping_element[$button_key]['#value'] = t('Update "@title" choice', array(
'@title' => $button_title,
));
$seen_buttons[$button_title] = 1;
}
else {
$wrapping_element[$button_key]['#value'] = t('Update "@title" choice (@number)', array(
'@title' => $button_title,
'@number' => ++$seen_buttons[$button_title],
));
}
// Attach custom data to the triggering element and submit button, so we can
// use it in both the process function and AJAX callback.
$ajax_data = array(
'wrapper' => $triggering_element['#ajax']['wrapper'],
'trigger_key' => $trigger_key,
'refresh_parents' => $refresh_parents,
// Keep track of duplicate wrappers so we don't add the same wrapper to the
// page more than once.
'duplicate_wrapper' => !empty($seen_ids[$triggering_element['#ajax']['wrapper']]),
David Rothstein
committed
$seen_ids[$triggering_element['#ajax']['wrapper']] = TRUE;
$triggering_element['#views_ui_ajax_data'] = $ajax_data;
$wrapping_element[$button_key]['#views_ui_ajax_data'] = $ajax_data;
David Rothstein
committed
* Processes a non-JavaScript fallback submit button to limit its validation errors.
David Rothstein
committed
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
function views_ui_add_limited_validation($element, &$form_state) {
// Retrieve the AJAX triggering element so we can determine its parents. (We
// know it's at the same level of the complete form array as the submit
// button, so all we have to do to find it is swap out the submit button's
// last array parent.)
$array_parents = $element['#array_parents'];
array_pop($array_parents);
$array_parents[] = $element['#views_ui_ajax_data']['trigger_key'];
$ajax_triggering_element = drupal_array_get_nested_value($form_state['complete form'], $array_parents);
// Limit this button's validation to the AJAX triggering element, so it can
// update the form for that change without requiring that the rest of the
// form be filled out properly yet.
$element['#limit_validation_errors'] = array($ajax_triggering_element['#parents']);
// If we are in the process of a form submission and this is the button that
// was clicked, the form API workflow in form_builder() will have already
// copied it to $form_state['triggering_element'] before our #process
// function is run. So we need to make the same modifications in $form_state
// as we did to the element itself, to ensure that #limit_validation_errors
// will actually be set in the correct place.
if (!empty($form_state['triggering_element'])) {
$clicked_button = &$form_state['triggering_element'];
if ($clicked_button['#name'] == $element['#name'] && $clicked_button['#value'] == $element['#value']) {
$clicked_button['#limit_validation_errors'] = $element['#limit_validation_errors'];
}
}
return $element;
David Rothstein
committed
* After-build function that adds a wrapper to a form region (for AJAX refreshes).
David Rothstein
committed
* This function inserts a wrapper around the region of the form that needs to
* be refreshed by AJAX, based on information stored in the corresponding
* submit button form element.
David Rothstein
committed
function views_ui_add_ajax_wrapper($element, &$form_state) {
// Don't add the wrapper <div> if the same one was already inserted on this
// form.
if (empty($element['#views_ui_ajax_data']['duplicate_wrapper'])) {
// Find the region of the complete form that needs to be refreshed by AJAX.
// This was earlier stored in a property on the element.
$complete_form = &$form_state['complete form'];
$refresh_parents = $element['#views_ui_ajax_data']['refresh_parents'];
$refresh_element = drupal_array_get_nested_value($complete_form, $refresh_parents);
// The HTML ID that AJAX expects was also stored in a property on the
// element, so use that information to insert the wrapper <div> here.
$id = $element['#views_ui_ajax_data']['wrapper'];
$refresh_element += array(
'#prefix' => '',
'#suffix' => '',
);
David Rothstein
committed
$refresh_element['#prefix'] = '<div id="' . $id . '" class="views-ui-ajax-wrapper">' . $refresh_element['#prefix'];
David Rothstein
committed
$refresh_element['#suffix'] .= '</div>';
David Rothstein
committed
// Copy the element that needs to be refreshed back into the form, with our
// modifications to it.
drupal_array_set_nested_value($complete_form, $refresh_parents, $refresh_element);
David Rothstein
committed
return $element;
}
David Rothstein
committed
* Updates a part of the add view form via AJAX.
*
* @return
* The part of the form that has changed.
*/
David Rothstein
committed
function views_ui_ajax_update_form($form, $form_state) {
// The region that needs to be updated was stored in a property of the
// triggering element by views_ui_add_ajax_trigger(), so all we have to do is
// retrieve that here.
return drupal_array_get_nested_value($form, $form_state['triggering_element']['#views_ui_ajax_data']['refresh_parents']);
David Rothstein
committed
* Non-Javascript fallback for updating the add view form.
David Rothstein
committed
function views_ui_nojs_submit($form, &$form_state) {
$form_state['rebuild'] = TRUE;
/**
* Validate the add view form.
*/
function views_ui_wizard_form_validate($form, &$form_state) {
$w = views_ui_get_wizard($form_state['values']['show']['wizard_key']);
$get_instance = $w['get_instance'];
$form_state['wizard_instance'] = $get_instance($w);
$errors = $form_state['wizard_instance']->validate($form, $form_state);
foreach ($errors as $name => $message) {
form_set_error($name, $message);
function views_ui_add_form_save_submit($form, &$form_state) {
try {
$view = $form_state['wizard_instance']->create_view($form, $form_state);
catch (ViewsWizardException $e) {
drupal_set_message($e->getMessage(), 'error');
$form_state['redirect'] = 'admin/structure/views';
}
$view->save();
menu_rebuild();
cache_clear_all('*', 'cache_views', TRUE);
cache_clear_all();
$form_state['redirect'] = 'admin/structure/views';
if (!empty($view->display['page'])) {
$display = $view->display['page'];
if ($display->handler->has_path()) {
$one_path = $display->handler->get_option('path');
if (strpos($one_path, '%') === FALSE) {
$form_state['redirect'] = $one_path; // PATH TO THE VIEW IF IT HAS ONE
drupal_set_message(t('Your view was saved. You may edit it from the list below.'));
}
* Process the add view form, 'continue'.
function views_ui_add_form_store_edit_submit($form, &$form_state) {
try {
$view = $form_state['wizard_instance']->create_view($form, $form_state);
}
catch (ViewsWizardException $e) {
drupal_set_message($e->getMessage(), 'error');
$form_state['redirect'] = 'admin/structure/views';
}
// Just cache it temporarily to edit it.
$form_state['redirect'] = 'admin/structure/views/view/' . $view->name;
}
/**
* Cancel the add view form.
*/
function views_ui_add_form_cancel_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/structure/views';
}
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
/**
* Form element validation handler for a taxonomy autocomplete field.
*
* This allows a taxonomy autocomplete field to be validated outside the
* standard Field API workflow, without passing in a complete field widget.
* Instead, all that is required is that $element['#field_name'] contain the
* name of the taxonomy autocomplete field that is being validated.
*
* This function is currently not used for validation directly, although it
* could be. Instead, it is only used to store the term IDs and vocabulary name
* in the element value, based on the tags that the user typed in.
*
* @see taxonomy_autocomplete_validate()
*/
function views_ui_taxonomy_autocomplete_validate($element, &$form_state) {
$value = array();
if ($tags = $element['#value']) {
// Get the machine names of the vocabularies we will search, keyed by the
// vocabulary IDs.
$field = field_info_field($element['#field_name']);
$vocabularies = array();
if (!empty($field['settings']['allowed_values'])) {
foreach ($field['settings']['allowed_values'] as $tree) {
if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
$vocabularies[$vocabulary->vid] = $tree['vocabulary'];
}
}
}
// Store the term ID of each (valid) tag that the user typed.
$typed_terms = drupal_explode_tags($tags);
foreach ($typed_terms as $typed_term) {
if ($terms = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => array_keys($vocabularies)))) {
$term = array_pop($terms);
$value['tids'][] = $term->tid;
}
}
// Store the term IDs along with the name of the vocabulary. Currently
// Views (as well as the Field UI) assumes that there will only be one
// vocabulary, although technically the API allows there to be more than
// one.
if (!empty($value['tids'])) {
$value['tids'] = array_unique($value['tids']);
$value['vocabulary'] = array_pop($vocabularies);
}
}
form_set_value($element, $value, $form_state);
}
/**
* Theme function; returns basic administrative information about a view.
*
* TODO: template + preprocess
*/
function theme_views_ui_view_info($variables) {
$view = $variables['view'];
$title = $view->get_human_name();
Earl Miles
committed
$displays = _views_ui_get_displays_list($view);
$displays = empty($displays) ? t('None') : format_plural(count($displays), 'Display', 'Displays') . ': ' . '<em>' . implode(', ', $displays) . '</em>';
switch ($view->type) {
case t('Default'):
default:
$type = t('In code');
break;
case t('Normal'):
$type = t('In database');
break;
case t('Overridden'):
$type = t('Database overriding code');
}
$output = '';
$output .= '<div class="views-ui-view-title">' . $title . "</div>\n";
$output .= '<div class="views-ui-view-displays">' . $displays . "</div>\n";
$output .= '<div class="views-ui-view-storage">' . $type . "</div>\n";
$output .= '<div class="views-ui-view-base">' . t('Type') . ': ' . $variables['base']. "</div>\n";
return $output;
}
/**
* Page to delete a view.
*/
function views_ui_break_lock_confirm($form, &$form_state, $view) {
$form_state['view'] = &$view;
$form = array();
if (empty($view->locked)) {
return t('There is no lock on view %view to break.', array('%name' => $view->name));
}
Katherine Senzee
committed
$cancel = 'admin/structure/views/view/' . $view->name . '/edit';
if (!empty($_REQUEST['cancel'])) {
$cancel = $_REQUEST['cancel'];
}
$account = user_load($view->locked->uid);
return confirm_form($form,
t('Are you sure you want to break the lock on view %name?',
array('%name' => $view->name)),
$cancel,
t('By breaking this lock, any unsaved changes made by !user will be lost!', array('!user' => theme('username', array('account' => $account)))),
t('Break lock'),
t('Cancel'));
}
/**
* Submit handler to break_lock a view.
*/
function views_ui_break_lock_confirm_submit(&$form, &$form_state) {
ctools_object_cache_clear_all('view', $form_state['view']->name);
Katherine Senzee
committed
$form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
drupal_set_message(t('The lock has been broken and you may now edit this view.'));
}
/**
* Page title callback for the Edit View page.
*/
function views_ui_edit_page_title($view) {
$bases = views_fetch_base_tables();
$name = $view->get_human_name();
if (isset($bases[$view->base_table])) {
$name .= ' (' . $bases[$view->base_table]['title'] . ')';
}
return $name;
* Form builder callback for editing a View.
*
* @todo Remove as many #prefix/#suffix lines as possible. Use #theme_wrappers
* instead.
*
* @todo Rename to views_ui_edit_view_form(). See that function for the "old"
* version.
function views_ui_edit_form($form, &$form_state, $view, $display_id = NULL) {
// Do not allow the form to be cached, because $form_state['view'] can become
// stale between page requests.
// @see views_ui_ajax_get_form() for how this affects #ajax.
// @todo To remove this and allow the form to be cacheable:
// - Change $form_state['view'] to $form_state['temporary']['view'].
// - Add a #process function to initialize $form_state['temporary']['view']
// on cached form submissions.
// - Update ctools_include() to support cached forms, or else use
// form_load_include().
Alex Bronstein
committed
$form_state['no_cache'] = TRUE;
Bojan Živanović
committed
ctools_include('dependent');
$form['#attached']['js'][] = ctools_attach_js('dependent');
$form['#attached']['js'][] = ctools_attach_js('collapsible-div');
Bojan Živanović
committed
$form['#tree'] = TRUE;
// @todo When more functionality is added to this form, cloning here may be
// too soon. But some of what we do with $view later in this function
// results in making it unserializable due to PDO limitations.
$form_state['view'] = clone($view);
$form['#attached']['library'][] = array('system', 'ui.tabs');
$form['#attached']['library'][] = array('system', 'ui.dialog');
$form['#attached']['library'][] = array('system', 'drupal.ajax');
$form['#attached']['library'][] = array('system', 'jquery.form');
Katherine Senzee
committed
// TODO: This should be getting added to the page when an ajax popup calls
// for it, instead of having to add it manually here.
$form['#attached']['js'][] = 'misc/tabledrag.js';
$form['#attached']['css'] = views_ui_get_admin_css();
$module_path = drupal_get_path('module', 'views_ui');
Jesse Beach
committed
$form['#attached']['js'][] = $module_path . '/js/views-admin.js';
$form['#attached']['js'][] = array(
'data' => array('views' => array('ajax' => array(
'id' => '#views-ajax-body',
'popup' => '#views-ajax-popup',
'defaultForm' => views_ui_get_default_ajax_message(),
))),
'type' => 'setting',
);
$form += array(
'#prefix' => '',
'#suffix' => '',
$form['#prefix'] = $form['#prefix'] . '<div class="views-edit-view views-admin clearfix">';
$form['#suffix'] = '</div>' . $form['#suffix'];
if (isset($view->locked) && is_object($view->locked)) {
'#theme_wrappers' => array('container'),
'#attributes' => array('class' => array('view-locked', 'messages', 'warning')),
'#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'))),
Earl Miles
committed
if (isset($view->vid) && $view->vid == 'new') {
Alex Bronstein
committed
$message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard the view.');
Earl Miles
committed
}
else {
Alex Bronstein
committed
$message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard your changes.');
Earl Miles
committed
}
Earl Miles
committed
$form['changed'] = array(
'#theme_wrappers' => array('container'),
'#attributes' => array('class' => array('view-changed', 'messages', 'warning')),
'#markup' => $message,
);
if (empty($view->changed)) {
$form['changed']['#attributes']['class'][] = 'js-hide';
David Rothstein
committed
}
David Rothstein
committed
$form['help_text'] = array(
Jesse Beach
committed
'#prefix' => '<div>',
'#suffix' => '</div>',
'#markup' => t('Modify the display(s) of your view below or add new displays.'),
David Rothstein
committed
);
$form['actions'] = array(
'#type' => 'actions',
'#weight' => 0,
);
if (empty($view->changed)) {
$form['actions']['#attributes'] = array(
'class' => array(
'js-hide',
),
);
}
$form['actions']['save'] = array(
'#type' => 'submit',
'#value' => t('Save'),
// Taken from the "old" UI. @TODO: Review and rename.
'#validate' => array('views_ui_edit_view_form_validate'),
'#submit' => array('views_ui_edit_view_form_submit'),
);
$form['actions']['cancel'] = array(
'#type' => 'submit',
Alex Bronstein
committed
'#value' => t('Cancel'),
'#submit' => array('views_ui_edit_view_form_cancel'),
);
David Rothstein
committed
'#prefix' => '<h1 class="unit-title clearfix">' . t('Displays') . '</h1>' . "\n" . '<div class="views-displays">',
'#suffix' => '</div>',
);
$form['displays']['top']['#theme_wrappers'] = array('views_container');
$form['displays']['top']['#attributes']['class'] = array('views-display-top');
Jesse Beach
committed
// Extra actions for the display
$form['displays']['top']['extra_actions'] = array(
'#theme' => 'links__ctools_dropbutton',
Jesse Beach
committed
'#attributes' => array(
'id' => 'views-display-extra-actions',
'class' => array(
'horizontal', 'right', 'links', 'actions',
),