You are here

function hierarchical_select_process in Hierarchical Select 5.3

Same name and namespace in other branches
  1. 5 hierarchical_select.module \hierarchical_select_process()
  2. 5.2 hierarchical_select.module \hierarchical_select_process()
  3. 6.3 hierarchical_select.module \hierarchical_select_process()

Hierarchical select form element type #process callback.

File

./hierarchical_select.module, line 316
This module defines the "hierarchical_select" form element, which is a greatly enhanced way for letting the user select items in a hierarchy.

Code

function hierarchical_select_process($element) {
  if (!isset($element['#value']['hsid'])) {

    // The HSID is stored in the session, to allow for multiple Hierarchical
    // Select form items on the same page of which at least one is added through
    // AHAH. A normal static variable won't do in this case, because then at
    // least two Hierarchical Select form items will have HSID 0, because they
    // are generated in different requests, both of which will have a first HSID
    // of 0. This will then cause problems on the page.
    if (!isset($_SESSION['hsid'])) {
      $_SESSION['hsid'] = 0;
    }
    else {

      // Let the HSID go from 0 to 99, then start over. Larger numbers are
      // pointless: who's going to use more than a hundred Hierarchical Select
      // form items on the same page?
      $_SESSION['hsid'] = ($_SESSION['hsid'] + 1) % 100;
    }
    $hsid = $_SESSION['hsid'];
  }
  else {
    $hsid = check_plain($element['#value']['hsid']);
  }
  $element['hsid'] = array(
    '#type' => 'hidden',
    '#value' => $hsid,
  );

  // A hierarchical_select form element expands to multiple items. For example
  // $element['hsid'] got set just above. If #value is not an array, then
  // form_set_value(), which is called by form_builder() will fail, because it
  // assumes that #value is an array, because we are trying to set a child of
  // it.
  if (!is_array($element['#value'])) {
    $element['#value'] = array(
      $element['#value'],
    );
  }

  // Store the #name property of each hierarchical_select form item, this is
  // necessary to find this form item back in an AJAX callback.
  _hierarchical_select_store_name($element, $hsid);

  // Set up Javascript and add settings specifically for the current
  // hierarchical select.
  _hierarchical_select_setup_js();
  $config = _hierarchical_select_inherit_default_config($element['#config']);
  drupal_add_js(array(
    'HierarchicalSelect' => array(
      'settings' => array(
        $hsid => array(
          'animationDelay' => $config['animation_delay'] == 0 ? (int) variable_get('hierarchical_select_animation_delay', 400) : $config['animation_delay'],
          'cacheId' => $config['module'] . '_' . implode('_', is_array($config['params']) ? $config['params'] : array()),
          'renderFlatSelect' => isset($config['render_flat_select']) ? (int) $config['render_flat_select'] : 0,
          'createNewItems' => isset($config['editability']['status']) ? (int) $config['editability']['status'] : 0,
          'createNewLevels' => isset($config['editability']['allow_new_levels']) ? (int) $config['editability']['allow_new_levels'] : 0,
          'resizable' => isset($config['resizable']) ? (int) $config['resizable'] : 0,
        ),
      ),
    ),
  ), 'setting');

  // Basic config validation and diagnostics.
  if (HS_DEVELOPER_MODE) {
    $diagnostics = array();
    if (!isset($config['module']) || empty($config['module'])) {
      $diagnostics[] = t("'module is not set!");
    }
    elseif (!module_exists($config['module'])) {
      $diagnostics[] = t('the module that should be used (module) is not installed!', array(
        '%module' => $config['module'],
      ));
    }
    else {
      $required_params = module_invoke($config['module'], 'hierarchical_select_params');
      $missing_params = array_diff($required_params, array_keys($config['params']));
      if (!empty($missing_params)) {
        $diagnostics[] = t("'params' is missing values for: ") . implode(', ', $missing_params) . '.';
      }
    }
    $config_id = isset($config['config_id']) && is_string($config['config_id']) ? $config['config_id'] : 'none';
    if (empty($diagnostics)) {
      _hierarchical_select_log("Config diagnostics (config id: {$config_id}): no problems found!");
    }
    else {
      $diagnostics_string = print_r($diagnostics, TRUE);
      $message = "Config diagnostics (config id: {$config_id}): {$diagnostics_string}";
      _hierarchical_select_log($message);
      $element['#type'] = 'item';
      $element['#value'] = '<p><span style="color:red;">Fix the indicated errors in the #config property first!</span><br />' . nl2br($message) . '</p>';
      return $element;
    }
  }

  // Calculate the selections in both the hierarchical select and the dropbox,
  // we need these before we can render anything.
  list($hs_selection, $db_selection) = _hierarchical_select_process_calculate_selections($element);
  if (HS_DEVELOPER_MODE) {
    _hierarchical_select_log("Calculated hierarchical select selection:");
    _hierarchical_select_log($hs_selection);
    if ($config['dropbox']['status']) {
      _hierarchical_select_log("Calculated dropbox selection:");
      _hierarchical_select_log($db_selection);
    }
  }

  // If the exclusive_lineages setting has been configured, and the dropbox
  // is enabled, then do the necessary processing to make exclusive lineages
  // possible.
  if (count($config['exclusive_lineages']) && $config['dropbox']['status']) {

    // When the form is first loaded, $db_selection will contain the selection
    // that we should check, but in updates, $hs_selection will.
    $selection = !empty($hs_selection) ? $hs_selection : $db_selection;

    // If the current selection of the hierarchical select matches one of the
    // configured exclusive items, then disable the dropbox (to ensure an
    // exclusive selection).
    if (in_array($selection, $config['exclusive_lineages']) || count($selection) == 1 && in_array($selection[0], $config['exclusive_lineages'])) {

      // An item at the root level.
      // By also updating the configuration stored in $element, we ensure that
      // the validation step, which extracts the configuration again, also gets
      // the updated config.
      $element['#config']['dropbox']['status'] = 0;
      $config = _hierarchical_select_inherit_default_config($element['#config']);

      // When the form is first loaded, $db_selection contained the selection
      // selection that we checked for. Since we've now disabled the dropbox,
      // we should overwrite $hs_selection with the value of $db_selection and
      // reset $db_selection.
      if (empty($hs_selection)) {
        $hs_selection = $db_selection;
        $db_selection = array();
      }
    }
  }

  // Generate the $hierarchy and $dropbox objects using the selections that
  // were just calculated.
  $dropbox = !$config['dropbox']['status'] ? FALSE : _hierarchical_select_dropbox_generate($config, $db_selection);
  $hierarchy = _hierarchical_select_hierarchy_generate($config, $hs_selection, $element['#required'], $dropbox);
  if (HS_DEVELOPER_MODE) {
    _hierarchical_select_log('Generated hierarchy in ' . $hierarchy->build_time['total'] . ' ms:');
    _hierarchical_select_log($hierarchy);
    if ($config['dropbox']['status']) {
      _hierarchical_select_log('Generated dropbox in ' . $dropbox->build_time . ' ms: ');
      _hierarchical_select_log($dropbox);
    }
  }

  // Store the hierarchy object in the element, we'll need this if the user's
  // browser supports the active cache system.
  $element['hierarchy'] = array(
    '#type' => 'value',
    '#value' => $hierarchy,
  );

  // Ensure that #tree is enabled!
  $element['#tree'] = TRUE;

  // If render_flat_select is enabled, render a flat select.
  if ($config['render_flat_select']) {
    $element['flat_select'] = _hierarchical_select_process_render_flat_select($hierarchy, $dropbox, $config);
  }

  // Render the hierarchical select.
  $element['hierarchical_select'] = array(
    '#theme' => 'hierarchical_select_selects_container',
  );
  $element['hierarchical_select']['selects'] = _hierarchical_select_process_render_hs_selects($hsid, $hierarchy);

  // The selects in the hierarchical select should inherit the #size property.
  foreach (element_children($element['hierarchical_select']['selects']) as $depth) {
    $element['hierarchical_select']['selects'][$depth]['#size'] = $element['#size'];
  }

  // Check if a new item is being created.
  $creating_new_item = FALSE;
  if (isset($element['#value']['hierarchical_select']['selects'])) {
    foreach ($element['#value']['hierarchical_select']['selects'] as $depth => $value) {
      if ($value == 'create_new_item' && _hierarchical_select_create_new_item_is_allowed($config, $depth)) {
        $creating_new_item = TRUE;

        // We want to override the select in which the "create_new_item"
        // option was selected and hide all selects after that, if they exist.
        for ($i = $depth; $i < count($hierarchy->lineage); $i++) {
          unset($element['hierarchical_select']['selects'][$i]);
        }
        $element['hierarchical_select']['create_new_item'] = array(
          '#prefix' => '<div class="' . str_replace('_', '-', $value) . '">',
          '#suffix' => '</div>',
        );
        $item_type_depth = $value == 'create_new_item' ? $depth : $depth + 1;
        $item_type = !empty($config['editability']['item_types'][$item_type_depth]) ? t($config['editability']['item_types'][$item_type_depth]) : t('item');
        $element['hierarchical_select']['create_new_item']['input'] = array(
          '#type' => 'textfield',
          '#size' => 20,
          '#maxlength' => 255,
          '#default_value' => t('new @item', array(
            '@item' => $item_type,
          )),
          '#attributes' => array(
            'title' => t('new @item', array(
              '@item' => $item_type,
            )),
            'class' => 'create-new-item-input',
          ),
          // Use a #theme callback to prevent the textfield from being wrapped
          // in a div. This simplifies the CSS and JS code.
          '#theme' => 'hierarchical_select_textfield',
        );
        $element['hierarchical_select']['create_new_item']['create'] = array(
          '#type' => 'button',
          '#value' => t('Create'),
          '#attributes' => array(
            'class' => 'create-new-item-create',
          ),
        );
        $element['hierarchical_select']['create_new_item']['cancel'] = array(
          '#type' => 'button',
          '#value' => t('Cancel'),
          '#attributes' => array(
            'class' => 'create-new-item-cancel',
          ),
        );
      }
    }
  }
  if ($config['dropbox']['status']) {
    if (!$creating_new_item) {

      // Append an "Add" button to the selects.
      $element['hierarchical_select']['dropbox_add'] = array(
        '#type' => 'button',
        '#value' => t('Add'),
        '#attributes' => array(
          'class' => 'add-to-dropbox',
        ),
      );
    }
    if ($config['dropbox']['limit'] > 0) {

      // Zero as dropbox limit means no limit.
      if (count($dropbox->lineages) == $config['dropbox']['limit']) {
        $element['dropbox_limit_warning'] = array(
          '#value' => t("You've reached the maximal number of items you can select."),
          '#prefix' => '<p class="hierarchical-select-dropbox-limit-warning">',
          '#suffix' => '</p>',
        );

        // Disable all child form elements of $element['hierarchical_select].
        _hierarchical_select_mark_as_disabled($element['hierarchical_select']);
      }
    }

    // Add the hidden part of the dropbox. This will be used to store the
    // currently selected lineages.
    $element['dropbox']['hidden'] = array(
      '#prefix' => '<div class="dropbox-hidden">',
      '#suffix' => '</div>',
    );
    $element['dropbox']['hidden'] = _hierarchical_select_process_render_db_hidden($hsid, $dropbox);

    // Add the dropbox-as-a-table that will be visible to the user.
    $element['dropbox']['visible'] = _hierarchical_select_process_render_db_visible($hsid, $dropbox);
  }

  // This button and accompanying help text will be hidden when Javascript is
  // enabled.
  $element['nojs'] = array(
    '#prefix' => '<div class="nojs">',
    '#suffix' => '</div>',
  );
  $element['nojs']['update_button'] = array(
    '#type' => 'button',
    '#value' => t('Update'),
    '#attributes' => array(
      'class' => 'update-button',
    ),
  );
  $element['nojs']['update_button_help_text'] = array(
    '#value' => _hierarchical_select_nojs_helptext($config['dropbox']['status']),
    '#prefix' => '<div class="help-text">',
    '#suffix' => '</div>',
  );

  // Ensure the render order is correct.
  $element['hierarchical_select']['#weight'] = 0;
  $element['dropbox_limit_warning']['#weight'] = 1;
  $element['dropbox']['#weight'] = 2;
  $element['nojs']['#weight'] = 3;

  // This prevents values from in $element['#post'] to be used instead of the
  // generated default values (#default_value).
  // For example: $element['hierarchical_select']['selects']['0']['#default_value']
  // is set to 'label_0' after an "Add" operation. When $element['#post'] is
  // NOT unset, the corresponding value in $element['#post'] will be used
  // instead of the default value that was set. This is undesired behavior.
  unset($element['#post']);

  // Finally, calculate the return value of this hierarchical_select form
  // element. This will be set in _hierarchical_select_validate(). (If we'd
  // set it now, it would be overridden again.)
  $element['#return_value'] = _hierarchical_select_process_calculate_return_value($hierarchy, $config['dropbox']['status'] ? $dropbox : FALSE, $config['module'], $config['params'], $config['save_lineage']);

  // Add a validate callback, which will:
  // - validate that the dropbox limit was not exceeded.
  // - set the return value of this form element.
  $element['#validate'] = array(
    '_hierarchical_select_validate' => array(),
  );
  if (HS_DEVELOPER_MODE) {
    $element['log'] = array(
      '#type' => 'value',
      '#value' => _hierarchical_select_log(NULL, TRUE),
    );
    drupal_add_js(array(
      'HierarchicalSelect' => array(
        'initialLog' => array(
          $hsid => $element['log']['#value'],
        ),
      ),
    ), 'setting');
  }

  // If the form item is marked as disabled, disable all child form items as
  // well.
  if ($element['#disabled']) {
    _hierarchical_select_mark_as_disabled($element);
  }
  return $element;
}