You are here

msnf.module in Multistep Nodeform 7

Same filename and directory in other branches
  1. 6 msnf.module

Main functions for module "Multistep Nodeform".

File

msnf.module
View source
<?php

/**
 * @file
 * Main functions for module "Multistep Nodeform".
 */

/**
 * Implements hook_menu().
 */
function msnf_menu() {
  $items = array();

  // Ensure the following is not executed until field_bundles is working and
  // tables are updated. Needed to avoid errors on initial installation.
  if (defined('MAINTENANCE_MODE')) {
    return $items;
  }

  // Create tabs for all possible bundles.
  foreach (entity_get_info() as $entity_type => $entity_info) {
    if (isset($entity_info['fieldable']) && $entity_info['fieldable']) {
      foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
        if (isset($bundle_info['admin'])) {

          // Extract path information from the bundle.
          $path = $bundle_info['admin']['path'];

          // Extract the argument position of the bundle name string
          // ('bundle argument') and pass that position to the menu loader. The
          // position needs to be casted into a string; otherwise it would be
          // replaced with the bundle name string.
          if (isset($bundle_info['admin']['bundle argument'])) {
            $bundle_arg = $bundle_info['admin']['bundle argument'];
            $bundle_pos = (string) $bundle_arg;
          }
          else {
            $bundle_arg = $bundle_name;
            $bundle_pos = '0';
          }

          // This is the position of the %msnf_step_menu placeholder in the
          // items below.
          $argument_position = count(explode('/', $path)) + 1;

          // Extract access information, providing defaults.
          $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array(
            'access callback',
            'access arguments',
          )));
          $access += array(
            'access callback' => 'user_access',
            'access arguments' => array(
              'administer site configuration',
            ),
          );
          $items["{$path}/steps/%msnf_step_menu/delete"] = array(
            'load arguments' => array(
              $entity_type,
              $bundle_arg,
              $bundle_pos,
              '%map',
            ),
            'title' => 'Delete',
            'page callback' => 'drupal_get_form',
            'page arguments' => array(
              'msnf_delete_form',
              $argument_position,
            ),
            'type' => MENU_CALLBACK,
            'file' => 'includes/msnf.field_ui.inc',
          ) + $access;
        }
      }
    }
  }
  return $items;
}

/**
 * Implements hook_permission().
 */
function msnf_permission() {
  return array(
    'administer form steps' => array(
      'title' => t('Administer form steps'),
      'description' => t('Display the administration for form steps.'),
    ),
  );
}

/**
 * Implements hook_block_info().
 */
