hierarchical_select.module

Tracking 6.x-3.x branch
  1. drupal
    1. 5 contributions/hierarchical_select/hierarchical_select.module
    2. 6 contributions/hierarchical_select/hierarchical_select.module

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

Functions & methods

NameDescription
hierarchical_select_after_buildHierarchical select form element type #after_build callback.
hierarchical_select_ajax_data_alterImplementation of hook_ajax_data_alter(). (Necessary for Views AJAX pager support.)
hierarchical_select_config_id_loadWildcard loader for Hierarchical Select config ID's.
hierarchical_select_elementsImplementation of hook_elements().
hierarchical_select_features_apiImplementation of hook_features_api().
hierarchical_select_flush_cachesImplementation of hook_flush_caches().
hierarchical_select_form_alterImplementation of hook_form_alter().
hierarchical_select_jsonMenu callback; format=text/json; generates and outputs the appropriate HTML.
hierarchical_select_menuImplementation of hook_menu().
hierarchical_select_processHierarchical select form element type #process callback.
hierarchical_select_requirementsImplementation of hook_requirements().
hierarchical_select_simpletestImplementation of hook_simpletest().
hierarchical_select_themeImplementation of hook_theme().
_hierarchical_select_add_js_settingsAbstraction around drupal_add_js() and Views' $form_state['js settings'].
_hierarchical_select_apply_entity_settingsGiven a level, apply the entity_count and require_entity settings.
_hierarchical_select_create_new_item_is_allowedHelper function to determine whether a given depth (i.e. the depth of a level) is allowed by the allowed_levels setting.
_hierarchical_select_dropbox_generateGenerate the dropbox object.
_hierarchical_select_dropbox_lineage_item_get_labelHelper function needed for the array_map() call in the dropbox sorting callback.
_hierarchical_select_dropbox_lineage_item_get_valueHelper function needed for the array_map() call in the dropbox lineages selections creation.
_hierarchical_select_dropbox_reconstruct_lineages_save_lineage_enabledHelper function to reconstruct the lineages given a set of selected items and the fact that the "save lineage" setting is enabled.
_hierarchical_select_dropbox_sortDropbox lineages sorting callback.
_hierarchical_select_form_has_hierarchical_selectDetect whether a form has at least one hierarchical_select form element.
_hierarchical_select_form_set_error_classSet the 'error' class on the appropriate part of Hierarchical Select, depending on its configuration.
_hierarchical_select_get_form_itemGet the form item that has the the given #name property.
_hierarchical_select_hierarchy_add_childinfoExtends a hierarchy object with child information: for each item in the hierarchy, the child count will be retrieved and stored in the hierarchy object, in the "childinfo" property. Items are grouped per level.
_hierarchical_select_hierarchy_enforce_deepestHelper function to update the lineage of the hierarchy to ensure that the user selects an item in the deepest level of the hierarchy.
_hierarchical_select_hierarchy_generateGenerate the hierarchy object.
_hierarchical_select_hierarchy_validateReset the selection if no valid item was selected. The first item in the array corresponds to the first selected term. As soon as an invalid item is encountered, the lineage from that level to the deeper levels should be unset. This is so to ignore…
_hierarchical_select_inherit_default_configInherit the default config from Hierarchical Selects' hook_elements().
_hierarchical_select_json_convert_hierarchy_to_cacheConvert a hierarchy object into an array of arrays that can be used for caching an entire hierarchy in a client-side database.
_hierarchical_select_logAppend messages to Hierarchical Select's log. Used when in developer mode.
_hierarchical_select_mark_as_disabledHelper function that marks every element in the given element as disabled.
_hierarchical_select_nojs_helptextHelper function that generates the help text is that is displayed to the user when Javascript is disabled.
_hierarchical_select_process_calculate_return_valueCalculate the return value of a hierarchical_select form element, based on the $hierarchy and $dropbox objects. We have to set a return value, because the values set and used by this form element ($element['#value]) are not easily usable in the…
_hierarchical_select_process_calculate_selectionsCalculates the flat selections of both the hierarchical select and the dropbox.
_hierarchical_select_process_get_db_selectionGet the current (flat) selection of the dropbox.
_hierarchical_select_process_get_hs_selectionGet the current (flat) selection of the hierarchical select.
_hierarchical_select_process_render_db_hiddenRender the hidden part of the dropbox. This part stores the lineages of all selections in the dropbox.
_hierarchical_select_process_render_db_visibleRender the visible part of the dropbox.
_hierarchical_select_process_render_flat_selectRender a flat select version of a hierarchical_select form element. This is necessary for backwards compatibility (together with some Javascript code) in case of GET forms.
_hierarchical_select_process_render_hs_selectsRender the selects in the hierarchical select.
_hierarchical_select_setup_jsHelper function to add the required Javascript files and settings.
_hierarchical_select_special_item_exclusiveHelper function needed for the array_filter() call to filter the items marked with the 'exclusive' property
_hierarchical_select_special_item_noneHelper function needed for the array_filter() call to filter the items marked with the 'none' property
_hierarchical_select_store_nameStore the #name property of the given form item, so we can retrieve a list of #name properties of hierarchical_select form items present in this form later.
_hierarchical_select_validateHierarchical select form element #element_validate callback.

Constants

NameDescription
HS_CACHE_LIFETIME_DEFAULT
HS_DEVELOPER_MODE

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * This module defines the "hierarchical_select" form element, which is a
  5. * greatly enhanced way for letting the user select items in a hierarchy.
  6. */
  7. // Make sure that the devel module is installed when you enable developer mode!
  8. define('HS_DEVELOPER_MODE', 0);
  9. // 6 hours cache life time for forms should be plenty.
  10. define('HS_CACHE_LIFETIME_DEFAULT', 21600);
  11. //----------------------------------------------------------------------------
  12. // Drupal core hooks.
  13. /**
  14. * Implementation of hook_menu().
  15. */
  16. function hierarchical_select_menu() {
  17. $items['hierarchical_select_json'] = array(
  18. 'page callback' => 'hierarchical_select_json',
  19. 'type' => MENU_CALLBACK,
  20. // TODO: Needs improvements. Ideally, this would inherit the permissions
  21. // of the form the Hierarchical Select was in.
  22. 'access callback' => TRUE,
  23. );
  24. $items['admin/settings/hierarchical_select'] = array(
  25. 'title' => 'Hierarchical Select',
  26. 'description' => 'Configure site-wide settings for the Hierarchical Select form element.',
  27. 'access arguments' => array('administer site configuration'),
  28. 'page callback' => 'drupal_get_form',
  29. 'page arguments' => array('hierarchical_select_admin_settings'),
  30. 'type' => MENU_NORMAL_ITEM,
  31. 'file' => 'hierarchical_select.admin.inc',
  32. );
  33. $items['admin/settings/hierarchical_select/settings'] = array(
  34. 'title' => 'Site-wide settings',
  35. 'access arguments' => array('administer site configuration'),
  36. 'weight' => -10,
  37. 'type' => MENU_DEFAULT_LOCAL_TASK,
  38. 'file' => 'hierarchical_select.admin.inc',
  39. );
  40. $items['admin/settings/hierarchical_select/configs'] = array(
  41. 'title' => 'Configurations',
  42. 'description' => 'All available Hierarchical Select configurations.',
  43. 'access arguments' => array('administer site configuration'),
  44. 'page callback' => 'hierarchical_select_admin_configs',
  45. 'type' => MENU_LOCAL_TASK,
  46. 'file' => 'hierarchical_select.admin.inc',
  47. );
  48. $items['admin/settings/hierarchical_select/implementations'] = array(
  49. 'title' => 'Implementations',
  50. 'description' => 'Features of each Hierarchical Select implementation.',
  51. 'access arguments' => array('administer site configuration'),
  52. 'page callback' => 'hierarchical_select_admin_implementations',
  53. 'type' => MENU_LOCAL_TASK,
  54. 'file' => 'hierarchical_select.admin.inc',
  55. );
  56. $items['admin/settings/hierarchical_select/export/%hierarchical_select_config_id'] = array(
  57. 'title' => 'Export',
  58. 'access arguments' => array('administer site configuration'),
  59. 'page callback' => 'drupal_get_form',
  60. 'page arguments' => array('hierarchical_select_admin_export', 4),
  61. 'type' => MENU_LOCAL_TASK,
  62. 'file' => 'hierarchical_select.admin.inc',
  63. );
  64. $items['admin/settings/hierarchical_select/import/%hierarchical_select_config_id'] = array(
  65. 'title' => 'Import',
  66. 'access arguments' => array('administer site configuration'),
  67. 'page callback' => 'drupal_get_form',
  68. 'page arguments' => array('hierarchical_select_admin_import', 4),
  69. 'type' => MENU_LOCAL_TASK,
  70. 'file' => 'hierarchical_select.admin.inc',
  71. );
  72. return $items;
  73. }
  74. /**
  75. * Implementation of hook_flush_caches().
  76. */
  77. function hierarchical_select_flush_caches() {
  78. return array('cache_hierarchical_select');
  79. }
  80. /**
  81. * Implementation of hook_form_alter().
  82. */
  83. function hierarchical_select_form_alter(&$form, $form_state, $form_id) {
  84. if (_hierarchical_select_form_has_hierarchical_select($form)) {
  85. $form['#after_build'][] = 'hierarchical_select_after_build';
  86. }
  87. }
  88. /**
  89. * Implementation of hook_elements().
  90. */
  91. function hierarchical_select_elements() {
  92. $type['hierarchical_select'] = array(
  93. '#input' => TRUE,
  94. '#process' => array('hierarchical_select_process'),
  95. '#config' => array(
  96. 'module' => 'some_module',
  97. 'params' => array(),
  98. 'save_lineage' => 0,
  99. 'enforce_deepest' => 0,
  100. 'entity_count' => 0,
  101. 'require_entity' => 0,
  102. 'resizable' => 1,
  103. 'level_labels' => array(
  104. 'status' => 0,
  105. 'labels' => array(),
  106. ),
  107. 'dropbox' => array(
  108. 'status' => 0,
  109. 'title' => t('All selections'),
  110. 'limit' => 0,
  111. 'reset_hs' => 1,
  112. ),
  113. 'editability' => array(
  114. 'status' => 0,
  115. 'item_types' => array(),
  116. 'allowed_levels' => array(),
  117. 'allow_new_levels' => 0,
  118. 'max_levels' => 3,
  119. ),
  120. 'animation_delay' => variable_get('hierarchical_select_animation_delay', 400),
  121. 'special_items' => array(),
  122. 'render_flat_select' => 0,
  123. 'path' => 'hierarchical_select_json',
  124. ),
  125. '#default_value' => -1,
  126. );
  127. return $type;
  128. }
  129. /**
  130. * Implementation of hook_requirements().
  131. */
  132. function hierarchical_select_requirements($phase) {
  133. $requirements = array();
  134. if ($phase == 'runtime') {
  135. // Check if all hook_update_n() hooks have been executed.
  136. require_once('includes/install.inc');
  137. drupal_load_updates();
  138. $updates = drupal_get_schema_versions('hierarchical_select');
  139. $current = drupal_get_installed_schema_version('hierarchical_select');
  140. $up_to_date = (end($updates) == $current);
  141. $hierarchical_select_weight = db_result(db_query("SELECT weight FROM {system} WHERE type = 'module' AND name = 'hierarchical_select'"));
  142. $core_overriding_modules = array('hs_book', 'hs_menu', 'hs_taxonomy');
  143. $path_errors = array();
  144. foreach ($core_overriding_modules as $module) {
  145. $filename = db_result(db_query("SELECT filename FROM {system} WHERE type = 'module' AND name = '%s'", $module));
  146. if (strpos($filename, 'modules/') === 0) {
  147. $module_info = drupal_parse_info_file(dirname($filename) ."/$module.info");
  148. $path_errors[] = t('!module', array('!module' => $module_info['name']));
  149. }
  150. }
  151. $weight_errors = array();
  152. foreach (module_implements('hierarchical_select_root_level') as $module) {
  153. $weight = db_result(db_query("SELECT weight FROM {system} WHERE name = '%s'", $module));
  154. if (!($hierarchical_select_weight > $weight)) {
  155. $filename = db_result(db_query("SELECT filename FROM {system} WHERE type = 'module' AND name = '%s'", $module));
  156. $module_info = drupal_parse_info_file(dirname($filename) ."/$module.info");
  157. $weight_errors[] = t('!module (!weight)', array('!module' => $module_info['name'], '!weight' => $weight));
  158. }
  159. }
  160. if ($up_to_date && !count($path_errors) && !count($weight_errors)) {
  161. $value = t('All updates installed. Implementation modules are installed correctly.');
  162. $description = '';
  163. $severity = REQUIREMENT_OK;
  164. }
  165. elseif ($path_errors) {
  166. $value = t('Modules incorrectly installed!');
  167. $description = t(
  168. "The following modules implement Hierarchical Select module for Drupal
  169. core modules, but are installed in the wrong location. They're
  170. installed in core's <code>modules</code> directory, but should be
  171. installed in either the <code>sites/all/modules</code> directory or a
  172. <code>sites/yoursite.com/modules</code> directory"
  173. ) .':'. theme('item_list', $path_errors);
  174. $severity = REQUIREMENT_ERROR;
  175. }
  176. elseif ($weight_errors) {
  177. $value = t('Module weight incorrectly configured!');
  178. $description = t(
  179. 'The weight of the Hierarchical Select module (!weight) is not
  180. strictly higher than the weight of the following modules',
  181. array('!weight' => $hierarchical_select_weight)
  182. ) .':'. theme('item_list', $weight_errors);
  183. $severity = REQUIREMENT_ERROR;
  184. }
  185. else {
  186. $value = t('Not all updates installed!');
  187. $description = t('Please run update.php to install the latest updates!
  188. You have installed update !installed_update, but the latest update is
  189. !latest_update!',
  190. array(
  191. '!installed_update' => $current,
  192. '!latest_update' => end($updates),
  193. )
  194. );
  195. $severity = REQUIREMENT_ERROR;
  196. }
  197. $requirements['hierarchical_select'] = array(
  198. 'title' => t('Hierarchical Select'),
  199. 'value' => $value,
  200. 'description' => $description,
  201. 'severity' => $severity,
  202. );
  203. }
  204. return $requirements;
  205. }
  206. /**
  207. * Implementation of hook_theme().
  208. */
  209. function hierarchical_select_theme() {
  210. return array(
  211. 'hierarchical_select_form_element' => array(
  212. 'file' => 'includes/theme.inc',
  213. 'arguments' => array('element' => NULL, 'value' => NULL),
  214. ),
  215. 'hierarchical_select' => array(
  216. 'file' => 'includes/theme.inc',
  217. 'arguments' => array('element' => NULL),
  218. ),
  219. 'hierarchical_select_selects_container' => array(
  220. 'file' => 'includes/theme.inc',
  221. 'arguments' => array('element' => NULL),
  222. ),
  223. 'hierarchical_select_select' => array(
  224. 'file' => 'includes/theme.inc',
  225. 'arguments' => array('element' => NULL),
  226. ),
  227. 'hierarchical_select_special_option' => array(
  228. 'file' => 'includes/theme.inc',
  229. 'arguments' => array('option' => NULL),
  230. ),
  231. 'hierarchical_select_textfield' => array(
  232. 'file' => 'includes/theme.inc',
  233. 'arguments' => array('element' => NULL),
  234. ),
  235. 'hierarchical_select_dropbox_table' => array(
  236. 'file' => 'includes/theme.inc',
  237. 'arguments' => array('element' => NULL),
  238. ),
  239. 'hierarchical_select_common_config_form_level_labels' => array(
  240. 'file' => 'includes/theme.inc',
  241. 'arguments' => array('form' => NULL),
  242. ),
  243. 'hierarchical_select_common_config_form_editability' => array(
  244. 'file' => 'includes/theme.inc',
  245. 'arguments' => array('form' => NULL),
  246. ),
  247. 'hierarchical_select_selection_as_lineages' => array(
  248. 'file' => 'includes/theme.inc',
  249. 'arguments' => array(
  250. 'selection' => NULL,
  251. 'config' => NULL,
  252. ),
  253. ),
  254. );
  255. }
  256. /**
  257. * Implementation of hook_simpletest().
  258. */
  259. function hierarchical_select_simpletest() {
  260. $dir = drupal_get_path('module', 'hierarchical_select') .'/tests';
  261. $tests = file_scan_directory($dir, '\.test$');
  262. return array_keys($tests);
  263. }
  264. /**
  265. * Implementation of hook_features_api().
  266. */
  267. function hierarchical_select_features_api() {
  268. return array(
  269. 'hierarchical_select' => array(
  270. 'name' => t('Hierarchical select configs'),
  271. 'feature_source' => TRUE,
  272. 'default_hook' => 'hierarchical_select_default_configs',
  273. 'default_file' => FEATURES_DEFAULTS_INCLUDED,
  274. 'file' => drupal_get_path('module', 'hierarchical_select') .'/hierarchical_select.features.inc',
  275. ),
  276. );
  277. }
  278. //----------------------------------------------------------------------------
  279. // Menu system callbacks.
  280. /**
  281. * Wildcard loader for Hierarchical Select config ID's.
  282. */
  283. function hierarchical_select_config_id_load($config_id) {
  284. $config = variable_get('hs_config_'. $config_id, FALSE);
  285. return ($config !== FALSE) ? $config['config_id'] : FALSE;
  286. }
  287. /**
  288. * Menu callback; format=text/json; generates and outputs the appropriate HTML.
  289. */
  290. function hierarchical_select_json() {
  291. // We are returning Javascript, so tell the browser. Ripped from Drupal 6's
  292. // drupal_json() function.
  293. drupal_set_header('Content-Type: text/javascript; charset=utf-8');
  294. $hs_form_build_id = $_POST['hs_form_build_id'];
  295. // In this context, we're in a node selection mode.
  296. // See http://drupal.org/node/683574#comment-3117926.
  297. if (module_exists('i18n') && isset($_POST['language'])) {
  298. i18n_selection_mode('node', $_POST['language']);
  299. }
  300. // Collect all necessary variables.
  301. $cached = cache_get($hs_form_build_id, 'cache_hierarchical_select');
  302. $storage = $cached->data;
  303. // Ensure that the form id in the POST array is the same as the one of the
  304. // stored parameters of the original form. For 99% of the forms, this step
  305. // is not necessary, but when a hierarchical_select form item is inside a
  306. // form in a subform_element in a form, then it is necessary.
  307. $form_id = $_POST['form_id'] = $storage['parameters'][0];
  308. if (HS_DEVELOPER_MODE) {
  309. _hierarchical_select_log("form_id: $form_id");
  310. _hierarchical_select_log("hs_form_build_id: $hs_form_build_id");
  311. }
  312. $form_state = &$storage['parameters'][1];
  313. // Include the file in which the form definition function lives.
  314. if (!empty($storage['file'])) {
  315. require_once($storage['file']);
  316. }
  317. // Also include files set in $form_state['form_load_files']. Set by CTools
  318. // Delegator, which is used by Panels (i.e. this is necessary for Panels
  319. // compatibility).
  320. if (isset($form_state['form_load_files'])) {
  321. foreach ($form_state['form_load_files'] as $file) {
  322. require_once './' . $file;
  323. }
  324. }
  325. // Retrieve and process the form.
  326. $form = call_user_func_array('drupal_retrieve_form', $storage['parameters']);
  327. drupal_prepare_form($form_id, $form, $form_state);
  328. $form['#post'] = $_POST;
  329. $form = form_builder($form_id, $form, $form_state);
  330. // Render only the relevant part of the form (i.e. the hierarchical_select
  331. // form item that has triggered this AJAX callback).
  332. $hsid = $_POST['hsid'];
  333. $name = $storage['#names'][$hsid];
  334. $part_of_form = _hierarchical_select_get_form_item($form, $name);
  335. $output = drupal_render($part_of_form);
  336. // If the user's browser supports the active cache system, then send the
  337. // currently requested hierarchy in an easy-to-manage form.
  338. $cache = array();
  339. if (isset($_POST['client_supports_caching'])) {
  340. if ($_POST['client_supports_caching'] == 'true') {
  341. $cache = _hierarchical_select_json_convert_hierarchy_to_cache($part_of_form['hierarchy']['#value']);
  342. }
  343. else if ($_POST['client_supports_caching'] == 'false') {
  344. // This indicates that a client-side cache is installed, but not working
  345. // properly.
  346. // TODO: figure out a clean way to notify the administrator.
  347. }
  348. }
  349. print drupal_to_js(array(
  350. 'cache' => $cache,
  351. 'output' => $output,
  352. 'log' => (isset($part_of_form['log']['#value'])) ? $part_of_form['log']['#value'] : NULL,
  353. ));
  354. exit;
  355. }
  356. //----------------------------------------------------------------------------
  357. // Forms API callbacks.
  358. /**
  359. * Hierarchical select form element type #process callback.
  360. */
  361. function hierarchical_select_process($element, $edit, &$form_state, $form) {
  362. if (!is_array($element['#value']) || !isset($element['#value']['hsid'])) {
  363. // The HSID is stored in the session, to allow for multiple Hierarchical
  364. // Select form items on the same page of which at least one is added through
  365. // AHAH. A normal static variable won't do in this case, because then at
  366. // least two Hierarchical Select form items will have HSID 0, because they
  367. // are generated in different requests, both of which will have a first HSID
  368. // of 0. This will then cause problems on the page.
  369. if (!isset($_SESSION['hsid'])) {
  370. $_SESSION['hsid'] = 0;
  371. }
  372. else {
  373. // Let the HSID go from 0 to 99, then start over. Larger numbers are
  374. // pointless: who's going to use more than a hundred Hierarchical Select
  375. // form items on the same page?
  376. $_SESSION['hsid'] = ($_SESSION['hsid'] + 1) % 100;
  377. }
  378. $hsid = $_SESSION['hsid'];
  379. }
  380. else {
  381. $hsid = check_plain($element['#value']['hsid']);
  382. }
  383. $element['hsid'] = array('#type' => 'hidden', '#value' => $hsid);
  384. // A hierarchical_select form element expands to multiple items. For example
  385. // $element['hsid'] got set just above. If #value is not an array, then
  386. // form_set_value(), which is called by form_builder() will fail, because it
  387. // assumes that #value is an array, because we are trying to set a child of
  388. // it.
  389. if (!is_array($element['#value'])) {
  390. $element['#value'] = array($element['#value']);
  391. }
  392. // Store the #name property of each hierarchical_select form item, this is
  393. // necessary to find this form item back in an AJAX callback.
  394. _hierarchical_select_store_name($element, $hsid);
  395. // Get the config and convert the 'special_items' setting to a more easily
  396. // accessible format.
  397. $config = $element['#config'];
  398. if (isset($config['special_items'])) {
  399. $special_items['exclusive'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_exclusive'));
  400. $special_items['none'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_none'));
  401. }
  402. // Set up Javascript and add settings specifically for the current
  403. // hierarchical select.
  404. $config = _hierarchical_select_inherit_default_config($element['#config']);
  405. _hierarchical_select_setup_js();
  406. _hierarchical_select_setup_js($form_state);
  407. $settings = array(
  408. 'HierarchicalSelect' => array(
  409. 'settings' => array(
  410. $hsid => array(
  411. 'animationDelay' => ($config['animation_delay'] == 0) ? (int) variable_get('hierarchical_select_animation_delay', 400) : $config['animation_delay'],
  412. 'cacheId' => $config['module'] .'_'. implode('_', (is_array($config['params'])) ? $config['params'] : array()),
  413. 'renderFlatSelect' => (isset($config['render_flat_select'])) ? (int) $config['render_flat_select'] : 0,
  414. 'createNewItems' => (isset($config['editability']['status'])) ? (int) $config['editability']['status'] : 0,
  415. 'createNewLevels' => (isset($config['editability']['allow_new_levels'])) ? (int) $config['editability']['allow_new_levels'] : 0,
  416. 'resizable' => (isset($config['resizable'])) ? (int) $config['resizable'] : 0,
  417. 'path' => $config['path'],
  418. ),
  419. ),
  420. )
  421. );
  422. _hierarchical_select_add_js_settings($settings, $form_state);
  423. // Basic config validation and diagnostics.
  424. if (HS_DEVELOPER_MODE) {
  425. $diagnostics = array();
  426. if (!isset($config['module']) || empty($config['module'])) {
  427. $diagnostics[] = t("'module is not set!");
  428. }
  429. elseif (!module_exists($config['module'])) {
  430. $diagnostics[] = t('the module that should be used (module) is not installed!', array('%module' => $config['module']));
  431. }
  432. else {
  433. $required_params = module_invoke($config['module'], 'hierarchical_select_params');
  434. $missing_params = array_diff($required_params, array_keys($config['params']));
  435. if (!empty($missing_params)) {
  436. $diagnostics[] = t("'params' is missing values for: ") . implode(', ', $missing_params) .'.';
  437. }
  438. }
  439. $config_id = (isset($config['config_id']) && is_string($config['config_id'])) ? $config['config_id'] : 'none';
  440. if (empty($diagnostics)) {
  441. _hierarchical_select_log("Config diagnostics (config id: $config_id): no problems found!");
  442. }
  443. else {
  444. $diagnostics_string = print_r($diagnostics, TRUE);
  445. $message = "Config diagnostics (config id: $config_id): $diagnostics_string";
  446. _hierarchical_select_log($message);
  447. $element['#type']= 'item';
  448. $element['#value'] = '<p><span style="color:red;">Fix the indicated errors in the #config property first!</span><br />'. nl2br($message) .'</p>';
  449. return $element;
  450. }
  451. }
  452. // Calculate the selections in both the hierarchical select and the dropbox,
  453. // we need these before we can render anything.
  454. list($hs_selection, $db_selection) = _hierarchical_select_process_calculate_selections($element);
  455. if (HS_DEVELOPER_MODE) {
  456. _hierarchical_select_log("Calculated hierarchical select selection:");
  457. _hierarchical_select_log($hs_selection);
  458. if ($config['dropbox']['status']) {
  459. _hierarchical_select_log("Calculated dropbox selection:");
  460. _hierarchical_select_log($db_selection);
  461. }
  462. }
  463. // If:
  464. // - the special_items setting has been configured
  465. // - at least one special item has the 'exclusive' property
  466. // - the dropbox is enabled
  467. // then do the necessary processing to make exclusive lineages possible.
  468. if (isset($special_items) && count($special_items['exclusive']) && $config['dropbox']['status']) {
  469. // When the form is first loaded, $db_selection will contain the selection
  470. // that we should check, but in updates, $hs_selection will.
  471. $selection = (!empty($hs_selection)) ? $hs_selection : $db_selection;
  472. // If the current selection of the hierarchical select matches one of the
  473. // configured exclusive items, then disable the dropbox (to ensure an
  474. // exclusive selection).
  475. $exclusive_item = array_intersect($selection, $special_items['exclusive']);
  476. if (count($exclusive_item)) {
  477. // By also updating the configuration stored in $element, we ensure that
  478. // the validation step, which extracts the configuration again, also gets
  479. // the updated config.
  480. $element['#config']['dropbox']['status'] = 0;
  481. $config = _hierarchical_select_inherit_default_config($element['#config']);
  482. // Set the hierarchical select to the exclusive item and make the
  483. // dropbox empty.
  484. $hs_selection = array(0 => reset($exclusive_item));
  485. $db_selection = array();
  486. }
  487. }
  488. // Generate the $hierarchy and $dropbox objects using the selections that
  489. // were just calculated.
  490. $dropbox = (!$config['dropbox']['status']) ? FALSE : _hierarchical_select_dropbox_generate($config, $db_selection);
  491. $hierarchy = _hierarchical_select_hierarchy_generate($config, $hs_selection, $element['#required'], $dropbox);
  492. if (HS_DEVELOPER_MODE) {
  493. _hierarchical_select_log('Generated hierarchy in '. $hierarchy->build_time['total'] .' ms:');
  494. _hierarchical_select_log($hierarchy);
  495. if ($config['dropbox']['status']) {
  496. _hierarchical_select_log('Generated dropbox in '. $dropbox->build_time .' ms: ');
  497. _hierarchical_select_log($dropbox);
  498. }
  499. }
  500. // Store the hierarchy object in the element, we'll need this if the user's
  501. // browser supports the active cache system.
  502. $element['hierarchy'] = array('#type' => 'value', '#value' => $hierarchy);
  503. // Ensure that #tree is enabled!
  504. $element['#tree'] = TRUE;
  505. // If render_flat_select is enabled, render a flat select.
  506. if ($config['render_flat_select']) {
  507. $element['flat_select'] = _hierarchical_select_process_render_flat_select($hierarchy, $dropbox, $config);
  508. }
  509. // Render the hierarchical select.
  510. $element['hierarchical_select'] = array(
  511. '#theme' => 'hierarchical_select_selects_container',
  512. );
  513. $element['hierarchical_select']['selects'] = _hierarchical_select_process_render_hs_selects($hsid, $hierarchy);
  514. // The selects in the hierarchical select should inherit the #size property.
  515. foreach (element_children($element['hierarchical_select']['selects']) as $depth) {
  516. $element['hierarchical_select']['selects'][$depth]['#size'] = isset($element['#size']) ? $element['#size'] : 0;
  517. }
  518. // Check if a new item is being created.
  519. $creating_new_item = FALSE;
  520. if (isset($element['#value']['hierarchical_select']['selects'])) {
  521. foreach ($element['#value']['hierarchical_select']['selects'] as $depth => $value) {
  522. if ($value == 'create_new_item' && _hierarchical_select_create_new_item_is_allowed($config, $depth)) {
  523. $creating_new_item = TRUE;
  524. // We want to override the select in which the "create_new_item"
  525. // option was selected and hide all selects after that, if they exist.
  526. for ($i = $depth; $i < count($hierarchy->lineage); $i++) {
  527. unset($element['hierarchical_select']['selects'][$i]);
  528. }
  529. $element['hierarchical_select']['create_new_item'] = array(
  530. '#prefix' => '<div class="'. str_replace('_', '-', $value) .'">',
  531. '#suffix' => '</div>',
  532. );
  533. $item_type_depth = ($value == 'create_new_item') ? $depth : $depth + 1;
  534. $item_type = (count($config['editability']['item_types']) == $item_type_depth)
  535. ? t($config['editability']['item_types'][$item_type_depth])
  536. : t('item');
  537. $element['hierarchical_select']['create_new_item']['input'] = array(
  538. '#type' => 'textfield',
  539. '#size' => 20,
  540. '#maxlength' => 255,
  541. '#default_value' => t('new @item', array('@item' => $item_type)),
  542. '#attributes' => array(
  543. 'title' => t('new @item', array('@item' => $item_type)),
  544. 'class' => 'create-new-item-input'
  545. ),
  546. // Use a #theme callback to prevent the textfield from being wrapped
  547. // in a div. This simplifies the CSS and JS code.
  548. '#theme' => 'hierarchical_select_textfield',
  549. );
  550. $element['hierarchical_select']['create_new_item']['create'] = array(
  551. '#type' => 'button',
  552. '#value' => t('Create'),
  553. '#attributes' => array('class' => 'create-new-item-create'),
  554. );
  555. $element['hierarchical_select']['create_new_item']['cancel'] = array(
  556. '#type' => 'button',
  557. '#value' => t('Cancel'),
  558. '#attributes' => array('class' => 'create-new-item-cancel'),
  559. );
  560. }
  561. }
  562. }
  563. if ($config['dropbox']['status']) {
  564. if (!$creating_new_item) {
  565. // Append an "Add" button to the selects.
  566. $element['hierarchical_select']['dropbox_add'] = array(
  567. '#type' => 'button',
  568. '#value' => t('Add'),
  569. '#attributes' => array('class' => 'add-to-dropbox'),
  570. );
  571. }
  572. if ($config['dropbox']['limit'] > 0) { // Zero as dropbox limit means no limit.
  573. if (count($dropbox->lineages) == $config['dropbox']['limit']) {
  574. $element['dropbox_limit_warning'] = array(
  575. '#value' => t("You've reached the maximal number of items you can select."),
  576. '#prefix' => '<p class="hierarchical-select-dropbox-limit-warning">',
  577. '#suffix' => '</p>',
  578. );
  579. // Disable all child form elements of $element['hierarchical_select].
  580. _hierarchical_select_mark_as_disabled($element['hierarchical_select']);
  581. }
  582. }
  583. // Add the hidden part of the dropbox. This will be used to store the
  584. // currently selected lineages.
  585. $element['dropbox']['hidden'] = array(
  586. '#prefix' => '<div class="dropbox-hidden">',
  587. '#suffix' => '</div>',
  588. );
  589. $element['dropbox']['hidden'] = _hierarchical_select_process_render_db_hidden($hsid, $dropbox);
  590. // Add the dropbox-as-a-table that will be visible to the user.
  591. $element['dropbox']['visible'] = _hierarchical_select_process_render_db_visible($hsid, $dropbox);
  592. }
  593. // This button and accompanying help text will be hidden when Javascript is
  594. // enabled.
  595. $element['nojs'] = array(
  596. '#prefix' => '<div class="nojs">',
  597. '#suffix' => '</div>',
  598. );
  599. $element['nojs']['update_button'] = array(
  600. '#type' => 'button',
  601. '#value' => t('Update'),
  602. '#attributes' => array('class' => 'update-button'),
  603. );
  604. $element['nojs']['update_button_help_text'] = array(
  605. '#value' => _hierarchical_select_nojs_helptext($config['dropbox']['status']),
  606. '#prefix' => '<div class="help-text">',
  607. '#suffix' => '</div>',
  608. );
  609. // Ensure the render order is correct.
  610. $element['hierarchical_select']['#weight'] = 0;
  611. $element['dropbox_limit_warning']['#weight'] = 1;
  612. $element['dropbox']['#weight'] = 2;
  613. $element['nojs']['#weight'] = 3;
  614. // This prevents values from in $element['#post'] to be used instead of the
  615. // generated default values (#default_value).
  616. // For example: $element['hierarchical_select']['selects']['0']['#default_value']
  617. // is set to 'label_0' after an "Add" operation. When $element['#post'] is
  618. // NOT unset, the corresponding value in $element['#post'] will be used
  619. // instead of the default value that was set. This is undesired behavior.
  620. if (isset($element['#post'])) {
  621. unset($element['#post']);
  622. }
  623. // Finally, calculate the return value of this hierarchical_select form
  624. // element. This will be set in _hierarchical_select_validate(). (If we'd
  625. // set it now, it would be overridden again.)
  626. $element['#return_value'] = _hierarchical_select_process_calculate_return_value($hierarchy, ($config['dropbox']['status']) ? $dropbox : FALSE, $config['module'], $config['params'], $config['save_lineage']);
  627. // Add a validate callback, which will:
  628. // - validate that the dropbox limit was not exceeded.
  629. // - set the return value of this form element.
  630. // Also make sure it is the *first* validate callback.
  631. $element['#element_validate'] = (isset($element['#element_validate'])) ? $element['#element_validate'] : array();
  632. $element['#element_validate'] = array_merge(array('_hierarchical_select_validate'), $element['#element_validate']);
  633. if (HS_DEVELOPER_MODE) {
  634. $element['log'] = array('#type' => 'value', '#value' => _hierarchical_select_log(NULL, TRUE));
  635. $settings = array(
  636. 'HierarchicalSelect' => array(
  637. 'initialLog' => array(
  638. $hsid => $element['log']['#value'],
  639. ),
  640. ),
  641. );
  642. _hierarchical_select_add_js_settings($settings, $form_state);
  643. }
  644. // If the form item is marked as disabled, disable all child form items as
  645. // well.
  646. if (isset($element['#disabled']) && $element['#disabled']) {
  647. _hierarchical_select_mark_as_disabled($element);
  648. }
  649. // Remove #options in case it's been added.
  650. if (isset($element['#options'])) {
  651. unset($element['#options']);
  652. }
  653. return $element;
  654. }
  655. /**
  656. * Hierarchical select form element type #after_build callback.
  657. */
  658. function hierarchical_select_after_build($form, &$form_state) {
  659. // TRICKY: Pageroute compatibility: avoid that the body of this #after_build
  660. // callback is executed twice.
  661. if (isset($form['hs_form_build_id'])) {
  662. return $form;
  663. }
  664. $names = _hierarchical_select_store_name(NULL, NULL, TRUE);
  665. if (!isset($_POST['hs_form_build_id']) && count($names)) {
  666. $parameters = (isset($form['#parameters'])) ? $form['#parameters'] : array();
  667. $menu_item = menu_get_item();
  668. // Collect information in this array, which will be used in dynamic form
  669. // updates, to …
  670. $storage = array(
  671. // … retrieve $form.
  672. 'parameters' => $parameters,
  673. // … determine which part of $form should be rendered.
  674. '#names' => $names,
  675. // … include the file in which the form function lives.
  676. 'file' => $menu_item['file'],
  677. );
  678. // Store the information needed for dynamic form updates in the cache, so
  679. // we can retrieve this in our JSON callbacks (to be able to rebuild and
  680. // render part of the form).
  681. $hs_form_build_id = 'hs_form_'. md5(mt_rand());
  682. $lifetime = variable_get('hierarchical_select_cache_lifetime', HS_CACHE_LIFETIME_DEFAULT);
  683. cache_set($hs_form_build_id, $storage, 'cache_hierarchical_select', time() + $lifetime);
  684. }
  685. elseif (isset($_POST['hs_form_build_id'])) {
  686. // Don't generate a new hs_form_build_id if this is a re-rendering of the
  687. // same form!
  688. $hs_form_build_id = $_POST['hs_form_build_id'];
  689. }
  690. // Store the hs_form_build_id in a hidden value, so that it gets POSTed.
  691. $form_element = array(
  692. '#type' => 'hidden',
  693. '#value' => $hs_form_build_id,
  694. // We have to set #parents manually because we want to send only
  695. // $form_element through form_builder(), not $form. If we set #parents,
  696. // form_builder() has all info it needs to generate #name and #id.
  697. '#parents' => array('hs_form_build_id'),
  698. );
  699. $form['hs_form_build_id'] = form_builder($form['form_id']['#value'], $form_element, $form_state);
  700. return $form;
  701. }
  702. /**
  703. * Hierarchical select form element #element_validate callback.
  704. */
  705. function _hierarchical_select_validate(&$element, &$form_state) {
  706. // If the dropbox is enabled and a dropbox limit is configured, check if
  707. // this limit is not exceeded.
  708. $config = _hierarchical_select_inherit_default_config($element['#config']);
  709. if ($config['dropbox']['status']) {
  710. if ($config['dropbox']['limit'] > 0) { // Zero as dropbox limit means no limit.
  711. // TRICKY: #element_validate is not called upon the initial rendering
  712. // (i.e. it is assumed that the default value is valid). However,
  713. // Hierarchical Select's config can influence the validity (i.e. how
  714. // many selections may be added to the dropbox). This means it's
  715. // possible the user has actually selected too many items without being
  716. // notified of this.
  717. $lineage_count = count($element['#value']['dropbox']['hidden']['lineages_selections']);
  718. if ($lineage_count > $config['dropbox']['limit']) {
  719. // TRICKY: this should propagate the error down to the children, but
  720. // this doesn't seem to happen, since for example the selects of the
  721. // hierarchical select don't get the error class set. Further
  722. // investigation needed.
  723. form_error(
  724. $element,
  725. t("You've selected %lineage-count items, but you're only allowed to select %dropbox-limit items.",
  726. array(
  727. '%lineage-count' => $lineage_count,
  728. '%dropbox-limit' => $config['dropbox']['limit'],
  729. )
  730. )
  731. );
  732. _hierarchical_select_form_set_error_class($element);
  733. }
  734. }
  735. }
  736. // Set the proper return value. I.e. instead of returning all the values
  737. // that are used for making the hierarchical_select form element type work,
  738. // we pass a flat array of item ids. e.g. for the taxonomy module, this will
  739. // be an array of term ids. If a single item is selected, this will not be
  740. // an array.
  741. // If the form item is disabled, set the default value as the return value,
  742. // because otherwise nothing would be returned (disabled form items are not
  743. // submitted, as described in the HTML standard).
  744. if (isset($element['#disabled']) && $element['#disabled']) {
  745. $element['#return_value'] = $element['#default_value'];
  746. }
  747. $element['#value'] = $element['#return_value'];
  748. form_set_value($element, $element['#value'], $form_state);
  749. // We have to check again for errors. This line is taken litterally from
  750. // form.inc, so it works in an identical way.
  751. if ($element['#required'] && (!count($element['#value']) || (is_string($element['#value']) && strlen(trim($element['#value'])) == 0))) {
  752. form_error($element, t('!name field is required.', array('!name' => $element['#title'])));
  753. _hierarchical_select_form_set_error_class($element);
  754. }
  755. }
  756. //----------------------------------------------------------------------------
  757. // Forms API #process callback:
  758. // Calculation of hierarchical select and dropbox selection.
  759. /**
  760. * Get the current (flat) selection of the hierarchical select.
  761. *
  762. * This selection is updatable by the user, because the values are retrieved
  763. * from the selects in $element['hierarchical_select']['selects'].
  764. *
  765. * @param $element
  766. * A hierarchical_select form element.
  767. * @return
  768. * An array (bag) containing the ids of the selected items in the
  769. * hierarchical select.
  770. */
  771. function _hierarchical_select_process_get_hs_selection($element) {
  772. $hs_selection = array();
  773. $config = _hierarchical_select_inherit_default_config($element['#config']);
  774. if (!empty($element['#value']['hierarchical_select']['selects'])) {
  775. if ($config['save_lineage']) {
  776. foreach ($element['#value']['hierarchical_select']['selects'] as $key => $value) {
  777. $hs_selection[] = $value;
  778. }
  779. }
  780. else {
  781. foreach ($element['#value']['hierarchical_select']['selects'] as $key => $value) {
  782. $hs_selection[] = $value;
  783. }
  784. $hs_selection = _hierarchical_select_hierarchy_validate($hs_selection, $config['module'], $config['params']);
  785. // Get the last valid value. (Only the deepest item gets saved). Make
  786. // sure $hs_selection is an array at all times.
  787. $hs_selection = ($hs_selection != -1) ? array(end($hs_selection)) : array();
  788. }
  789. }
  790. return $hs_selection;
  791. }
  792. /**
  793. * Get the current (flat) selection of the dropbox.
  794. *
  795. * This selection is not updatable by the user, because the values are
  796. * retrieved from the hidden values in
  797. * $element['dropbox']['hidden']['lineages_selections']. This selection can
  798. * only be updated by the server, i.e. when the user clicks the "Add" button.
  799. * But this selection can still be reduced in size if the user has marked
  800. * dropbox entries (lineages) for removal.
  801. *
  802. * @param $element
  803. * A hierarchical_select form element.
  804. * @return
  805. * An array (bag) containing the ids of the selected items in the
  806. * dropbox.
  807. */
  808. function _hierarchical_select_process_get_db_selection($element) {
  809. $db_selection = array();
  810. if (!empty($element['#value']['dropbox']['hidden']['lineages_selections'])) {
  811. // This is only present in #value if at least one "Remove" checkbox was
  812. // checked, so ensure that we're doing something valid.
  813. $remove_from_db_selection = (!isset($element['#value']['dropbox']['visible']['lineages'])) ? array() : array_keys($element['#value']['dropbox']['visible']['lineages']);
  814. // Add all selections to the dropbox selection, except for the ones that
  815. // are scheduled for removal.
  816. foreach ($element['#value']['dropbox']['hidden']['lineages_selections'] as $x => $selection) {
  817. if (!in_array($x, $remove_from_db_selection)) {
  818. $db_selection = array_merge($db_selection, unserialize($selection));
  819. }
  820. }
  821. // Ensure that the last item of each selection that was scheduled for
  822. // removal is completely absent from the dropbox selection.
  823. // In case of a tree with multiple parents, the same item can exist in
  824. // different entries, and thus it would stay in the selection. When the
  825. // server then reconstructs all lineages, the lineage we're removing, will
  826. // also be reconstructed: it will seem as if the removing didn't work!
  827. // This will not break removing dropbox entries for hierarchies without
  828. // multiple parents, since items at the deepest level are always unique to
  829. // that specific lineage.
  830. // Easier explanation at http://drupal.org/node/221210#comment-733715.
  831. foreach ($remove_from_db_selection as $key => $x) {
  832. $item = end(unserialize($element['#value']['dropbox']['hidden']['lineages_selections'][$x]));
  833. $position = array_search($item, $db_selection);
  834. if ($position) {
  835. unset($db_selection[$position]);
  836. }
  837. }
  838. $db_selection = array_unique($db_selection);
  839. }
  840. return $db_selection;
  841. }
  842. /**
  843. * Calculates the flat selections of both the hierarchical select and the
  844. * dropbox.
  845. *
  846. * @param $element
  847. * A hierarchical_select form element.
  848. * @return
  849. * An array of the following structure:
  850. * array(
  851. * $hierarchical_select_selection = array(), // Flat list of selected ids.
  852. * $dropbox_selection = array(),
  853. * )
  854. * with both of the subarrays flat lists of selected ids. The
  855. * _hierarchical_select_hierarchy_generate() and
  856. * _hierarchical_select_dropbox_generate() functions should be applied on
  857. * these respective subarrays.
  858. *
  859. * @see _hierarchical_select_hierarchy_generate()
  860. * @see _hierarchical_select_dropbox_generate()
  861. */
  862. function _hierarchical_select_process_calculate_selections(&$element) {
  863. $hs_selection = array(); // hierarchical select selection
  864. $db_selection = array(); // dropbox selection
  865. $config = _hierarchical_select_inherit_default_config($element['#config']);
  866. $dropbox = (bool) $config['dropbox']['status'];
  867. // When:
  868. // - no data was POSTed
  869. // - or #value is set directly and not by a Hierarchical Select POST (and
  870. // therefor set either manually or by another module),
  871. // then use the value of #default_value, or when available, of #value.
  872. if (!isset($element['#post']) || (!isset($element['#value']['hierarchical_select']) && !isset($element['#value']['dropbox']))) {
  873. $value = (isset($element['#value'])) ? $element['#value'] : $element['#default_value'];
  874. $value = (is_array($value)) ? $value : array($value);
  875. if ($dropbox) {
  876. $db_selection = $value;
  877. }
  878. else {
  879. $hs_selection = $value;
  880. }
  881. }
  882. else {
  883. $op = (isset($element['#post']['op'])) ? $element['#post']['op'] : NULL;
  884. if ($dropbox && $op == t('Add')) {
  885. $hs_selection = _hierarchical_select_process_get_hs_selection($element);
  886. $db_selection = _hierarchical_select_process_get_db_selection($element);
  887. // Add $hs_selection to $db_selection (automatically filters to keep
  888. // only the unique ones).
  889. $db_selection = array_merge($db_selection, $hs_selection);
  890. // Only reset $hs_selection if the user has configured it that way.
  891. if ($config['dropbox']['reset_hs']) {
  892. $hs_selection = array();
  893. }
  894. }
  895. else if ($op == t('Create')) {
  896. // This code handles both the creation of a new item in an existing
  897. // level and the creation of an item that also creates a new level.
  898. // TODO: http://drupal.org/node/253868
  899. // TODO: http://drupal.org/node/253869
  900. $label = trim($element['#value']['hierarchical_select']['create_new_item']['input']);
  901. $selects = isset($element['#value']['hierarchical_select']['selects']) ? $element['#value']['hierarchical_select']['selects'] : array();
  902. $depth = count($selects);
  903. $parent = ($depth > 0) ? end($selects) : 0;
  904. // Disallow items with empty labels; allow the user again to create a
  905. // (proper) new item.
  906. if (empty($label)) {
  907. $element['#value']['hierarchical_select']['selects'][count($selects)] = 'create_new_item';
  908. }
  909. // Ensure that this new item will not violate the max_levels and
  910. // allowed_levels settings.
  911. else if (
  912. (count(module_invoke($config['module'], 'hierarchical_select_children', $parent, $config['params']))
  913. || $config['editability']['max_levels'] == 0
  914. || $depth < $config['editability']['max_levels']
  915. )
  916. &&
  917. (_hierarchical_select_create_new_item_is_allowed($config, $depth))
  918. ) {
  919. // Create the new item in the hierarchy and retrieve its value.
  920. $value = module_invoke($config['module'], 'hierarchical_select_create_item', check_plain($label), $parent, $config['params']);
  921. // Ensure the newly created item will be selected after rendering.
  922. if ($value) {
  923. // Pretend there was a select where the "create new item" section
  924. // was, and assign it the value of the item that was just created.
  925. $element['#value']['hierarchical_select']['selects'][count($selects)] = $value;
  926. }
  927. }
  928. $hs_selection = _hierarchical_select_process_get_hs_selection($element);
  929. if ($dropbox) {
  930. $db_selection = _hierarchical_select_process_get_db_selection($element);
  931. }
  932. }
  933. else {
  934. // This handles the cases of:
  935. // - $op == t('Update')
  936. // - $op == t('Cancel') (used when creating a new item or a new level)
  937. // - any other submit button, e.g. the "Preview" button
  938. $hs_selection = _hierarchical_select_process_get_hs_selection($element);
  939. if ($dropbox) {
  940. $db_selection = _hierarchical_select_process_get_db_selection($element);
  941. }
  942. }
  943. }
  944. // Prevent doubles in either array.
  945. $hs_selection = array_unique($hs_selection);
  946. $db_selection = array_unique($db_selection);
  947. return array($hs_selection, $db_selection);
  948. }
  949. //----------------------------------------------------------------------------
  950. // Forms API #process callback:
  951. // Rendering (generation of FAPI code) of hierarchical select and dropbox.
  952. /**
  953. * Render the selects in the hierarchical select.
  954. *
  955. * @param $hsid
  956. * A hierarchical select id.
  957. * @param $hierarchy
  958. * A hierarchy object.
  959. * @return
  960. * A structured array for use in the Forms API.
  961. */
  962. function _hierarchical_select_process_render_hs_selects($hsid, $hierarchy) {
  963. $form['#tree'] = TRUE;
  964. $form['#prefix'] = '<div class="selects">';
  965. $form['#suffix'] = '</div>';
  966. foreach ($hierarchy->lineage as $depth => $selected_item) {
  967. $form[$depth] = array(
  968. '#type' => 'select',
  969. '#options' => $hierarchy->levels[$depth],
  970. '#default_value' => $selected_item,
  971. // Use a #theme callback to prevent the select from being wrapped in a
  972. // div. This simplifies the CSS and JS code. Also sets a special class
  973. // on the level label option, if any, to make level label styles
  974. // possible.
  975. '#theme' => 'hierarchical_select_select',
  976. // Add child information. When a child has no children, its
  977. // corresponding "option" element will be marked as such.
  978. '#childinfo' => (isset($hierarchy->childinfo[$depth])) ? $hierarchy->childinfo[$depth] : NULL,
  979. );
  980. }
  981. return $form;
  982. }
  983. /**
  984. * Render the hidden part of the dropbox. This part stores the lineages of all
  985. * selections in the dropbox.
  986. *
  987. * @param $hsid
  988. * A hierarchical select id.
  989. * @param $dropbox
  990. * A dropbox object.
  991. * @return
  992. * A structured array for use in the Forms API.
  993. */
  994. function _hierarchical_select_process_render_db_hidden($hsid, $dropbox) {
  995. $element['#tree'] = TRUE;
  996. foreach ($dropbox->lineages_selections as $x => $lineage_selection) {
  997. $element['lineages_selections'][$x] = array('#type' => 'hidden', '#value' => serialize($lineage_selection));
  998. }
  999. return $element;
  1000. }
  1001. /**
  1002. * Render the visible part of the dropbox.
  1003. *
  1004. * @param $hsid
  1005. * A hierarchical select id.
  1006. * @param $dropbox
  1007. * A dropbox object.
  1008. * @return
  1009. * A structured array for use in the Forms API.
  1010. */
  1011. function _hierarchical_select_process_render_db_visible($hsid, $dropbox) {
  1012. $element['#tree'] = TRUE;
  1013. $element['#theme'] = 'hierarchical_select_dropbox_table';
  1014. // This information is necessary for the #theme callback.
  1015. $element['title'] = array('#type' => 'value', '#value' => t($dropbox->title));
  1016. $element['separator'] = array('#type' => 'value', '#value' => '›');
  1017. $element['is_empty'] = array('#type' => 'value', '#value' => empty($dropbox->lineages));
  1018. if (!empty($dropbox->lineages)) {
  1019. foreach ($dropbox->lineages as $x => $lineage) {
  1020. // Store position information for the lineage. This will be used in the
  1021. // #theme callback.
  1022. $element['lineages'][$x] = array(
  1023. '#zebra' => (($x + 1) % 2 == 0) ? 'even' : 'odd',
  1024. '#first' => ($x == 0) ? 'first' : '',
  1025. '#last' => ($x == count($dropbox->lineages) - 1) ? 'last' : '',
  1026. );
  1027. // Create a 'markup' element for each item in the lineage.
  1028. foreach ($lineage as $depth => $item) {
  1029. // The item is selected when save_lineage is enabled (i.e. each item
  1030. // will be selected), or when the item is the last item in the current
  1031. // lineage.
  1032. $is_selected = $dropbox->save_lineage || ($depth == count($lineage) - 1);
  1033. $element['lineages'][$x][$depth] = array(
  1034. '#value' => $item['label'],
  1035. '#prefix' => '<span class="dropbox-item'. (($is_selected) ? ' dropbox-selected-item' : '') .'">',
  1036. '#suffix' => '</span>',
  1037. );
  1038. }
  1039. // Finally, create a "Remove" checkbox for the lineage.
  1040. $element['lineages'][$x]['remove'] = array(
  1041. '#type' => 'checkbox',
  1042. '#title' => t('Remove'),
  1043. );
  1044. }
  1045. }
  1046. return $element;
  1047. }
  1048. /**
  1049. * Render a flat select version of a hierarchical_select form element. This is
  1050. * necessary for backwards compatibility (together with some Javascript code)
  1051. * in case of GET forms.
  1052. *
  1053. * @param $hierarchy
  1054. * A hierarchy object.
  1055. * @param $dropbox
  1056. * A dropbox object.
  1057. * @param $config
  1058. * A config array with at least the following settings:
  1059. * - module
  1060. * - params
  1061. * - dropbox
  1062. * - status
  1063. * @return
  1064. * A structured array for use in the Forms API.
  1065. */
  1066. function _hierarchical_select_process_render_flat_select($hierarchy, $dropbox, $config) {
  1067. $selection = array();
  1068. if ($config['dropbox']['status']) {
  1069. foreach ($dropbox->lineages_selections as $lineage_selection) {
  1070. $selection = array_merge($selection, $lineage_selection);
  1071. }
  1072. }
  1073. else {
  1074. $selection = $hierarchy->lineage;
  1075. }
  1076. $options = array();
  1077. foreach ($selection as $value) {
  1078. $is_valid = module_invoke($config['module'], 'hierarchical_select_valid_item', $value, $config['params']);
  1079. if ($is_valid) {
  1080. $options[$value] = $value;
  1081. }
  1082. }
  1083. $element = array(
  1084. '#type' => 'select',
  1085. '#multiple' => ($config['save_lineage'] || $config['dropbox']['status']),
  1086. '#options' => $options,
  1087. '#value' => array_keys($options),
  1088. // Use a #theme callback to prevent the select from being wrapped in a
  1089. // div. This simplifies the CSS and JS code.
  1090. '#theme' => 'hierarchical_select_select',
  1091. '#attributes' => array('class' => 'flat-select'),
  1092. );
  1093. return $element;
  1094. }
  1095. /**
  1096. * Calculate the return value of a hierarchical_select form element, based on
  1097. * the $hierarchy and $dropbox objects. We have to set a return value, because
  1098. * the values set and used by this form element ($element['#value]) are not
  1099. * easily usable in the Forms API; we want to return a flat list of item ids.
  1100. *
  1101. * @param $hierarchy
  1102. * A hierarchy object.
  1103. * @param $dropbox
  1104. * Optional. A dropbox object.
  1105. * @param $module
  1106. * The module that should be used for HS hooks.
  1107. * @param $params
  1108. * Optional. An array of parameters, which may be necessary for some
  1109. * implementations.
  1110. * @param $save_lineage
  1111. * Whether the save_lineage setting is enabled or not.
  1112. * @return
  1113. * A single item id or a flat array of item ids.
  1114. */
  1115. function _hierarchical_select_process_calculate_return_value($hierarchy, $dropbox = FALSE, $module, $params, $save_lineage) {
  1116. if (!$dropbox) {
  1117. $return_value = _hierarchical_select_hierarchy_validate($hierarchy->lineage, $module, $params);
  1118. // If the save_lineage setting is disabled, keep only the deepest item.
  1119. if (!$save_lineage) {
  1120. $return_value = (is_array($return_value)) ? end($return_value) : NULL;
  1121. }
  1122. // Prevent a return value of -1. -1 is used for HS' internal system and
  1123. // means "nothing selected", but to Drupal it *will* seam like a valid
  1124. // value. Therefore, we set it to NULL.
  1125. $return_value = ($return_value != -1) ? $return_value : NULL;
  1126. }
  1127. else {
  1128. $return_value = array();
  1129. foreach ($dropbox->lineages_selections as $x => $selection) {
  1130. if (!$save_lineage) {
  1131. // An entry in the dropbox when the save_lineage setting is disabled
  1132. // is only the deepest item of the generated lineage.
  1133. $return_value[] = end($selection);
  1134. }
  1135. else {
  1136. // An entry in the dropbox when the save_lineage setting is enabled is
  1137. // the entire generated lineage, if it's valid (i.e. if the user has
  1138. // not tampered with it).
  1139. $lineage = _hierarchical_select_hierarchy_validate($selection, $module, $params);
  1140. $return_value = array_merge($return_value, $lineage);
  1141. }
  1142. }
  1143. $return_value = array_unique($return_value);
  1144. }
  1145. return $return_value;
  1146. }
  1147. //----------------------------------------------------------------------------
  1148. // Private functions.
  1149. /**
  1150. * Inherit the default config from Hierarchical Selects' hook_elements().
  1151. *
  1152. * @param $config
  1153. * A config array with at least the following settings:
  1154. * - module
  1155. * - params
  1156. * @return
  1157. * An updated config array.
  1158. */
  1159. function _hierarchical_select_inherit_default_config($config, $defaults_override = array()) {
  1160. // Set defaults for unconfigured settings. Get the defaults from our
  1161. // hook_elements() implementation. Default properties from this hook are
  1162. // applied automatically, but properties inside properties, such as is the
  1163. // case for Hierarchical Select's #config property, aren't applied.
  1164. $type = hierarchical_select_elements();
  1165. $defaults = $type['hierarchical_select']['#config'];
  1166. // Don't inherit the module and params settings.
  1167. unset($defaults['module']);
  1168. unset($defaults['params']);
  1169. // Allow the defaults to be overridden.
  1170. $defaults = array_smart_merge($defaults, $defaults_override);
  1171. // Apply the defaults to the config.
  1172. $config = array_smart_merge($defaults, $config);
  1173. return $config;
  1174. }
  1175. /**
  1176. * Helper function to add the required Javascript files and settings.
  1177. *
  1178. * @param $form_state
  1179. * A form state array. Necessary to set the callback URL for Hierarchical
  1180. * Select through _hierarchical_select_add_js_settings().
  1181. */
  1182. function _hierarchical_select_setup_js(&$form_state = NULL) {
  1183. global $language;
  1184. static $ran_once;
  1185. static $js_settings_added;
  1186. $jquery_ui_components = array(
  1187. 'effects.core',
  1188. 'effects.drop',
  1189. );
  1190. if (!$js_settings_added && isset($form_state)) {
  1191. $url = base_path();
  1192. $url .= variable_get('clean_url', 0) ? '' : 'index.php?q=';
  1193. // Prefix URL with language path when i18n is enabled and when path-based
  1194. // negotiation is being used.
  1195. $negotiation = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
  1196. if (module_exists('i18n') && ($language->prefix != '') && ($negotiation == LANGUAGE_NEGOTIATION_PATH_DEFAULT || $negotiation == LANGUAGE_NEGOTIATION_PATH)) {
  1197. $url .= $language->prefix . '/';
  1198. }
  1199. if (module_exists('purl')) {
  1200. $options = array();
  1201. purl_url_outbound_alter($url, $options, '');
  1202. $url = str_replace('//', '/', '/' . $url);
  1203. }
  1204. _hierarchical_select_add_js_settings(array('HierarchicalSelect' => array('basePath' => $url, 'getArguments' => drupal_query_string_encode($_GET, array('q')))), $form_state);
  1205. $js_settings_added = TRUE;
  1206. }
  1207. if (!$ran_once) {
  1208. $ran_once = TRUE;
  1209. // Add the CSS and JS, set the URL that should be used by all hierarchical
  1210. // selects.
  1211. drupal_add_css(drupal_get_path('module', 'hierarchical_select') .'/hierarchical_select.css');
  1212. drupal_add_js(drupal_get_path('module', 'hierarchical_select') .'/hierarchical_select.js');
  1213. if (variable_get('hierarchical_select_js_cache_system', 0) == 1) {
  1214. drupal_add_js(drupal_get_path('module', 'hierarchical_select') .'/hierarchical_select_cache.js');
  1215. }
  1216. if (!module_exists('jquery_form')) {
  1217. drupal_add_js(drupal_get_path('module', 'hierarchical_select') .'/hierarchical_select_formtoarray.js');
  1218. }
  1219. else {
  1220. jquery_form_add();
  1221. }
  1222. if (!module_exists('jquery_ui')) {
  1223. foreach ($jquery_ui_components as $component) {
  1224. drupal_add_js(drupal_get_path('module', 'hierarchical_select') ."/js/jquery.ui/$component.js");
  1225. }
  1226. }
  1227. else {
  1228. jquery_ui_add($jquery_ui_components);
  1229. }
  1230. }
  1231. }
  1232. /**
  1233. * Convert a hierarchy object into an array of arrays that can be used for
  1234. * caching an entire hierarchy in a client-side database.
  1235. *
  1236. * @param $hierarchy
  1237. * A hierarchy object.
  1238. * @return
  1239. * An array of arrays.
  1240. */
  1241. function _hierarchical_select_json_convert_hierarchy_to_cache($hierarchy) {
  1242. // Convert the hierarchy object to an array of values like these:
  1243. // array('value' => $term_id, 'label => $term_name, 'parent' => $term_id)
  1244. $cache = array();
  1245. foreach ($hierarchy->levels as $depth => $items) {
  1246. $weight = 0;
  1247. foreach ($items as $value => $label) {
  1248. $weight++;
  1249. $cache[] = array(
  1250. 'value' => $value,
  1251. 'label' => $label,
  1252. 'parent' => ($depth == 0) ? 0 : $hierarchy->lineage[$depth - 1],
  1253. 'weight' => $weight,
  1254. );
  1255. }
  1256. }
  1257. // The last item in the lineage never has any children.
  1258. $value = end($hierarchy->lineage);
  1259. $cache[] = array(
  1260. 'value' => $value .'-has-no-children', // Construct a pseudo-value (will never be actually used).
  1261. 'label' => '',
  1262. 'parent' => $value,
  1263. 'weight' => 0,
  1264. );
  1265. return $cache;
  1266. }
  1267. /**
  1268. * Helper function that marks every element in the given element as disabled.
  1269. *
  1270. * @param &$element
  1271. * The element of which we want to mark all elements as disabled.
  1272. * @return
  1273. * A structured array for use in the Forms API.
  1274. */
  1275. function _hierarchical_select_mark_as_disabled(&$element) {
  1276. $element['#disabled'] = TRUE;
  1277. // Recurse through all children:
  1278. foreach (element_children($element) as $key) {
  1279. if (isset($element[$key]) && $element[$key]) {
  1280. _hierarchical_select_mark_as_disabled($element[$key]);
  1281. }
  1282. }
  1283. }
  1284. /**
  1285. * Helper function to determine whether a given depth (i.e. the depth of a
  1286. * level) is allowed by the allowed_levels setting.
  1287. *
  1288. * @param $config
  1289. * A config array with at least the following settings:
  1290. * - editability
  1291. * - allowed_levels
  1292. * @param $depth
  1293. * A depth, starting from 0.
  1294. * @return
  1295. * 0 or 1 if it allowed_levels is set for the given depth, 1 otherwise.
  1296. */
  1297. function _hierarchical_select_create_new_item_is_allowed($config, $depth) {
  1298. return (isset($config['editability']['allowed_levels'][$depth])) ? $config['editability']['allowed_levels'][$depth] : 1;
  1299. }
  1300. /**
  1301. * Helper function that generates the help text is that is displayed to the
  1302. * user when Javascript is disabled.
  1303. *
  1304. * @param $dropbox_is_enabled
  1305. * Indicates if the dropbox is enabled or not, the help text will be
  1306. * adjusted depending on this value.
  1307. * @return
  1308. * The generated help text (in HTML).
  1309. */
  1310. function _hierarchical_select_nojs_helptext($dropbox_is_enabled) {
  1311. $output = '';
  1312. // The options that will be used in the unordered list.
  1313. $items = array(
  1314. t('<span class="highlight">enable Javascript</span> in your browser and then refresh this page, for a much enhanced experience.'),
  1315. t('<span class="highlight">click the <em>Update</em> button</span> every time you want to update the selection'),
  1316. );
  1317. $items[1] .= (!$dropbox_is_enabled) ? '.' : t(", or when you've checked some checkboxes for entries in the dropbox you'd like to remove.");
  1318. $output .= '<span class="warning">';
  1319. $output .= t("You don't have Javascript enabled.");
  1320. $output .= '</span> ';
  1321. $output .= '<span class="ask-to-hover">';
  1322. $output .= t('Hover for more information!');
  1323. $output .= '</span> ';
  1324. $output .= t("But don't worry: you can still use this web site! You have two options:");
  1325. $output .= theme('item_list', $items, NULL, 'ul', array('class' => 'solutions'));
  1326. return $output;
  1327. }
  1328. /**
  1329. * Get the form item that has the the given #name property.
  1330. *
  1331. * @param $form
  1332. * A structured array for use in the Forms API.
  1333. * @param $name
  1334. * A #name value.
  1335. * @return
  1336. * A form item.
  1337. */
  1338. function _hierarchical_select_get_form_item($form, $name) {
  1339. if (isset($form['#name']) && $form['#name'] == $name) {
  1340. return $form;
  1341. }
  1342. // The current form item apparently is not the one we're looking for, so try
  1343. // to find it in the child form items.
  1344. foreach (element_children($form) as $child) {
  1345. $form_item = _hierarchical_select_get_form_item($form[$child], $name);
  1346. if ($form_item !== FALSE) {
  1347. return $form_item;
  1348. }
  1349. }
  1350. // No match in the children either, so return FALSE.
  1351. return FALSE;
  1352. }
  1353. /**
  1354. * Store the #name property of the given form item, so we can retrieve a list
  1355. * of #name properties of hierarchical_select form items present in this form
  1356. * later.
  1357. *
  1358. * @param $form_item
  1359. * Optional. A hierarchical_select form item.
  1360. * @param $hsid
  1361. * Optional. A hierarchical select ID.
  1362. * @param $reset
  1363. * Optional. Flag that marks if the stored #name properties should be reset.
  1364. * @return
  1365. * The stored #name properties per hierarchical_select form item.
  1366. */
  1367. function _hierarchical_select_store_name($form_item = NULL, $hsid = NULL, $reset = FALSE) {
  1368. static $names;
  1369. if ($reset) {
  1370. $ret = $names;
  1371. $names = array();
  1372. return $ret;
  1373. }
  1374. if (isset($form_item) && isset($hsid)) {
  1375. $names[$hsid] = $form_item['#name'];
  1376. }
  1377. return $names;
  1378. }
  1379. /**
  1380. * Detect whether a form has at least one hierarchical_select form element.
  1381. *
  1382. * @param $form
  1383. * A structured array for use in the Forms API.
  1384. * @return
  1385. * TRUE if the form contains a hierarchical_select form element, FALSE
  1386. * otherwise.
  1387. */
  1388. function _hierarchical_select_form_has_hierarchical_select($form) {
  1389. if (isset($form['#type']) && $form['#type'] == 'hierarchical_select') {
  1390. return TRUE;
  1391. }
  1392. else {
  1393. $has_hierarchical_select = FALSE;
  1394. foreach (element_children($form) as $name) {
  1395. if (is_array($form[$name])) {
  1396. $has_hierarchical_select = _hierarchical_select_form_has_hierarchical_select($form[$name]);
  1397. if ($has_hierarchical_select) {
  1398. break;
  1399. }
  1400. }
  1401. }
  1402. return $has_hierarchical_select;
  1403. }
  1404. }
  1405. /**
  1406. * Set the 'error' class on the appropriate part of Hierarchical Select,
  1407. * depending on its configuration.
  1408. *
  1409. * @param $element
  1410. * A Hierarchical Select form item.
  1411. */
  1412. function _hierarchical_select_form_set_error_class(&$element) {
  1413. $config = _hierarchical_select_inherit_default_config($element['#config']);
  1414. if ($config['dropbox']['status']) {
  1415. form_error($element['dropbox']['visible']);
  1416. }
  1417. else {
  1418. for ($i = 0; $i < count(element_children($element['hierarchical_select']['selects'])); $i++) {
  1419. form_error($element['hierarchical_select']['selects'][$i]);
  1420. }
  1421. }
  1422. }
  1423. /**
  1424. * Append messages to Hierarchical Select's log. Used when in developer mode.
  1425. *
  1426. * @param $item
  1427. * Either a message (string) or an array.
  1428. * @param $reset
  1429. * Reset the stored log.
  1430. * @return
  1431. * Only when the log is being reset, the stored log is returned.
  1432. */
  1433. function _hierarchical_select_log($item, $reset = FALSE) {
  1434. static $log;
  1435. if ($reset) {
  1436. $copy_of_log = $log;
  1437. $log = array();
  1438. return $copy_of_log;
  1439. }
  1440. $log[] = $item;
  1441. }
  1442. //----------------------------------------------------------------------------
  1443. // Hierarchy object generation functions.
  1444. /**
  1445. * Generate the hierarchy object.
  1446. *
  1447. * @param $config
  1448. * A config array with at least the following settings:
  1449. * - module
  1450. * - params
  1451. * - enforce_deepest
  1452. * - save_lineage
  1453. * - level_labels
  1454. * - status
  1455. * - labels
  1456. * - editability
  1457. * - status
  1458. * - allow_new_levels
  1459. * - max_levels
  1460. * @param $selection
  1461. * The selection based on which a HS should be rendered.
  1462. * @param $required
  1463. * Whether the form element is required or not. (#required in Forms API)
  1464. * @param $dropbox
  1465. * A dropbox object, or FALSE.
  1466. * @return
  1467. * A hierarchy object.
  1468. */
  1469. function _hierarchical_select_hierarchy_generate($config, $selection, $required, $dropbox = FALSE) {
  1470. $hierarchy = new stdClass();
  1471. // Convert the 'special_items' setting to a more easily accessible format.
  1472. if (isset($config['special_items'])) {
  1473. $special_items['exclusive'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_exclusive'));
  1474. $special_items['none'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_none'));
  1475. }
  1476. //
  1477. // Build the lineage.
  1478. //
  1479. $start_lineage = microtime();
  1480. // If save_linage is enabled, reconstruct the lineage. This is necessary
  1481. // because e.g. the taxonomy module stores the terms by order of weight and
  1482. // lexicography, rather than by hierarchy.
  1483. if ($config['save_lineage'] && is_array($selection) && count($selection) >= 2) {
  1484. // Ensure the item in the root level is the first item in the selection.
  1485. $root_level = array_keys(module_invoke($config['module'], 'hierarchical_select_root_level', $config['params']));
  1486. for ($i = 0; $i < count($selection); $i++) {
  1487. if (in_array($selection[$i], $root_level)) {
  1488. if ($i != 0) { // Don't swap if it's already the first item.
  1489. list($selection[0], $selection[$i]) = array($selection[$i], $selection[0]);
  1490. }
  1491. break;
  1492. }
  1493. }
  1494. // Reconstruct all sublevels.
  1495. for ($i = 0; $i < count($selection); $i++) {
  1496. $children = array_keys(module_invoke($config['module'], 'hierarchical_select_children', $selection[$i], $config['params']));
  1497. // Ensure the next item in the selection is a child of the current item.
  1498. for ($j = $i + 1; $j < count($selection); $j++) {
  1499. if (in_array($selection[$j], $children)) {
  1500. list($selection[$j], $selection[$i + 1]) = array($selection[$i + 1], $selection[$j]);
  1501. }
  1502. }
  1503. }
  1504. }
  1505. // Validate the hierarchy.
  1506. $selection = _hierarchical_select_hierarchy_validate($selection, $config['module'], $config['params']);
  1507. // When nothing is currently selected, set the root level to:
  1508. // - "<none>" (or its equivalent special item) when:
  1509. // - enforce_deepest is enabled *and* level labels are enabled *and*
  1510. // no root level label is set (1), or
  1511. // - the dropbox is enabled *and* at least one selection has been added
  1512. // to the dropbox (2)
  1513. // - "label_0" (the root level label) in all other cases.
  1514. if ($selection == -1) {
  1515. $root_level = module_invoke($config['module'], 'hierarchical_select_root_level', $config['params']);
  1516. $first_case = $config['enforce_deepest'] && $config['level_labels']['status'] && !isset($config['level_labels']['labels'][0]);
  1517. $second_case = $dropbox && count($dropbox->lineages) > 0;
  1518. // If
  1519. // - the special_items setting has been configured, and
  1520. // - one special item has the 'none' property
  1521. // then we'll use the special item instead of the normal "<none>" option.
  1522. $none_option = (count($special_items['none'])) ? $special_items['none'][0] : 'none';
  1523. // Set "<none>" option (or its equivalent special item), or "label_0".
  1524. $hierarchy->lineage[0] = ($first_case || $second_case) ? $none_option : 'label_0';
  1525. }
  1526. else {
  1527. // If save_lineage setting is enabled, then the selection *is* a lineage.
  1528. // If it's disabled, we have to generate one ourselves based on the
  1529. // (deepest) selected item.
  1530. if ($config['save_lineage']) {
  1531. // When the form element is optional, the "<none>" setting can be
  1532. // selected, thus only the first level will be displayed. As a result,
  1533. // we won't receive an array as the selection, but only a single item.
  1534. // We convert this into an array.
  1535. $hierarchy->lineage = (is_array($selection)) ? $selection : array(0 => $selection);
  1536. }
  1537. else {
  1538. $selection = (is_array($selection)) ? $selection[0] : $selection;
  1539. if (module_invoke($config['module'], 'hierarchical_select_valid_item', $selection, $config['params'])) {
  1540. $hierarchy->lineage = module_invoke($config['module'], 'hierarchical_select_lineage', $selection, $config['params']);
  1541. }
  1542. else {
  1543. // If the selected item is invalid, then start with an empty lineage.
  1544. $hierarchy->lineage = array();
  1545. }
  1546. }
  1547. }
  1548. // If enforce_deepest is enabled, ensure that the lineage goes as deep as
  1549. // possible: append values of items that will be selected by default.
  1550. if ($config['enforce_deepest'] && !in_array($hierarchy->lineage[0], array('none', 'label_0'))) {
  1551. $hierarchy->lineage = _hierarchical_select_hierarchy_enforce_deepest($hierarchy->lineage, $config['module'], $config['params']);
  1552. }
  1553. $end_lineage = microtime();
  1554. //
  1555. // Build the levels.
  1556. //
  1557. $start_levels = microtime();
  1558. // Start building the levels, initialize with the root level.
  1559. $hierarchy->levels[0] = module_invoke($config['module'], 'hierarchical_select_root_level', $config['params']);
  1560. $hierarchy->levels[0] = _hierarchical_select_apply_entity_settings($hierarchy->levels[0], $config);
  1561. // Prepend a "<create new item>" option to the root level when:
  1562. // - the editability setting is enabled, and
  1563. // - the hook is implemented (this is an optional hook), and
  1564. // - the allowed_levels setting allows to create new items at this level.
  1565. if ($config['editability']['status']
  1566. && module_hook($config['module'], 'hierarchical_select_create_item')
  1567. && _hierarchical_select_create_new_item_is_allowed($config, 0)
  1568. ) {
  1569. $item_type = (count($config['editability']['item_types']) > 0)
  1570. ? t($config['editability']['item_types'][0])
  1571. : t('item');
  1572. $option = theme('hierarchical_select_special_option', t('create new !item_type', array('!item_type' => $item_type)));
  1573. $hierarchy->levels[0] = array('create_new_item' => $option) + $hierarchy->levels[0];
  1574. }
  1575. // Prepend a "<none>" option to the root level when:
  1576. // - the form element is optional (1), or
  1577. // - enforce_deepest is enabled (2), or
  1578. // - the dropbox is enabled *and* at least one selection has been added to
  1579. // the dropbox (3)
  1580. // except when:
  1581. // - the special_items setting has been configured, and
  1582. // - one special item has the 'none' property
  1583. $first_case = !$required;
  1584. $second_case = $config['enforce_deepest'];
  1585. $third_case = $dropbox && count($dropbox->lineages) > 0;
  1586. if (($first_case || $second_case || $third_case) && !count($special_items['none'])) {
  1587. $option = theme('hierarchical_select_special_option', t('none'));
  1588. $hierarchy->levels[0] = array('none' => $option) + $hierarchy->levels[0];
  1589. }
  1590. // Calculate the lineage's depth (starting from 0).
  1591. $max_depth = count($hierarchy->lineage) - 1;
  1592. // Build all sublevels, based on the lineage.
  1593. for ($depth = 1; $depth <= $max_depth; $depth++) {
  1594. $hierarchy->levels[$depth] = module_invoke($config['module'], 'hierarchical_select_children', $hierarchy->lineage[$depth - 1], $config['params']);
  1595. $hierarchy->levels[$depth] = _hierarchical_select_apply_entity_settings($hierarchy->levels[$depth], $config);
  1596. }
  1597. if ($config['enforce_deepest']) {
  1598. // Prepend a "<create new item>" option to each level below the root level
  1599. // when:
  1600. // - the editability setting is enabled, and
  1601. // - the hook is implemented (this is an optional hook), and
  1602. // - the allowed_levels setting allows to create new items at this level.
  1603. if ($config['editability']['status'] && module_hook($config['module'], 'hierarchical_select_create_item')) {
  1604. for ($depth = 1; $depth <= $max_depth; $depth++) {
  1605. $item_type = (count($config['editability']['item_types']) == $depth)
  1606. ? t($config['editability']['item_types'][$depth])
  1607. : t('item');
  1608. $option = theme('hierarchical_select_special_option', t('create new !item_type', array('!item_type' => $item_type)));
  1609. if (_hierarchical_select_create_new_item_is_allowed($config, $depth)) {
  1610. $hierarchy->levels[$depth] = array('create_new_item' => $option) + $hierarchy->levels[$depth];
  1611. }
  1612. }
  1613. }
  1614. // If level labels are enabled and the root label is set, prepend it.
  1615. if ($config['level_labels']['status'] && isset($config['level_labels']['labels'][0])) {
  1616. $hierarchy->levels[0] = array('label_0' => t($config['level_labels']['labels'][0])) + $hierarchy->levels[0];
  1617. }
  1618. }
  1619. else if (!$config['enforce_deepest']) {
  1620. // Prepend special options to every level.
  1621. for ($depth = 0; $depth <= $max_depth; $depth++) {
  1622. // Prepend a "<create new item>" option to the current level when:
  1623. // - this is not the root level (the root level already has this), and
  1624. // - the editability setting is enabled, and
  1625. // - the hook is implemented (this is an optional hook), and
  1626. // - the allowed_levels setting allows to create new items at this level.
  1627. if ($depth > 0
  1628. && $config['editability']['status']
  1629. && module_hook($config['module'], 'hierarchical_select_create_item')
  1630. && _hierarchical_select_create_new_item_is_allowed($config, $depth)
  1631. ) {
  1632. $item_type = (count($config['editability']['item_types']) == $depth)
  1633. ? t($config['editability']['item_types'][$depth])
  1634. : t('item');
  1635. $option = theme('hierarchical_select_special_option', t('create new !item_type', array('!item_type' => $item_type)));
  1636. $hierarchy->levels[$depth] = array('create_new_item' => $option) + $hierarchy->levels[$depth];
  1637. }
  1638. // Level label: set an empty level label if they've been disabled.
  1639. $label = ($config['level_labels']['status'] && isset($config['level_labels']['labels'][$depth])) ? t($config['level_labels']['labels'][$depth]) : '';
  1640. $hierarchy->levels[$depth] = array('label_'. $depth => $label) + $hierarchy->levels[$depth];
  1641. }
  1642. // If the root level label is empty and the none option is present, remove
  1643. // the root level label because it's conceptually identical.
  1644. if ($hierarchy->levels[0]['label_0'] == '' && isset($hierarchy->levels[0]['none'])) {
  1645. unset($hierarchy->levels[0]['label_0']);
  1646. // Update the selected lineage when necessary to prevent an item that
  1647. // doesn't exist from being "selected" internally.
  1648. if ($hierarchy->lineage[0] == 'label_0') {
  1649. $hierarchy->lineage[0] = 'none';
  1650. }
  1651. }
  1652. // Add one more level if appropriate.
  1653. $parent = $hierarchy->lineage[$max_depth];
  1654. if (module_invoke($config['module'], 'hierarchical_select_valid_item', $parent, $config['params'])) {
  1655. $children = module_invoke($config['module'], 'hierarchical_select_children', $parent, $config['params']);
  1656. if (count($children)) {
  1657. // We're good, let's add one level!
  1658. $depth = $max_depth + 1;
  1659. $hierarchy->levels[$depth] = array();
  1660. // Prepend a "<create new item>" option to the current level when:
  1661. // - the editability setting is enabled, and
  1662. // - the hook is implemented (this is an optional hook), and
  1663. // - the allowed_levels setting allows to create new items at this level.
  1664. if ($config['editability']['status']
  1665. && module_hook($config['module'], 'hierarchical_select_create_item')
  1666. && _hierarchical_select_create_new_item_is_allowed($config, $depth)
  1667. ) {
  1668. $item_type = (count($config['editability']['item_types']) == $depth)
  1669. ? t($config['editability']['item_types'][$depth])
  1670. : t('item');
  1671. $option = theme('hierarchical_select_special_option', t('create new !item_type', array('!item_type' => $item_type)));
  1672. $hierarchy->levels[$depth] = array('create_new_item' => $option);
  1673. }
  1674. // Level label: set an empty level label if they've been disabled.
  1675. $hierarchy->lineage[$depth] = 'label_'. $depth;
  1676. $label = ($config['level_labels']['status']) ? t($config['level_labels']['labels'][$depth]) : '';
  1677. $hierarchy->levels[$depth] = array('label_'. $depth => $label) + $hierarchy->levels[$depth] + $children;
  1678. $hierarchy->levels[$depth] = _hierarchical_select_apply_entity_settings($hierarchy->levels[$depth], $config);
  1679. }
  1680. }
  1681. }
  1682. // Add an extra level with only a level label and a "<create new item>"
  1683. // option, if:
  1684. // - the editability setting is enabled
  1685. // - the allow_new_levels setting is enabled
  1686. // - an additional level is permitted by the max_levels setting
  1687. // - the deepest item of the lineage is a valid item
  1688. // NOTE: this uses an optional hook, so we also check if it's implemented.
  1689. if ($config['editability']['status']
  1690. && $config['editability']['allow_new_levels']
  1691. && ($config['editability']['max_levels'] == 0 || count($hierarchy->lineage) < $config['editability']['max_levels'])
  1692. && module_invoke($config['module'], 'hierarchical_select_valid_item', end($hierarchy->lineage), $config['params'])
  1693. && module_hook($config['module'], 'hierarchical_select_create_item')
  1694. ) {
  1695. $depth = $max_depth + 1;
  1696. // Level label: set an empty level label if they've been disabled.
  1697. $hierarchy->lineage[$depth] = 'label_'. $depth;
  1698. $label = ($config['level_labels']['status']) ? t($config['level_labels']['labels'][$depth]) : '';
  1699. // Item type.
  1700. $item_type = (count($config['editability']['item_types']) == $depth)
  1701. ? t($config['editability']['item_types'][$depth])
  1702. : t('item');
  1703. // The new level with only a level label and a "<create new item>" option.
  1704. $option = theme('hierarchical_select_special_option', t('create new !item_type', array('!item_type' => $item_type)));
  1705. $hierarchy->levels[$depth] = array(
  1706. 'label_'. $depth => $label,
  1707. 'create_new_item' => $option,
  1708. );
  1709. }
  1710. // Calculate the time it took to generate the levels.
  1711. $end_levels = microtime();
  1712. // Add child information.
  1713. $start_childinfo = microtime();
  1714. $hierarchy = _hierarchical_select_hierarchy_add_childinfo($hierarchy, $config);
  1715. $end_childinfo = microtime();
  1716. // Calculate the time it took to build the hierarchy object.
  1717. $hierarchy->build_time['total'] = ($end_childinfo - $start_lineage) * 1000;
  1718. $hierarchy->build_time['lineage'] = ($end_lineage - $start_lineage) * 1000;
  1719. $hierarchy->build_time['levels'] = ($end_levels - $start_levels) * 1000;
  1720. $hierarchy->build_time['childinfo'] = ($end_childinfo - $start_childinfo) * 1000;
  1721. return $hierarchy;
  1722. }
  1723. /**
  1724. * Given a level, apply the entity_count and require_entity settings.
  1725. *
  1726. * @param $level
  1727. * A level in the hierarchy.
  1728. * @param $config
  1729. * A config array with at least the following settings:
  1730. * - module
  1731. * - params
  1732. * - entity_count
  1733. * - require_entity
  1734. * @return
  1735. * The updated level
  1736. */
  1737. function _hierarchical_select_apply_entity_settings($level, $config) {
  1738. if (isset($config['special_items'])) {
  1739. $special_items['exclusive'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_exclusive'));
  1740. $special_items['none'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_none'));
  1741. }
  1742. // Only do something when the entity_count or the require_entity (or both)
  1743. // settings are enabled.
  1744. // NOTE: this uses the optional "hierarchical_selectentity_count" hook, so
  1745. // we also check if it's implemented.
  1746. if (($config['entity_count'] || $config['require_entity']) && module_hook($config['module'], 'hierarchical_select_entity_count')) {
  1747. foreach ($level as $item => $label) {
  1748. // We don't want to alter internal or special items.
  1749. if (!preg_match('/(none|label_\d+|create_new_item)/', $item)
  1750. && !in_array($item, $special_items['exclusive'])
  1751. && !in_array($item, $special_items['none'])
  1752. )
  1753. {
  1754. $entity_count = module_invoke($config['module'], 'hierarchical_select_entity_count', $item, $config['params']);
  1755. // When the require_entity setting is enabled and the entity count is
  1756. // zero, then remove the item from the level.
  1757. // When the item is not removed from the level due to the above and
  1758. // the entity_count setting is enabled, update the label of the item
  1759. // to include the entity count.
  1760. if ($config['require_entity'] && $entity_count == 0) {
  1761. unset($level[$item]);
  1762. }
  1763. elseif ($config['entity_count']) {
  1764. $level[$item] = "$label ($entity_count)";
  1765. }
  1766. }
  1767. }
  1768. }
  1769. return $level;
  1770. }
  1771. /**
  1772. * Extends a hierarchy object with child information: for each item in the
  1773. * hierarchy, the child count will be retrieved and stored in the hierarchy
  1774. * object, in the "childinfo" property. Items are grouped per level.
  1775. *
  1776. * @param $hierarchy
  1777. * A hierarchy object with the "levels" property set.
  1778. * @param $config
  1779. * A config array with at least the following settings:
  1780. * - module
  1781. * - params
  1782. * @return
  1783. * An updated hierarchy object with the "childinfo" property set.
  1784. */
  1785. function _hierarchical_select_hierarchy_add_childinfo($hierarchy, $config) {
  1786. foreach ($hierarchy->levels as $depth => $level) {
  1787. foreach (array_keys($level) as $item) {
  1788. if (!preg_match('/(none|label_\d+|create_new_item)/', $item)) {
  1789. $hierarchy->childinfo[$depth][$item] = count(module_invoke($config['module'], 'hierarchical_select_children', $item, $config['params']));
  1790. }
  1791. }
  1792. }
  1793. return $hierarchy;
  1794. }
  1795. /**
  1796. * Reset the selection if no valid item was selected. The first item in the
  1797. * array corresponds to the first selected term. As soon as an invalid item
  1798. * is encountered, the lineage from that level to the deeper levels should be
  1799. * unset. This is so to ignore selection of a level label.
  1800. *
  1801. * @param $selection
  1802. * Either a single item id or an array of item ids.
  1803. * @param $module
  1804. * The module that should be used for HS hooks.
  1805. * @param $params
  1806. * The module that should be passed to HS hooks.
  1807. * @return
  1808. * The updated selection.
  1809. */
  1810. function _hierarchical_select_hierarchy_validate($selection, $module, $params) {
  1811. $valid = TRUE;
  1812. $selection_levels = count($selection);
  1813. for ($i = 0; $i < $selection_levels; $i++) {
  1814. // As soon as one invalid item has been found, we'll stop validating; all
  1815. // subsequently selected items will be removed from the selection.
  1816. if ($valid) {
  1817. $valid = module_invoke($module, 'hierarchical_select_valid_item', $selection[$i], $params);
  1818. if ($i > 0) {
  1819. $parent = $selection[$i - 1];
  1820. $child = $selection[$i];
  1821. $children = array_keys(module_invoke($module, 'hierarchical_select_children', $parent, $params));
  1822. $valid = $valid && in_array($child, $children);
  1823. }
  1824. }
  1825. if (!$valid) {
  1826. unset($selection[$i]);
  1827. }
  1828. }
  1829. if (empty($selection)) {
  1830. $selection = -1;
  1831. }
  1832. return $selection;
  1833. }
  1834. /**
  1835. * Helper function to update the lineage of the hierarchy to ensure that the
  1836. * user selects an item in the deepest level of the hierarchy.
  1837. *
  1838. * @param $lineage
  1839. * The lineage up to the deepest selection the user has made so far.
  1840. * @param $module
  1841. * The module that should be used for HS hooks.
  1842. * @param $params
  1843. * The params that should be passed to HS hooks.
  1844. * @return
  1845. * The updated lineage.
  1846. */
  1847. function _hierarchical_select_hierarchy_enforce_deepest($lineage, $module, $params) {
  1848. // Use the deepest item as the first parent. Then apply this algorithm:
  1849. // 1) get the parent's children, stop if no children
  1850. // 2) choose the first child as the option that is selected by default, by
  1851. // adding it to the lineage of the hierarchy
  1852. // 3) make this child the parent, go to step 1.
  1853. $parent = end($lineage); // The last item in the lineage is the deepest one.
  1854. $children = module_invoke($module, 'hierarchical_select_children', $parent, $params);
  1855. while (count($children)) {
  1856. $first_child = reset(array_keys($children));
  1857. $lineage[] = $first_child;
  1858. $parent = $first_child;
  1859. $children = module_invoke($module, 'hierarchical_select_children', $parent, $params);
  1860. }
  1861. return $lineage;
  1862. }
  1863. //----------------------------------------------------------------------------
  1864. // Dropbox object generation functions.
  1865. /**
  1866. * Generate the dropbox object.
  1867. *
  1868. * @param $config
  1869. * A config array with at least the following settings:
  1870. * - module
  1871. * - save_lineage
  1872. * - params
  1873. * - dropbox
  1874. * - title
  1875. * @param $selection
  1876. * The selection based on which a dropbox should be generated.
  1877. * @return
  1878. * A dropbox object.
  1879. */
  1880. function _hierarchical_select_dropbox_generate($config, $selection) {
  1881. $dropbox = new stdClass();
  1882. $start = microtime();
  1883. $dropbox->title = (!empty($config['dropbox']['title'])) ? $config['dropbox']['title'] : t('All selections');
  1884. $dropbox->lineages = array();
  1885. $dropbox->lineages_selections = array();
  1886. // Clean selection.
  1887. foreach ($selection as $key => $item) {
  1888. if (!module_invoke($config['module'], 'hierarchical_select_valid_item', $item, $config['params'])) {
  1889. unset($selection[$key]);
  1890. }
  1891. }
  1892. if (!empty($selection)) {
  1893. // Store the "save lineage" setting, needed in the rendering layer.
  1894. $dropbox->save_lineage = $config['save_lineage'];
  1895. if ($config['save_lineage']) {
  1896. $dropbox->lineages = _hierarchical_select_dropbox_reconstruct_lineages_save_lineage_enabled($config['module'], $selection, $config['params']);
  1897. }
  1898. else {
  1899. // Retrieve the lineage of each item.
  1900. foreach ($selection as $item) {
  1901. $dropbox->lineages[] = module_invoke($config['module'], 'hierarchical_select_lineage', $item, $config['params']);
  1902. }
  1903. // We will also need the labels of each item in the rendering layer.
  1904. foreach ($dropbox->lineages as $id => $lineage) {
  1905. foreach ($lineage as $level => $item) {
  1906. $dropbox->lineages[$id][$level] = array('value' => $item, 'label' => check_plain(module_invoke($config['module'], 'hierarchical_select_item_get_label', $item, $config['params'])));
  1907. }
  1908. }
  1909. }
  1910. usort($dropbox->lineages, '_hierarchical_select_dropbox_sort');
  1911. // Now store each lineage's selection too. This is needed on the client side
  1912. // to enable the remove button to let the server know which selected items
  1913. // should be removed.
  1914. foreach ($dropbox->lineages as $id => $lineage) {
  1915. if ($config['save_lineage']) {
  1916. // Store the entire lineage.
  1917. $dropbox->lineages_selections[$id] = array_map('_hierarchical_select_dropbox_lineage_item_get_value', $lineage);
  1918. }
  1919. else {
  1920. // Store only the last (aka the deepest) value of the lineage.
  1921. $dropbox->lineages_selections[$id][0] = $lineage[count($lineage) - 1]['value'];
  1922. }
  1923. }
  1924. }
  1925. // Calculate the time it took to build the dropbox object.
  1926. $dropbox->build_time = (microtime() - $start) * 1000;
  1927. return $dropbox;
  1928. }
  1929. /**
  1930. * Helper function to reconstruct the lineages given a set of selected items
  1931. * and the fact that the "save lineage" setting is enabled.
  1932. *
  1933. * Note that it's impossible to predict how many lineages if we know the
  1934. * number of selected items, exactly because the "save lineage" setting is
  1935. * enabled.
  1936. *
  1937. * Worst case time complexity is O(n^3), optimizations are still possible.
  1938. *
  1939. * @param $module
  1940. * The module that should be used for HS hooks.
  1941. * @param $selection
  1942. * The selection based on which a dropbox should be generated.
  1943. * @param $params
  1944. * Optional. An array of parameters, which may be necessary for some
  1945. * implementations.
  1946. * @return
  1947. * An array of dropbox lineages.
  1948. */
  1949. function _hierarchical_select_dropbox_reconstruct_lineages_save_lineage_enabled($module, $selection, $params) {
  1950. // We have to reconstruct all lineages from the given set of selected items.
  1951. // That means: we have to reconstruct every possible combination!
  1952. $lineages = array();
  1953. $root_level = module_invoke($module, 'hierarchical_select_root_level', $params);
  1954. foreach ($selection as $key => $item) {
  1955. // Create new lineage if the item can be found in the root level.
  1956. if (array_key_exists($item, $root_level)) {
  1957. $lineages[][0] = array('value' => $item, 'label' => $root_level[$item]);
  1958. unset($selection[$key]);
  1959. }
  1960. }
  1961. // Keep on trying as long as at least one lineage has been extended.
  1962. $at_least_one = TRUE;
  1963. for ($i = 0; $at_least_one; $i++) {
  1964. $at_least_one = FALSE;
  1965. $num = count($lineages);
  1966. // Try to improve every lineage. Make sure we don't iterate over
  1967. // possibly new lineages.
  1968. for ($id = 0; $id < $num; $id++) {
  1969. $children = module_invoke($module, 'hierarchical_select_children', $lineages[$id][$i]['value'], $params);
  1970. $child_added_to_lineage = FALSE;
  1971. foreach (array_keys($children) as $child) {
  1972. if (in_array($child, $selection)) {
  1973. if (!$child_added_to_lineage) {
  1974. // Add the child to the lineage.
  1975. $lineages[$id][$i + 1] = array('value' => $child, 'label' => $children[$child]);
  1976. $child_added_to_lineage = TRUE;
  1977. $at_least_one = TRUE;
  1978. }
  1979. else {
  1980. // Create new lineage based on current one and add the child.
  1981. $lineage = $lineages[$id];
  1982. $lineage[$i + 1] = array('value' => $child, 'label' => $children[$child]);;
  1983. // Add the new lineage to the set of lineages
  1984. $lineages[] = $lineage;
  1985. }
  1986. }
  1987. }
  1988. }
  1989. }
  1990. return $lineages;
  1991. }
  1992. /**
  1993. * Dropbox lineages sorting callback.
  1994. *
  1995. * @param $lineage_a
  1996. * The first lineage.
  1997. * @param $lineage_b
  1998. * The second lineage.
  1999. * @return
  2000. * An integer that determines which of the two lineages comes first.
  2001. */
  2002. function _hierarchical_select_dropbox_sort($lineage_a, $lineage_b) {
  2003. $string_a = implode('', array_map('_hierarchical_select_dropbox_lineage_item_get_label', $lineage_a));
  2004. $string_b = implode('', array_map('_hierarchical_select_dropbox_lineage_item_get_label', $lineage_b));
  2005. return strcmp($string_a, $string_b);
  2006. }
  2007. /**
  2008. * Helper function needed for the array_map() call in the dropbox sorting
  2009. * callback.
  2010. *
  2011. * @param $item
  2012. * An item in a dropbox lineage.
  2013. * @return
  2014. * The value associated with the "label" key of the item.
  2015. */
  2016. function _hierarchical_select_dropbox_lineage_item_get_label($item) {
  2017. return t($item['label']);
  2018. }
  2019. /**
  2020. * Helper function needed for the array_map() call in the dropbox lineages
  2021. * selections creation.
  2022. *
  2023. * @param $item
  2024. * An item in a dropbox lineage.
  2025. * @return
  2026. * The value associated with the "value" key of the item.
  2027. */
  2028. function _hierarchical_select_dropbox_lineage_item_get_value($item) {
  2029. return $item['value'];
  2030. }
  2031. /**
  2032. * Smarter version of array_merge_recursive: overwrites scalar values.
  2033. *
  2034. * From: http://www.php.net/manual/en/function.array-merge-recursive.php#82976.
  2035. */
  2036. if (!function_exists('array_smart_merge')) {
  2037. function array_smart_merge($array, $override) {
  2038. if (is_array($array) && is_array($override)) {
  2039. foreach ($override as $k => $v) {
  2040. if (isset($array[$k]) && is_array($v) && is_array($array[$k])) {
  2041. $array[$k] = array_smart_merge($array[$k], $v);
  2042. }
  2043. else {
  2044. $array[$k] = $v;
  2045. }
  2046. }
  2047. }
  2048. return $array;
  2049. }
  2050. }
  2051. /**
  2052. * Helper function needed for the array_filter() call to filter the items
  2053. * marked with the 'exclusive' property
  2054. *
  2055. * @param $item
  2056. * An item in the 'special_items' setting.
  2057. * @return
  2058. * TRUE if it's marked with the 'exclusive' property, FALSE otherwise.
  2059. */
  2060. function _hierarchical_select_special_item_exclusive($item) {
  2061. return in_array('exclusive', $item);
  2062. }
  2063. /**
  2064. * Helper function needed for the array_filter() call to filter the items
  2065. * marked with the 'none' property
  2066. *
  2067. * @param $item
  2068. * An item in the 'special_items' setting.
  2069. * @return
  2070. * TRUE if it's marked with the 'none' property, FALSE otherwise.
  2071. */
  2072. function _hierarchical_select_special_item_none($item) {
  2073. return in_array('none', $item);
  2074. }
  2075. /**
  2076. * Abstraction around drupal_add_js() and Views' $form_state['js settings'].
  2077. *
  2078. * @param $settings
  2079. * The JS settings you'd like to add.
  2080. * @param $form_state
  2081. * A form state array.
  2082. */
  2083. function _hierarchical_select_add_js_settings($settings, &$form_state) {
  2084. global $views_ajax_form_js_settings;
  2085. // If we're on a Views-powered form, we must use $form_state['js settings'].
  2086. if (isset($form_state['view']) && isset($form_state['ajax']) && !empty($form_state['ajax'])) {
  2087. $form_state['js settings'] = (!isset($form_state['js settings']) || !is_array($form_state['js settings'])) ? array() : $form_state['js settings'];
  2088. $form_state['js settings'] = array_merge_recursive($form_state['js settings'], $settings);
  2089. }
  2090. // Otherwise, use drupal_add_js().
  2091. else {
  2092. drupal_add_js($settings, 'setting');
  2093. // Necessary for Views AJAX pager support.
  2094. // Also see hierarchical_select_ajax_data_alter().
  2095. if (isset($settings['HierarchicalSelect']['settings'])) {
  2096. $views_ajax_form_js_settings = $settings['HierarchicalSelect']['settings'];
  2097. }
  2098. }
  2099. }
  2100. /**
  2101. * Implementation of hook_ajax_data_alter().
  2102. * (Necessary for Views AJAX pager support.)
  2103. */
  2104. function hierarchical_select_ajax_data_alter(&$object, $module, $view) {
  2105. global $views_ajax_form_js_settings;
  2106. if (!empty($views_ajax_form_js_settings)) {
  2107. $object->hs_drupal_js_settings = $views_ajax_form_js_settings;
  2108. $object->__callbacks = array_merge(array('Drupal.HierarchicalSelect.ajaxViewPagerSettingsUpdate'), $object->__callbacks);
  2109. }
  2110. }