/** * Render the top of the display so it can be updated during ajax operations. */ public function renderDisplayTop(ViewUI $view) { $display_id = $this->displayID; $element['#theme_wrappers'][] = 'views_ui_container'; $element['#attributes']['class'] = array('views-display-top', 'clearfix'); $element['#attributes']['id'] = array('views-display-top'); // Extra actions for the display $element['extra_actions'] = array('#type' => 'dropbutton', '#attributes' => array('id' => 'views-display-extra-actions'), '#links' => array('edit-details' => array('title' => $this->t('Edit view name/description'), 'href' => "admin/structure/views/nojs/edit-details/{$view->id()}/{$display_id}", 'attributes' => array('class' => array('views-ajax-link'))), 'analyze' => array('title' => $this->t('Analyze view'), 'href' => "admin/structure/views/nojs/analyze/{$view->id()}/{$display_id}", 'attributes' => array('class' => array('views-ajax-link'))), 'duplicate' => array('title' => $this->t('Duplicate view')) + $view->urlInfo('duplicate')->toArray(), 'reorder' => array('title' => $this->t('Reorder displays'), 'href' => "admin/structure/views/nojs/reorder-displays/{$view->id()}/{$display_id}", 'attributes' => array('class' => array('views-ajax-link'))))); if ($view->access('delete')) { $element['extra_actions']['#links']['delete'] = array('title' => $this->t('Delete view')) + $view->urlInfo('delete-form')->toArray(); } // Let other modules add additional links here. \Drupal::moduleHandler()->alter('views_ui_display_top_links', $element['extra_actions']['#links'], $view, $display_id); if (isset($view->type) && $view->type != $this->t('Default')) { if ($view->type == $this->t('Overridden')) { $element['extra_actions']['#links']['revert'] = array('title' => $this->t('Revert view'), 'href' => "admin/structure/views/view/{$view->id()}/revert", 'query' => array('destination' => "admin/structure/views/view/{$view->id()}")); } else { $element['extra_actions']['#links']['delete'] = array('title' => $this->t('Delete view'), 'href' => "admin/structure/views/view/{$view->id()}/delete"); } } // Determine the displays available for editing. if ($tabs = $this->getDisplayTabs($view)) { if ($display_id) { $tabs[$display_id]['#active'] = TRUE; } $tabs['#prefix'] = '<h2 class="visually-hidden">' . $this->t('Secondary tabs') . '</h2><ul id = "views-display-menu-tabs" class="tabs secondary">'; $tabs['#suffix'] = '</ul>'; $element['tabs'] = $tabs; } // Buttons for adding a new display. foreach (Views::fetchPluginNames('display', NULL, array($view->get('base_table'))) as $type => $label) { $element['add_display'][$type] = array('#type' => 'submit', '#value' => $this->t('Add !display', array('!display' => $label)), '#limit_validation_errors' => array(), '#submit' => array(array($this, 'submitDisplayAdd'), array($this, 'submitDelayDestination')), '#attributes' => array('class' => array('add-display')), '#process' => array_merge(array('views_ui_form_button_was_clicked'), element_info_property('submit', '#process', array())), '#values' => array($this->t('Add !display', array('!display' => $label)), $label)); } return $element; }
/** * 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_path = $form_state->get('path') ?: current_path(); // 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-' . drupal_html_id($form_id), '#submit' => array(array($this, 'standardSubmit')), '#button_type' => 'primary', '#ajax' => array('path' => $form_path)); // 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'), element_info_property($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_path), '#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 buildForm(array $form, FormStateInterface $form_state) { $account = \Drupal::currentUser(); $form = ['#attached' => ['library' => ['webform/webform']]]; $form_state->loadInclude('webform', 'inc', 'includes/webform.components'); $form_state->loadInclude('webform', 'inc', 'includes/webform.submissions'); // For ajax requests, $form_state->getValue('details') is missing. Restore // from storage, if available, for multi-page forms. if (empty($form_state->getValue('details')) && !empty($form_state->getValue(['storage', 'details']))) { $form_state->setValue('details', $form_state->getValue(['storage', 'details'])); } $node = $this->node; $submission = $this->node; $resume_draft = $this->resume_draft; $filter = $this->filter; // If in a multi-step form, a submission ID may be specified in form state. // Load this submission. This allows anonymous users to use auto-save. if (empty($submission) && !empty($form_state->getValue(['details', 'sid']))) { $submission = webform_get_submission($node->id(), $form_state->getValue(['details', 'sid'])); } $finished = isset($submission->is_draft) ? !$submission->is_draft : 0; $submit_button_text = $finished ? $this->t('Save') : (empty($node->webform['submit_text']) ? $this->t('Submit') : $node->webform['submit_text']); // Bind arguments to $form to make them available in theming and form_alter. $form['#node'] = $node; $form['#submission'] = $submission; $form['#filter'] = $filter; // Add a theme function for this form. $form['#theme'] = ['webform_form_' . $node->id(), 'webform_form']; // Add a CSS class for all client forms. $form['#attributes']['class'][] = 'webform-client-form'; $form['#attributes']['class'][] = 'webform-client-form-' . $node->id(); // Set the encoding type (necessary for file uploads). $form['#attributes']['enctype'] = 'multipart/form-data'; // Sometimes when displaying a webform as a teaser or block, a custom action // property is set to direct the user to the node page. if (!empty($node->webform['action'])) { $form['#action'] = $node->webform['action']; } // @todo Convert these to methods. $form['#submit'] = ['webform_client_form_pages', 'webform_client_form_submit']; $form['#validate'] = ['webform_client_form_validate']; // Add includes for used component types and pre/post validation handlers $form['#process'] = ['webform_client_form_process']; if (is_array($node->webform['components']) && !empty($node->webform['components'])) { // Prepare a new form array. $form['submitted'] = ['#tree' => TRUE]; $form['details'] = ['#tree' => TRUE]; // Put the components into a tree structure. if (!isset($form_state->getStorage()['component_tree'])) { $form_state->set(['webform', 'component_tree'], []); $form_state->set(['webform', 'page_count'], 1); $form_state->set(['webform', 'page_num'], 1); _webform_components_tree_build($node->webform['components'], $form_state->get(['webform', 'component_tree']), 0, $form_state->get(['webform', 'page_count'])); // If preview is enabled, increase the page count by one. if ($node->webform['preview']) { $form_state->set(['webform', 'page_count'], $form_state->get(['webform', 'page_count']) + 1); } $form_state->set(['webform', 'preview'], $node->webform['preview']); // If this is the first time this draft has been restore and presented // to the user, let them know that they are looking at a draft, rather // than a new form. This applies to the node view page, but not to a // submission edit page (where they presumably know what submission they // are editing). if ($resume_draft && empty($form_state->getUserInput())) { drupal_set_message($this->t('A partially-completed form was found. Please complete the remaining portions.')); } } else { $form_state->set(['webform', 'component_tree'], $form_state->getStorage()['component_tree']); $form_state->set(['webform', 'page_count'], $form_state->getStorage()['page_count']); $form_state->set(['webform', 'page_num'], $form_state->getStorage()['page_num']); $form_state->set(['webform', 'preview'], $form_state->getStorage()['preview']); } // Set the input values based on whether we're editing an existing // submission or not. $input_values = isset($submission->data) ? $submission->data : []; // Form state storage override any default submission information. Convert // the value structure to always be an array, matching $submission->data. if (isset($form_state->getStorage()['submitted'])) { foreach ($form_state->getStorage()['submitted'] as $cid => $data) { $input_values[$cid] = is_array($data) ? $data : [$data]; } } // Form state values override any default submission information. Convert // the value structure to always be an array, matching $submission->data. if ($form_state->getValue('submitted')) { foreach ($form_state->getValue('submitted') as $cid => $data) { $input_values[$cid] = is_array($data) ? $data : [$data]; } } // Generate conditional topological order & report any errors. $sorter = webform_get_conditional_sorter($node); $sorter->reportErrors(); // Execute the conditionals on the current input values $input_values = $sorter->executeConditionals($input_values); // Allow values from other pages to be sent to browser for conditionals. $form['#conditional_values'] = $input_values; // Allow components access to most up-to-date values. $form_state['#conditional_values'] = $input_values; // For resuming a previous draft, find the next page after the last // validated page. if (!isset($form_state->getStorage()['page_num']) && $submission && $submission->is_draft && $submission->highest_valid_page) { // Find the // 1) previous/next non-empty page, or // 2) the preview page, or // 3) the preview page, forcing its display if the form would // unexpectedly submit, or // 4) page 1 even if empty, if no other previous page would be shown $form_state->set(['webform', 'page_num'], $submission->highest_valid_page); do { $form_state->set(['webform', 'page_num'], $form_state->get(['webform', 'page_num']) + 1); } while (!webform_get_conditional_sorter($node)->pageVisibility($form_state->get(['webform', 'page_num']))); if (!$form_state->get(['webform', 'preview']) && $form_state->get(['webform', 'page_num']) == $form_state->get(['webform', 'page_count']) + (int) (!$form_state->get(['webform', 'preview']))) { // Force a preview to avert an unintended submission via Next. $form_state->set(['webform', 'preview'], TRUE); $form_state->set(['webform', 'page_count'], $form_state->get(['webform', 'page_count']) + 1); } // The form hasn't been submitted (ever) and the preview code will // expect $form_state['values']['submitted'] to be set from a previous // submission, so provide these values here. $form_state->setValue('submitted', $input_values); $form_state->setStorage(['submitted' => $input_values]); } // Shorten up our variable names. $component_tree = $form_state->get(['webform', 'component_tree']); $page_count = $form_state->get(['webform', 'page_count']); $page_num = $form_state->get(['webform', 'page_num']); $preview = $form_state->get(['webform', 'preview']); if ($page_count > 1) { $page_labels = webform_page_labels($node, $form_state); $form['progressbar'] = ['#theme' => 'webform_progressbar', '#node' => $node, '#page_num' => $page_num, '#page_count' => count($page_labels), '#page_labels' => $page_labels, '#weight' => -100]; } // Check whether a previous submission was truncated. The length of the // client form is not estimated before submission because a) the // determination may not be accurate for some webform components and b) // the error will be apparent upon submission. webform_input_vars_check($form, $form_state, 'submitted'); // Recursively add components to the form. The unfiltered version of the // form (typically used in Form Builder), includes all components. foreach ($component_tree['children'] as $cid => $component) { if ($component['type'] == 'pagebreak') { $next_page_labels[$component['page_num'] - 1] = !empty($component['extra']['next_page_label']) ? $component['extra']['next_page_label'] : $this->t('Next Page >'); $prev_page_labels[$component['page_num']] = !empty($component['extra']['prev_page_label']) ? $component['extra']['prev_page_label'] : $this->t('< Previous Page'); } if (!$filter || $sorter->componentVisibility($cid, $page_num)) { $component_value = isset($input_values[$cid]) ? $input_values[$cid] : NULL; _webform_client_form_add_component($node, $component, $component_value, $form['submitted'], $form, $input_values, 'form', $page_num, $filter); } } if ($preview) { $next_page_labels[$page_count - 1] = $node->webform['preview_next_button_label'] ? $node->webform['preview_next_button_label'] : $this->t('Preview'); $prev_page_labels[$page_count] = $node->webform['preview_prev_button_label'] ? $node->webform['preview_prev_button_label'] : $this->t('< Previous'); } // Add the preview if needed. if ($preview && $page_num === $page_count) { $preview_submission = webform_submission_create($node, $account, $form_state, TRUE, $submission); $preview_message = $node->webform['preview_message']; if (strlen(trim(strip_tags($preview_message))) === 0) { $preview_message = $this->t('Please review your submission. Your submission is not complete until you press the "!button" button!', ['!button' => $submit_button_text]); } $form['preview_message'] = ['#type' => 'markup', '#markup' => webform_replace_tokens($preview_message, $node, $preview_submission, NULL, $node->webform['preview_message_format'])]; $form['preview'] = webform_submission_render($node, $preview_submission, NULL, 'html', $node->webform['preview_excluded_components']); $form['#attributes']['class'][] = 'preview'; } // These form details help managing data upon submission. $form['details']['nid'] = ['#type' => 'value', '#value' => $node->id()]; $form['details']['sid'] = ['#type' => 'hidden', '#value' => isset($submission->sid) ? $submission->sid : NULL]; $form['details']['uid'] = ['#type' => 'value', '#value' => isset($submission->uid) ? $submission->uid : $account->id()]; $form['details']['page_num'] = ['#type' => 'hidden', '#value' => $page_num]; $form['details']['page_count'] = ['#type' => 'hidden', '#value' => $page_count]; $form['details']['finished'] = ['#type' => 'hidden', '#value' => $finished]; // Add process functions to remove the IDs forced upon buttons and wrappers. $actions_pre_render = array_merge(element_info_property('actions', '#pre_render', []), ['webform_pre_render_remove_id']); $buttons_pre_render = array_merge(element_info_property('submit', '#pre_render', []), ['webform_pre_render_remove_id']); // Add buttons for pages, drafts, and submissions. $form['actions'] = ['#type' => 'actions', '#weight' => 1000, '#pre_render' => $actions_pre_render]; // Add the draft button. if ($node->webform['allow_draft'] && (empty($submission) || $submission->is_draft) && $account->id() != 0) { $form['actions']['draft'] = ['#type' => 'submit', '#value' => $this->t('Save Draft'), '#weight' => -2, '#validate' => ['webform_client_form_prevalidate'], '#attributes' => ['formnovalidate' => 'formnovalidate', 'class' => ['webform-draft']], '#pre_render' => $buttons_pre_render]; } // Add the submit button(s). if ($page_num > 1) { $form['actions']['previous'] = ['#type' => 'submit', '#value' => $prev_page_labels[$page_num], '#weight' => 5, '#validate' => [], '#attributes' => ['formnovalidate' => 'formnovalidate', 'class' => ['webform-previous']], '#pre_render' => $buttons_pre_render]; } if ($page_num == $page_count) { $form['actions']['submit'] = ['#type' => 'submit', '#value' => $submit_button_text, '#weight' => 10, '#attributes' => ['class' => ['webform-submit', 'button-primary']], '#pre_render' => $buttons_pre_render]; } elseif ($page_num < $page_count) { $form['actions']['next'] = ['#type' => 'submit', '#value' => $next_page_labels[$page_num], '#weight' => 10, '#attributes' => ['class' => ['webform-next', 'button-primary']], '#pre_render' => $buttons_pre_render]; } } return $form; }