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) {
Daniel Wehner
committed
// Try to find the admin css file for non-core themes.
if (!in_array($theme_key, array('garland', 'seven', 'bartik'))) {
$theme_path = drupal_get_path('theme', $theme_key);
// First search in the css directory, then in the root folder of the theme.
if (file_exists($theme_path . "/css/views-admin.$theme_key.css")) {
$list[$theme_path . "/css/views-admin.$theme_key.css"] = array(
'group' => CSS_THEME,
);
}
else if (file_exists($theme_path . "/views-admin.$theme_key.css")) {
$list[$theme_path . "/views-admin.$theme_key.css"] = array(
'group' => CSS_THEME,
);
}
}
else {
$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'))));
* 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');
$combined = $show_query && $show_stats;
Katherine Senzee
committed
$rows = array('query' => array(), 'statistics' => array());
$output = '';
$errors = $view->validate();
if ($errors === TRUE) {
$view->views_ui_context = TRUE;
// AJAX happens via $_POST but everything expects exposed data to
// be in GET. Copy stuff but remove ajax-framework specific keys.
// If we're clicking on links in a preview, though, we could actually
// still have some in $_GET, so we use $_REQUEST to ensure we get it all.
$exposed_input = $_REQUEST;
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) {
if (isset($exposed_input[$key])) {
unset($exposed_input[$key]);
}
}
$view->set_exposed_input($exposed_input);
Daniel Wehner
committed
if (!$view->set_display($display_id)) {
return t('Invalid display id @display', array('@display' => $display_id));
}
$view->set_arguments($args);
// Store the current view URL for later use:
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.
$original_path = current_path();
$_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();
Daniel Wehner
committed
$preview = $view->preview($display_id, $args);
Alex Bronstein
committed
views_ui_contextual_links_suppress_pop();
// Reset variables.
unset($view->override_path);
$_GET['q'] = $original_path;
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') {
Earl Miles
committed
$quoted = $query->getArguments();
Katherine Senzee
committed
$connection = Database::getConnection();
foreach ($quoted as $key => $val) {
Daniel Wehner
committed
if (is_array($val)) {
$quoted[$key] = implode(', ', array_map(array($connection, 'quote'), $val));
}
else {
$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;
}
/**
* 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',
Daniel Wehner
committed
'#maxlength' => 128,
'#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 => $wizard) {
$options[$key] = $wizard['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(),
* Helper form element validator: integer.
*
* @see _element_validate_integer_positive
*
* The problem with this is that the function is private so it's not guaranteed
* that it might not be renamed/changed. In the future field.module or something else
* should provide a public validate function.
*/
function views_element_validate_integer($element, &$form_state) {
$value = $element['#value'];
if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) {
form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title'])));
}
}
David Rothstein
committed
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
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
* 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
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
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
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
* 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
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
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
// 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
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
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) {
$wizard = views_ui_get_wizard($form_state['values']['show']['wizard_key']);
$form_state['wizard'] = $wizard;
$get_instance = $wizard['get_instance'];
$form_state['wizard_instance'] = $get_instance($wizard);
$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';
}
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
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
/**
* 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['#markup'] = t('There is no lock on view %view to break.', array('%name' => $view->name));
return $form;
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.'));
}
/**
* Helper function to return the used display_id for the edit page
*
* This function handles access to the display.
*/
function views_ui_edit_page_display($view, $display_id) {
// Determine the displays available for editing.
Daniel Wehner
committed
if ($tabs = views_ui_edit_page_display_tabs($view, $display_id)) {
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
// If a display isn't specified, use the first one.
if (empty($display_id)) {
foreach ($tabs as $id => $tab) {
if (!isset($tab['#access']) || $tab['#access']) {
$display_id = $id;
break;
}
}
}
// If a display is specified, but we don't have access to it, return
// an access denied page.
if ($display_id && (!isset($tabs[$display_id]) || (isset($tabs[$display_id]['#access']) && !$tabs[$display_id]['#access']))) {
return MENU_ACCESS_DENIED;
}
return $display_id;
}
elseif ($display_id) {
return MENU_ACCESS_DENIED;
}
else {
$display_id = NULL;
}
return $display_id;
}
/**
* Page callback for the Edit View page.
*/
function views_ui_edit_page($view, $display_id = NULL) {
$display_id = views_ui_edit_page_display($view, $display_id);
if (!in_array($display_id, array(MENU_ACCESS_DENIED, MENU_NOT_FOUND))) {
$build = array();
$build['edit_form'] = drupal_get_form('views_ui_edit_form', $view, $display_id);
Daniel Wehner
committed
$build['preview'] = views_ui_build_preview($view, $display_id, FALSE);
}
else {
$build = $display_id;
}
return $build;
}
Daniel Wehner
committed
function views_ui_build_preview($view, $display_id, $render = TRUE) {
if (isset($_POST['ajax_html_ids'])) {
unset($_POST['ajax_html_ids']);
}
$build = array(
'#theme_wrappers' => array('container'),
'#attributes' => array('id' => 'views-preview-wrapper', 'class' => 'views-admin clearfix'),
);
$form_state = array('build_info' => array('args' => array($view, $display_id)));
$build['controls'] = drupal_build_form('views_ui_preview_form', $form_state);
$args = array();
if (!empty($form_state['values']['view_args'])) {
$args = explode('/', $form_state['values']['view_args']);
}
$build['preview'] = array(
'#theme_wrappers' => array('container'),
'#attributes' => array('id' => 'views-live-preview'),
Daniel Wehner
committed
'#markup' => $render ? views_ui_preview($view->clone_view(), $display_id, $args) : '',
);
return $build;
}
* 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;
Earl Miles
committed
if ($display_id) {
Daniel Wehner
committed
if (!$view->set_display($display_id)) {
$form['#markup'] = t('Invalid display id @display', array('@display' => $display_id));
return $form;
}
Earl Miles
committed
$view->fix_missing_relationships();
}
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';