You are here

function _hierarchical_select_hierarchy_generate in Hierarchical Select 6.3

Same name and namespace in other branches
  1. 5.3 hierarchical_select.module \_hierarchical_select_hierarchy_generate()
  2. 7.3 hierarchical_select.module \_hierarchical_select_hierarchy_generate()

Generate the hierarchy object.

Parameters

$config: A config array with at least the following settings:

  • module
  • params
  • enforce_deepest
  • save_lineage
  • level_labels
    • status
    • labels
  • editability
    • status
    • allow_new_levels
    • max_levels

$selection: The selection based on which a HS should be rendered.

$required: Whether the form element is required or not. (#required in Forms API)

$dropbox: A dropbox object, or FALSE.

Return value

A hierarchy object.

2 calls to _hierarchical_select_hierarchy_generate()
HierarchicalSelectInternals::generate in tests/internals.test
hierarchical_select_process in ./hierarchical_select.module
Hierarchical select form element type #process callback.

File

./hierarchical_select.module, line 1632
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_hierarchy_generate($config, $selection, $required, $dropbox = FALSE) {
  $hierarchy = new stdClass();

  // Convert the 'special_items' setting to a more easily accessible format.
  if (isset($config['special_items'])) {
    $special_items['exclusive'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_exclusive'));
    $special_items['none'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_none'));
  }

  //
  // Build the lineage.
  //
  $start_lineage = microtime();

  // If save_linage is enabled, reconstruct the lineage. This is necessary
  // because e.g. the taxonomy module stores the terms by order of weight and
  // lexicography, rather than by hierarchy.
  if ($config['save_lineage'] && is_array($selection) && count($selection) >= 2) {

    // Ensure the item in the root level is the first item in the selection.
    $root_level = array_keys(module_invoke($config['module'], 'hierarchical_select_root_level', $config['params']));
    for ($i = 0; $i < count($selection); $i++) {
      if (in_array($selection[$i], $root_level)) {
        if ($i != 0) {

          // Don't swap if it's already the first item.
          list($selection[0], $selection[$i]) = array(
            $selection[$i],
            $selection[0],
          );
        }
        break;
      }
    }

    // Reconstruct all sublevels.
    for ($i = 0; $i < count($selection); $i++) {
      $children = array_keys(module_invoke($config['module'], 'hierarchical_select_children', $selection[$i], $config['params']));

      // Ensure the next item in the selection is a child of the current item.
      for ($j = $i + 1; $j < count($selection); $j++) {
        if (in_array($selection[$j], $children)) {
          list($selection[$j], $selection[$i + 1]) = array(
            $selection[$i + 1],
            $selection[$j],
          );
        }
      }
    }
  }

  // Validate the hierarchy.
  $selection = _hierarchical_select_hierarchy_validate($selection, $config['module'], $config['params']);

  // When nothing is currently selected, set the root level to:
  // - "<none>" (or its equivalent special item) when:
  //    - enforce_deepest is enabled *and* level labels are enabled *and*
  //      no root level label is set (1), or
  //    - the dropbox is enabled *and* at least one selection has been added
  //      to the dropbox (2)
  // - "label_0" (the root level label) in all other cases.
  if ($selection == -1) {
    $root_level = module_invoke($config['module'], 'hierarchical_select_root_level', $config['params']);
    $first_case = $config['enforce_deepest'] && $config['level_labels']['status'] && !isset($config['level_labels']['labels'][0]);
    $second_case = $dropbox && count($dropbox->lineages) > 0;

    // If
    // - the special_items setting has been configured, and
    // - one special item has the 'none' property
    // then we'll use the special item instead of the normal "<none>" option.
    $none_option = count($special_items['none']) ? $special_items['none'][0] : 'none';

    // Set "<none>" option (or its equivalent special item), or "label_0".
    $hierarchy->lineage[0] = $first_case || $second_case ? $none_option : 'label_0';
  }
  else {

    // If save_lineage setting is enabled, then the selection *is* a lineage.
    // If it's disabled, we have to generate one ourselves based on the
    // (deepest) selected item.
    if ($config['save_lineage']) {

      // When the form element is optional, the "<none>" setting can be
      // selected, thus only the first level will be displayed. As a result,
      // we won't receive an array as the selection, but only a single item.
      // We convert this into an array.
      $hierarchy->lineage = is_array($selection) ? $selection : array(
        0 => $selection,
      );
    }
    else {
      $selection = is_array($selection) ? $selection[0] : $selection;
      if (module_invoke($config['module'], 'hierarchical_select_valid_item', $selection, $config['params'])) {
        $hierarchy->lineage = module_invoke($config['module'], 'hierarchical_select_lineage', $selection, $config['params']);
      }
      else {

        // If the selected item is invalid, then start with an empty lineage.
        $hierarchy->lineage = array();
      }
    }
  }

  // If enforce_deepest is enabled, ensure that the lineage goes as deep as
  // possible: append values of items that will be selected by default.
  if ($config['enforce_deepest'] && !in_array($hierarchy->lineage[0], array(
    'none',
    'label_0',
  ))) {
    $hierarchy->lineage = _hierarchical_select_hierarchy_enforce_deepest($hierarchy->lineage, $config['module'], $config['params']);
  }
  $end_lineage = microtime();

  //
  // Build the levels.
  //
  $start_levels = microtime();

  // Start building the levels, initialize with the root level.
  $hierarchy->levels[0] = module_invoke($config['module'], 'hierarchical_select_root_level', $config['params']);
  $hierarchy->levels[0] = _hierarchical_select_apply_entity_settings($hierarchy->levels[0], $config);

  // Prepend a "<create new item>" option to the root level when:
  // - the editability setting is enabled, and
  // - the hook is implemented (this is an optional hook), and
  // - the allowed_levels setting allows to create new items at this level.
  if ($config['editability']['status'] && module_hook($config['module'], 'hierarchical_select_create_item') && _hierarchical_select_create_new_item_is_allowed($config, 0)) {
    $item_type = count($config['editability']['item_types']) > 0 ? t($config['editability']['item_types'][0]) : t('item');
    $option = theme('hierarchical_select_special_option', t('create new !item_type', array(
      '!item_type' => $item_type,
    )));
    $hierarchy->levels[0] = array(
      'create_new_item' => $option,
    ) + $hierarchy->levels[0];
  }

  // Prepend a "<none>" option to the root level when:
  // - the form element is optional (1), or
  // - enforce_deepest is enabled (2), or
  // - the dropbox is enabled *and* at least one selection has been added to
  //   the dropbox (3)
  // except when:
  // - the special_items setting has been configured, and
  // - one special item has the 'none' property
  $first_case = !$required;
  $second_case = $config['enforce_deepest'];
  $third_case = $dropbox && count($dropbox->lineages) > 0;
  if (($first_case || $second_case || $third_case) && !count($special_items['none'])) {
    $option = theme('hierarchical_select_special_option', t('none'));
    $hierarchy->levels[0] = array(
      'none' => $option,
    ) + $hierarchy->levels[0];
  }

  // Calculate the lineage's depth (starting from 0).
  $max_depth = count($hierarchy->lineage) - 1;

  // Build all sublevels, based on the lineage.
  for ($depth = 1; $depth <= $max_depth; $depth++) {
    $hierarchy->levels[$depth] = module_invoke($config['module'], 'hierarchical_select_children', $hierarchy->lineage[$depth - 1], $config['params']);
    $hierarchy->levels[$depth] = _hierarchical_select_apply_entity_settings($hierarchy->levels[$depth], $config);
  }
  if ($config['enforce_deepest']) {

    // Prepend a "<create new item>" option to each level below the root level
    // when:
    // - the editability setting is enabled, and
    // - the hook is implemented (this is an optional hook), and
    // - the allowed_levels setting allows to create new items at this level.
    if ($config['editability']['status'] && module_hook($config['module'], 'hierarchical_select_create_item')) {
      for ($depth = 1; $depth <= $max_depth; $depth++) {
        $item_type = count($config['editability']['item_types']) == $depth ? t($config['editability']['item_types'][$depth]) : t('item');
        $option = theme('hierarchical_select_special_option', t('create new !item_type', array(
          '!item_type' => $item_type,
        )));
        if (_hierarchical_select_create_new_item_is_allowed($config, $depth)) {
          $hierarchy->levels[$depth] = array(
            'create_new_item' => $option,
          ) + $hierarchy->levels[$depth];
        }
      }
    }

    // If level labels are enabled and the root label is set, prepend it.
    if ($config['level_labels']['status'] && isset($config['level_labels']['labels'][0])) {
      $hierarchy->levels[0] = array(
        'label_0' => t($config['level_labels']['labels'][0]),
      ) + $hierarchy->levels[0];
    }
  }
  else {
    if (!$config['enforce_deepest']) {

      // Prepend special options to every level.
      for ($depth = 0; $depth <= $max_depth; $depth++) {

        // Prepend a "<create new item>" option to the current level when:
        // - this is not the root level (the root level already has this), and
        // - the editability setting is enabled, and
        // - the hook is implemented (this is an optional hook), and
        // - the allowed_levels setting allows to create new items at this level.
        if ($depth > 0 && $config['editability']['status'] && module_hook($config['module'], 'hierarchical_select_create_item') && _hierarchical_select_create_new_item_is_allowed($config, $depth)) {
          $item_type = count($config['editability']['item_types']) == $depth ? t($config['editability']['item_types'][$depth]) : t('item');
          $option = theme('hierarchical_select_special_option', t('create new !item_type', array(
            '!item_type' => $item_type,
          )));
          $hierarchy->levels[$depth] = array(
            'create_new_item' => $option,
          ) + $hierarchy->levels[$depth];
        }

        // Level label: set an empty level label if they've been disabled.
        $label = $config['level_labels']['status'] && isset($config['level_labels']['labels'][$depth]) ? t($config['level_labels']['labels'][$depth]) : '';
        $hierarchy->levels[$depth] = array(
          'label_' . $depth => $label,
        ) + $hierarchy->levels[$depth];
      }

      // If the root level label is empty and the none option is present, remove
      // the root level label because it's conceptually identical.
      if ($hierarchy->levels[0]['label_0'] == '' && isset($hierarchy->levels[0]['none'])) {
        unset($hierarchy->levels[0]['label_0']);

        // Update the selected lineage when necessary to prevent an item that
        // doesn't exist from being "selected" internally.
        if ($hierarchy->lineage[0] == 'label_0') {
          $hierarchy->lineage[0] = 'none';
        }
      }

      // Add one more level if appropriate.
      $parent = $hierarchy->lineage[$max_depth];
      if (module_invoke($config['module'], 'hierarchical_select_valid_item', $parent, $config['params'])) {
        $children = module_invoke($config['module'], 'hierarchical_select_children', $parent, $config['params']);
        if (count($children)) {

          // We're good, let's add one level!
          $depth = $max_depth + 1;
          $hierarchy->levels[$depth] = array();

          // Prepend a "<create new item>" option to the current level when:
          // - the editability setting is enabled, and
          // - the hook is implemented (this is an optional hook), and
          // - the allowed_levels setting allows to create new items at this level.
          if ($config['editability']['status'] && module_hook($config['module'], 'hierarchical_select_create_item') && _hierarchical_select_create_new_item_is_allowed($config, $depth)) {
            $item_type = count($config['editability']['item_types']) == $depth ? t($config['editability']['item_types'][$depth]) : t('item');
            $option = theme('hierarchical_select_special_option', t('create new !item_type', array(
              '!item_type' => $item_type,
            )));
            $hierarchy->levels[$depth] = array(
              'create_new_item' => $option,
            );
          }

          // Level label: set an empty level label if they've been disabled.
          $hierarchy->lineage[$depth] = 'label_' . $depth;
          $label = $config['level_labels']['status'] ? t($config['level_labels']['labels'][$depth]) : '';
          $hierarchy->levels[$depth] = array(
            'label_' . $depth => $label,
          ) + $hierarchy->levels[$depth] + $children;
          $hierarchy->levels[$depth] = _hierarchical_select_apply_entity_settings($hierarchy->levels[$depth], $config);
        }
      }
    }
  }

  // Add an extra level with only a level label and a "<create new item>"
  // option, if:
  // - the editability setting is enabled
  // - the allow_new_levels setting is enabled
  // - an additional level is permitted by the max_levels setting
  // - the deepest item of the lineage is a valid item
  // NOTE: this uses an optional hook, so we also check if it's implemented.
  if ($config['editability']['status'] && $config['editability']['allow_new_levels'] && ($config['editability']['max_levels'] == 0 || count($hierarchy->lineage) < $config['editability']['max_levels']) && module_invoke($config['module'], 'hierarchical_select_valid_item', end($hierarchy->lineage), $config['params']) && module_hook($config['module'], 'hierarchical_select_create_item')) {
    $depth = $max_depth + 1;

    // Level label: set an empty level label if they've been disabled.
    $hierarchy->lineage[$depth] = 'label_' . $depth;
    $label = $config['level_labels']['status'] ? t($config['level_labels']['labels'][$depth]) : '';

    // Item type.
    $item_type = count($config['editability']['item_types']) == $depth ? t($config['editability']['item_types'][$depth]) : t('item');

    // The new level with only a level label and a "<create new item>" option.
    $option = theme('hierarchical_select_special_option', t('create new !item_type', array(
      '!item_type' => $item_type,
    )));
    $hierarchy->levels[$depth] = array(
      'label_' . $depth => $label,
      'create_new_item' => $option,
    );
  }

  // Calculate the time it took to generate the levels.
  $end_levels = microtime();

  // Add child information.
  $start_childinfo = microtime();
  $hierarchy = _hierarchical_select_hierarchy_add_childinfo($hierarchy, $config);
  $end_childinfo = microtime();

  // Calculate the time it took to build the hierarchy object.
  $hierarchy->build_time['total'] = ($end_childinfo - $start_lineage) * 1000;
  $hierarchy->build_time['lineage'] = ($end_lineage - $start_lineage) * 1000;
  $hierarchy->build_time['levels'] = ($end_levels - $start_levels) * 1000;
  $hierarchy->build_time['childinfo'] = ($end_childinfo - $start_childinfo) * 1000;
  return $hierarchy;
}