View source  
  <?php
define('HS_DEVELOPER_MODE', 0);
function hierarchical_select_help($path, $arg) {
  switch ($path) {
    
    case 'admin/help#hierarchical_select':
      return t("Hierarchical Select has the ability to save the entire lineage\n      of a selection or only the 'deepest' selection. You can configure it to\n      force the user to make a selection as deep as possible in the tree, or\n      allow the user to select an item anywhere in the tree. Levels can be\n      labeled, you can configure limit the number of items that can be selected,\n      configure a title for the dropbox, choose a site-wide animation delay,\n      and so on. You can even create new items and levels through Hierarchical\n      Select!");
  }
}
function hierarchical_select_menu() {
  $items['hierarchical_select_ajax'] = array(
    'page callback' => 'hierarchical_select_ajax',
    'delivery callback' => 'ajax_deliver',
    'access arguments' => array(
      'access content',
    ),
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/content/hierarchical_select'] = array(
    'title' => 'Hierarchical Select',
    'description' => 'Configure site-wide settings for the Hierarchical Select form element.',
    'access arguments' => array(
      'administer site configuration',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hierarchical_select_admin_settings',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'hierarchical_select.admin.inc',
  );
  $items['admin/config/content/hierarchical_select/settings'] = array(
    'title' => 'Site-wide settings',
    'access arguments' => array(
      'administer site configuration',
    ),
    'weight' => -10,
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'file' => 'hierarchical_select.admin.inc',
  );
  $items['admin/config/content/hierarchical_select/configs'] = array(
    'title' => 'Configurations',
    'description' => 'All available Hierarchical Select configurations.',
    'access arguments' => array(
      'administer site configuration',
    ),
    'page callback' => 'hierarchical_select_admin_configs',
    'type' => MENU_LOCAL_TASK,
    'file' => 'hierarchical_select.admin.inc',
  );
  $items['admin/config/content/hierarchical_select/implementations'] = array(
    'title' => 'Implementations',
    'description' => 'Features of each Hierarchical Select implementation.',
    'access arguments' => array(
      'administer site configuration',
    ),
    'page callback' => 'hierarchical_select_admin_implementations',
    'type' => MENU_LOCAL_TASK,
    'file' => 'hierarchical_select.admin.inc',
  );
  $items['admin/config/content/hierarchical_select/export/%hierarchical_select_config_id'] = array(
    'title' => 'Export',
    'access arguments' => array(
      'administer site configuration',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hierarchical_select_admin_export',
      5,
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'hierarchical_select.admin.inc',
  );
  $items['admin/config/content/hierarchical_select/import/%hierarchical_select_config_id'] = array(
    'title' => 'Import',
    'access arguments' => array(
      'administer site configuration',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hierarchical_select_admin_import',
      5,
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'hierarchical_select.admin.inc',
  );
  return $items;
}
function hierarchical_select_element_info() {
  $types['hierarchical_select'] = array(
    '#input' => TRUE,
    '#process' => array(
      'form_hierarchical_select_process',
    ),
    '#theme' => array(
      'hierarchical_select',
    ),
    '#theme_wrappers' => array(
      'form_element',
    ),
    '#config' => array(
      'module' => 'some_module',
      'params' => array(),
      'save_lineage' => 0,
      'enforce_deepest' => 0,
      'resizable' => 1,
      'level_labels' => array(
        'status' => 0,
        'labels' => array(),
      ),
      'dropbox' => array(
        'status' => 0,
        'title' => t('All selections'),
        'limit' => 0,
        'reset_hs' => 1,
        'sort' => 1,
      ),
      'editability' => array(
        'status' => 0,
        'item_types' => array(),
        'allowed_levels' => array(),
        'allow_new_levels' => 0,
        'max_levels' => 3,
      ),
      'entity_count' => array(
        'enabled' => 0,
        'require_entity' => 0,
        'settings' => array(
          'count_children' => 0,
          'entity_types' => array(),
        ),
      ),
      'animation_delay' => variable_get('hierarchical_select_animation_delay', 400),
      'special_items' => array(),
      'render_flat_select' => 0,
    ),
    '#default_value' => -1,
  );
  $types['hierarchical_select_item_separator'] = array(
    '#theme' => 'hierarchical_select_item_separator',
  );
  return $types;
}
function hierarchical_select_requirements($phase) {
  $requirements = array();
  if ($phase == 'runtime') {
    
    require_once DRUPAL_ROOT . '/' . 'includes/install.inc';
    drupal_load_updates();
    $updates = drupal_get_schema_versions('hierarchical_select');
    $current = drupal_get_installed_schema_version('hierarchical_select');
    $up_to_date = end($updates) == $current;
    $hierarchical_select_weight = db_query("SELECT weight FROM {system} WHERE type = :type AND name = :name", array(
      ':type' => 'module',
      ':name' => 'hierarchical_select',
    ))
      ->fetchField();
    $core_overriding_modules = array(
      'hs_book',
      'hs_menu',
      'hs_taxonomy',
    );
    $path_errors = array();
    foreach ($core_overriding_modules as $module) {
      $filename = db_query("SELECT filename FROM {system} WHERE type = :type AND name = :name", array(
        ':type' => 'module',
        ':name' => $module,
      ))
        ->fetchField();
      if (strpos($filename, 'modules/') === 0) {
        $module_info = drupal_parse_info_file(dirname($filename) . "/{$module}.info");
        $path_errors[] = t('!module', array(
          '!module' => $module_info['name'],
        ));
      }
    }
    if ($up_to_date && !count($path_errors)) {
      $value = t('All updates installed. HS API implementation modules correctly installed.');
      $description = '';
      $severity = REQUIREMENT_OK;
    }
    elseif ($path_errors) {
      $value = t('Modules incorrectly installed!');
      $description = t("The following modules implement Hierarchical Select module for Drupal\n        core modules, but are installed in the wrong location. They're\n        installed in core's <code>modules</code> directory, but should be\n        installed in either the <code>sites/all/modules</code> directory or a\n        <code>sites/yoursite.com/modules</code> directory") . ':' . theme('item_list', array(
        'items' => $path_errors,
      ));
      $severity = REQUIREMENT_ERROR;
    }
    else {
      $value = t('Not all updates installed!');
      $description = t('Please run update.php to install the latest updates!
        You have installed update !installed_update, but the latest update is
        !latest_update!', array(
        '!installed_update' => $current,
        '!latest_update' => end($updates),
      ));
      $severity = REQUIREMENT_ERROR;
    }
    $requirements['hierarchical_select'] = array(
      'title' => t('Hierarchical Select'),
      'value' => $value,
      'description' => $description,
      'severity' => $severity,
    );
  }
  return $requirements;
}
function hierarchical_select_theme() {
  return array(
    'hierarchical_select_form_element' => array(
      'file' => 'includes/theme.inc',
      'variables' => array(
        'element' => NULL,
        'value' => NULL,
      ),
    ),
    'hierarchical_select' => array(
      'file' => 'includes/theme.inc',
      'render element' => 'element',
    ),
    'hierarchical_select_selects_container' => array(
      'file' => 'includes/theme.inc',
      'render element' => 'element',
    ),
    'hierarchical_select_select' => array(
      'file' => 'includes/theme.inc',
      'render element' => 'element',
    ),
    'hierarchical_select_item_separator' => array(
      'file' => 'includes/theme.inc',
      'render element' => 'element',
    ),
    'hierarchical_select_special_option' => array(
      'file' => 'includes/theme.inc',
      'variables' => array(
        'option' => NULL,
      ),
    ),
    'hierarchical_select_dropbox_table' => array(
      'file' => 'includes/theme.inc',
      'render element' => 'element',
    ),
    'hierarchical_select_common_config_form_level_labels' => array(
      'file' => 'includes/theme.inc',
      'render element' => 'form',
    ),
    'hierarchical_select_common_config_form_editability' => array(
      'file' => 'includes/theme.inc',
      'render element' => 'form',
    ),
    'hierarchical_select_selection_as_lineages' => array(
      'file' => 'includes/theme.inc',
      'variables' => array(
        'selection' => NULL,
        'config' => NULL,
      ),
    ),
  );
}
function hierarchical_select_features_api() {
  return array(
    'hierarchical_select' => array(
      'name' => t('Hierarchical select configs'),
      'feature_source' => TRUE,
      'default_hook' => 'hierarchical_select_default_configs',
      'default_file' => FEATURES_DEFAULTS_INCLUDED,
      'file' => drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.features.inc',
    ),
  );
}
function hierarchical_select_menu_site_status_alter(&$menu_site_status, $path) {
  global $language;
  
  if (0 === strpos($_GET['q'], 'hierarchical_select_ajax') && !empty($_POST['hs_current_language'])) {
    $languages = language_list();
    if (isset($languages[$_POST['hs_current_language']])) {
      
      $language = $languages[$_POST['hs_current_language']];
    }
  }
}
function hierarchical_select_config_id_load($config_id) {
  $config = variable_get('hs_config_' . $config_id, FALSE);
  return $config !== FALSE ? $config['config_id'] : FALSE;
}
function hierarchical_select_ajax() {
  $form_parents = func_get_args();
  list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
  
  drupal_process_form($form['#form_id'], $form, $form_state);
  $element = drupal_array_get_nested_value($form, $form_parents);
  
  $output = theme('status_messages') . drupal_render($element);
  
  $commands[] = array(
    'command' => 'hierarchicalSelectUpdate',
    'output' => $output,
  );
  $new_settings = _hs_new_setting_ajax(FALSE);
  foreach ($new_settings as $new_setting) {
    $commands[] = array(
      'command' => 'hierarchicalSelectSettingsUpdate',
      'hsid' => $new_setting['hsid'],
      'settings' => $new_setting['settings'],
    );
  }
  $context = array(
    'form' => $form,
    'form_state' => $form_state,
    'element' => $element,
  );
  drupal_alter('hierarchical_select_ajax_commands', $commands, $context);
  return array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
}
function _hs_process_determine_hsid($element, &$form_state) {
  
  if (!isset($element['#value']) || !is_array($element['#value']) || !array_key_exists('hsid', $element['#value'])) {
    $hsid = uniqid();
  }
  else {
    $hsid = check_plain($element['#value']['hsid']);
  }
  return $hsid;
}
function _hs_process_shortcut_special_items($config) {
  $special_items = array();
  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'));
  }
  return $special_items;
}
function _hs_process_attach_css_js($element, $hsid, &$form_state, $complete_form) {
  global $language;
  
  $element['#attached']['library'][] = array(
    'system',
    'ui',
  );
  $element['#attached']['library'][] = array(
    'system',
    'drupal.ajax',
  );
  $element['#attached']['library'][] = array(
    'system',
    'jquery.form',
  );
  $element['#attached']['library'][] = array(
    'system',
    'effects',
  );
  $element['#attached']['library'][] = array(
    'system',
    'effects.drop',
  );
  $element['#attached']['css'][] = drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.css';
  $element['#attached']['js'][] = drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.js';
  if (variable_get('hierarchical_select_js_cache_system', 0) == 1) {
    $element['#attached']['js'][] = drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select_cache.js';
  }
  if (!isset($form_state['storage']['hs']['js_settings_sent'])) {
    $form_state['storage']['hs']['js_settings_sent'] = array();
  }
  
  if ($form_state['process_input'] === TRUE) {
    $form_state['storage']['hs']['js_settings_sent'] = array();
  }
  if (!isset($form_state['storage']['hs']['js_settings_sent'][$hsid]) || isset($form_state['storage']['hs']['js_settings_sent'][$hsid]) && (isset($form_state['triggering_element']) && $form_state['triggering_element']['#type'] == 'submit')) {
    $config = _hierarchical_select_inherit_default_config($element['#config']);
    $settings = array(
      'HierarchicalSelect' => array(
        
        'hs_current_language' => $language->language,
        'settings' => array(
          "hs-{$hsid}" => array(
            'animationDelay' => $config['animation_delay'] == 0 ? (int) variable_get('hierarchical_select_animation_delay', 400) : $config['animation_delay'],
            'cacheId' => $config['module'] . '_' . md5(serialize($config['params'])),
            '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,
            'ajax_url' => url('hierarchical_select_ajax/' . implode('/', $element['#array_parents'])),
          ),
        ),
      ),
    );
    if (!isset($_POST['hsid'])) {
      $element['#attached']['js'][] = array(
        'type' => 'setting',
        'data' => $settings,
      );
    }
    else {
      $element['#attached']['_hs_new_setting_ajax'][] = array(
        $hsid,
        $settings['HierarchicalSelect']['settings']["hs-{$hsid}"],
      );
    }
    $form_state['storage']['hs']['js_settings_sent'][$hsid] = TRUE;
  }
  return $element;
}
function _hs_new_setting_ajax($hsid = FALSE, $settings = NULL) {
  static $hs_settings = array();
  if ($hsid !== FALSE) {
    $hs_settings[] = array(
      'hsid' => $hsid,
      'settings' => $settings,
    );
  }
  return $hs_settings;
}
function _hs_process_developer_mode_log_diagnostics(&$element) {
  if (HS_DEVELOPER_MODE) {
    $config = $element['#config'];
    $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);
      $title = $element['#title'];
      $element = array();
      $element['#type'] = 'item';
      $element['#title'] = $title;
      $element['#markup'] = '<p><span style="color:red;">Fix the indicated errors in the #config property first!</span><br />' . nl2br($message) . '</p>';
      return FALSE;
    }
  }
  return TRUE;
}
function _hs_process_developer_mode_log_selections($config, $hs_selection, $db_selection) {
  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);
    }
  }
}
function _hs_process_developer_mode_log_hierarchy_and_dropbox($config, $hierarchy, $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);
    }
  }
}
function _hs_process_developer_mode_send_log_js($element, $hsid) {
  if (HS_DEVELOPER_MODE) {
    $log = _hierarchical_select_log(NULL, TRUE);
    $settings = array(
      'HierarchicalSelect' => array(
        'initialLog' => array(
          "hs-{$hsid}" => $log,
        ),
      ),
    );
    $element['#attached']['js'][] = array(
      'type' => 'setting',
      'data' => $settings,
    );
  }
  return $element;
}
function _hs_process_exclusive_lineages($element, $hs_selection, $db_selection) {
  $config = $element['#config'];
  $special_items = _hs_process_shortcut_special_items($config);
  
  if (!empty($special_items) && count($special_items['exclusive']) && $config['dropbox']['status']) {
    
    $selection = !empty($hs_selection) ? $hs_selection : $db_selection;
    
    $exclusive_item = array_intersect($selection, $special_items['exclusive']);
    if (count($exclusive_item)) {
      
      $element['#config']['dropbox']['status'] = 0;
      
      $hs_selection = array(
        0 => reset($exclusive_item),
      );
      $db_selection = array();
    }
  }
  return array(
    $element,
    $hs_selection,
    $db_selection,
  );
}
function _hs_process_render_create_new_item($element, $hierarchy) {
  $creating_new_item = FALSE;
  
  $element['hierarchical_select']['create_new_item'] = array(
    '#prefix' => '<div class="create-new-item">',
    '#suffix' => '</div>',
    '#after_build' => array(
      'hierarchical_select_create_new_item_after_build',
    ),
  );
  
  $element['hierarchical_select']['create_new_item']['create'] = array(
    '#type' => 'submit',
    '#value' => t('Create'),
    '#attributes' => array(
      'class' => array(
        'create-new-item-create',
      ),
    ),
    '#limit_validation_errors' => array(
      $element['#parents'],
    ),
    '#validate' => array(),
    '#submit' => array(
      'hierarchical_select_ajax_update_submit',
    ),
  );
  $element['hierarchical_select']['create_new_item']['cancel'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel'),
    '#attributes' => array(
      'class' => array(
        'create-new-item-cancel',
      ),
    ),
    '#limit_validation_errors' => array(
      $element['#parents'],
    ),
    '#validate' => array(),
    '#submit' => array(
      'hierarchical_select_ajax_update_submit',
    ),
  );
  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($element['#config'], $depth)) {
        $creating_new_item = TRUE;
        
        if ($depth == 0) {
          unset($element['hierarchical_select']['selects']);
        }
        else {
          for ($i = $depth; $i < count($hierarchy->lineage); $i++) {
            unset($element['hierarchical_select']['selects'][$i]);
          }
        }
        $item_type_depth = $value == 'create_new_item' ? $depth : $depth + 1;
        $item_type = count($element['#config']['editability']['item_types']) == $item_type_depth ? t($element['#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' => array(
              'create-new-item-input',
            ),
          ),
          
          '#theme_wrappers' => array(),
          
          '#weight' => -1,
        );
      }
    }
  }
  $element['hierarchical_select']['create_new_item']['#creating_new_item'] = $creating_new_item;
  return array(
    $element,
    $creating_new_item,
  );
}
function hierarchical_select_create_new_item_after_build(array $element) {
  $element['#access'] = $element['#creating_new_item'];
  return $element;
}
function _hs_process_render_dropbox($element, $hsid, $creating_new_item, $dropbox, $form_state) {
  $config = $element['#config'];
  if ($config['dropbox']['status']) {
    if (!$creating_new_item) {
      
      $element['hierarchical_select']['dropbox_add'] = array(
        '#type' => 'submit',
        '#value' => t('Add'),
        '#attributes' => array(
          'class' => array(
            'add-to-dropbox',
          ),
        ),
        '#limit_validation_errors' => array(
          $element['#parents'],
        ),
        '#validate' => array(),
        '#submit' => array(
          'hierarchical_select_ajax_update_submit',
        ),
      );
    }
    if ($config['dropbox']['limit'] > 0) {
      
      if (count($dropbox->lineages) >= $config['dropbox']['limit']) {
        $element['dropbox_limit_warning'] = array(
          '#markup' => t("You've reached the maximum number of items you can select."),
          '#prefix' => '<p class="hierarchical-select-dropbox-limit-warning">',
          '#suffix' => '</p>',
        );
        
        $element['hierarchical_select']['dropbox_add']['#attributes']['disabled'] = TRUE;
      }
    }
    
    if (isset($dropbox->lineages_selections)) {
      $form_state['storage']['hs'][$hsid]['dropbox_lineages_selections'] = $dropbox->lineages_selections;
    }
    
    $element['dropbox']['visible'] = _hs_process_render_db_table($hsid, $dropbox);
  }
  return array(
    $element,
    $form_state,
  );
}
function _hs_process_render_nojs($element, $config) {
  
  $element['nojs'] = array(
    '#prefix' => '<div class="nojs">',
    '#suffix' => '</div>',
  );
  $element['nojs']['update_button'] = array(
    '#type' => 'submit',
    '#value' => t('Update'),
    '#attributes' => array(
      'class' => array(
        'update-button',
      ),
    ),
    '#limit_validation_errors' => array(
      $element['#parents'],
    ),
    '#validate' => array(),
    '#submit' => array(
      'hierarchical_select_ajax_update_submit',
    ),
    '#ajax' => array(
      'callback' => 'menu_link_weight_parent_ajax_callback',
      'wrapper' => 'menu-link-weight-wrapper',
    ),
  );
  $element['nojs']['update_button_help_text'] = array(
    '#markup' => _hierarchical_select_nojs_helptext($config['dropbox']['status']),
    '#prefix' => '<div class="help-text">',
    '#suffix' => '</div>',
  );
  return $element;
}
function form_hierarchical_select_process($element, &$form_state, $complete_form) {
  if (arg(0) != 'hierarchical_select_ajax') {
    
    $cid = isset($element['#parents']) ? implode("-", $element['#parents']) : implode("-", $element['#field_parents']);
    
    $elhsid = drupal_array_get_nested_value($element, array(
      '#value',
      'hsid',
    ));
    if (!isset($elhsid)) {
      
      $cached = drupal_array_get_nested_value($form_state, array(
        'storage',
        'hs',
        'hs_fields',
        $cid,
      ));
    }
    if (empty($cached)) {
      $docache = TRUE;
    }
    else {
      
      return $cached;
    }
  }
  
  $hsid = _hs_process_determine_hsid($element, $form_state);
  
  $config = $element['#config'];
  
  $element = _hs_process_attach_css_js($element, $hsid, $form_state, $complete_form);
  
  if (!_hs_process_developer_mode_log_diagnostics($element)) {
    return $element;
  }
  
  $hs_selection = $db_selection = array();
  list($hs_selection, $db_selection) = _hierarchical_select_process_calculate_selections($element, $hsid, $form_state);
  
  _hs_process_developer_mode_log_selections($config, $hs_selection, $db_selection);
  
  list($element, $hs_selection, $db_selection) = _hs_process_exclusive_lineages($element, $hs_selection, $db_selection);
  $config = $element['#config'];
  
  $dropbox = !$config['dropbox']['status'] ? FALSE : _hierarchical_select_dropbox_generate($config, $db_selection);
  $hierarchy = _hierarchical_select_hierarchy_generate($config, $hs_selection, $element['#required'], $dropbox);
  
  _hs_process_developer_mode_log_hierarchy_and_dropbox($config, $hierarchy, $dropbox);
  
  $element['#return_value'] = _hierarchical_select_process_calculate_return_value($hierarchy, $config['dropbox']['status'] ? $dropbox : FALSE, $config['module'], $config['params'], $config['save_lineage']);
  if (!is_array($element['#return_value'])) {
    $element['#return_value'] = array(
      $element['#return_value'],
    );
  }
  
  $element['#element_validate'] = isset($element['#element_validate']) ? $element['#element_validate'] : array();
  $element['#element_validate'] = array_merge(array(
    '_hierarchical_select_validate',
  ), $element['#element_validate']);
  
  $form_state['cache'] = TRUE;
  
  $element['#tree'] = TRUE;
  
  $element['hsid'] = array(
    '#type' => 'hidden',
    '#value' => $hsid,
  );
  
  if ($config['render_flat_select']) {
    $element['flat_select'] = _hs_process_render_flat_select($hierarchy, $dropbox, $config);
    
    if (empty($element['flat_select']['#options'])) {
      unset($element['flat_select']);
    }
  }
  
  $element['hierarchical_select'] = array(
    '#theme' => 'hierarchical_select_selects_container',
  );
  $size = isset($element['#size']) ? $element['#size'] : 0;
  $element['hierarchical_select']['selects'] = _hs_process_render_hs_selects($hsid, $hierarchy, $size);
  
  list($element, $creating_new_item) = _hs_process_render_create_new_item($element, $hierarchy);
  
  list($element, $form_state) = _hs_process_render_dropbox($element, $hsid, $creating_new_item, $dropbox, $form_state);
  
  $element = _hs_process_render_nojs($element, $config);
  
  $element['hierarchical_select']['#weight'] = 0;
  $element['dropbox_limit_warning']['#weight'] = 1;
  $element['dropbox']['#weight'] = 2;
  $element['nojs']['#weight'] = 3;
  
  if (isset($element['#disabled']) && $element['#disabled']) {
    _hierarchical_select_mark_as_disabled($element);
  }
  
  if (empty($docache)) {
    if (!isset($form_state['triggering_element']) || $form_state['triggering_element']['#value'] != t('Preview') && $form_state['triggering_element']['#value'] != t('View changes')) {
      if (isset($form_state['input']) && is_array($form_state['input'])) {
        drupal_array_set_nested_value($form_state['input'], $element['#array_parents'], array());
      }
    }
  }
  else {
    
    $form_state['storage']['hs']['hs_fields'][$cid] = $element;
  }
  
  $element = _hs_process_developer_mode_send_log_js($element, $hsid);
  return $element;
}
function hierarchical_select_ajax_update_submit($form, &$form_state) {
  $form_state['no_redirect'] = TRUE;
}
function _hierarchical_select_validate(&$element, &$form_state) {
  
  $hsid = $element['hsid']['#value'];
  $config = _hierarchical_select_inherit_default_config($element['#config']);
  if ($config['dropbox']['status']) {
    if ($config['dropbox']['limit'] > 0) {
      
      $lineage_count = count($form_state['storage']['hs'][$hsid]['dropbox_lineages_selections']);
      if ($lineage_count > $config['dropbox']['limit']) {
        
        form_error($element, t("You've selected %lineage-count items, but you're only allowed to select %dropbox-limit items.", array(
          '%lineage-count' => $lineage_count,
          '%dropbox-limit' => $config['dropbox']['limit'],
        )));
        _hierarchical_select_form_set_error_class($element);
      }
    }
  }
  
  if (isset($element['#disabled']) && $element['#disabled']) {
    $element['#return_value'] = $element['#default_value'];
  }
  $element['#value'] = $element['#return_value'];
  form_set_value($element, $element['#value'], $form_state);
  
  if ($element['#required'] && (!isset($form_state['submit_handlers'][0]) || $form_state['submit_handlers'][0] !== 'hierarchical_select_ajax_update_submit') && (!count($element['#value']) || is_string($element['#value']) && strlen(trim($element['#value'])) == 0)) {
    form_error($element, t('!name field is required.', array(
      '!name' => $element['#title'],
    )));
    _hierarchical_select_form_set_error_class($element);
  }
}
function _hierarchical_select_process_get_hs_selection($element) {
  $hs_selection = array();
  $config = _hierarchical_select_inherit_default_config($element['#config']);
  if (!empty($element['#value']['hierarchical_select']['selects'])) {
    if ($config['save_lineage']) {
      foreach ($element['#value']['hierarchical_select']['selects'] as $key => $value) {
        $hs_selection[] = $value;
      }
    }
    else {
      foreach ($element['#value']['hierarchical_select']['selects'] as $key => $value) {
        $hs_selection[] = $value;
      }
      $hs_selection = _hierarchical_select_hierarchy_validate($hs_selection, $config['module'], $config['params']);
      
      $hs_selection = $hs_selection != -1 ? array(
        end($hs_selection),
      ) : array();
    }
  }
  return $hs_selection;
}
function _hierarchical_select_process_get_db_selection($element, $hsid, &$form_state) {
  $db_selection = array();
  if (!empty($form_state['storage']['hs'][$hsid]['dropbox_lineages_selections'])) {
    
    $remove_from_db_selection = array();
    if (isset($element['#value']['dropbox']['visible']['lineages'])) {
      foreach ($element['#value']['dropbox']['visible']['lineages'] as $x => $remove_value) {
        if ($remove_value['remove'] === '1') {
          
          $remove_from_db_selection[] = substr($x, 8);
          
          $elm =& $form_state['input'];
          foreach ($element['#parents'] as $parent) {
            $elm =& $elm[$parent];
          }
          unset($elm['dropbox']['visible']['lineages'][$x]['remove']);
        }
      }
    }
    
    foreach ($form_state['storage']['hs'][$hsid]['dropbox_lineages_selections'] as $x => $selection) {
      if (!in_array($x, $remove_from_db_selection)) {
        $db_selection = array_merge($db_selection, $selection);
      }
    }
    
    foreach ($remove_from_db_selection as $key => $x) {
      $item = end($form_state['storage']['hs'][$hsid]['dropbox_lineages_selections'][$x]);
      $position = array_search($item, $db_selection);
      if ($position) {
        unset($db_selection[$position]);
      }
    }
    $db_selection = array_unique($db_selection);
  }
  return $db_selection;
}
function _hierarchical_select_process_calculate_selections(&$element, $hsid, &$form_state) {
  $hs_selection = array();
  
  $db_selection = array();
  
  $config = _hierarchical_select_inherit_default_config($element['#config']);
  $dropbox = (bool) $config['dropbox']['status'];
  
  if (empty($form_state['input']) || !isset($element['#value']['hierarchical_select']) && !isset($element['#value']['dropbox'])) {
    $value = !empty($element['#value']) ? $element['#value'] : $element['#default_value'];
    $value = is_array($value) ? $value : array(
      $value,
    );
    if ($dropbox) {
      $db_selection = $value;
    }
    else {
      $hs_selection = $value;
    }
  }
  else {
    $op = isset($form_state['input']['op']) && isset($form_state['input']['hsid']) && $form_state['input']['hsid'] == $hsid ? $form_state['input']['op'] : NULL;
    if ($dropbox && $op == t('Add')) {
      $hs_selection = _hierarchical_select_process_get_hs_selection($element);
      $db_selection = _hierarchical_select_process_get_db_selection($element, $hsid, $form_state);
      
      $db_selection = array_unique(array_merge($db_selection, $hs_selection));
      
      if ((bool) $config['dropbox']['reset_hs']) {
        $hs_selection = array();
      }
    }
    elseif ($op == t('Create')) {
      
      $label = trim($element['#value']['hierarchical_select']['create_new_item']['input']);
      $selects = isset($element['#value']['hierarchical_select']['selects']) ? $element['#value']['hierarchical_select']['selects'] : array();
      $depth = count($selects);
      $parent = $depth > 0 ? end($selects) : 0;
      
      if (empty($label)) {
        $element['#value']['hierarchical_select']['selects'][count($selects)] = 'create_new_item';
      }
      elseif ((count(module_invoke($config['module'], 'hierarchical_select_children', $parent, $config['params'])) || $config['editability']['max_levels'] == 0 && $config['allowed_max_depth'] == 0 || $config['editability']['max_levels'] == 0 && $depth < $config['allowed_max_depth'] || $depth < $config['editability']['max_levels'] && $config['allowed_max_depth'] == 0 || $depth < $config['editability']['max_levels'] && $depth < $config['allowed_max_depth']) && _hierarchical_select_create_new_item_is_allowed($config, $depth)) {
        
        $value = module_invoke($config['module'], 'hierarchical_select_create_item', check_plain($label), $parent, $config['params']);
        
        if ($value) {
          
          $element['#value']['hierarchical_select']['selects'][count($selects)] = $value;
        }
      }
      $hs_selection = _hierarchical_select_process_get_hs_selection($element);
      if ($dropbox) {
        $db_selection = _hierarchical_select_process_get_db_selection($element, $hsid, $form_state);
      }
    }
    else {
      
      $hs_selection = _hierarchical_select_process_get_hs_selection($element);
      if ($dropbox) {
        $db_selection = _hierarchical_select_process_get_db_selection($element, $hsid, $form_state);
      }
    }
  }
  
  $hs_selection = array_unique($hs_selection, SORT_REGULAR);
  $db_selection = array_unique($db_selection, SORT_REGULAR);
  return array(
    $hs_selection,
    $db_selection,
  );
}
function _hs_process_render_hs_selects($hsid, $hierarchy, $size) {
  $form['#tree'] = TRUE;
  $form['#prefix'] = '<div class="selects">';
  $form['#suffix'] = '</div>';
  foreach ($hierarchy->lineage as $depth => $selected_item) {
    $form[$depth] = array(
      '#type' => 'select',
      '#options' => $hierarchy->levels[$depth],
      '#default_value' => $selected_item,
      '#size' => $size,
      
      '#theme_wrappers' => array(),
      
      '#theme' => 'hierarchical_select_select',
      
      '#childinfo' => isset($hierarchy->childinfo[$depth]) ? $hierarchy->childinfo[$depth] : NULL,
      
      '#validated' => TRUE,
    );
  }
  return $form;
}
function _hs_process_render_db_table($hsid, $dropbox) {
  $element['#tree'] = TRUE;
  $element['#theme'] = 'hierarchical_select_dropbox_table';
  
  $element['title'] = array(
    '#type' => 'value',
    '#value' => t($dropbox->title),
  );
  $element['separator'] = array(
    '#type' => 'value',
    '#value' => '›',
  );
  $element['is_empty'] = array(
    '#type' => 'value',
    '#value' => empty($dropbox->lineages),
  );
  if (!empty($dropbox->lineages)) {
    foreach ($dropbox->lineages as $x => $lineage) {
      
      $element['lineages']["lineage-{$x}"] = array(
        '#zebra' => ($x + 1) % 2 == 0 ? 'even' : 'odd',
        '#first' => $x == 0 ? 'first' : '',
        '#last' => $x == count($dropbox->lineages) - 1 ? 'last' : '',
      );
      
      foreach ($lineage as $depth => $item) {
        
        $is_selected = $dropbox->save_lineage || $depth == count($lineage) - 1;
        $element['lineages']["lineage-{$x}"][$depth] = array(
          '#markup' => $item['label'],
          '#prefix' => '<span class="dropbox-item' . ($is_selected ? ' dropbox-selected-item' : '') . '">',
          '#suffix' => '</span>',
        );
      }
      
      $element['lineages']["lineage-{$x}"]['remove'] = array(
        '#type' => 'checkbox',
        '#title' => t('Remove'),
      );
    }
  }
  return $element;
}
function _hs_process_render_flat_select($hierarchy, $dropbox, $config) {
  $selection = array();
  if ($config['dropbox']['status']) {
    foreach ($dropbox->lineages_selections as $lineage_selection) {
      $selection = array_merge($selection, $lineage_selection);
    }
  }
  else {
    $selection = $hierarchy->lineage;
  }
  $options = array();
  foreach ($selection as $value) {
    $is_valid = module_invoke($config['module'], 'hierarchical_select_valid_item', $value, $config['params']);
    if ($is_valid) {
      $options[$value] = $value;
    }
  }
  $element = array(
    '#type' => 'select',
    '#multiple' => $config['save_lineage'] || $config['dropbox']['status'],
    '#options' => $options,
    '#value' => array_keys($options),
    
    '#theme' => 'hierarchical_select_select',
    '#attributes' => array(
      'class' => array(
        'flat-select',
      ),
    ),
  );
  return $element;
}
function _hierarchical_select_process_calculate_return_value($hierarchy, $dropbox = FALSE, $module, $params, $save_lineage) {
  if (!$dropbox) {
    $return_value = _hierarchical_select_hierarchy_validate($hierarchy->lineage, $module, $params);
    
    if (!$save_lineage) {
      $return_value = is_array($return_value) ? end($return_value) : NULL;
    }
    
    $return_value = $return_value != -1 ? $return_value : NULL;
  }
  else {
    $return_value = array();
    foreach ($dropbox->lineages_selections as $x => $selection) {
      if (!$save_lineage) {
        
        $return_value[] = end($selection);
      }
      else {
        
        $lineage = _hierarchical_select_hierarchy_validate($selection, $module, $params);
        $return_value = array_merge($return_value, $lineage);
      }
    }
    $return_value = array_unique($return_value);
  }
  return $return_value;
}
function _hierarchical_select_inherit_default_config($config, $defaults_override = array()) {
  
  $type = hierarchical_select_element_info();
  $defaults = $type['hierarchical_select']['#config'];
  
  unset($defaults['module']);
  unset($defaults['params']);
  
  $defaults = array_smart_merge($defaults, $defaults_override);
  
  $config = array_smart_merge($defaults, $config);
  return $config;
}
function _hierarchical_select_json_convert_hierarchy_to_cache($hierarchy) {
  
  $cache = array();
  foreach ($hierarchy->levels as $depth => $items) {
    $weight = 0;
    foreach ($items as $value => $label) {
      $weight++;
      $cache[] = array(
        'value' => $value,
        'label' => $label,
        'parent' => $depth == 0 ? 0 : $hierarchy->lineage[$depth - 1],
        'weight' => $weight,
      );
    }
  }
  
  $value = end($hierarchy->lineage);
  $cache[] = array(
    'value' => $value . '-has-no-children',
    
    'label' => '',
    'parent' => $value,
    'weight' => 0,
  );
  return $cache;
}
function _hierarchical_select_mark_as_disabled(&$element) {
  
  $element['#attributes']['disabled'] = TRUE;
  
  foreach (element_children($element) as $key) {
    if (isset($element[$key]) && $element[$key]) {
      _hierarchical_select_mark_as_disabled($element[$key]);
    }
  }
}
function _hierarchical_select_create_new_item_is_allowed($config, $depth) {
  return isset($config['editability']['allowed_levels'][$depth]) ? $config['editability']['allowed_levels'][$depth] : 1;
}
function _hierarchical_select_nojs_helptext($dropbox_is_enabled) {
  $output = '<noscript>';
  
  $items = array(
    t('<span class="highlight">enable Javascript</span> in your browser and then refresh this page, for a much enhanced experience.'),
    t('<span class="highlight">click the <em>Update</em> button</span> every time you want to update the selection'),
  );
  $items[1] .= !$dropbox_is_enabled ? '.' : t(", or when you've checked some checkboxes for entries in the dropbox you'd like to remove.");
  $output .= '<span class="warning">';
  $output .= t("You don't have Javascript enabled.");
  $output .= '</span> ';
  $output .= '<span class="ask-to-hover">';
  $output .= t('Hover for more information!');
  $output .= '</span> ';
  $output .= t("But don't worry: you can still use this web site! You have two options:");
  $output .= theme('item_list', array(
    'items' => $items,
    'title' => NULL,
    'type' => 'ul',
    'attributes' => array(
      'class' => array(
        'solutions',
      ),
    ),
  ));
  $output .= '</noscript>';
  return $output;
}
function _hierarchical_select_form_set_error_class(&$element) {
  $config = _hierarchical_select_inherit_default_config($element['#config']);
  if ($config['dropbox']['status']) {
    form_error($element['dropbox']['visible']);
  }
  else {
    for ($i = 0; $i < count(element_children($element['hierarchical_select']['selects'])); $i++) {
      form_error($element['hierarchical_select']['selects'][$i]);
    }
  }
}
function _hierarchical_select_log($item, $reset = FALSE) {
  static $log;
  if ($reset) {
    $copy_of_log = $log;
    $log = array();
    return $copy_of_log;
  }
  $log[] = $item;
}
function _hierarchical_select_hierarchy_generate($config, $selection, $required, $dropbox = FALSE) {
  $hierarchy = new stdClass();
  
  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'));
  }
  
  $start_lineage = microtime(TRUE);
  
  if ($config['save_lineage'] && is_array($selection) && count($selection) >= 2) {
    
    $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) {
          
          list($selection[0], $selection[$i]) = array(
            $selection[$i],
            $selection[0],
          );
        }
        break;
      }
    }
    
    for ($i = 0; $i < count($selection); $i++) {
      $children = array_keys(module_invoke($config['module'], 'hierarchical_select_children', $selection[$i], $config['params']));
      
      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],
          );
        }
      }
    }
  }
  
  $selection = _hierarchical_select_hierarchy_validate($selection, $config['module'], $config['params']);
  
  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;
    
    $none_option = isset($special_items) && count($special_items['none']) ? $special_items['none'][0] : 'none';
    
    $hierarchy->lineage[0] = $first_case || $second_case ? $none_option : 'label_0';
  }
  else {
    
    if ($config['save_lineage']) {
      
      $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 {
        
        $hierarchy->lineage = array();
      }
    }
  }
  
  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(TRUE);
  
  $start_levels = microtime(TRUE);
  
  $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);
  
  if (!empty($config['editability']['status']) && module_hook($config['module'], 'hierarchical_select_create_item') && ($config['module'] == 'hs_taxonomy' && (user_access('administer taxonomy') || user_access('edit terms in ' . $config['params']['vid']))) && _hierarchical_select_create_new_item_is_allowed($config, 0)) {
    $item_type = isset($config['editability']['item_types']) && count($config['editability']['item_types']) > 0 ? t($config['editability']['item_types'][0]) : t('item');
    $option = theme('hierarchical_select_special_option', array(
      'option' => t('create new !item_type', array(
        '!item_type' => $item_type,
      )),
    ));
    $hierarchy->levels[0] = array(
      'create_new_item' => $option,
    ) + $hierarchy->levels[0];
  }
  
  $first_case = !$required;
  $second_case = $config['enforce_deepest'];
  $third_case = $dropbox && count($dropbox->lineages) > 0;
  if (($first_case || $second_case || $third_case) && (!$config['level_labels']['status'] && isset($special_items) && !count($special_items['none']))) {
    $option = theme('hierarchical_select_special_option', array(
      'option' => t('none'),
    ));
    $hierarchy->levels[0] = array(
      'none' => $option,
    ) + $hierarchy->levels[0];
  }
  
  $max_depth = count($hierarchy->lineage) - 1;
  
  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']) {
    
    if (!empty($config['editability']['status']) && ($config['module'] == 'hs_taxonomy' && (user_access('administer taxonomy') || user_access('edit terms in ' . $config['params']['vid']))) && 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', array(
          '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 ($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']) {
      
      for ($depth = 0; $depth <= $max_depth; $depth++) {
        
        if ($depth > 0 && !empty($config['editability']['status']) && module_hook($config['module'], 'hierarchical_select_create_item') && ($config['module'] == 'hs_taxonomy' && (user_access('administer taxonomy') || user_access('edit terms in ' . $config['params']['vid']))) && _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', array(
            'option' => t('create new !item_type', array(
              '!item_type' => $item_type,
            )),
          ));
          $hierarchy->levels[$depth] = array(
            'create_new_item' => $option,
          ) + $hierarchy->levels[$depth];
        }
        
        $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 ($hierarchy->levels[0]['label_0'] == '' && isset($hierarchy->levels[0]['none'])) {
        unset($hierarchy->levels[0]['label_0']);
        
        if ($hierarchy->lineage[0] == 'label_0') {
          $hierarchy->lineage[0] = 'none';
        }
      }
      
      $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)) {
          
          $depth = $max_depth + 1;
          $hierarchy->levels[$depth] = array();
          
          if (!empty($config['editability']['status']) && module_hook($config['module'], 'hierarchical_select_create_item') && ($config['module'] == 'hs_taxonomy' && (user_access('administer taxonomy') || user_access('edit terms in ' . $config['params']['vid']))) && _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', array(
              'option' => t('create new !item_type', array(
                '!item_type' => $item_type,
              )),
            ));
            $hierarchy->levels[$depth] = array(
              'create_new_item' => $option,
            );
          }
          
          $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);
        }
      }
    }
  }
  
  if (!empty($config['editability']['status']) && !empty($config['editability']['allow_new_levels']) && ($config['editability']['max_levels'] == 0 || count($hierarchy->lineage) < $config['editability']['max_levels']) && ($config['allowed_max_depth'] == 0 || count($hierarchy->lineage) < $config['allowed_max_depth']) && module_invoke($config['module'], 'hierarchical_select_valid_item', end($hierarchy->lineage), $config['params']) && ($config['module'] == 'hs_taxonomy' && (user_access('administer taxonomy') || user_access('edit terms in ' . $config['params']['vid']))) && module_hook($config['module'], 'hierarchical_select_create_item')) {
    $depth = $max_depth + 1;
    
    $hierarchy->lineage[$depth] = 'label_' . $depth;
    $label = $config['level_labels']['status'] ? t($config['level_labels']['labels'][$depth]) : '';
    
    $item_type = count($config['editability']['item_types']) >= $depth ? t($config['editability']['item_types'][$depth]) : t('item');
    
    $option = theme('hierarchical_select_special_option', array(
      'option' => t('create new !item_type', array(
        '!item_type' => $item_type,
      )),
    ));
    $hierarchy->levels[$depth] = array(
      'label_' . $depth => $label,
      'create_new_item' => $option,
    );
  }
  
  $end_levels = microtime(TRUE);
  
  $start_childinfo = microtime(TRUE);
  $hierarchy = _hierarchical_select_hierarchy_add_childinfo($hierarchy, $config);
  $end_childinfo = microtime(TRUE);
  
  $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;
}
function _hierarchical_select_apply_entity_settings($level, $config) {
  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'));
  }
  
  if (isset($config['entity_count']['enabled']) && ($config['entity_count']['enabled'] || $config['entity_count']['require_entity']) && module_hook($config['module'], 'hierarchical_select_entity_count')) {
    foreach ($level as $item => $label) {
      
      if (!preg_match('/(none|label_\\d+|create_new_item)/', $item) && !in_array($item, $special_items['exclusive']) && !in_array($item, $special_items['none'])) {
        
        $config['params'] += array(
          'entity_count' => array(
            'settings' => array(
              'count_children' => $config['entity_count']['settings']['count_children'],
              'entity_types' => $config['entity_count']['settings']['entity_types'],
            ),
          ),
        );
        $entity_count = module_invoke($config['module'], 'hierarchical_select_entity_count', $item, $config['params']);
        
        if ($config['entity_count']['require_entity'] && $entity_count == 0) {
          unset($level[$item]);
        }
        elseif ($config['entity_count']['enabled']) {
          $level[$item] = "{$label} ({$entity_count})";
        }
      }
    }
  }
  return $level;
}
function _hierarchical_select_hierarchy_add_childinfo($hierarchy, $config) {
  foreach ($hierarchy->levels as $depth => $level) {
    foreach (array_keys($level) as $item) {
      if (!preg_match('/(none|label_\\d+|create_new_item)/', $item)) {
        $hierarchy->childinfo[$depth][$item] = count(module_invoke($config['module'], 'hierarchical_select_children', $item, $config['params']));
      }
    }
  }
  return $hierarchy;
}
function _hierarchical_select_hierarchy_validate($selection, $module, $params) {
  $valid = TRUE;
  $selection_levels = count($selection);
  for ($i = 0; $i < $selection_levels; $i++) {
    
    if ($valid) {
      $valid = module_invoke($module, 'hierarchical_select_valid_item', $selection[$i], $params);
      if ($i > 0) {
        $parent = $selection[$i - 1];
        $child = $selection[$i];
        $children = array_keys(module_invoke($module, 'hierarchical_select_children', $parent, $params));
        $valid = $valid && in_array($child, $children);
      }
    }
    if (!$valid) {
      unset($selection[$i]);
    }
  }
  if (empty($selection)) {
    $selection = -1;
  }
  if (is_array($selection)) {
    
    $selection = array_values($selection);
  }
  return $selection;
}
function _hierarchical_select_hierarchy_enforce_deepest($lineage, $module, $params) {
  
  $parent = end($lineage);
  
  $children = module_invoke($module, 'hierarchical_select_children', $parent, $params);
  while (count($children)) {
    $keys = array_keys($children);
    $first_child = $keys[0];
    $lineage[] = $first_child;
    $parent = $first_child;
    $children = module_invoke($module, 'hierarchical_select_children', $parent, $params);
  }
  return $lineage;
}
function _hierarchical_select_dropbox_generate($config, $selection) {
  $dropbox = new stdClass();
  $start = microtime(TRUE);
  $dropbox->title = !empty($config['dropbox']['title']) ? filter_xss_admin($config['dropbox']['title']) : t('All selections');
  $dropbox->lineages = array();
  $dropbox->lineages_selections = array();
  
  foreach ($selection as $key => $item) {
    if (!module_invoke($config['module'], 'hierarchical_select_valid_item', $item, $config['params'])) {
      unset($selection[$key]);
    }
  }
  if (!empty($selection)) {
    
    $dropbox->save_lineage = $config['save_lineage'];
    if ($config['save_lineage']) {
      $dropbox->lineages = _hierarchical_select_dropbox_reconstruct_lineages_save_lineage_enabled($config['module'], $selection, $config['params']);
    }
    else {
      
      foreach ($selection as $item) {
        $dropbox->lineages[] = module_invoke($config['module'], 'hierarchical_select_lineage', $item, $config['params']);
      }
      
      foreach ($dropbox->lineages as $id => $lineage) {
        foreach ($lineage as $level => $item) {
          $dropbox->lineages[$id][$level] = array(
            'value' => $item,
            'label' => module_invoke($config['module'], 'hierarchical_select_item_get_label', $item, $config['params']),
          );
        }
      }
    }
    
    foreach ($dropbox->lineages as $id => $lineage) {
      foreach ($lineage as $level => $item) {
        $dropbox->lineages[$id][$level]['label'] = check_plain($dropbox->lineages[$id][$level]['label']);
      }
    }
    if (!isset($config['dropbox']['sort']) || $config['dropbox']['sort']) {
      usort($dropbox->lineages, '_hierarchical_select_dropbox_sort');
    }
    
    foreach ($dropbox->lineages as $id => $lineage) {
      if ($config['save_lineage']) {
        
        $dropbox->lineages_selections[$id] = array_map('_hierarchical_select_dropbox_lineage_item_get_value', $lineage);
      }
      else {
        
        $dropbox->lineages_selections[$id][0] = $lineage[count($lineage) - 1]['value'];
      }
    }
  }
  
  $dropbox->build_time = (microtime(TRUE) - $start) * 1000;
  return $dropbox;
}
function _hierarchical_select_dropbox_reconstruct_lineages_save_lineage_enabled($module, $selection, $params) {
  
  $lineages = array();
  $root_level = module_invoke($module, 'hierarchical_select_root_level', $params);
  foreach ($selection as $key => $item) {
    
    if (array_key_exists($item, $root_level)) {
      $lineages[][0] = array(
        'value' => $item,
        'label' => $root_level[$item],
      );
      unset($selection[$key]);
    }
  }
  
  $at_least_one = TRUE;
  for ($level = 0; $at_least_one; $level++) {
    $at_least_one = FALSE;
    $num = count($lineages);
    
    for ($id = 0; $id < $num; $id++) {
      
      if (!isset($lineages[$id][$level])) {
        continue;
      }
      $children = module_invoke($module, 'hierarchical_select_children', $lineages[$id][$level]['value'], $params);
      $child_added_to_lineage = FALSE;
      foreach (array_keys($children) as $child) {
        if (in_array((string) $child, $selection)) {
          if (!$child_added_to_lineage) {
            
            $lineages[$id][$level + 1] = array(
              'value' => $child,
              'label' => $children[$child],
            );
            $child_added_to_lineage = TRUE;
            $at_least_one = TRUE;
          }
          else {
            
            $lineage = $lineages[$id];
            $lineage[$level + 1] = array(
              'value' => $child,
              'label' => $children[$child],
            );
            
            $lineages[] = $lineage;
          }
        }
      }
    }
  }
  return $lineages;
}
function _hierarchical_select_dropbox_sort($lineage_a, $lineage_b) {
  $string_a = implode('', array_map('_hierarchical_select_dropbox_lineage_item_get_label', $lineage_a));
  $string_b = implode('', array_map('_hierarchical_select_dropbox_lineage_item_get_label', $lineage_b));
  return strcmp($string_a, $string_b);
}
function _hierarchical_select_dropbox_lineage_item_get_label($item) {
  return t($item['label']);
}
function _hierarchical_select_dropbox_lineage_item_get_value($item) {
  return $item['value'];
}
if (!function_exists('array_smart_merge')) {
  function array_smart_merge($array, $override) {
    if (is_array($array) && is_array($override)) {
      foreach ($override as $k => $v) {
        if (isset($array[$k]) && is_array($v) && is_array($array[$k])) {
          $array[$k] = array_smart_merge($array[$k], $v);
        }
        else {
          $array[$k] = $v;
        }
      }
    }
    return $array;
  }
}
function _hierarchical_select_special_item_exclusive($item) {
  return in_array('exclusive', $item);
}
function _hierarchical_select_special_item_none($item) {
  return in_array('none', $item);
}