/** * Provides markup for associating a tray trigger with a tray element. * * A tray is a responsive container that wraps renderable content. Trays * present content well on small and large screens alike. * * @param array $element * A renderable array. * * @return array * A renderable array. */ public static function preRenderToolbarItem($element) { // Assign each item a unique ID. $id = Html::getUniqueId('toolbar-item'); // Provide attributes for a toolbar item. $attributes = array('id' => $id); // If tray content is present, markup the tray and its associated trigger. if (!empty($element['tray'])) { // Provide attributes necessary for trays. $attributes += array('data-toolbar-tray' => $id . '-tray', 'aria-owns' => $id, 'role' => 'button', 'aria-pressed' => 'false'); // Merge in module-provided attributes. $element['tab'] += array('#attributes' => array()); $element['tab']['#attributes'] += $attributes; $element['tab']['#attributes']['class'][] = 'trigger'; // Provide attributes for the tray theme wrapper. $attributes = array('id' => $id . '-tray', 'data-toolbar-tray' => $id . '-tray', 'aria-owned-by' => $id); // Merge in module-provided attributes. if (!isset($element['tray']['#wrapper_attributes'])) { $element['tray']['#wrapper_attributes'] = array(); } $element['tray']['#wrapper_attributes'] += $attributes; $element['tray']['#wrapper_attributes']['class'][] = 'toolbar-tray'; } $element['tab']['#attributes']['class'][] = 'toolbar-item'; return $element; }
/** * Pre-render callback: Renders a link into #markup. * * Doing so during pre_render gives modules a chance to alter the link parts. * * @param array $element * A structured array whose keys form the arguments to _l(): * - #title: The link text to pass as argument to _l(). * - #url: The URL info either pointing to a route or a non routed path. * - #options: (optional) An array of options to pass to _l() or the link * generator. * * @return array * The passed-in element containing a rendered link in '#markup'. */ public static function preRenderLink($element) { // By default, link options to pass to _l() are normally set in #options. $element += array('#options' => array()); // However, within the scope of renderable elements, #attributes is a valid // way to specify attributes, too. Take them into account, but do not override // attributes from #options. if (isset($element['#attributes'])) { $element['#options'] += array('attributes' => array()); $element['#options']['attributes'] += $element['#attributes']; } // This #pre_render callback can be invoked from inside or outside of a Form // API context, and depending on that, a HTML ID may be already set in // different locations. #options should have precedence over Form API's #id. // #attributes have been taken over into #options above already. if (isset($element['#options']['attributes']['id'])) { $element['#id'] = $element['#options']['attributes']['id']; } elseif (isset($element['#id'])) { $element['#options']['attributes']['id'] = $element['#id']; } // Conditionally invoke self::preRenderAjaxForm(), if #ajax is set. if (isset($element['#ajax']) && !isset($element['#ajax_processed'])) { // If no HTML ID was found above, automatically create one. if (!isset($element['#id'])) { $element['#id'] = $element['#options']['attributes']['id'] = HtmlUtility::getUniqueId('ajax-link'); } $element = static::preRenderAjaxForm($element); } if (!empty($element['#url'])) { $options = NestedArray::mergeDeep($element['#url']->getOptions(), $element['#options']); $element['#markup'] = \Drupal::l($element['#title'], $element['#url']->setOptions($options)); } return $element; }
/** * {@inheritdoc} */ protected function preprocessVariables(Variables $variables, $hook, array $info) { // Retrieve the ID, generating one if needed. $id = $variables->getAttribute('id', Html::getUniqueId($variables->offsetGet('id', 'bootstrap-carousel'))); unset($variables['id']); // Build slides. foreach ($variables->slides as $key => &$slide) { if (!isset($slide['attributes'])) { $slide['attributes'] = []; } $slide['attributes'] = new Attribute($slide['attributes']); } // Build controls. if ($variables->controls) { $left_icon = Bootstrap::glyphicon('chevron-left'); $right_icon = Bootstrap::glyphicon('chevron-right'); $url = Url::fromUserInput("#{$id}"); $variables->controls = ['left' => ['#type' => 'link', '#title' => new FormattableMarkup(Element::create($left_icon)->render() . '<span class="sr-only">@text</span>', ['@text' => t('Previous')]), '#url' => $url, '#attributes' => ['class' => ['left', 'carousel-control'], 'role' => 'button', 'data-slide' => 'prev']], 'right' => ['#type' => 'link', '#title' => new FormattableMarkup(Element::create($right_icon)->render() . '<span class="sr-only">@text</span>', ['@text' => t('Next')]), '#url' => $url, '#attributes' => ['class' => ['right', 'carousel-control'], 'role' => 'button', 'data-slide' => 'next']]]; } // Build indicators. if ($variables->indicators) { $variables->indicators = ['#theme' => 'item_list__bootstrap_carousel_indicators', '#list_type' => 'ol', '#items' => array_keys($variables->slides), '#target' => "#{$id}", '#start_index' => $variables->start_index]; } // Ensure all attributes are proper objects. $this->preprocessAttributes($variables, $hook, $info); }
/** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); $form['#title'] = $this->t('Edit subqueue %label', ['%label' => $this->entity->label()]); // Since the form has ajax buttons, the $wrapper_id will change each time // one of those buttons is clicked. Therefore the whole form has to be // replaced, otherwise the buttons will have the old $wrapper_id and will // only work on the first click. if ($form_state->has('subqueue_form_wrapper_id')) { $wrapper_id = $form_state->get('subqueue_form_wrapper_id'); } else { $wrapper_id = Html::getUniqueId($this->getFormId() . '-wrapper'); } $form_state->set('subqueue_form_wrapper_id', $wrapper_id); $form['#prefix'] = '<div id="' . $wrapper_id . '">'; $form['#suffix'] = '</div>'; // @todo Consider creating a 'Machine name' field widget. $form['name'] = [ '#type' => 'machine_name', '#default_value' => $this->entity->id(), '#machine_name' => array( 'exists' => '\Drupal\entityqueue\Entity\EntitySubqueue::load', 'source' => ['title', 'widget', 0, 'value'], ), '#disabled' => !$this->entity->isNew(), '#weight' => -5, '#access' => !$this->entity->getQueue()->getHandlerPlugin()->hasAutomatedSubqueues(), ]; return $form; }
/** * {@inheritdoc} */ protected function preprocessVariables(Variables $variables, $hook, array $info) { // Retrieve the ID, generating one if needed. $id = $variables->getAttribute('id', Html::getUniqueId($variables->offsetGet('id', 'bootstrap-panel'))); unset($variables['id']); // Handle collapsible state. if ($variables['heading'] && $variables['collapsible']) { // Retrieve the body ID attribute. if ($body_id = $variables->getAttribute('id', "{$id}--content", 'body_attributes')) { // Ensure the target is set. if ($variables['target'] = $variables->offsetGet('target', "#{$body_id}")) { // Set additional necessary attributes to the heading. $variables->setAttributes(['aria-controls' => preg_replace('/^#/', '', $variables['target']), 'aria-expanded' => !$variables['collapsed'] ? 'true' : 'false', 'aria-pressed' => !$variables['collapsed'] ? 'true' : 'false', 'data-toggle' => 'collapse', 'role' => 'button'], 'heading_attributes'); } } } // Ensure there is a valid panel state. if (!$variables->offsetGet('panel_type')) { $variables->offsetSet('panel_type', 'default'); } // Convert the description variable. $this->preprocessDescription($variables, $hook, $info); // Ensure all attributes are proper objects. $this->preprocessAttributes($variables, $hook, $info); }
/** * Submit Mobile Blocks settings. * @param $values * @param $theme * @param $generated_files_path */ function at_core_submit_mobile_blocks($values, $theme, $generated_files_path) { $mobile_blocks_css = array(); // TODO entityManager() is deprecated, but how to replace? $theme_blocks = \Drupal::entityManager()->getStorage('block')->loadByProperties(['theme' => $theme]); if (!empty($theme_blocks)) { foreach ($theme_blocks as $block_key => $block_values) { $block_id = $block_values->id(); if (isset($values['settings_mobile_block_show_' . $block_id]) && $values['settings_mobile_block_show_' . $block_id] == 1) { $block_selector = '#' . Html::getUniqueId('block-' . $block_id); $mobile_blocks_css[] = $block_selector . ' {display:none}' . "\n"; $mobile_blocks_css[] = '.is-mobile ' . $block_selector . ' {display:block}' . "\n"; } if (isset($values['settings_mobile_block_hide_' . $block_id]) && $values['settings_mobile_block_hide_' . $block_id] == 1) { $block_selector = '#' . Html::getUniqueId('block-' . $block_id); $mobile_blocks_css[] = '.is-mobile ' . $block_selector . ' {display:none}' . "\n"; $mobile_blocks_css[] = $block_selector . ' {display:block}' . "\n"; } } } if (!empty($mobile_blocks_css)) { $file_name = 'mobile-blocks.css'; $filepath = $generated_files_path . '/' . $file_name; file_unmanaged_save_data($mobile_blocks_css, $filepath, FILE_EXISTS_REPLACE); } }
/** * @file * Save Breadcrumb CSS to file */ function at_core_submit_mobile_blocks($values, $theme, $generated_files_path) { $mobile_blocks_css = array(); $theme_blocks = entity_load_multiple_by_properties('block', ['theme' => $theme]); if (!empty($theme_blocks)) { foreach ($theme_blocks as $block_key => $block_values) { $block_id = $block_values->id(); if (isset($values['settings_mobile_block_show_' . $block_id]) && $values['settings_mobile_block_show_' . $block_id] == 1) { $block_selector = '#' . Html::getUniqueId('block-' . $block_id); $mobile_blocks_css[] = $block_selector . ' {display:none}' . "\n"; $mobile_blocks_css[] = '.is-mobile ' . $block_selector . ' {display:block}' . "\n"; } if (isset($values['settings_mobile_block_hide_' . $block_id]) && $values['settings_mobile_block_hide_' . $block_id] == 1) { $block_selector = '#' . Html::getUniqueId('block-' . $block_id); $mobile_blocks_css[] = '.is-mobile ' . $block_selector . ' {display:none}' . "\n"; $mobile_blocks_css[] = $block_selector . ' {display:block}' . "\n"; } } } if (!empty($mobile_blocks_css)) { $file_name = 'mobile-blocks.css'; $filepath = $generated_files_path . '/' . $file_name; file_unmanaged_save_data($mobile_blocks_css, $filepath, FILE_EXISTS_REPLACE); } }
/** * {@inheritdoc} */ public function getFormId() { /* @var $transition WorkflowTransitionInterface */ $transition = $this->entity; $field_name = $transition->getFieldName(); /* @var $entity EntityInterface */ // Entity may be empty on VBO bulk form. // $entity = $transition->getTargetEntity(); // Compose Form Id from string + Entity Id + Field name. // Field ID contains entity_type, bundle, field_name. // The Form Id is unique, to allow for multiple forms per page. // $workflow_type_id = $transition->getWorkflowId(); // Field name contains implicit entity_type & bundle (since 1 field per entity) // $entity_type = $transition->getTargetEntityTypeId(); // $entity_id = $transition->getTargetEntityId();; // Emulate nodeForm convention. if ($transition->id()) { $suffix = 'edit_form'; } else { $suffix = 'form'; } $form_id = implode('_', array('workflow_transition', $field_name, $suffix)); $form_id = Html::getUniqueId($form_id); return $form_id; }
/** * Overrides \Drupal\views\Plugin\views\display\PathPluginBase::render(). */ public function render() { if (!empty($this->options['geolocation_field'])) { $geo_field = $this->options['geolocation_field']; $this->view->field[$geo_field]->options['exclude'] = TRUE; } else { // TODO: Throw some exception here, we're done. return []; } if (!empty($this->options['title_field'])) { $title_field = $this->options['title_field']; $this->view->field[$title_field]->options['exclude'] = TRUE; } $id = \Drupal\Component\Utility\Html::getUniqueId($this->pluginId); $build = ['#theme' => 'geolocation_common_map_display', '#id' => $id, '#attached' => ['library' => ['geolocation/geolocation.commonmap'], 'drupalSettings' => ['geolocation' => ['commonMap' => ['id' => $id]]]]]; foreach ($this->view->result as $row) { if (!empty($title_field)) { $title_field_handler = $this->view->field[$title_field]; $title_build = array('#theme' => $title_field_handler->themeFunctions(), '#view' => $title_field_handler->view, '#field' => $title_field_handler, '#row' => $row); } $geo_items = $this->view->field[$geo_field]->getItems($row); foreach ($geo_items as $delta => $item) { $geolocation = $item['raw']; $position = ['lat' => $geolocation->lat, 'lng' => $geolocation->lng]; $build['#locations'][] = ['#theme' => 'geolocation_common_map_location', '#content' => $this->view->rowPlugin->render($row), '#title' => empty($title_build) ? '' : $title_build, '#position' => $position]; } } $centre = NULL; foreach ($this->options['centre'] as $id => $option) { if (empty($option['enable'])) { continue; } switch ($id) { case 'fixed_value': $centre = ['lat' => (double) $option['settings']['latitude'], 'lng' => (double) $option['settings']['longitude']]; break; case preg_match('/proximity_filter_*/', $id) ? true : false: $filter_id = substr($id, 17); $handler = $this->displayHandler->getHandler('filter', $filter_id); if ($handler->value['lat'] && $handler->value['lng']) { $centre = ['lat' => (double) $handler->value['lat'], 'lng' => (double) $handler->value['lng']]; } break; case 'first_row': if (!empty($build['#locations'][0]['#position'])) { $centre = $build['#locations'][0]['#position']; } break; } if (!empty($centre['lat']) || !empty($centre['lng']) || !empty($centre['locate'])) { // We're done, no need for further options. break; } } if (!empty($centre)) { $build['#centre'] = $centre; } return $build; }
/** * Processes a container element. * * @param array $element * An associative array containing the properties and children of the * container. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. * * @return array * The processed element. */ public static function processContainer(&$element, FormStateInterface $form_state, &$complete_form) { // Generate the ID of the element if it's not explicitly given. if (!isset($element['#id'])) { $element['#id'] = HtmlUtility::getUniqueId(implode('-', $element['#parents']) . '-wrapper'); } return $element; }
/** * Returns a ID that is guaranteed uniqueness. * * @return string * A unique id to be used to generate aria attributes. */ public function getAriaId() { static $id; if (!isset($id)) { $id = Html::getUniqueId($this->get('id')); } return $id; }
/** * {@inheritdoc} */ public function preprocessVariables(Variables $variables) { // Ensure a unique ID, generating one if needed. $id = $variables->getAttribute('id', Html::getUniqueId($variables->offsetGet('id', 'progress-bar'))); $variables->setAttribute('id', $id); unset($variables['id']); // Preprocess attributes. $this->preprocessAttributes(); }
/** * Pre-processes variables for the "bootstrap_panel" theme hook. * * See template for list of available variables. * * @see bootstrap-panel.html.twig * * @ingroup theme_preprocess */ function bootstrap_preprocess_bootstrap_panel(&$variables) { $element = $variables['element']; Element::setAttributes($element, array('id')); Element\RenderElement::setAttributes($element); $variables['attributes'] = $element['#attributes']; $variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL; $variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL; $variables['title_display'] = isset($element['#title_display']) ? $element['#title_display'] : NULL; $variables['children'] = $element['#children']; $variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL; $variables['legend']['title'] = !empty($element['#title']) ? Xss::filterAdmin($element['#title']) : ''; $variables['legend']['attributes'] = new Attribute(); $variables['legend_span']['attributes'] = new Attribute(); if (!empty($element['#description'])) { $description_id = $element['#attributes']['id'] . '--description'; $description_attributes['id'] = $description_id; $variables['description']['attributes'] = new Attribute($description_attributes); $variables['description']['content'] = $element['#description']; // Add the description's id to the fieldset aria attributes. $variables['attributes']['aria-describedby'] = $description_id; } $variables['collapsible'] = FALSE; if (isset($element['#collapsible'])) { $variables['collapsible'] = $element['#collapsible']; $variables['attributes']['class'][] = 'collapsible'; } $variables['collapsed'] = FALSE; if (isset($element['#collapsed'])) { $variables['collapsed'] = $element['#collapsed']; } // Force grouped fieldsets to not be collapsible (for vertical tabs). if (!empty($element['#group'])) { $variables['collapsible'] = FALSE; $variables['collapsed'] = FALSE; } if (!isset($element['#id']) && $variables['collapsible']) { $element['#id'] = \Drupal\Component\Utility\Html::getUniqueId('bootstrap-panel'); } $variables['target'] = NULL; if (isset($element['#id'])) { if (!isset($variables['attributes']['id'])) { $variables['attributes']['id'] = $element['#id']; } $variables['target'] = '#' . $element['#id'] . ' > .collapse'; } // Iterate over optional variables. $keys = array('description', 'prefix', 'suffix', 'title', 'value'); foreach ($keys as $key) { $variables[$key] = !empty($element["#{$key}"]) ? $element["#{$key}"] : FALSE; } }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $config = $this->config('payment_form.payment_type'); $form['plugin_selector'] = $this->getPluginSelector($form_state)->buildSelectorForm([], $form_state); $limit_allowed_plugins_id = Html::getUniqueId('limit_allowed_plugins'); $form['limit_allowed_plugins'] = ['#default_value' => $config->get('limit_allowed_plugins'), '#id' => $limit_allowed_plugins_id, '#title' => $this->t('Limit allowed payment methods'), '#type' => 'checkbox']; $allowed_plugin_ids = $config->get('allowed_plugin_ids'); $options = []; foreach ($this->paymentMethodManager->getDefinitions() as $definition) { $options[$definition['id']] = $definition['label']; } $form['allowed_plugin_ids'] = ['#default_value' => $allowed_plugin_ids, '#multiple' => TRUE, '#options' => $options, '#states' => ['visible' => ['#' . $limit_allowed_plugins_id => ['checked' => TRUE]]], '#title' => $this->t('Allowed payment methods'), '#type' => 'select']; return $form + parent::buildForm($form, $form_state); }
/** * Tests default and custom block categories. */ public function testBlockCategory() { $this->drupalLogin($this->drupalCreateUser(array('administer views', 'administer blocks'))); // Create a new view in the UI. $edit = array(); $edit['label'] = $this->randomString(); $edit['id'] = strtolower($this->randomMachineName()); $edit['show[wizard_key]'] = 'standard:views_test_data'; $edit['description'] = $this->randomString(); $edit['block[create]'] = TRUE; $edit['block[style][row_plugin]'] = 'fields'; $this->drupalPostForm('admin/structure/views/add', $edit, t('Save and edit')); // Test that the block was given a default category corresponding to its // base table. $arguments = array(':id' => 'edit-category-lists-views', ':li_class' => 'views-block' . Html::getClass($edit['id']) . '-block-1', ':href' => \Drupal::Url('block.admin_add', array('plugin_id' => 'views_block:' . $edit['id'] . '-block_1', 'theme' => 'classy')), ':text' => $edit['label']); $this->drupalGet('admin/structure/block'); $elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments); $this->assertTrue(!empty($elements), 'The test block appears in the category for its base table.'); // Duplicate the block before changing the category. $this->drupalPostForm('admin/structure/views/view/' . $edit['id'] . '/edit/block_1', array(), t('Duplicate @display_title', array('@display_title' => 'Block'))); $this->assertUrl('admin/structure/views/view/' . $edit['id'] . '/edit/block_2'); // Change the block category to a random string. $this->drupalGet('admin/structure/views/view/' . $edit['id'] . '/edit/block_1'); $label = t('Lists (Views)'); $link = $this->xpath('//a[@id="views-block-1-block-category" and normalize-space(text())=:label]', array(':label' => $label)); $this->assertTrue(!empty($link)); $this->clickLink($label); $category = $this->randomString(); $this->drupalPostForm(NULL, array('block_category' => $category), t('Apply')); // Duplicate the block after changing the category. $this->drupalPostForm(NULL, array(), t('Duplicate @display_title', array('@display_title' => 'Block'))); $this->assertUrl('admin/structure/views/view/' . $edit['id'] . '/edit/block_3'); $this->drupalPostForm(NULL, array(), t('Save')); // Test that the blocks are listed under the correct categories. $category_id = Html::getUniqueId('edit-category-' . SafeMarkup::checkPlain($category)); $arguments[':id'] = $category_id; $this->drupalGet('admin/structure/block'); $elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments); $this->assertTrue(!empty($elements), 'The test block appears in the custom category.'); $arguments = array(':id' => 'edit-category-lists-views', ':li_class' => 'views-block' . Html::getClass($edit['id']) . '-block-2', ':href' => \Drupal::Url('block.admin_add', array('plugin_id' => 'views_block:' . $edit['id'] . '-block_2', 'theme' => 'classy')), ':text' => $edit['label']); $elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments); $this->assertTrue(!empty($elements), 'The first duplicated test block remains in the original category.'); $arguments = array(':id' => $category_id, ':li_class' => 'views-block' . Html::getClass($edit['id']) . '-block-3', ':href' => \Drupal::Url('block.admin_add', array('plugin_id' => 'views_block:' . $edit['id'] . '-block_3', 'theme' => 'classy')), ':text' => $edit['label']); $elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments); $this->assertTrue(!empty($elements), 'The second duplicated test block appears in the custom category.'); }
/** * {@inheritdoc} */ public function preprocessVariables(Variables $variables, $hook, array $info) { if (!empty($variables['description'])) { $variables['description'] = FieldFilteredMarkup::create($variables['description']); } $descriptions = []; $cardinality = $variables['cardinality']; if (isset($cardinality)) { if ($cardinality == -1) { $descriptions[] = t('Unlimited number of files can be uploaded to this field.'); } else { $descriptions[] = \Drupal::translation()->formatPlural($cardinality, 'One file only.', 'Maximum @count files.'); } } $upload_validators = $variables['upload_validators']; if (isset($upload_validators['file_validate_size'])) { $descriptions[] = t('@size limit.', ['@size' => format_size($upload_validators['file_validate_size'][0])]); } if (isset($upload_validators['file_validate_extensions'])) { $extensions = new FormattableMarkup('<code>@extensions</code>', ['@extensions' => implode(', ', explode(' ', $upload_validators['file_validate_extensions'][0]))]); $descriptions[] = t('Allowed types: @extensions.', ['@extensions' => $extensions]); } if (isset($upload_validators['file_validate_image_resolution'])) { $max = $upload_validators['file_validate_image_resolution'][0]; $min = $upload_validators['file_validate_image_resolution'][1]; if ($min && $max && $min == $max) { $descriptions[] = t('Images must be exactly <strong>@size</strong> pixels.', ['@size' => $max]); } elseif ($min && $max) { $descriptions[] = t('Images must be larger than <strong>@min</strong> pixels. Images larger than <strong>@max</strong> pixels will be resized.', ['@min' => $min, '@max' => $max]); } elseif ($min) { $descriptions[] = t('Images must be larger than <strong>@min</strong> pixels.', ['@min' => $min]); } elseif ($max) { $descriptions[] = t('Images larger than <strong>@max</strong> pixels will be resized.', ['@max' => $max]); } } $variables['descriptions'] = $descriptions; if ($descriptions) { $build = array(); $id = Html::getUniqueId('upload-instructions'); $build['toggle'] = ['#type' => 'link', '#title' => t('Upload requirements'), '#url' => Url::fromUserInput("#{$id}"), '#icon' => Bootstrap::glyphicon('question-sign'), '#attributes' => ['class' => ['icon-before'], 'data-toggle' => 'popover', 'data-html' => 'true', 'data-placement' => 'bottom', 'data-title' => t('Upload requirements')]]; $build['requirements'] = ['#type' => 'container', '#theme_wrappers' => ['container__file_upload_help'], '#attributes' => ['id' => $id, 'class' => ['hidden', 'help-block'], 'aria-hidden' => 'true']]; $build['requirements']['descriptions'] = ['#theme' => 'item_list__file_upload_help', '#items' => $descriptions]; $variables['popover'] = $build; } }
/** * Expands a radios element into individual radio elements. */ public static function processRadios(&$element, FormStateInterface $form_state, &$complete_form) { if (count($element['#options']) > 0) { $weight = 0; foreach ($element['#options'] as $key => $choice) { // Maintain order of options as defined in #options, in case the element // defines custom option sub-elements, but does not define all option // sub-elements. $weight += 0.001; $element += array($key => array()); // Generate the parents as the autogenerator does, so we will have a // unique id for each radio button. $parents_for_id = array_merge($element['#parents'], array($key)); $element[$key] += array('#type' => 'radio', '#title' => $choice, '#return_value' => $key, '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE, '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#id' => HtmlUtility::getUniqueId('edit-' . implode('-', $parents_for_id)), '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, '#error_no_message' => TRUE, '#weight' => $weight); } } return $element; }
/** * Determine the target selector for the OpenDialogCommand. * * @param array &$options * The 'target' option, if set, is used, and then removed from $options. * @param RouteMatchInterface $route_match * When no 'target' option is set in $options, $route_match is used instead * to determine the target. * * @return string * The target selector. */ protected function determineTargetSelector(array &$options, RouteMatchInterface $route_match) { // Generate the target wrapper for the dialog. if (isset($options['target'])) { // If the target was nominated in the incoming options, use that. $target = $options['target']; // Ensure the target includes the #. if (substr($target, 0, 1) != '#') { $target = '#' . $target; } // This shouldn't be passed on to jQuery.ui.dialog. unset($options['target']); } else { // Generate a target based on the route id. $route_name = $route_match->getRouteName(); $target = '#' . Html::getUniqueId("drupal-dialog-{$route_name}"); } return $target; }
/** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { /** @var \Drupal\address\Entity\ZoneInterface $zone */ $zone = $this->entity; $user_input = $form_state->getUserInput(); $form['#tree'] = TRUE; $form['name'] = ['#type' => 'textfield', '#title' => $this->t('Name'), '#default_value' => $zone->getName(), '#maxlength' => 255, '#required' => TRUE]; $form['id'] = ['#type' => 'machine_name', '#title' => $this->t('Machine name'), '#default_value' => $zone->getId(), '#machine_name' => ['exists' => '\\Drupal\\address\\Entity\\Zone::load', 'source' => ['name']], '#maxlength' => 255, '#required' => TRUE]; $form['scope'] = ['#type' => 'textfield', '#title' => $this->t('Scope'), '#description' => $this->t('Used to group zones by purpose. Examples: tax, shipping.'), '#default_value' => $zone->getScope(), '#maxlength' => 255]; $form['priority'] = ['#type' => 'weight', '#title' => $this->t('Priority'), '#description' => $this->t('Zones with a higher priority will be matched first.'), '#default_value' => (int) $zone->getPriority(), '#delta' => 10]; $wrapper_id = Html::getUniqueId('zone-members-ajax-wrapper'); $form['members'] = ['#type' => 'table', '#header' => [$this->t('Type'), $this->t('Zone member'), $this->t('Weight'), $this->t('Operations')], '#tabledrag' => [['action' => 'order', 'relationship' => 'sibling', 'group' => 'zone-member-order-weight']], '#weight' => 5, '#prefix' => '<div id="' . $wrapper_id . '">', '#suffix' => '</div>']; $index = 0; /** @var \Drupal\address\Plugin\ZoneMember\ZoneMemberInterface $member */ foreach ($zone->getMembers() as $key => $member) { $member_form =& $form['members'][$index]; $member_form['#attributes']['class'][] = 'draggable'; $member_form['#weight'] = isset($user_input['members'][$index]) ? $user_input['members'][$index]['weight'] : $member->getWeight(); $member_form['type'] = ['#type' => 'markup', '#markup' => $member->getPluginDefinition()['name']]; $member_parents = ['members', $index, 'form']; $member_form_state = $this->buildMemberFormState($member_parents, $form_state); $member_form['form'] = $member->buildConfigurationForm([], $member_form_state); $member_form['form']['#element_validate'] = ['::memberFormValidate']; $member_form['weight'] = ['#type' => 'weight', '#title' => $this->t('Weight for @title', ['@title' => $member->getName()]), '#title_display' => 'invisible', '#default_value' => $member->getWeight(), '#attributes' => ['class' => ['zone-member-order-weight']]]; $member_form['remove'] = ['#type' => 'submit', '#name' => 'remove_member' . $index, '#value' => $this->t('Remove'), '#limit_validation_errors' => [], '#submit' => ['::removeMemberSubmit'], '#member_index' => $index, '#ajax' => ['callback' => '::membersAjax', 'wrapper' => $wrapper_id]]; $index++; } // Sort the members by weight. Ensures weight is preserved on ajax refresh. uasort($form['members'], ['\\Drupal\\Component\\Utility\\SortArray', 'sortByWeightProperty']); $plugins = []; foreach ($this->memberManager->getDefinitions() as $plugin => $definition) { $plugins[$plugin] = $definition['name']; } $form['members']['_new'] = ['#tree' => FALSE]; $form['members']['_new']['type'] = ['#prefix' => '<div class="zone-member-new">', '#suffix' => '</div>']; $form['members']['_new']['type']['plugin'] = ['#type' => 'select', '#title' => $this->t('Zone member type'), '#title_display' => 'invisible', '#options' => $plugins, '#empty_value' => '']; $form['members']['_new']['type']['add_member'] = ['#type' => 'submit', '#value' => $this->t('Add'), '#validate' => ['::addMemberValidate'], '#submit' => ['::addMemberSubmit'], '#limit_validation_errors' => [['plugin']], '#ajax' => ['callback' => '::membersAjax', 'wrapper' => $wrapper_id]]; $form['members']['_new']['member'] = ['data' => []]; $form['members']['_new']['operations'] = ['data' => []]; return parent::form($form, $form_state); }
/** * Builds the customer form. * * @param array $form * The parent form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param \Drupal\commerce_order\Entity\OrderInterface $order * The current order, if known. * * @return array * The parent form with the customer form elements added. */ public function buildCustomerForm(array $form, FormStateInterface $form_state, OrderInterface $order = NULL) { $selected_customer_type = $form_state->getValue(['customer_type'], 'existing'); $wrapper_id = Html::getUniqueId('customer-fieldset-wrapper'); $form['customer'] = ['#type' => 'fieldset', '#title' => t('Customer'), '#prefix' => '<div id="' . $wrapper_id . '">', '#suffix' => '</div>']; $form['customer']['customer_type'] = ['#type' => 'radios', '#title' => t('Order for'), '#title_display' => 'invisible', '#attributes' => ['class' => ['container-inline']], '#required' => TRUE, '#options' => ['existing' => t('Existing customer'), 'new' => t('New customer')], '#default_value' => $selected_customer_type, '#ajax' => ['callback' => [$this, 'customerFormAjax'], 'wrapper' => $wrapper_id]]; if ($selected_customer_type == 'existing') { $form['customer']['uid'] = ['#type' => 'entity_autocomplete', '#title' => t('Search'), '#attributes' => ['class' => ['container-inline']], '#placeholder' => t('Search by username or email address'), '#target_type' => 'user', '#selection_settings' => ['match_operator' => 'CONTAINS', 'include_anonymous' => FALSE]]; } else { // New customer. $form['customer']['uid'] = ['#type' => 'value', '#value' => 0]; $form['customer']['mail'] = ['#type' => 'email', '#title' => t('Email'), '#required' => TRUE]; $form['customer']['password'] = ['#type' => 'container']; $form['customer']['password']['generate'] = ['#type' => 'checkbox', '#title' => t('Generate password'), '#default_value' => 1]; // The password_confirm element needs to be wrapped in order for #states // to work properly. See https://www.drupal.org/node/1427838. $form['customer']['password']['password_confirm_wrapper'] = ['#type' => 'container', '#states' => ['visible' => [':input[name="generate"]' => ['checked' => FALSE]]]]; // We cannot make this required due to HTML5 validation. $form['customer']['password']['password_confirm_wrapper']['pass'] = ['#type' => 'password_confirm', '#size' => 25]; } return $form; }
/** * Overrides \Drupal\views\Plugin\views\display\PathPluginBase::render(). */ public function render() { if (!empty($this->options['geolocation_field'])) { $geo_field = $this->options['geolocation_field']; $this->view->field[$geo_field]->options['exclude'] = TRUE; } else { // TODO: Throw some exception here, we're done. return []; } if (!empty($this->options['title_field'])) { $title_field = $this->options['title_field']; $this->view->field[$title_field]->options['exclude'] = TRUE; } $id = \Drupal\Component\Utility\Html::getUniqueId($this->pluginId); $build = ['#theme' => 'geolocation_common_map_display', '#id' => $id, '#attached' => ['library' => ['geolocation/geolocation.commonmap'], 'drupalSettings' => ['geolocation' => ['commonMap' => ['id' => $id]]]]]; foreach ($this->view->result as $row) { $title = empty($title_field) ? '' : $this->view->field[$title_field]->theme($row); $geo_items = $this->view->field[$geo_field]->getItems($row); foreach ($geo_items as $delta => $item) { $geolocation = $item['raw']; $position = ['lat' => $geolocation->lat, 'lng' => $geolocation->lng]; $build['#locations'][] = ['#theme' => 'geolocation_common_map_location', '#content' => $this->view->rowPlugin->render($row), '#title' => $title, '#position' => $position]; } } $centre = ['lat' => 0, 'lng' => 0]; switch ($this->options['centre']) { case 'fixed_value': $centre = ['lat' => (double) $this->options['centre_fixed_values']['latitude'], 'lng' => (double) $this->options['centre_fixed_values']['longitude']]; break; case 'first_row': default: if (!empty($build['#locations'][0]['#position'])) { $centre = $build['#locations'][0]['#position']; } break; } $build['#centre'] = $centre; return $build; }
/** * {@inheritdoc} */ protected function preprocessVariables(Variables $variables, $hook, array $info) { // Immediately log an error and return if Bootstrap modals are not enabled. if (!$this->theme->getSetting('modal_enabled')) { \Drupal::logger('bootstrap')->error(t('Bootstrap modals are not enabled.')); return; } // Retrieve the ID, generating one if needed. $id = $variables->getAttribute('id', Html::getUniqueId($variables->offsetGet('id', 'bootstrap-modal'))); $variables->setAttribute('id', $id); unset($variables['id']); if ($variables->title) { $title_id = $variables->getAttribute('id', "{$id}--title", $variables::TITLE); $variables->setAttribute('id', $title_id, $variables::TITLE); $variables->setAttribute('aria-labelledby', $title_id); } // Use a provided modal size or retrieve the default theme setting. $variables->size = $variables->size ?: $this->theme->getSetting('modal_size'); // Convert the description variable. $this->preprocessDescription($variables, $hook, $info); // Ensure all attributes are proper objects. $this->preprocessAttributes($variables, $hook, $info); }
/** * Tests that the block form has a theme selector when not passed via the URL. */ public function testBlockThemeSelector() { // Install all themes. \Drupal::service('theme_handler')->install(['bartik', 'seven', 'stark']); $theme_settings = $this->config('system.theme'); foreach (['bartik', 'seven', 'stark'] as $theme) { $this->drupalGet('admin/structure/block/list/' . $theme); $this->assertTitle(t('Block layout') . ' | Drupal'); // Select the 'Powered by Drupal' block to be placed. $block = array(); $block['id'] = strtolower($this->randomMachineName()); $block['theme'] = $theme; $block['region'] = 'content'; $this->drupalPostForm('admin/structure/block/add/system_powered_by_block', $block, t('Save block')); $this->assertText(t('The block configuration has been saved.')); $this->assertUrl('admin/structure/block/list/' . $theme . '?block-placement=' . Html::getClass($block['id'])); // Set the default theme and ensure the block is placed. $theme_settings->set('default', $theme)->save(); $this->drupalGet(''); $elements = $this->xpath('//div[@id = :id]', array(':id' => Html::getUniqueId('block-' . $block['id']))); $this->assertTrue(!empty($elements), 'The block was found.'); } }
/** * {@inheritdoc} */ public function optionLink($text, $section, $class = '', $title = '') { if (!trim($text)) { $text = $this->t('Broken field'); } if (!empty($class)) { $text = SafeMarkup::format('<span>@text</span>', array('@text' => $text)); } if (empty($title)) { $title = $text; } return \Drupal::l($text, new Url('views_ui.form_display', array('js' => 'nojs', 'view' => $this->view->storage->id(), 'display_id' => $this->display['id'], 'type' => $section), array('attributes' => array('class' => array('views-ajax-link', $class), 'title' => $title, 'id' => Html::getUniqueId('views-' . $this->display['id'] . '-' . $section))))); }
/** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { /** @var \Drupal\comment\CommentInterface $comment */ $comment = $this->entity; $entity = $this->entityManager->getStorage($comment->getCommentedEntityTypeId())->load($comment->getCommentedEntityId()); $field_name = $comment->getFieldName(); $field_definition = $this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()]; $config = $this->config('user.settings'); // In several places within this function, we vary $form on: // - The current user's permissions. // - Whether the current user is authenticated or anonymous. // - The 'user.settings' configuration. // - The comment field's definition. $form['#cache']['contexts'][] = 'user.permissions'; $form['#cache']['contexts'][] = 'user.roles:authenticated'; $this->renderer->addCacheableDependency($form, $config); $this->renderer->addCacheableDependency($form, $field_definition->getConfig($entity->bundle())); // Use #comment-form as unique jump target, regardless of entity type. $form['#id'] = Html::getUniqueId('comment_form'); $form['#theme'] = array('comment_form__' . $entity->getEntityTypeId() . '__' . $entity->bundle() . '__' . $field_name, 'comment_form'); $anonymous_contact = $field_definition->getSetting('anonymous'); $is_admin = $comment->id() && $this->currentUser->hasPermission('administer comments'); if (!$this->currentUser->isAuthenticated() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT) { $form['#attached']['library'][] = 'core/drupal.form'; $form['#attributes']['data-user-info-from-browser'] = TRUE; } // If not replying to a comment, use our dedicated page callback for new // Comments on entities. if (!$comment->id() && !$comment->hasParentComment()) { $form['#action'] = $this->url('comment.reply', array('entity_type' => $entity->getEntityTypeId(), 'entity' => $entity->id(), 'field_name' => $field_name)); } $comment_preview = $form_state->get('comment_preview'); if (isset($comment_preview)) { $form += $comment_preview; } $form['author'] = array(); // Display author information in a details element for comment moderators. if ($is_admin) { $form['author'] += array('#type' => 'details', '#title' => $this->t('Administration')); } // Prepare default values for form elements. $author = ''; if ($is_admin) { if (!$comment->getOwnerId()) { $author = $comment->getAuthorName(); } $status = $comment->getStatus(); if (empty($comment_preview)) { $form['#title'] = $this->t('Edit comment %title', array('%title' => $comment->getSubject())); } } else { $status = $this->currentUser->hasPermission('skip comment approval') ? CommentInterface::PUBLISHED : CommentInterface::NOT_PUBLISHED; } $date = ''; if ($comment->id()) { $date = !empty($comment->date) ? $comment->date : DrupalDateTime::createFromTimestamp($comment->getCreatedTime()); } // The uid field is only displayed when a user with the permission // 'administer comments' is editing an existing comment from an // authenticated user. $owner = $comment->getOwner(); $form['author']['uid'] = ['#type' => 'entity_autocomplete', '#target_type' => 'user', '#default_value' => $owner->isAnonymous() ? NULL : $owner, '#selection_settings' => ['include_anonymous' => FALSE], '#title' => $this->t('Authored by'), '#description' => $this->t('Leave blank for %anonymous.', ['%anonymous' => $config->get('anonymous')]), '#access' => $is_admin]; // The name field is displayed when an anonymous user is adding a comment or // when a user with the permission 'administer comments' is editing an // existing comment from an anonymous user. $form['author']['name'] = array('#type' => 'textfield', '#title' => $is_admin ? $this->t('Name for @anonymous', ['@anonymous' => $config->get('anonymous')]) : $this->t('Your name'), '#default_value' => $author, '#required' => $this->currentUser->isAnonymous() && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT, '#maxlength' => 60, '#access' => $this->currentUser->isAnonymous() || $is_admin, '#size' => 30, '#attributes' => ['data-drupal-default-value' => $config->get('anonymous')]); if ($is_admin) { // When editing a comment only display the name textfield if the uid field // is empty. $form['author']['name']['#states'] = ['visible' => [':input[name="uid"]' => array('empty' => TRUE)]]; } // Add author email and homepage fields depending on the current user. $form['author']['mail'] = array('#type' => 'email', '#title' => $this->t('Email'), '#default_value' => $comment->getAuthorEmail(), '#required' => $this->currentUser->isAnonymous() && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT, '#maxlength' => 64, '#size' => 30, '#description' => $this->t('The content of this field is kept private and will not be shown publicly.'), '#access' => $comment->getOwner()->isAnonymous() && $is_admin || $this->currentUser->isAnonymous() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT); $form['author']['homepage'] = array('#type' => 'url', '#title' => $this->t('Homepage'), '#default_value' => $comment->getHomepage(), '#maxlength' => 255, '#size' => 30, '#access' => $is_admin || $this->currentUser->isAnonymous() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT); // Add administrative comment publishing options. $form['author']['date'] = array('#type' => 'datetime', '#title' => $this->t('Authored on'), '#default_value' => $date, '#size' => 20, '#access' => $is_admin); $form['author']['status'] = array('#type' => 'radios', '#title' => $this->t('Status'), '#default_value' => $status, '#options' => array(CommentInterface::PUBLISHED => $this->t('Published'), CommentInterface::NOT_PUBLISHED => $this->t('Not published')), '#access' => $is_admin); return parent::form($form, $form_state, $comment); }
/** * Creates checkbox or radio elements to populate a tableselect table. * * @param array $element * An associative array containing the properties and children of the * tableselect element. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. * * @return array * The processed element. */ public static function processTableselect(&$element, FormStateInterface $form_state, &$complete_form) { if ($element['#multiple']) { $value = is_array($element['#value']) ? $element['#value'] : array(); } else { // Advanced selection behavior makes no sense for radios. $element['#js_select'] = FALSE; } $element['#tree'] = TRUE; if (count($element['#options']) > 0) { if (!isset($element['#default_value']) || $element['#default_value'] === 0) { $element['#default_value'] = array(); } // Create a checkbox or radio for each item in #options in such a way that // the value of the tableselect element behaves as if it had been of type // checkboxes or radios. foreach ($element['#options'] as $key => $choice) { // Do not overwrite manually created children. if (!isset($element[$key])) { if ($element['#multiple']) { $title = ''; if (isset($element['#options'][$key]['title']) && is_array($element['#options'][$key]['title'])) { if (!empty($element['#options'][$key]['title']['data']['#title'])) { $title = new TranslatableMarkup('Update @title', array('@title' => $element['#options'][$key]['title']['data']['#title'])); } } $element[$key] = array('#type' => 'checkbox', '#title' => $title, '#title_display' => 'invisible', '#return_value' => $key, '#default_value' => isset($value[$key]) ? $key : NULL, '#attributes' => $element['#attributes']); } else { // Generate the parents as the autogenerator does, so we will have a // unique id for each radio button. $parents_for_id = array_merge($element['#parents'], array($key)); $element[$key] = array('#type' => 'radio', '#title' => '', '#return_value' => $key, '#default_value' => $element['#default_value'] == $key ? $key : NULL, '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#id' => HtmlUtility::getUniqueId('edit-' . implode('-', $parents_for_id)), '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL); } if (isset($element['#options'][$key]['#weight'])) { $element[$key]['#weight'] = $element['#options'][$key]['#weight']; } } } } else { $element['#value'] = array(); } return $element; }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { // Don't show the form when batch operations are in progress. if ($batch = batch_get() && isset($batch['current_set'])) { return array('#theme' => ''); } // Make sure that we validate because this form might be submitted // multiple times per page. $form_state->setValidationEnforced(); /** @var \Drupal\views\ViewExecutable $view */ $view = $form_state->get('view'); $display =& $form_state->get('display'); $form_state->setUserInput($view->getExposedInput()); // Let form plugins know this is for exposed widgets. $form_state->set('exposed', TRUE); // Check if the form was already created if ($cache = $this->exposedFormCache->getForm($view->storage->id(), $view->current_display)) { return $cache; } $form['#info'] = array(); // Go through each handler and let it generate its exposed widget. foreach ($view->display_handler->handlers as $type => $value) { /** @var \Drupal\views\Plugin\views\ViewsHandlerInterface $handler */ foreach ($view->{$type} as $id => $handler) { if ($handler->canExpose() && $handler->isExposed()) { // Grouped exposed filters have their own forms. // Instead of render the standard exposed form, a new Select or // Radio form field is rendered with the available groups. // When an user choose an option the selected value is split // into the operator and value that the item represents. if ($handler->isAGroup()) { $handler->groupForm($form, $form_state); $id = $handler->options['group_info']['identifier']; } else { $handler->buildExposedForm($form, $form_state); } if ($info = $handler->exposedInfo()) { $form['#info']["{$type}-{$id}"] = $info; } } } } $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#name' => '', '#type' => 'submit', '#value' => $this->t('Apply'), '#id' => Html::getUniqueId('edit-submit-' . $view->storage->id())); $form['#action'] = $view->hasUrl() ? $view->getUrl()->toString() : Url::fromRoute('<current>')->toString(); $form['#theme'] = $view->buildThemeFunctions('views_exposed_form'); $form['#id'] = Html::cleanCssIdentifier('views_exposed_form-' . SafeMarkup::checkPlain($view->storage->id()) . '-' . SafeMarkup::checkPlain($display['id'])); /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */ $exposed_form_plugin = $view->display_handler->getPlugin('exposed_form'); $exposed_form_plugin->exposedFormAlter($form, $form_state); // Save the form. $this->exposedFormCache->setForm($view->storage->id(), $view->current_display, $form); return $form; }
/** * Provide a standard set of Apply/Cancel/OK buttons for the forms. Also provide * a hidden op operator because the forms plugin doesn't seem to properly * provide which button was clicked. * * TODO: Is the hidden op operator still here somewhere, or is that part of the * docblock outdated? */ public function getStandardButtons(&$form, FormStateInterface $form_state, $form_id, $name = NULL) { $form['actions'] = array('#type' => 'actions'); if (empty($name)) { $name = t('Apply'); if (!empty($this->stack) && count($this->stack) > 1) { $name = t('Apply and continue'); } $names = array(t('Apply'), t('Apply and continue')); } // Views provides its own custom handling of AJAX form submissions. Usually // this happens at the same path, but custom paths may be specified in // $form_state. $form_url = $form_state->get('url') ?: Url::fromRouteMatch(\Drupal::routeMatch()); // Forms that are purely informational set an ok_button flag, so we know not // to create an "Apply" button for them. if (!$form_state->get('ok_button')) { $form['actions']['submit'] = array('#type' => 'submit', '#value' => $name, '#id' => 'edit-submit-' . Html::getUniqueId($form_id), '#submit' => array(array($this, 'standardSubmit')), '#button_type' => 'primary', '#ajax' => array('url' => $form_url)); // Form API button click detection requires the button's #value to be the // same between the form build of the initial page request, and the // initial form build of the request processing the form submission. // Ideally, the button's #value shouldn't change until the form rebuild // step. However, \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm() // implements a different multistep form workflow than the Form API does, // and adjusts $view->stack prior to form processing, so we compensate by // extending button click detection code to support any of the possible // button labels. if (isset($names)) { $form['actions']['submit']['#values'] = $names; $form['actions']['submit']['#process'] = array_merge(array('views_ui_form_button_was_clicked'), \Drupal::service('element_info')->getInfoProperty($form['actions']['submit']['#type'], '#process', array())); } // If a validation handler exists for the form, assign it to this button. $form['actions']['submit']['#validate'][] = [$form_state->getFormObject(), 'validateForm']; } // Create a "Cancel" button. For purely informational forms, label it "OK". $cancel_submit = function_exists($form_id . '_cancel') ? $form_id . '_cancel' : array($this, 'standardCancel'); $form['actions']['cancel'] = array('#type' => 'submit', '#value' => !$form_state->get('ok_button') ? t('Cancel') : t('Ok'), '#submit' => array($cancel_submit), '#validate' => array(), '#ajax' => array('path' => $form_url), '#limit_validation_errors' => array()); // Compatibility, to be removed later: // TODO: When is "later"? // We used to set these items on the form, but now we want them on the $form_state: if (isset($form['#title'])) { $form_state->set('title', $form['#title']); } if (isset($form['#section'])) { $form_state->set('#section', $form['#section']); } // Finally, we never want these cached -- our object cache does that for us. $form['#no_cache'] = TRUE; }
/** * {@inheritdoc} */ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state) { // Initialize as unprocessed. $element['#processed'] = FALSE; // Use element defaults. if (isset($element['#type']) && empty($element['#defaults_loaded']) && ($info = $this->elementInfo->getInfo($element['#type']))) { // Overlay $info onto $element, retaining preexisting keys in $element. $element += $info; $element['#defaults_loaded'] = TRUE; } // Assign basic defaults common for all form elements. $element += array('#required' => FALSE, '#attributes' => array(), '#title_display' => 'before', '#description_display' => 'after', '#errors' => NULL); // Special handling if we're on the top level form element. if (isset($element['#type']) && $element['#type'] == 'form') { if (!empty($element['#https']) && !UrlHelper::isExternal($element['#action'])) { global $base_root; // Not an external URL so ensure that it is secure. $element['#action'] = str_replace('http://', 'https://', $base_root) . $element['#action']; } // Store a reference to the complete form in $form_state prior to building // the form. This allows advanced #process and #after_build callbacks to // perform changes elsewhere in the form. $form_state->setCompleteForm($element); // Set a flag if we have a correct form submission. This is always TRUE // for programmed forms coming from self::submitForm(), or if the form_id // coming from the POST data is set and matches the current form_id. $input = $form_state->getUserInput(); if ($form_state->isProgrammed() || !empty($input) && (isset($input['form_id']) && $input['form_id'] == $form_id)) { $form_state->setProcessInput(); if (isset($element['#token'])) { $input = $form_state->getUserInput(); if (empty($input['form_token']) || !$this->csrfToken->validate($input['form_token'], $element['#token'])) { // Set an early form error to block certain input processing since // that opens the door for CSRF vulnerabilities. $this->setInvalidTokenError($form_state); // This value is checked in self::handleInputElement(). $form_state->setInvalidToken(TRUE); // Make sure file uploads do not get processed. $this->requestStack->getCurrentRequest()->files = new FileBag(); } } } else { $form_state->setProcessInput(FALSE); } // All form elements should have an #array_parents property. $element['#array_parents'] = array(); } if (!isset($element['#id'])) { $unprocessed_id = 'edit-' . implode('-', $element['#parents']); $element['#id'] = Html::getUniqueId($unprocessed_id); // Provide a selector usable by JavaScript. As the ID is unique, its not // possible to rely on it in JavaScript. $element['#attributes']['data-drupal-selector'] = Html::getId($unprocessed_id); } else { // Provide a selector usable by JavaScript. As the ID is unique, its not // possible to rely on it in JavaScript. $element['#attributes']['data-drupal-selector'] = Html::getId($element['#id']); } // Add the aria-describedby attribute to associate the form control with its // description. if (!empty($element['#description'])) { $element['#attributes']['aria-describedby'] = $element['#id'] . '--description'; } // Handle input elements. if (!empty($element['#input'])) { $this->handleInputElement($form_id, $element, $form_state); } // Allow for elements to expand to multiple elements, e.g., radios, // checkboxes and files. if (isset($element['#process']) && !$element['#processed']) { foreach ($element['#process'] as $callback) { $complete_form =& $form_state->getCompleteForm(); $element = call_user_func_array($form_state->prepareCallback($callback), array(&$element, &$form_state, &$complete_form)); } $element['#processed'] = TRUE; } // We start off assuming all form elements are in the correct order. $element['#sorted'] = TRUE; // Recurse through all child elements. $count = 0; if (isset($element['#access'])) { $access = $element['#access']; $inherited_access = NULL; if ($access instanceof AccessResultInterface && !$access->isAllowed() || $access === FALSE) { $inherited_access = $access; } } foreach (Element::children($element) as $key) { // Prior to checking properties of child elements, their default // properties need to be loaded. if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = $this->elementInfo->getInfo($element[$key]['#type']))) { $element[$key] += $info; $element[$key]['#defaults_loaded'] = TRUE; } // Don't squash an existing tree value. if (!isset($element[$key]['#tree'])) { $element[$key]['#tree'] = $element['#tree']; } // Children inherit #access from parent. if (isset($inherited_access)) { $element[$key]['#access'] = $inherited_access; } // Make child elements inherit their parent's #disabled and #allow_focus // values unless they specify their own. foreach (array('#disabled', '#allow_focus') as $property) { if (isset($element[$property]) && !isset($element[$key][$property])) { $element[$key][$property] = $element[$property]; } } // Don't squash existing parents value. if (!isset($element[$key]['#parents'])) { // Check to see if a tree of child elements is present. If so, // continue down the tree if required. $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], array($key)) : array($key); } // Ensure #array_parents follows the actual form structure. $array_parents = $element['#array_parents']; $array_parents[] = $key; $element[$key]['#array_parents'] = $array_parents; // Assign a decimal placeholder weight to preserve original array order. if (!isset($element[$key]['#weight'])) { $element[$key]['#weight'] = $count / 1000; } else { // If one of the child elements has a weight then we will need to sort // later. unset($element['#sorted']); } $element[$key] = $this->doBuildForm($form_id, $element[$key], $form_state); $count++; } // The #after_build flag allows any piece of a form to be altered // after normal input parsing has been completed. if (isset($element['#after_build']) && !isset($element['#after_build_done'])) { foreach ($element['#after_build'] as $callback) { $element = call_user_func_array($form_state->prepareCallback($callback), array($element, &$form_state)); } $element['#after_build_done'] = TRUE; } // If there is a file element, we need to flip a flag so later the // form encoding can be set. if (isset($element['#type']) && $element['#type'] == 'file') { $form_state->setHasFileElement(); } // Final tasks for the form element after self::doBuildForm() has run for // all other elements. if (isset($element['#type']) && $element['#type'] == 'form') { // If there is a file element, we set the form encoding. if ($form_state->hasFileElement()) { $element['#attributes']['enctype'] = 'multipart/form-data'; } // Allow Ajax submissions to the form action to bypass verification. This // is especially useful for multipart forms, which cannot be verified via // a response header. $element['#attached']['drupalSettings']['ajaxTrustedUrl'][$element['#action']] = TRUE; // If a form contains a single textfield, and the ENTER key is pressed // within it, Internet Explorer submits the form with no POST data // identifying any submit button. Other browsers submit POST data as // though the user clicked the first button. Therefore, to be as // consistent as we can be across browsers, if no 'triggering_element' has // been identified yet, default it to the first button. $buttons = $form_state->getButtons(); if (!$form_state->isProgrammed() && !$form_state->getTriggeringElement() && !empty($buttons)) { $form_state->setTriggeringElement($buttons[0]); } $triggering_element = $form_state->getTriggeringElement(); // If the triggering element specifies "button-level" validation and // submit handlers to run instead of the default form-level ones, then add // those to the form state. if (isset($triggering_element['#validate'])) { $form_state->setValidateHandlers($triggering_element['#validate']); } if (isset($triggering_element['#submit'])) { $form_state->setSubmitHandlers($triggering_element['#submit']); } // If the triggering element executes submit handlers, then set the form // state key that's needed for those handlers to run. if (!empty($triggering_element['#executes_submit_callback'])) { $form_state->setSubmitted(); } // Special processing if the triggering element is a button. if (!empty($triggering_element['#is_button'])) { // Because there are several ways in which the triggering element could // have been determined (including from input variables set by // JavaScript or fallback behavior implemented for IE), and because // buttons often have their #name property not derived from their // #parents property, we can't assume that input processing that's // happened up until here has resulted in // $form_state->getValue(BUTTON_NAME) being set. But it's common for // forms to have several buttons named 'op' and switch on // $form_state->getValue('op') during submit handler execution. $form_state->setValue($triggering_element['#name'], $triggering_element['#value']); } } return $element; }
/** * Tests the exposed block functionality. */ public function testExposedBlock() { $this->drupalCreateContentType(['type' => 'page']); $view = Views::getView('test_exposed_block'); $view->setDisplay('page_1'); $block = $this->drupalPlaceBlock('views_exposed_filter_block:test_exposed_block-page_1'); $this->drupalGet('test_exposed_block'); // Test there is an exposed form in a block. $xpath = $this->buildXPathQuery('//div[@id=:id]/form/@id', array(':id' => Html::getUniqueId('block-' . $block->id()))); $this->assertFieldByXpath($xpath, $this->getExpectedExposedFormId($view), 'Expected form found in views block.'); // Test there is not an exposed form in the view page content area. $xpath = $this->buildXPathQuery('//div[@class="view-content"]/form/@id', array(':id' => Html::getUniqueId('block-' . $block->id()))); $this->assertNoFieldByXpath($xpath, $this->getExpectedExposedFormId($view), 'No exposed form found in views content region.'); // Test there is only one views exposed form on the page. $elements = $this->xpath('//form[@id=:id]', array(':id' => $this->getExpectedExposedFormId($view))); $this->assertEqual(count($elements), 1, 'One exposed form block found.'); // Test that the correct option is selected after form submission. $this->assertCacheContext('url'); $this->assertOptionSelected('edit-type', 'All'); foreach (['All', 'article', 'page'] as $argument) { $this->drupalGet('test_exposed_block', ['query' => ['type' => $argument]]); $this->assertCacheContext('url'); $this->assertOptionSelected('edit-type', $argument); } }