0

When building a custom form with Form API and making it a GET, how can one suppress form build id, token, form id, op and other special Drupal variables from getting into the URL on submit, without breaking the form (e.g., caching or other mechanisms) for users (anonymous or otherwise)?

Views is able to do this via the ViewsExposedForm class, but I don't quite understand what it is doing.

Edit: here is one form I am talking about:

<?php

namespace Drupal\mymodule_search\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Component\Utility\Xss;
use Drupal\Component\Utility\Unicode;

/**
 * Contains the custom Solr search form.
 *
 * @package Drupal\mymodule_search\Form
 */
class KeywordForm extends FormBase {

  /**
   * Symfony\Component\HttpFoundation\RequestStack definition.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * Constructor for our class.
   */
  public function __construct(RequestStack $request_stack) {
    $this->requestStack = $request_stack;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('request_stack')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'mymodule_search_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $keywords = Xss::filter($this->requestStack->getCurrentRequest()->get('keywords'));
    $site_ids = $this->requestStack->getCurrentRequest()->get('site_id');
    $sort_by = $this->requestStack->getCurrentRequest()->get('sort_by');
    $sort_order = $this->requestStack->getCurrentRequest()->get('sort_order');
    $search_config = $this->config('mymodule_search.settings');
    $site_id = $search_config->get('site_id');

    $form['#attributes']['class'][] = 'search-result__form';

    if (!is_array($site_ids) || empty($site_ids)) {
      $site_ids = [$site_id => $site_id];
    }

    $input = $form_state->getUserInput();

    if (!empty($input['sort_by'])) {
      $sort_by = $input['sort_by'];
    }
    else {
      $sort_by = 'score';
    }

    if (!empty($input['sort_order'])) {
      $sort_order = $input['sort_order'];
    }
    else {
      $sort_order = 'DESC';
    }

    $form['keywords'] = [
      '#type' => 'textfield',
      '#default_value' => Unicode::strlen($keywords) ? $keywords : '',
      '#prefix' => '<div class="search-box__input js-search-input">',
      '#suffix' => '</div>',
      '#attributes' => [
        'placeholder' => Unicode::strlen($keywords) ? $keywords : t('Search for...'),
      ],
    ];

    $form['site_id'] = [
      '#type' => 'hidden',
      '#value' => $site_ids,
    ];

    $form['sort_by'] = [
      '#type' => 'hidden',
      '#value' => $sort_by,
    ];

    $form['sort_order'] = [
      '#type' => 'hidden',
      '#value' => $sort_order,
    ];

    $form['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Search'),
      '#attributes' => [
        'class' => [
          'search-box__button',
        ],
        'data-twig-suggestion' => 'search_results_submit',
      ],
    ];

    $form['#cache']['contexts'][] = 'session';
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $form_state->setRedirect(
      'mymodule_search.search',
      [
        'keywords' => $form_state->getValue('keywords'),
        'site_id' => $form_state->getValue('site_id'),
        'sort_by' => $form_state->getValue('sort_by'),
        'sort_order' => $form_state->getValue('sort_order'),
      ],
      [
        'query' => [
          'keywords' => $form_state->getValue('keywords'),
          'site_id' => $form_state->getValue('site_id'),
          'sort_by' => $form_state->getValue('sort_by'),
          'sort_order' => $form_state->getValue('sort_order'),
        ],
      ]
    );
  }

}

I don't think I need to have setRedirect in the submit method, but it was the only way to get a "clean URL" query string. When I change to a GET type, kiamlaluno below says you can call $form_state->cleanValues() to remove Drupal specific things from being in the URL, but that has no effect either, and a bunch of stuff is still in the URL.

At the same time, the form is POSTing to the same URL, which triggers buildForm again - the form state will be empty and I can't see what the user just entered, which is undesirable. I can see this with xdebug, buildForm is called twice, the first time you can see all $form_state values, and the second time is empty.

1

This D7 approach also works in D8:

   $form['submit'] = array(
      '#type' => 'submit',
      '#value' => 'Make Payment',
      '#name' => '',
  );

  $form['#after_build'][] = 'mymodule_form_modify';

  function mymodule_form_modify($form){
    unset($form['form_token']);
    unset($form['form_build_id']);
    unset($form['form_id']);
    return $form;
  }

Credits: https://www.drupal.org/node/821932#comment-6017528

0

The FormState class has a method for that, FormState::cleanValues(), which by default cleans out these values:

  • form_id
  • form_token
  • form_build_id
  • op
  • The value associated with every button in the form

If then you need to clean out more values, you can use FormState::addCleanValueKey().

FormState::cleanValues() returns the FormState object, so you can use that method in chained calls, like in the following code, for example.

$values = $form_state->cleanValues()->getValues();
  • Except when I change the method to GET, submitForm is no longer called in my form class. – Kevin Jun 22 '17 at 10:52
  • Let me give it another shot, there may be some weirdness going on and xdebug not triggering. – Kevin Jun 22 '17 at 10:56
  • No, submitForm is not being called when the form is a GET. – Kevin Jun 22 '17 at 12:18
  • I've updated the OG question with an example. – Kevin Jun 22 '17 at 12:40
0

I got this to work by copying the search form from core. The key is in search_form_search_block_form_alter

/**
 * Implements hook_form_FORM_ID_alter() for the search_block_form form.
 *
 * Since the exposed form is a GET form, we don't want it to send the form
 * tokens. However, you cannot make this happen in the form builder function
 * itself, because the tokens are added to the form after the builder function
 * is called. So, we have to do it in a form_alter.
 *
 * @see \Drupal\search\Form\SearchBlockForm
 */
function search_form_search_block_form_alter(&$form, FormStateInterface $form_state) {
  $form['form_build_id']['#access'] = FALSE;
  $form['form_token']['#access'] = FALSE;
  $form['form_id']['#access'] = FALSE;
}

This approach works with $form['#method'] = 'get'; and you don't need a redirect or processing in submitForm.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.