2009-05-17 11:16:51 +00:00
< ? php
/**
* @ file
* Provides the Views ' administrative interface .
*/
2012-12-17 21:54:13 +00:00
use Drupal\Component\Utility\NestedArray ;
2014-03-17 18:39:20 +00:00
use Drupal\Component\Utility\Tags ;
2014-07-31 00:50:42 +00:00
use Drupal\Core\Form\FormStateInterface ;
2012-11-01 23:30:09 +00:00
use Drupal\views\ViewExecutable ;
2013-03-13 21:29:08 +00:00
use Drupal\views\Views ;
2012-05-15 16:07:21 +00:00
2009-05-17 11:16:51 +00:00
/**
* Converts a form element in the add view wizard to be AJAX - enabled .
*
* 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 .
*/
function views_ui_add_ajax_trigger ( & $wrapping_element , $trigger_key , $refresh_parents ) {
$seen_ids = & drupal_static ( __FUNCTION__ . ':seen_ids' , array ());
$seen_buttons = & drupal_static ( __FUNCTION__ . ':seen_buttons' , array ());
// 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 (
'#type' => 'submit' ,
// 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 ],
2012-08-12 11:37:59 +00:00
));
2009-05-17 11:16:51 +00:00
}
// 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' ]]),
);
$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 ;
}
/**
* Processes a non - JavaScript fallback submit button to limit its validation errors .
*/
2014-07-31 00:50:42 +00:00
function views_ui_add_limited_validation ( $element , FormStateInterface $form_state ) {
2009-05-17 11:16:51 +00:00
// 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' ];
2012-12-17 21:54:13 +00:00
$ajax_triggering_element = NestedArray :: getValue ( $form_state [ 'complete_form' ], $array_parents );
2009-05-17 11:16:51 +00:00
// 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 ;
}
/**
* After - build function that adds a wrapper to a form region ( for AJAX refreshes ) .
*
* 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 .
*/
2014-07-31 00:50:42 +00:00
function views_ui_add_ajax_wrapper ( $element , FormStateInterface $form_state ) {
2009-05-17 11:16:51 +00:00
// 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.
2012-05-15 19:49:20 +00:00
$complete_form = & $form_state [ 'complete_form' ];
2009-05-17 11:16:51 +00:00
$refresh_parents = $element [ '#views_ui_ajax_data' ][ 'refresh_parents' ];
2012-12-17 21:54:13 +00:00
$refresh_element = NestedArray :: getValue ( $complete_form , $refresh_parents );
2009-05-17 11:16:51 +00:00
// 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' => '' ,
);
$refresh_element [ '#prefix' ] = '<div id="' . $id . '" class="views-ui-ajax-wrapper">' . $refresh_element [ '#prefix' ];
$refresh_element [ '#suffix' ] .= '</div>' ;
// Copy the element that needs to be refreshed back into the form, with our
// modifications to it.
2012-12-17 21:54:13 +00:00
NestedArray :: setValue ( $complete_form , $refresh_parents , $refresh_element );
2009-05-17 11:16:51 +00:00
}
return $element ;
}
/**
* Updates a part of the add view form via AJAX .
*
* @ return
* The part of the form that has changed .
*/
2014-08-04 11:21:15 +00:00
function views_ui_ajax_update_form ( $form , FormStateInterface $form_state ) {
2009-05-17 11:16:51 +00:00
// 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.
2012-12-17 21:54:13 +00:00
return NestedArray :: getValue ( $form , $form_state [ 'triggering_element' ][ '#views_ui_ajax_data' ][ 'refresh_parents' ]);
2009-05-17 11:16:51 +00:00
}
/**
* Non - Javascript fallback for updating the add view form .
*/
2014-07-31 00:50:42 +00:00
function views_ui_nojs_submit ( $form , FormStateInterface $form_state ) {
2009-05-17 11:16:51 +00:00
$form_state [ 'rebuild' ] = TRUE ;
}
/**
* 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 ()
*/
2014-07-31 00:50:42 +00:00
function views_ui_taxonomy_autocomplete_validate ( $element , FormStateInterface $form_state ) {
2009-05-17 11:16:51 +00:00
$value = array ();
if ( $tags = $element [ '#value' ]) {
// Get the machine names of the vocabularies we will search, keyed by the
// vocabulary IDs.
2014-05-15 17:26:18 +00:00
$field_storage_definitions = \Drupal :: entityManager () -> getFieldStorageDefinitions ( $element [ '#entity_type' ]);
2014-07-18 18:56:27 +00:00
$field_storage = $field_storage_definitions [ $element [ '#field_name' ]];
2009-05-17 11:16:51 +00:00
$vocabularies = array ();
2014-07-18 18:56:27 +00:00
$allowed_values = $field_storage -> getSetting ( 'allowed_values' );
2013-10-04 07:55:32 +00:00
if ( ! empty ( $allowed_values )) {
foreach ( $allowed_values as $tree ) {
2012-12-19 17:06:30 +00:00
if ( $vocabulary = entity_load ( 'taxonomy_vocabulary' , $tree [ 'vocabulary' ])) {
$vocabularies [ $vocabulary -> id ()] = $tree [ 'vocabulary' ];
2009-05-17 11:16:51 +00:00
}
}
}
// Store the term ID of each (valid) tag that the user typed.
2014-03-17 18:39:20 +00:00
$typed_terms = Tags :: explode ( $tags );
2009-05-17 11:16:51 +00:00
foreach ( $typed_terms as $typed_term ) {
2012-08-22 11:43:56 +00:00
if ( $terms = entity_load_multiple_by_properties ( 'taxonomy_term' , array ( 'name' => trim ( $typed_term ), 'vid' => array_keys ( $vocabularies )))) {
2009-05-17 11:16:51 +00:00
$term = array_pop ( $terms );
2013-04-26 16:10:49 +00:00
$value [ 'tids' ][] = $term -> id ();
2009-05-17 11:16:51 +00:00
}
}
// 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 );
}
/**
* Add a < select > dropdown for a given section , allowing the user to
* change whether this info is stored on the default display or on
* the current display .
*/
2014-07-31 00:50:42 +00:00
function views_ui_standard_display_dropdown ( & $form , FormStateInterface $form_state , $section ) {
2013-11-11 18:59:24 +00:00
$view = $form_state [ 'view' ];
2009-05-17 11:16:51 +00:00
$display_id = $form_state [ 'display_id' ];
2013-07-20 19:32:31 +00:00
$executable = $view -> getExecutable ();
2012-11-01 23:30:09 +00:00
$displays = $executable -> displayHandlers ;
$current_display = $executable -> display_handler ;
2009-05-17 11:16:51 +00:00
2013-01-23 01:23:19 +00:00
// @todo Move this to a separate function if it's needed on any forms that
2009-05-17 11:16:51 +00:00
// don't have the display dropdown.
2013-01-23 01:23:19 +00:00
$form [ 'override' ] = array (
2013-10-16 19:18:57 +00:00
'#prefix' => '<div class="views-override clearfix container-inline" data-drupal-views-offset="top">' ,
2013-01-23 01:23:19 +00:00
'#suffix' => '</div>' ,
'#weight' => - 1000 ,
'#tree' => TRUE ,
);
// Add the "2 of 3" progress indicator.
2012-09-26 20:26:34 +00:00
if ( $form_progress = $view -> getFormProgress ()) {
2009-05-17 11:16:51 +00:00
$form [ 'progress' ][ '#markup' ] = '<div id="views-progress-indicator">' . t ( '@current of @total' , array ( '@current' => $form_progress [ 'current' ], '@total' => $form_progress [ 'total' ])) . '</div>' ;
$form [ 'progress' ][ '#weight' ] = - 1001 ;
}
2012-09-21 05:53:42 +00:00
if ( $current_display -> isDefaultDisplay ()) {
2009-05-17 11:16:51 +00:00
return ;
}
// Determine whether any other displays have overrides for this section.
$section_overrides = FALSE ;
2012-09-21 05:53:42 +00:00
$section_defaulted = $current_display -> isDefaulted ( $section );
2009-05-17 11:16:51 +00:00
foreach ( $displays as $id => $display ) {
if ( $id === 'default' || $id === $display_id ) {
continue ;
}
2012-09-21 05:53:42 +00:00
if ( $display && ! $display -> isDefaulted ( $section )) {
2009-05-17 11:16:51 +00:00
$section_overrides = TRUE ;
}
}
$display_dropdown [ 'default' ] = ( $section_overrides ? t ( 'All displays (except overridden)' ) : t ( 'All displays' ));
2012-09-21 05:53:42 +00:00
$display_dropdown [ $display_id ] = t ( 'This @display_type (override)' , array ( '@display_type' => $current_display -> getPluginId ()));
2009-05-17 11:16:51 +00:00
// Only display the revert option if we are in a overridden section.
if ( ! $section_defaulted ) {
$display_dropdown [ 'default_revert' ] = t ( 'Revert to default' );
}
$form [ 'override' ][ 'dropdown' ] = array (
'#type' => 'select' ,
'#title' => t ( 'For' ), // @TODO: Translators may need more context than this.
'#options' => $display_dropdown ,
);
2012-09-21 05:53:42 +00:00
if ( $current_display -> isDefaulted ( $section )) {
2009-05-17 11:16:51 +00:00
$form [ 'override' ][ 'dropdown' ][ '#default_value' ] = 'defaults' ;
}
else {
$form [ 'override' ][ 'dropdown' ][ '#default_value' ] = $display_id ;
}
}
/**
2013-10-16 19:18:57 +00:00
* Create the menu path for one of our standard AJAX forms based upon known
2009-05-17 11:16:51 +00:00
* information about the form .
*/
2014-08-04 11:21:15 +00:00
function views_ui_build_form_path ( FormStateInterface $form_state ) {
2009-05-17 11:16:51 +00:00
$ajax = empty ( $form_state [ 'ajax' ]) ? 'nojs' : 'ajax' ;
2013-01-19 05:10:42 +00:00
$name = $form_state [ 'view' ] -> id ();
2013-10-16 19:18:57 +00:00
$path = " admin/structure/views/ $ajax / $form_state[form_key] / $name / $form_state[display_id] " ;
2013-03-03 22:01:21 +00:00
if ( isset ( $form_state [ 'type' ])) {
2013-10-16 19:18:57 +00:00
$path .= '/' . $form_state [ 'type' ];
2009-05-17 11:16:51 +00:00
}
2013-03-03 22:01:21 +00:00
if ( isset ( $form_state [ 'id' ])) {
2013-10-16 19:18:57 +00:00
$path .= '/' . $form_state [ 'id' ];
2009-05-17 11:16:51 +00:00
}
2013-10-16 19:18:57 +00:00
return $path ;
2009-05-17 11:16:51 +00:00
}
/**
* #process callback for a button; determines if a button is the form's triggering element.
*
* The Form API has logic to determine the form ' s triggering element based on
2013-12-05 18:02:36 +00:00
* the data in POST . However , it only checks buttons based on a single #value
2009-05-17 11:16:51 +00:00
* per button . This function may be added to a button ' s #process callbacks to
* extend button click detection to support multiple #values per button. If the
2013-12-05 18:02:36 +00:00
* data in POST matches any value in the button ' s #values array, then the
2009-05-17 11:16:51 +00:00
* button is detected as having been clicked . This can be used when the value
* ( label ) of the same logical button may be different based on context ( e . g . ,
* " Apply " vs . " Apply and continue " ) .
*
* @ see _form_builder_handle_input_element ()
* @ see _form_button_was_clicked ()
*/
2014-07-31 00:50:42 +00:00
function views_ui_form_button_was_clicked ( $element , FormStateInterface $form_state ) {
2009-05-17 11:16:51 +00:00
$process_input = empty ( $element [ '#disabled' ]) && ( $form_state [ 'programmed' ] || ( $form_state [ 'process_input' ] && ( ! isset ( $element [ '#access' ]) || $element [ '#access' ])));
2012-11-21 19:37:44 +00:00
if ( $process_input && ! isset ( $form_state [ 'triggering_element' ]) && ! empty ( $element [ '#is_button' ]) && isset ( $form_state [ 'input' ][ $element [ '#name' ]]) && isset ( $element [ '#values' ]) && in_array ( $form_state [ 'input' ][ $element [ '#name' ]], $element [ '#values' ], TRUE )) {
2009-05-17 11:16:51 +00:00
$form_state [ 'triggering_element' ] = $element ;
}
return $element ;
}