You are here

multiform.module in Multiple forms 7

Same filename and directory in other branches
  1. 8 multiform.module
  2. 7.2 multiform.module

Module file for the multiform module, which creates a forms.

File

multiform.module
View source
<?php

/**
 * @file
 * Module file for the multiform module, which creates a forms.
 */

/**
 * Returns a form containing a number of other forms.
 *
 * When the forms are submitted their first button is pressed in array order.
 * Make sure you rearrange the buttons in your form accordingly in a form_alter
 * when using this function.
 *
 * @param ...
 *   Every argument is a list of arguments to be passed to drupal_get_form.
 *   For example, if the first form is called as
 *   drupal_get_form($form_id1, $arg1, $arg2); and
 *   the second as drupal_get_form($form_id2, $arg3, $arg4) call
 *   multiform_get_form(array($form_id1, $arg1, $arg2), array($form_id2, $arg3, $arg4)).
 */
function multiform_get_form() {
  $all_args = func_get_args();
  $redirect = NULL;
  $form = element_info('form');
  $form['#id'] = 'multiform';
  $form['#attributes'] = array();

  // We need a $form_id so that the submission of other forms (like search) do
  // not disturb us.
  $form['form_id'] = array(
    '#type' => 'hidden',
    '#value' => 'multiform',
    '#id' => drupal_html_id("edit-multiform"),
    '#name' => 'form_id',
    '#attributes' => array(),
  );
  $build_id = 'form-' . drupal_random_key();

  // We need a $form_build_id because the buttons are cached and the values
  // belonging to them in $_POST are handed to each form so those can recognize
  // the buttons pressed.
  $form['form_build_id'] = array(
    '#type' => 'hidden',
    '#value' => $build_id,
    '#id' => $build_id,
    '#name' => 'form_build_id',
    '#attributes' => array(),
  );

  // This is where buttons will be collected.
  $form['buttons'] = array(
    '#type' => 'actions',
    '#build_id' => $build_id,
    '#weight' => 1000,
    '#attributes' => array(),
  );
  $form_state_save = array();
  $button_names = array();

  // The only way to support $_GET would be to accept $form_state. Maybe later.
  if ($_POST && isset($_POST['form_id']) && $_POST['form_id'] == 'multiform' && !empty($_POST['form_build_id'])) {
    $form_state_save['input'] = $_POST;
    $_files_save = $_FILES;

    // Retrieve buttons.
    if ($button_elements = form_get_cache($_POST['form_build_id'], $form_state_save)) {
      foreach ($button_elements as $button) {

        // For each button, save it's name. Later on we will need the button
        // names because the buttons are used in the multiform but their values
        // in $_POST (if it exists) needs to be handed down to each form so
        // those can recognize the button press.
        $name = 'op';
        if (is_array($button)) {
          $name = isset($button['#name']) ? $button['#name'] : 'op';
        }
        else {
          $name = isset($button_elements['#name']) ? $button_elements['#name'] : 'op';
        }
        $button_names[$name] = $name;
      }
    }
  }
  foreach ($all_args as $key => $args) {
    $form_id = array_shift($args);

    // Reset $form_state and disable redirection.
    $form_state = array(
      'no_redirect' => TRUE,
    );

    // This line is copied literally from drupal_get_form().
    $form_state['build_info']['args'] = $args;
    $index = $form_id . '_' . $key;
    if (isset($form_state_save['input']['multiform'][$index])) {

      // drupal_build_form() honors our $form_state['input'] setup.
      $form_state['input'] = $form_state_save['input']['multiform'][$index];

      // Pass in the information about pressed buttons too.
      foreach ($button_names as $name) {
        if (isset($form_state_save['input'][$name])) {
          $form_state['input'][$name] = $form_state_save['input'][$name];
        }
      }
    }
    if (isset($_files_save['multiform']['name'][$index])) {
      $_FILES = array();
      foreach (array(
        'name',
        'type',
        'tmp_name',
        'error',
        'size',
      ) as $files_key) {

        // PHP is seriously messed up, dude.
        foreach ($_files_save['multiform'][$files_key][$index] as $running_out_of_indexes => $data) {
          $_FILES[$running_out_of_indexes][$files_key] = $data;
        }
      }
    }

    // Build and process this form.
    $current_form = drupal_build_form($form_id, $form_state);

    // Do not render the <form> tags. Instead we render the <form> as a <div>.
    $form_wrapper_index = array_search('form', $current_form['#theme_wrappers']);
    if ($form_wrapper_index !== FALSE) {
      $current_form['#theme_wrappers'][$form_wrapper_index] = 'container';
    }
    _multiform_get_form($current_form, $form['buttons'], $index);

    // Unset any attributes specifics to form tags.
    $disallowed_attributes = array(
      'enctype',
      'action',
      'method',
    );
    $current_form['#attributes'] = array_diff_key($current_form['#attributes'], array_flip($disallowed_attributes));
    $form['multiform'][$index] = $current_form;
    if (isset($form_state['has_file_element'])) {
      $form['#attributes']['enctype'] = 'multipart/form-data';
    }

    // Keep the redirect from the first form.
    if (!$key) {
      $redirect = isset($form_state['redirect']) ? $form_state : array();
    }
  }
  form_set_cache($build_id, $form['buttons'], $form_state_save);
  drupal_alter('multiform_get_form', $form_state_save, $redirect, $all_args);
  if (!empty($form_state_save['input'])) {

    // We forced $form_state['no_redirect'] to TRUE above, so unset it in order
    // to allow the redirection to proceed.
    unset($redirect['no_redirect']);
    $errors = form_get_errors();

    // Do not redirect with errors.
    if (empty($errors)) {
      drupal_redirect_form($redirect);
    }
  }
  return $form;
}

/**
 * Recursive helper for multiform_get_form().
 */
function _multiform_get_form(&$element, &$buttons, $form_id) {

  // Recurse.
  foreach (element_children($element) as $key) {
    _multiform_get_form($element[$key], $buttons, $form_id);
  }

  // Save but do not display buttons. Note that this is done before the #name
  // is changed. This way the buttons belong to the top form and their values
  // can be handed to each form.
  if (isset($element['#button_type'])) {
    $buttons[$element['#value']] = $element;
    $element['#access'] = FALSE;
  }
  else {

    // By only changing $element['#name'] form API is not affected but the
    // browser will put the element values into _POST where multiform_get_form
    // expects them.
    if (isset($element['#name'])) {

      // If the name was op then we want multiform[$form_id][op]. If it was
      // foo[bar] then we want multiform[$form_id][foo][bar].
      $element['#name'] = "multiform[{$form_id}]" . preg_replace('/^[^[]+/', '[\\0]', $element['#name']);
    }

    // We repeat this for any $element['#attributes']['name'], which show up in
    // the rendering arrays of certain multi-value select elements.
    if (isset($element['#attributes']['name'])) {
      $element['#attributes']['name'] = "multiform[{$form_id}]" . preg_replace('/^[^[]+/', '[\\0]', $element['#attributes']['name']);
    }
  }
}

Functions

Namesort descending Description
multiform_get_form Returns a form containing a number of other forms.
_multiform_get_form Recursive helper for multiform_get_form().