function msnf_block_info() {
  $blocks = array();
  $blocks['msnf_step_data'] = array(
    'info' => t('Multistep Nodeform: Step information'),
    'cache' => DRUPAL_NO_CACHE,
  );
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function msnf_block_view($delta = '') {
  $data = drupal_static('msnf_step_data');
  if (!empty($data)) {
    $block = array();
    $block['subject'] = t('Form steps');
    $block['content'] = array(
      '#theme' => 'msnf_block_step_info',
      '#steps' => $data['steps'],
      '#current_step' => $data['current_step'],
      '#form_state' => $data['form_state'],
      // Attach styling.
      '#attached' => array(
        'css' => array(
          drupal_get_path('module', 'msnf') . '/theme/msnf.steps.css',
        ),
      ),
    );
    return $block;
  }
}

/**
 * Menu Wildcard loader function to load step definitions.
 *
 * @param <string> $step_name
 *   The name of the step, as contained in the path.
 * @param <string> $entity_type
 *   The name of the entity.
 * @param <string> $bundle_name
 *   The name of the bundle, as contained in the path.
 * @param <int> $bundle_pos
 *   The position of $bundle_name in $map.
 * @param <array> $map
 *   The translated menu router path argument map.
 */
function msnf_step_menu_load($step_name, $entity_type, $bundle_name, $bundle_pos, $map) {
  if ($bundle_pos > 0) {
    $bundle = $map[$bundle_pos];
    $bundle_name = field_extract_bundle($entity_type, $bundle);
  }
  return msnf_load_step($step_name, $entity_type, $bundle_name);
}

/**
 * Loads a step definition.
 *
 * @param <string> $step_name
 *   The name of the form step.
 * @param <string> $entity_type
 *   The name of the entity.
 * @param <string> $bundle_name
 *   The name of the bundle.
 */
function msnf_load_step($step_name, $entity_type, $bundle_name) {
  ctools_include('export');
  $objects = ctools_export_load_object('msnf_step', 'conditions', array(
    'step_name' => $step_name,
    'entity_type' => $entity_type,
    'bundle' => $bundle_name,
  ));
  $object = array_shift($objects);
  if ($object && isset($object->data)) {
    return msnf_unpack($object);
  }
  return $object;
}

/**
 * Implements hook_ctools_plugin_api().
 */
function msnf_ctools_plugin_api($owner, $api) {
  if ($owner == 'msnf' && $api == 'msnf') {
    return array(
      'version' => 1,
    );
  }
}

/**
 * Implements hook_theme().
 */
function msnf_theme() {
  return array(
    'msnf_form_step' => array(
      'render element' => 'element',
    ),
    'msnf_block_step_info' => array(
      'variables' => array(
        'steps' => array(),
        'current_step' => NULL,
        'form_state' => array(),
      ),
      'path' => drupal_get_path('module', 'msnf') . '/theme',
      'template' => 'msnf-block-step-info',
    ),
    'msnf_form_step_info' => array(
      'variables' => array(
        'steps' => array(),
        'current_step' => NULL,
      ),
      'path' => drupal_get_path('module', 'msnf') . '/theme',
      'template' => 'msnf-form-step-info',
    ),
  );
}

/**
 * Implements hook_field_attach_delete_bundle().
 *
 * @param <string> $entity_type
 *   Type of entity.
 * @param <string> $bundle
 *   Bundle name.
 */
function msnf_field_attach_delete_bundle($entity_type, $bundle) {
  ctools_include('export');
  $list = msnf_read_steps(array(
    'bundle' => $bundle,
    'entity_type' => $entity_type,
  ));

  // Delete the entity's entry from msnf_step of all entities.
  // We fetch the form steps first to assign the removal task to ctools.
  if (isset($list[$entity_type], $list[$entity_type][$bundle])) {
    foreach ($list[$entity_type][$bundle] as $group) {
      ctools_export_crud_delete('msnf_step', $group);
    }
  }
}

/**
 * Implements hook_field_extra_fields().
 */
function msnf_field_extra_fields() {
  $extra = array();
  foreach (node_type_get_types() as $type) {
    $step_info = msnf_info_steps('node', $type->type);
    if (empty($step_info)) {

      // There are no steps for this type so skip to next.
      continue;
    }
    $extra['node'][$type->type]['form'] = array(
      'additional_settings' => array(
        'label' => t('Additional Information'),
        'description' => t('Node module element'),
        'weight' => 100,
      ),
    );
  }
  return $extra;
}

/**
 * Implements hook_field_attach_form().
 */
function msnf_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {

  // Load steps for this form.
  msnf_attach_steps($form, $form_state);
  if (empty($form['#steps'])) {

    // Nothing to do here.
    return;
  }

  // Init first step if no step is set before.
  if (!isset($form_state['storage']['step'])) {
    $step_names = array_keys($form['#steps']);
    $form_state['storage']['step'] = $step_names[0];
  }

  // If one specific step should be displayed ...
  if (isset($_GET['step'])) {
    $step_requested = filter_xss_admin($_GET['step']);
    _msnf_form_step_set_current($step_requested, $form, $form_state);
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function msnf_form_field_ui_field_overview_form_alter(&$form, &$form_state, $form_id) {
  form_load_include($form_state, 'inc', 'msnf', 'includes/msnf.field_ui');
  msnf_field_ui_overview_form_alter($form, $form_state);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function msnf_form_node_form_alter(&$form, &$form_state, $form_id) {
  if (isset($form['#steps'])) {

    // Init static step data.
    $step_data =& drupal_static('msnf_step_data');
    $step_data = array(
      'steps' => $form['#steps'],
      'current_step' => _msnf_form_step_get_current($form, $form_state),
      'form_state' => $form_state,
    );
  }
  if (empty($form['#steps'])) {

    // No need for further modifications.
    return;
  }

  // Attach styling.
  $form['#attached']['css'][] = drupal_get_path('module', 'msnf') . '/theme/msnf.field_ui.css';

  // Add step buttons to form.
  _msnf_form_attach_buttons($form, $form_state);

  // Hide all elements that do not belong to the current step.
  _msnf_hide_fields($form, $form_state);

  // Restore field values.
  _msnf_restore_values($form, $form_state);

  // Add a custom validation handler to rebuild the form.
  $form['#validate'][] = 'msnf_entity_form_validate';
}

/**
 * Implements hook_msnf_formatter_info().
 */
function msnf_msnf_formatter_info() {
  return array(
    'default' => array(
      'label' => t('Default'),
      'description' => t('The form step renders the containing form elements in a single div with a title and description.'),
      'instance_settings' => array(
        'description' => '',
        'show_label' => 1,
        'label_element' => 'h3',
        'classes' => '',
        'skip_non_required' => 1,
        'hide_if_empty' => 0,
        'buttons' => array(
          'previous' => t('Back'),
          'next' => t('Next'),
          'skip' => t('Skip next step'),
        ),
      ),
    ),
  );
}

/**
 * Implements hook_msnf_format_settings().
 * If the step has no format settings, default ones will be added.
 *
 * @params <object> $step
 *   The step object.
 *
 * @return <array> $form
 *   The form element for the format settings.
 */
function msnf_msnf_format_settings($step) {

  // Add a wrapper for extra settings to use by others.
  $form = array(
    'instance_settings' => array(
      '#tree' => TRUE,
      '#weight' => 2,
    ),
  );
  $step_types = msnf_formatter_info();
  $formatter = $step_types[$step->format_type];
  $form['instance_settings']['skip_non_required'] = array(
    '#type' => 'checkbox',
    '#title' => t('Allow skipping this step if it contains no required fields.'),
    '#default_value' => isset($step->format_settings['instance_settings']['skip_non_required']) ? $step->format_settings['instance_settings']['skip_non_required'] : (isset($formatter['instance_settings']['skip_non_required']) ? $formatter['instance_settings']['skip_non_required'] : ''),
    '#weight' => 1,
  );
  $form['instance_settings']['hide_if_empty'] = array(
    '#type' => 'checkbox',
    '#title' => t('Hide the entire step if it contains no fields or none of the fields is accessible by the user.'),
    '#default_value' => isset($step->format_settings['instance_settings']['hide_if_empty']) ? $step->format_settings['instance_settings']['hide_if_empty'] : (isset($formatter['instance_settings']['hide_if_empty']) ? $formatter['instance_settings']['hide_if_empty'] : ''),
    '#weight' => 1,
  );
  if (isset($formatter['instance_settings']['classes'])) {
    $form['instance_settings']['classes'] = array(
      '#title' => t('Extra CSS classes'),
      '#type' => 'textfield',
      '#default_value' => isset($step->format_settings['instance_settings']['classes']) ? $step->format_settings['instance_settings']['classes'] : (isset($formatter['instance_settings']['classes']) ? $formatter['instance_settings']['classes'] : ''),
      '#weight' => 3,
      '#element_validate' => array(
        'msnf_validate_css_class',
      ),
    );
  }
  if (isset($formatter['instance_settings']['description'])) {
    $form['instance_settings']['description'] = array(
      '#title' => t('Step description'),
      '#type' => 'textarea',
      '#rows' => 3,
      '#default_value' => isset($step->format_settings['instance_settings']['description']) ? $step->format_settings['instance_settings']['description'] : (isset($formatter['instance_settings']['description']) ? $formatter['instance_settings']['description'] : ''),
      '#weight' => 0,
    );
  }

  // Add optional instance_settings.
  switch ($step->format_type) {
    case 'default':
      $form['instance_settings']['show_label'] = array(
        '#title' => t('Show label'),
        '#type' => 'select',
        '#options' => array(
          0 => t('No'),
          1 => t('Yes'),
        ),
        '#default_value' => isset($step->format_settings['instance_settings']['show_label']) ? $step->format_settings['instance_settings']['show_label'] : $formatter['instance_settings']['show_label'],
        '#weight' => 2,
      );
      $form['instance_settings']['label_element'] = array(
        '#title' => t('Label element'),
        '#type' => 'select',
        '#options' => array(
          'h2' => t('Header 2'),
          'h3' => t('Header 3'),
        ),
        '#default_value' => isset($step->format_settings['instance_settings']['label_element']) ? $step->format_settings['instance_settings']['label_element'] : $formatter['instance_settings']['label_element'],
        '#weight' => 2,
      );
      $form['instance_settings']['buttons'] = array(
        '#title' => t('Button labels'),
        '#type' => 'fieldset',
        '#tree' => TRUE,
        '#weight' => 3,
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );
      $form['instance_settings']['buttons']['previous'] = array(
        '#title' => t('Back'),
        '#type' => 'textfield',
        '#description' => t('Text to use as label for back-button. Leave empty to use the title of the previous step.'),
        '#default_value' => isset($step->format_settings['instance_settings']['buttons']['previous']) ? $step->format_settings['instance_settings']['buttons']['previous'] : (isset($formatter['instance_settings']['buttons']['previous']) ? $formatter['instance_settings']['buttons']['previous'] : ''),
        '#weight' => 0,
      );
      $form['instance_settings']['buttons']['next'] = array(
        '#title' => t('Next'),
        '#type' => 'textfield',
        '#description' => t('Text to use as label for next-button. Leave empty to use the title of the next step.'),
        '#default_value' => isset($step->format_settings['instance_settings']['buttons']['next']) ? $step->format_settings['instance_settings']['buttons']['next'] : (isset($formatter['instance_settings']['buttons']['next']) ? $formatter['instance_settings']['buttons']['next'] : ''),
        '#weight' => 1,
      );
      $form['instance_settings']['buttons']['skip'] = array(
        '#title' => t('Skip'),
        '#type' => 'textfield',
        '#description' => t('Text to use as label for skip-button.'),
        '#default_value' => isset($step->format_settings['instance_settings']['buttons']['skip']) ? $step->format_settings['instance_settings']['buttons']['skip'] : (isset($formatter['instance_settings']['buttons']['skip']) ? $formatter['instance_settings']['buttons']['skip'] : ''),
        '#weight' => 2,
      );
      break;
    default:
  }
  return $form;
}

/**
 * Helper function to prepare basic variables needed for most formatters.
 */
function msnf_pre_render_prepare(&$step) {

  // Prepare extra classes.
  $step->classes = array(
    'step-' . $step->format_type,
    str_replace('_', '-', $step->step_name),
  );
  if (isset($step->format_settings['instance_settings']) && !empty($step->format_settings['instance_settings']['skip_non_required'])) {
    $step->classes[] = 'skippable';
  }
  $step->classes = implode(' ', $step->classes);
  if (isset($step->format_settings['instance_settings'], $step->format_settings['instance_settings']['classes'])) {
    $step->classes .= ' ' . check_plain($step->format_settings['instance_settings']['classes']);
  }
  $step->description = isset($step->format_settings['instance_settings']['description']) ? filter_xss_admin($step->format_settings['instance_settings']['description']) : '';
}

/**
 * Implements hook_msnf_step_pre_render().
 *
 * @param <array> $elements
 *   Array of elements to render.
 * @param <object> $step
 *   The step info.
 * @param <array> $form
 *   The form where the element needs to be rendered.
 */
function msnf_msnf_step_pre_render(&$element, &$step, &$form) {

  // Prepare step rendering.
  msnf_pre_render_prepare($step);
  $element['#id'] = $form['#entity_type'] . '_' . $form['#bundle'] . '_' . $step->step_name;
  $element['#weight'] = $step->weight;

  // Call the pre render function for the format type.
  $function = "msnf_step_pre_render_" . str_replace("-", "_", $step->format_type);
  if (function_exists($function)) {
    $function($element, $step, $form);
  }
}

/**
 * Implements msnf_step_pre_render_<format-type>.
 * Format type: 'default'.
 *
 * @param <array> $element
 *   The step form element.
 * @param <object> $step
 *   The step object prepared for pre_render.
 * @param <string> $form
 *   The root element or form.
 */
function msnf_step_pre_render_default(&$element, $step, &$form) {
  $element['msnf_form_step_info'] = array(
    '#theme' => 'msnf_form_step_info',
    '#steps' => $form['#steps'],
    '#current_step' => $step,
    '#weight' => -100,
  );
}

/**
 * Implements hook_msnf_format_summary().
 */
function msnf_msnf_format_summary($step) {
  $step_form = module_invoke_all('msnf_format_settings', $step);
  $output = '';
  if (isset($step->format_settings['instance_settings'])) {
    $last = end($step->format_settings['instance_settings']);
    foreach ($step->format_settings['instance_settings'] as $key => $value) {
      if (!is_numeric($value) && empty($value)) {
        continue;
      }
      $output .= '<strong>' . $key . '</strong> ';
      if (isset($step_form['instance_settings'], $step_form['instance_settings'][$key]['#options'])) {
        $value = $step_form['instance_settings'][$key]['#options'][$value];
      }
      if (is_array($value)) {
        $output .= '<br />';
        $last_value = end($value);
        foreach ($value as $vkey => $vvalue) {
          $output .= '&nbsp;&nbsp;<strong>' . $vkey . '</strong> ';

          // Shorten the string.
          if (drupal_strlen($vvalue) > 38) {
            $vvalue = truncate_utf8($vvalue, 50, TRUE, TRUE);
          }
          elseif (is_numeric($value)) {
            $vvalue = $vvalue == '1' ? t('yes') : t('no');
          }
          $output .= check_plain($vvalue);
          $output .= $last_value == $vvalue ? ' ' : '<br />';
        }
        continue;
      }

      // Shorten the string.
      if (drupal_strlen($value) > 38) {
        $value = truncate_utf8($value, 50, TRUE, TRUE);
      }
      elseif (is_numeric($value)) {
        $value = $value == '1' ? t('yes') : t('no');
      }
      $output .= check_plain($value);
      $output .= $last == $value ? ' ' : '<br />';
    }
  }
  return $output;
}

/**
 * Implements hook_element_info().
 */
function msnf_element_info() {
  $types['msnf_form_step'] = array(
    '#theme_wrappers' => array(
      'msnf_form_step',
    ),
    '#default_tab' => '',
    '#process' => array(
      'msnf_form_process_msnf_form_step',
    ),
  );
  return $types;
}

/**
 * Implements hook_field_attach_rename_bundle().
 */
function msnf_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
  db_query('UPDATE {msnf_step} SET bundle = :bundle WHERE bundle = :old_bundle AND entity_type = :entity_type', array(
    ':bundle' => $bundle_new,
    ':old_bundle' => $bundle_old,
    ':entity_type' => $entity_type,
  ));
}

/**
 * Creates a single form step.
 *
 * @param <array> $element
 *   An associative array containing the properties and children of the step.
 * @param <array> $form_state
 *   The $form_state array for the form this widget belongs to.
 *
 * @return <array>
 *   The processed element.
 */
function msnf_form_process_msnf_form_step($element, &$form_state) {

  // Inject a new element as child.
  $element['step'] = array(
    '#type' => 'markup',
    '#theme_wrappers' => array(),
    '#parents' => $element['#parents'],
  );
  return $element;
}

/**
 * Returns HTML for a single form step.
 *
 * @param <array> $variables
 *   An associative array containing:
 *   - element: An associative array containing the properties and children of
 *     the form included in the step. Properties used: #children.
 *
 * @ingroup themeable
 */
function theme_msnf_form_step($variables) {
  $element = $variables['element'];
  $output = $element['#children'];
  return $output;
}

/**
 * Define variables used in msnf-block-step-info.tpl.php.
 *
 * @param <array> $vars
 *   Variables available for use in msnf-block-step-info.tpl.php.
 *
 * @see msnf-block-step-info.tpl.php.
 */
function msnf_preprocess_msnf_block_step_info(&$vars) {
  $items = array();
  foreach ($vars['steps'] as $step_name => $step) {
    $item = array(
      'data' => msnf_translate(array(
        $step->step_name,
        $step->bundle,
        'label',
      ), $step->label),
      'title' => msnf_translate(array(
        $step->step_name,
        $step->bundle,
        'description',
      ), $step->format_settings['instance_settings']['description']),
      'class' => array(
        'msnf-form-step',
      ),
    );
    if ($step->identifier == $vars['current_step']->identifier) {

      // Add "active" class for current step.
      $item['class'][] = 'active';
    }
    $items[] = $item;
  }
  $vars['steps_rendered'] = theme('item_list', array(
    'items' => $items,
    'attributes' => array(
      'class' => array(
        'msnf-forms-steps',
      ),
    ),
  ));
}

/**
 * Define variables used in msnf-form-step-info.tpl.php.
 */
function msnf_preprocess_msnf_form_step_info(&$vars) {
  $vars['step_classes_array'] = array();
  $vars['step_classes_array'][] = 'step-format';

  // Get step settings.
  $vars['show_step_title'] = isset($vars['current_step']->format_settings['instance_settings']['show_label']) ? $vars['current_step']->format_settings['instance_settings']['show_label'] : 0;
  $vars['title_element'] = isset($vars['current_step']->format_settings['instance_settings']['label_element']) ? $vars['current_step']->format_settings['instance_settings']['label_element'] : 'h2';

  // Add classes defined in step configuration.
  $vars['step_classes_array'][] = filter_xss($vars['current_step']->classes);
  $vars['step_title'] = msnf_translate(array(
    $vars['current_step']->step_name,
    $vars['current_step']->bundle,
    'label',
  ), $vars['current_step']->label);
  $vars['step_description'] = msnf_translate(array(
    $vars['current_step']->step_name,
    $vars['current_step']->bundle,
    'description',
  ), $vars['current_step']->description);
  $vars['step_classes'] = implode(' ', $vars['step_classes_array']);

  // Which step is this (number).
  $vars['current_position'] = array_search($vars['current_step']->step_name, array_keys($vars['steps'])) + 1;
  $vars['step_count'] = count($vars['steps']);
}

/**
 * Get all form steps.
 *
 * @param <string> $entity_type
 *   The name of the entity.
 * @param <string> $bundle
 *   The name of the bundle.
 * @param <boolean> $reset
 *   Whether to reset the cache or not.
 * @param <array> $form
 *   The form the steps are (or will be) attached to.
 * @param <array> $form
 *   The form the steps are (or will be) attached to.
 *
 * @return <array>
 *   List of all defined steps for the given entity type and bundle.
 */
function msnf_info_steps($entity_type, $bundle = NULL, $reset = FALSE, $form = array(), $form_state = array()) {
  $steps_cached =& drupal_static(__FUNCTION__, FALSE);
  if (!$steps_cached || $reset) {
    if (!$reset && ($cached = cache_get('msnf_steps', 'cache_field'))) {
      $steps_cached = $cached->data;
    }
    else {
      $steps_cached = msnf_read_steps();
      cache_set('msnf_steps', $steps_cached, 'cache_field');
    }
  }

  // Allow other modules to alter step information.
  $context = array(
    'form' => $form,
    'form_state' => $form_state,
    'entity_type' => $entity_type,
    'bundle' => $bundle,
  );
  drupal_alter('msnf_info_steps', $steps_cached, $context);
  if (isset($steps_cached[$entity_type])) {
    if (isset($steps_cached[$entity_type][$bundle])) {
      return $steps_cached[$entity_type][$bundle];
    }
    elseif ($bundle === NULL) {
      return $steps_cached[$entity_type];
    }
  }
  return array();
}

/**
 * Read all form steps.
 *
 * @param <array> $params
 *   Parameters for the query
 *   - $name The name of the entity.
 *   - $bundle The name of the bundle.
 *
 * @return <array>
 *   Array with all available steps keyed by entity type.
 */
function msnf_read_steps($params = array()) {
  $steps = array();
  ctools_include('export');
  if (empty($params)) {
    $records = ctools_export_load_object('msnf_step');
  }
  else {
    $records = ctools_export_load_object('msnf_step', 'conditions', $params);
  }
  foreach ($records as $step) {

    // Deleted form steps.
    if (isset($step->disabled) && $step->disabled) {
      continue;
    }
    $steps[$step->entity_type][$step->bundle][$step->step_name] = msnf_unpack($step);
  }
  return $steps;
}

/**
 * Checks if a form step exists in required context.
 *
 * @param <string> $step_name
 *   The name of the form step.
 * @param <string> $entity_type
 *   The name of the entity.
 * @param <string> $bundle
 *   The bundle for the entity.
 */
function msnf_step_exists($step_name, $entity_type, $bundle) {
  $steps = msnf_read_steps();
  return !empty($steps[$entity_type][$bundle][$step_name]);
}

/**
 * Unpacks a database row in a Step object.
 * @param <object> $step
 *   Database result object with stored step data.
 * @return <object> $step
 *   A form step object.
 */
function msnf_unpack($step) {

  // Extract unserialized data.
  if (isset($step->data)) {
    $data = $step->data;
    unset($step->data);
    $step->label = $data['label'];
    $step->weight = $data['weight'];
    $step->children = $data['children'];
    $step->format_type = !empty($data['format_type']) ? $data['format_type'] : 'default';
    if (isset($data['format_settings'])) {
      $step->format_settings = $data['format_settings'];
    }
    if (module_exists('i18n_string')) {
      $identifier = sprintf('msnf_step:%s:%s:label', $step->step_name, $step->bundle);
      $step->label = i18n_string_translate($identifier, $step->label);
    }
  }
  return $step;
}

/**
 * Packs a Step object into a database row.
 * @param <object> $step
 *   The object to pack.
 * @return <object> $record
 *   Database row object, ready to be inserted/updated.
 */
function msnf_pack($step) {
  $record = clone $step;
  $record->data = array(
    'label' => $record->label,
    'weight' => $record->weight,
    'children' => $record->children,
    'format_type' => !empty($record->format_type) ? $record->format_type : 'default',
  );
  if (isset($record->format_settings)) {
    $record->data['format_settings'] = $record->format_settings;
  }
  return $record;
}

/**
 * Delete a single step.
 *
 * @param <object> $step
 *   A step definition.
 * @param <boolean> $ctools_crud
 *   Whether this function is called by the ctools crud delete.
 */
function msnf_step_export_delete($step, $ctools_crud = TRUE) {
  $query = db_delete('msnf_step');
  if (isset($step->identifier)) {
    $query
      ->condition('identifier', $step->identifier);
    if (!$ctools_crud) {
      ctools_export_crud_disable('msnf_step', $step->identifier);
    }
  }
  elseif (isset($step->id)) {
    $query
      ->condition('id', $step->id);
  }
  $query
    ->execute();
  cache_clear_all('msnf_steps', 'cache_field');
  module_invoke_all('msnf_delete_step', $step);
}

/**
 * Saves a single step definition.
 *
 * @param <object> $step
 *   The step definition to save.
 */
function msnf_step_save(&$step) {

  // Prepare the record.
  $object = msnf_pack($step);
  if (isset($object->export_type) && $object->export_type & EXPORT_IN_DATABASE) {

    // Existing record.
    $update = array(
      'id',
    );
    module_invoke_all('msnf_update_step', $object);
  }
  else {

    // New record.
    $update = array();
    $object->export_type = EXPORT_IN_DATABASE;
    module_invoke_all('msnf_create_step', $object);
  }

  // Write the record.
  return drupal_write_record('msnf_step', $object, $update);
}

/**
 * Function to retrieve all format possibilities for the form steps.
 */
function msnf_formatter_info() {
  $cache =& drupal_static(__FUNCTION__, array());
  if (empty($cache)) {
    if ($cached = cache_get('msnf_formatter_info', 'cache_field')) {
      $formatters = $cached->data;
    }
    else {
      $formatters = array();
      $formatters += module_invoke_all('msnf_formatter_info');
      cache_set('msnf_formatter_info', $formatters, 'cache_field');
    }
    $cache = $formatters;
  }
  return $cache;
}

/**
 * Attach steps to the (form) build.
 *
 * @param <array> $element
 *   The part of the form.
 */
function msnf_attach_steps(&$element, &$form_state) {
  $entity_type = $element['#entity_type'];
  $bundle = $element['#bundle'];

  // Retrieve step information for this entity type and bundleand attach it to
  // the element.
  $steps = msnf_info_steps($entity_type, $bundle, $element, $form_state);
  $element['#steps'] = _msnf_remove_empty_steps($steps, $element, $form_state);

  // Sort steps by weight.
  uasort($element['#steps'], '_msnf_step_sort');

  // Create a lookup array.
  $step_children = array();
  foreach ($element['#steps'] as $step_name => $step) {
    foreach ($step->children as $child) {
      $step_children[$child] = $step_name;
    }
  }
  $element['#step_children'] = $step_children;

  // Add a pre render callback.
  $element['#pre_render'][] = 'msnf_build_pre_render';
}

/**
 * Process callback.
 */
function msnf_build_pre_render($element) {

  // Skip the nesting and step functions if no steps are defined.
  // This could be because you don't see them in the UI or programmatically.
  if (empty($element['#steps'])) {
    return $element;
  }

  // Nest the fields in the corresponding steps.
  msnf_fields_nest($element);

  // Allow others to alter the pre_rendered build.
  drupal_alter('msnf_build_pre_render', $element);
  return $element;
}

/**
 * Recursive function to nest fields in the steps.
 *
 * This function will take out all the elements in the form and
 * place them in the correct container element.
 *
 * @param <array> $element
 *   The current element to analyse.
 */
function msnf_fields_nest(&$element) {

  // Create all steps and keep a flat list of references to these steps.
  $step_references = array();
  foreach ($element['#steps'] as $step_name => $step) {
    $element[$step_name] = array();
    $step_references[$step_name] =& $element[$step_name];
  }

  // Move all children to their parents. Use the flat list of references for
  // direct access as we don't know where in the root_element hierarchy the
  // parent currently is situated.
  foreach ($element['#step_children'] as $child_name => $parent_name) {

    // Block denied fields (#access) before they are put in groups.
    // Fields (not groups) that don't have children (like field_permissions) are removed
    // in field_group_field_group_build_pre_render_alter.
    if (isset($element[$child_name]) && (!isset($element[$child_name]['#access']) || $element[$child_name]['#access'])) {

      // If this is a group, we have to use a reference to keep the reference
      // list intact (but if it is a field we don't mind).
      $step_references[$parent_name][$child_name] =& $element[$child_name];
    }

    // The child has been copied to its parent: remove it from the root element.
    unset($element[$child_name]);
  }

  // Bring extra element wrappers to achieve a grouping of fields.
  // This will mainly be prefix and suffix altering.
  foreach ($element['#steps'] as $step_name => $step) {
    msnf_pre_render($step_references[$step_name], $step, $element);
  }
}

/**
 * Function to pre render the step element.
 *
 * @param <array> $element
 *   Element to render.
 * @param <object> $step
 *   The current step object.
 * @param <array> $form
 *   The form that contains the element.
 *
 * @see msnf_fields_nest()
 */
function msnf_pre_render(&$element, $step, &$form) {

  // Only run the pre_render function if the step has elements.
  if ($element == array()) {
    return;
  }

  // Let modules define their wrapping element.
  // Note that the step element has no properties, only elements.
  foreach (module_implements('msnf_step_pre_render') as $module) {
    $function = $module . '_msnf_step_pre_render';
    if (function_exists($function)) {

      // The intention here is to have the opportunity to alter the
      // elements, as defined in hook_msnf_formatter_info.
      // Note, implement $element by reference!
      $function($element, $step, $form);
    }
  }

  // Allow others to alter the pre_render.
  drupal_alter('msnf_pre_render', $element, $step, $form);
}

/**
 * Custom validation callback.
 *
 * @param <array> $form
 *   Form to validate.
 * @param <array> $form_state
 *   Current form state.
 */
function msnf_entity_form_validate($form, &$form_state) {
  if (!isset($form['#steps']) || count($form['#steps']) == 0) {

    // Nothing to do here.
    return;
  }
  if (!isset($form_state['triggering_element']['#name']) || !in_array($form_state['triggering_element']['#name'], array(
    'previous',
    'skip',
    'next',
  ))) {
    return;
  }
  $form_state['rebuild'] = TRUE;
  $form_state['cache'] = TRUE;
  if (isset($form_state['node'])) {

    // Update node object based on submitted form values.
    entity_form_submit_build_entity('node', $form_state['node'], $form, $form_state);
  }
  if (($current_step = _msnf_form_step_get_current($form, $form_state)) === FALSE) {

    // Step not found. Do nothing.
    return;
  }
  $step_names = array_keys($form['#steps']);
  $step_index = array_search($current_step->step_name, $step_names);
  if ($form_state['triggering_element']['#name'] == 'next') {
    $step_index++;
  }
  elseif ($form_state['triggering_element']['#name'] == 'skip') {
    $step_index += 2;
  }
  elseif ($form_state['triggering_element']['#name'] == 'previous') {
    $step_index--;
  }
  if (isset($step_names[$step_index])) {

    // Set name of next step to display.
    $form_state['storage']['step'] = $step_names[$step_index];
  }
  else {

    // Display first step.
    $form_state['storage']['step'] = array_shift($step_names);
  }
}

/**
 * Helper function to get the current form step from $form_state.
 *
 * @param <array> $form
 *   The form where the step has been defined.
 * @param <array> $form_state
 *   Current form state.
 *
 * @return <object>
 *   The current form step or <code>FALSE</code> if the step could not be found.
 */
function _msnf_form_step_get_current($form, $form_state) {
  if (!isset($form['#steps']) || count($form['#steps']) == 0) {

    // Nothing to do here.
    return FALSE;
  }

  // Get all defined steps for this form.
  $steps = $form['#steps'];
  if (!isset($form_state['storage']['step'])) {
    return array_shift($steps);
  }
  if (isset($steps[$form_state['storage']['step']])) {
    return $steps[$form_state['storage']['step']];
  }

  // Fallback, step not found.
  return FALSE;
}

/**
 * Helper method to set the current form step.
 *
 * The step is set to active only, if all preceding steps are skippable. If one
 * of the preceding steps is not skippable the first skippable step before the
 * requested is activated.
 *
 * @param <string> $step_name
 *   Name of step to set active.
 * @param <array> $form
 *   Reference to current entity form.
 * @param <array> $form_state
 *   Reference to current form state.
 * @param <int> $limit
 *   Maximum number of function calls to prevent recursion. Internally used
 *   only.
 */
function _msnf_form_step_set_current($step_name, &$form, &$form_state, $limit = 10) {
  if (!empty($form_state['triggering_element']) || $limit <= 0) {

    // Do not set the current step if the form has been submitted or the
    // recursion limit has been reached.
    return;
  }
  $step_names = isset($form['#steps']) ? array_keys($form['#steps']) : array();
  if (!in_array($step_name, $step_names) || !isset($form_state['storage']['step'])) {
    return;
  }

  // Check if there is any step before the requested one that has empty
  // required fields.
  $skippable_steps = _msnf_steps_get_skippable($form, $form_state);
  $steps_required = array_values(array_diff($step_names, array_keys($skippable_steps)));

  // Position of step in all steps.
  $step_position = array_search($step_name, $step_names);

  // Get position of first required step.
  $first_required_step = reset($steps_required);
  $step_position_required = array_search($first_required_step, $step_names);
  if ($step_position > $step_position_required || count($steps_required) > 0 && $step_position === FALSE) {

    // Try to activate the first required step.
    _msnf_form_step_set_current($first_required_step, $form, $form_state, --$limit);
  }
  else {

    // The requested step is not in or at first position of the list of
    // required steps.
    $form_state['storage']['step'] = $step_name;
  }
}

/**
 * Helper method to add the required buttons to a form.
 *
 * @param <array> $form
 *   The form where the buttons will be attached to.
 * @param <array> $form_state
 *   Current form state.
 */
function _msnf_form_attach_buttons(&$form, &$form_state) {
  $steps = $form['#steps'];
  if (($current_step = _msnf_form_step_get_current($form, $form_state)) === FALSE) {

    // Step not found. Do nothing.
    return;
  }
  $step_settings = $current_step->format_settings['instance_settings'];

  // Translate button labels.
  foreach ($step_settings['buttons'] as $key => $label) {
    if (empty($label)) {
      continue;
    }
    $step_settings['buttons'][$key] = msnf_translate(array(
      $current_step->step_name,
      $current_step->bundle,
      "button_{$key}",
    ), $label);
  }

  // Try to load previous and next step.
  $previous_step = FALSE;
  $next_step = FALSE;
  $step_names = array_keys($steps);
  $current_index = array_search($current_step->step_name, $step_names);
  if ($current_index > 0 && isset($step_names[$current_index - 1])) {

    // Seems we have a step before the current one.
    $previous_step = $steps[$step_names[$current_index - 1]];
  }
  if ($current_index < count($steps) - 1 && isset($step_names[$current_index + 1])) {

    // Seems we have a step after the current one.
    $next_step = $steps[$step_names[$current_index + 1]];
  }

  // Update button labels if needed.
  if ($next_step && $step_settings['buttons']['next'] !== FALSE && drupal_strlen($step_settings['buttons']['next']) == 0) {

    // Set label of step as button label.
    $step_settings['buttons']['next'] = msnf_translate(array(
      $next_step->step_name,
      $next_step->bundle,
      'label',
    ), $next_step->label);
  }
  if ($previous_step && $step_settings['buttons']['previous'] !== FALSE && drupal_strlen($step_settings['buttons']['previous']) == 0) {

    // Set label of step as button label.
    $step_settings['buttons']['previous'] = msnf_translate(array(
      $previous_step->step_name,
      $previous_step->bundle,
      'label',
    ), $previous_step->label);
  }

  // Todo: 'actions' is not defined in all entity forms (e.g. term).
  if (!isset($form['actions'])) {
    $form['actions'] = array(
      '#type' => 'actions',
    );
  }

  // Create a list of all buttons the form may have.
  if ($current_index > 0 && $step_settings['buttons']['previous'] !== FALSE) {

    // Add "previous" button.
    $form['actions']['previous'] = array(
      '#type' => 'submit',
      '#access' => TRUE,
      '#value' => $step_settings['buttons']['previous'],
      '#weight' => 4,
      '#name' => 'previous',
      '#submit' => array(),
      '#limit_validation_errors' => array(),
    );
  }

  // Get the list of steps that may be skipped (no required fields and admin
  // decided to make this step skippable.
  $skippable_steps = _msnf_steps_get_skippable($form, $form_state);

  // Get list of remaining steps.
  $remaining_steps = array_slice($step_names, $current_index + 1);
  if ($current_index < count($steps) - 1) {

    // Add "next" button.
    if ($step_settings['buttons']['next'] !== FALSE) {
      $form['actions']['next'] = array(
        '#type' => 'submit',
        '#access' => TRUE,
        '#value' => $step_settings['buttons']['next'],
        '#weight' => 7,
        '#name' => 'next',
      );
    }

    // Is skipping the next step allowed?
    $skip_next = isset($skippable_steps[$next_step->step_name]);
    $skip_next &= $next_step->format_settings['instance_settings']['skip_non_required'];

    // The last step may never be skipped.
    $skip_next &= $current_index < count($steps) - 2;
    if ($skip_next && $step_settings['buttons']['skip'] !== FALSE) {

      // Add button to skip the next step.
      $form['actions']['skip'] = array(
        '#type' => 'submit',
        '#access' => TRUE,
        '#value' => $step_settings['buttons']['skip'],
        '#weight' => 8,
        '#name' => 'skip',
      );

      // Todo. If all further steps may be skipped ...
    }
  }
  if (isset($form['actions']['submit']) && $current_index != count($steps) - 1) {

    // Hide save button until we display the last step or all further steps may
    // be skipped.
    $form['actions']['submit']['#access'] = count(array_diff($remaining_steps, array_keys($skippable_steps))) == 0;
  }
}

/**
 * Hide all fields that are not associated to the current step.
 *
 * @param <array> $form
 *   Form to hide the fields from.
 * @param <array> $form_state
 *   Current form state.
 */
function _msnf_hide_fields(&$form, &$form_state) {
  if (($current_step = _msnf_form_step_get_current($form, $form_state)) === FALSE) {

    // Step not found. Do nothing.
    return;
  }

  // Get the names of all form children.
  $form_elements = element_children($form);

  // Hide all elements that do not belong to the current step.
  foreach ($form_elements as $element_name) {
    if ('title' !== $element_name && (empty($form[$element_name]['#type']) || in_array($form[$element_name]['#type'], array(
      'value',
      'actions',
      'token',
      'hidden',
    )))) {

      // Value elements and actions needs to be present on each step.
      continue;
    }
    if (isset($form['#step_children'][$element_name]) && $form['#step_children'][$element_name] == $current_step->step_name) {

      // Element is a child of the current step.
      continue;
    }
    if (!empty($form[$element_name]['#group']) && in_array($form[$element_name]['#group'], $current_step->children)) {

      // Element is part of a group within this step. Display the element.
      continue;
    }

    // Maybe the element is in a field group?
    if (module_exists('field_group') && isset($form['#fieldgroups'])) {
      foreach ($form['#fieldgroups'] as $group_name => $group) {
        if (isset($form['#step_children'][$group_name]) && $form['#step_children'][$group_name] == $current_step->step_name && _msnf_fieldgroup_has_element($form['#fieldgroups'], $group, $element_name)) {

          // Element is a child of a fieldgroup which is a child of this step.
          continue 2;
        }
      }
    }

    // Hide the element.
    $form[$element_name]['#access'] = FALSE;

    // Make sure the element is not required at this time!
    _msnf_element_unset_required($form[$element_name]);
  }

  // Todo: hide preview on step change.
}

/**
 * Helper function to check whether an element is nested within a fieldgroup or
 * one of the fieldgroups child groups.
 *
 * @param <array> $groups
 *   Array with all available groups (needed for recursion).
 * @param <object> $group
 *   Group to search for the element.
 * @param <string> $element_name
 *   Name of form element to find in $group.
 * @param <int> $depth
 *   Maximum number of levels to recurse. Internally used only.
 *
 * @return <boolean>
 *   TRUE if $element_name is in $group or one of its child groups, otherwise
 *   FALSE.
 */
function _msnf_fieldgroup_has_element($groups, $group, $element_name, $depth = 10) {
  if ($depth <= 0) {

    // Recursion limit reached.
    return FALSE;
  }
  $in_group = FALSE;

  // Element is direct child of group?
  $in_group = $in_group || in_array($element_name, $group->children);
  if (!$in_group) {

    // Check child groups.
    foreach ($group->children as $child_group_name) {
      if (isset($groups[$child_group_name])) {
        $in_group = $in_group || _msnf_fieldgroup_has_element($groups, $groups[$child_group_name], $element_name, --$depth);
      }
    }
  }
  return $in_group;
}

/**
 * Helper method to make an element and all its children optional.
 *
 * @param <array> $element
 *   The element to make optional.
 * @param <int> $depth
 *   Maximum depth of children to process. Internally used.
 */
function _msnf_element_unset_required(&$element, $depth = 10) {
  if (isset($element['#required'])) {
    $element['#required'] = FALSE;
  }
  if ($depth <= 0) {

    // Make sure we do not recurse forever.
    return;
  }
  foreach (element_children($element) as $key) {
    _msnf_element_unset_required($element[$key], --$depth);
  }
}

/**
 * Restore field values.
 *
 * @param <array> $form
 *   The form to restore the values on.
 * @param <array> $form_state
 *   Current form state.
 */
function _msnf_restore_values(&$form, &$form_state) {
  if (isset($form_state['node'])) {
    $form += _field_invoke_default('form', 'node', $form_state['node'], $form, $form_state);
  }
}

/**
 * Helper function to get all steps that may be skipped.
 *
 * @param array $form
 *   The form containing the steps to be checked.
 *
 * @return <array>
 *   Array with all skippable steps for the given form.
 * @param <array> $form_state
 *   Current form state.
 */
function _msnf_steps_get_skippable($form, $form_state) {
  if (!isset($form['#steps']) || count($form['#steps']) == 0) {

    // Nothing to do here.
    return array();
  }
  $skippable = array();
  foreach ($form['#steps'] as $step_name => $step) {
    if ($step->format_settings['instance_settings']['skip_non_required']) {
      $has_required = FALSE;

      // Check elements attached to this step.
      foreach ($step->children as $element) {
        if (!isset($form[$element]) && !isset($form['#fieldgroups'][$element])) {
          continue;
        }
        if (isset($form['#fieldgroups'][$element])) {

          // Element is a fieldgroup so process its children.
          foreach ($form['#group_children'] as $element_name => $group) {
            if ($group == $element && isset($form[$element_name])) {
              $has_required = $has_required || _msnf_element_required($form, $element_name);
            }
          }
        }
        else {
          $has_required = $has_required || _msnf_element_required($form, $element);
        }
      }
      if ($has_required == FALSE) {
        $skippable[$step_name] = $step;
      }
    }
  }

  // Allow other modules to alter the list of skippable steps.
  $context = array(
    'form' => $form,
    'form_state' => $form_state,
  );
  drupal_alter('msnf_steps_skippable', $skippable, $context);
  return $skippable;
}

/**
 * Helper function to remove steps with no (accessible) fields.
 *
 * @param <array> $steps
 *   List of step objects.
 * @param <array> $form
 *   The form where the step has been defined.
 *
 * @return <array>
 *   List of steps with at least one accessible field.
 */
function _msnf_remove_empty_steps($steps, $form, $form_state) {
  global $user;
  $steps_filtered = array();
  foreach ($steps as $step_name => $step) {
    if (empty($step->format_settings['instance_settings']['hide_if_empty'])) {

      // Step should not be hidden if empty so skip processing.
      $steps_filtered[$step_name] = $step;
      continue;
    }
    if (count($step->children) == 0) {

      // Step does not have any elements attached to it. Hide it!
      continue;
    }
    $access = FALSE;
    foreach ($step->children as $field_name) {
      if (($field = field_info_field($field_name)) !== NULL) {
        $access |= field_access('view', $field, 'node', $form_state['node']);
      }
      if (isset($form[$field_name]['#access'])) {
        $access |= $form[$field_name]['#access'];
      }
      else {

        // If an element does not have the #access-attribute we assume it is
        // accessible.
        $access = TRUE;
        break;

        // No further processing needed.
      }
    }
    if ($access) {

      // At least one field is accessible so add this step.
      $steps_filtered[$step_name] = $step;
    }
  }
  return $steps_filtered;
}

/**
 * Helper function to test if an element is required for form submissions.
 *
 * If the field already has a value (e.g. while editing the node) it is not
 * required anymore!
 *
 * @param <array> $form
 *   Form that contains $element_name.
 * @param <string> $element_name
 *   Name of element to check.
 *
 * @return <boolean>
 *   TRUE if the element is required, otherwise FALSE.
 */
function _msnf_element_required($form, $element_name) {
  $element = $form[$element_name];
  $required = isset($element['#required']) ? $element['#required'] : FALSE;

  // Check if the element has a value (equals to non-required).
  $field_name = isset($element['#field_name']) ? $element['#field_name'] : $element_name;
  $field_info = field_info_field($field_name);
  if (is_array($field_info)) {
    $empty_function = $field_info['module'] . '_field_is_empty';
    if (function_exists($empty_function) && isset($form['#entity_type']) && isset($form['#entity'])) {
      if (($items = field_get_items($form['#entity_type'], $form['#entity'], $element_name)) !== FALSE && count($items) > 0) {
        foreach ($items as $value) {
          if ($empty_function($value, $field_info) == FALSE) {
            $required = FALSE;
          }
        }
      }
      else {
        foreach (element_children($element) as $key) {
          $required = $required || _msnf_element_required($element, $key);
        }
      }
    }
  }
  else {
    if ($element_name == 'title' && isset($element['#title'])) {

      // If title is set, it is no longer required for us.
      $required = isset($element['#default_value']) && is_scalar($element['#default_value']) ? drupal_strlen($element['#default_value']) == 0 : TRUE;
    }
    else {
      $required = FALSE;
    }
  }
  return $required;
}

/**
 * Function used by uasort to sort objects (steps) by weight.
 *
 * @param <object> $a
 *   Base object to use for comparison.
 * @param <object> $b
 *   Object to compare to.
 *
 * @return <int>
 *   Indicator whether the weight of $a is smaller, equal or greater than that
 *   of $b.
 */
function _msnf_step_sort($a, $b) {
  $a_weight = is_object($a) && isset($a->weight) ? $a->weight : 0;
  $b_weight = is_object($b) && isset($b->weight) ? $b->weight : 0;
  if ($a_weight == $b_weight) {
    return 0;
  }
  return $a_weight < $b_weight ? -1 : 1;
}

/**
 * Wrapper function for i18n_string() if i18n_string enabled.
 *
 * @param <string> $name
 *   Name of the string, like "msnf_step:<step_name>:<content_type>"
 * @param <string> $string
 *   String in default language
 *
 * @return <string>
 *   Translated string (or original text if no translation exists).
 */
function msnf_translate($name, $string) {
  if (!module_exists('i18n_string')) {
    return filter_xss($string);
  }
  if (is_array($name)) {
    $name = implode(':', $name);
  }
  $translation = i18n_string('msnf_step:' . $name, filter_xss($string));
  return html_entity_decode($translation, ENT_QUOTES);
}

Functions

Namesort descending Description
msnf_attach_steps Attach steps to the (form) build.
msnf_block_info Implements hook_block_info().
msnf_block_view Implements hook_block_view().
msnf_build_pre_render Process callback.
msnf_ctools_plugin_api Implements hook_ctools_plugin_api().
msnf_element_info Implements hook_element_info().
msnf_entity_form_validate Custom validation callback.
msnf_fields_nest Recursive function to nest fields in the steps.
msnf_field_attach_delete_bundle Implements hook_field_attach_delete_bundle().
msnf_field_attach_form Implements hook_field_attach_form().
msnf_field_attach_rename_bundle Implements hook_field_attach_rename_bundle().
msnf_field_extra_fields Implements hook_field_extra_fields().
msnf_formatter_info Function to retrieve all format possibilities for the form steps.
msnf_form_field_ui_field_overview_form_alter Implements hook_form_FORM_ID_alter().
msnf_form_node_form_alter Implements hook_form_FORM_ID_alter().
msnf_form_process_msnf_form_step Creates a single form step.
msnf_info_steps Get all form steps.
msnf_load_step Loads a step definition.
msnf_menu Implements hook_menu().
msnf_msnf_formatter_info Implements hook_msnf_formatter_info().
msnf_msnf_format_settings Implements hook_msnf_format_settings(). If the step has no format settings, default ones will be added.
msnf_msnf_format_summary Implements hook_msnf_format_summary().
msnf_msnf_step_pre_render Implements hook_msnf_step_pre_render().
msnf_pack Packs a Step object into a database row.
msnf_permission Implements hook_permission().
msnf_preprocess_msnf_block_step_info Define variables used in msnf-block-step-info.tpl.php.
msnf_preprocess_msnf_form_step_info Define variables used in msnf-form-step-info.tpl.php.
msnf_pre_render Function to pre render the step element.
msnf_pre_render_prepare Helper function to prepare basic variables needed for most formatters.
msnf_read_steps Read all form steps.
msnf_step_exists Checks if a form step exists in required context.
msnf_step_export_delete Delete a single step.
msnf_step_menu_load Menu Wildcard loader function to load step definitions.
msnf_step_pre_render_default Implements msnf_step_pre_render_<format-type>. Format type: 'default'.
msnf_step_save Saves a single step definition.
msnf_theme Implements hook_theme().
msnf_translate Wrapper function for i18n_string() if i18n_string enabled.
msnf_unpack Unpacks a database row in a Step object.
theme_msnf_form_step Returns HTML for a single form step.
_msnf_element_required Helper function to test if an element is required for form submissions.
_msnf_element_unset_required Helper method to make an element and all its children optional.
_msnf_fieldgroup_has_element Helper function to check whether an element is nested within a fieldgroup or one of the fieldgroups child groups.
_msnf_form_attach_buttons Helper method to add the required buttons to a form.
_msnf_form_step_get_current Helper function to get the current form step from $form_state.
_msnf_form_step_set_current Helper method to set the current form step.
_msnf_hide_fields Hide all fields that are not associated to the current step.
_msnf_remove_empty_steps Helper function to remove steps with no (accessible) fields.
_msnf_restore_values Restore field values.
_msnf_steps_get_skippable Helper function to get all steps that may be skipped.
_msnf_step_sort Function used by uasort to sort objects (steps) by weight.