Parent Directory
|
Revision Log
|
Revision Graph
Fixed #597786 by boneless: 'allowed_levels' in 'editability' not working properly when 'enforce_deepest' is enabled
| 1 | <?php |
| 2 | // $Id: hierarchical_select.module,v 1.180 2009/10/30 23:51:37 wimleers Exp $ |
| 3 | |
| 4 | /** |
| 5 | * @file |
| 6 | * This module defines the "hierarchical_select" form element, which is a |
| 7 | * greatly enhanced way for letting the user select items in a hierarchy. |
| 8 | */ |
| 9 | |
| 10 | |
| 11 | // Make sure that the devel module is installed when you enable developer mode! |
| 12 | define('HS_DEVELOPER_MODE', 0); |
| 13 | |
| 14 | |
| 15 | //---------------------------------------------------------------------------- |
| 16 | // Drupal core hooks. |
| 17 | |
| 18 | /** |
| 19 | * Implementation of hook_menu(). |
| 20 | */ |
| 21 | function hierarchical_select_menu() { |
| 22 | $items['hierarchical_select_json'] = array( |
| 23 | 'page callback' => 'hierarchical_select_json', |
| 24 | 'type' => MENU_CALLBACK, |
| 25 | // TODO: Needs improvements. Ideally, this would inherit the permissions |
| 26 | // of the form the Hierarchical Select was in. |
| 27 | 'access callback' => TRUE, |
| 28 | ); |
| 29 | $items['admin/settings/hierarchical_select'] = array( |
| 30 | 'title' => 'Hierarchical Select', |
| 31 | 'description' => 'Configure site-wide settings for the Hierarchical Select form element.', |
| 32 | 'access arguments' => array('administer site configuration'), |
| 33 | 'page callback' => 'drupal_get_form', |
| 34 | 'page arguments' => array('hierarchical_select_admin_settings'), |
| 35 | 'type' => MENU_NORMAL_ITEM, |
| 36 | 'file' => 'hierarchical_select.admin.inc', |
| 37 | ); |
| 38 | $items['admin/settings/hierarchical_select/settings'] = array( |
| 39 | 'title' => 'Site-wide settings', |
| 40 | 'access arguments' => array('administer site configuration'), |
| 41 | 'weight' => -10, |
| 42 | 'type' => MENU_DEFAULT_LOCAL_TASK, |
| 43 | 'file' => 'hierarchical_select.admin.inc', |
| 44 | ); |
| 45 | $items['admin/settings/hierarchical_select/configs'] = array( |
| 46 | 'title' => 'Configurations', |
| 47 | 'description' => 'All available Hierarchical Select configurations.', |
| 48 | 'access arguments' => array('administer site configuration'), |
| 49 | 'page callback' => 'hierarchical_select_admin_configs', |
| 50 | 'type' => MENU_LOCAL_TASK, |
| 51 | 'file' => 'hierarchical_select.admin.inc', |
| 52 | ); |
| 53 | $items['admin/settings/hierarchical_select/implementations'] = array( |
| 54 | 'title' => 'Implementations', |
| 55 | 'description' => 'Features of each Hierarchical Select implementation.', |
| 56 | 'access arguments' => array('administer site configuration'), |
| 57 | 'page callback' => 'hierarchical_select_admin_implementations', |
| 58 | 'type' => MENU_LOCAL_TASK, |
| 59 | 'file' => 'hierarchical_select.admin.inc', |
| 60 | ); |
| 61 | $items['admin/settings/hierarchical_select/export/%hierarchical_select_config_id'] = array( |
| 62 | 'title' => 'Export', |
| 63 | 'access arguments' => array('administer site configuration'), |
| 64 | 'page callback' => 'drupal_get_form', |
| 65 | 'page arguments' => array('hierarchical_select_admin_export', 4), |
| 66 | 'type' => MENU_LOCAL_TASK, |
| 67 | 'file' => 'hierarchical_select.admin.inc', |
| 68 | ); |
| 69 | $items['admin/settings/hierarchical_select/import/%hierarchical_select_config_id'] = array( |
| 70 | 'title' => 'Import', |
| 71 | 'access arguments' => array('administer site configuration'), |
| 72 | 'page callback' => 'drupal_get_form', |
| 73 | 'page arguments' => array('hierarchical_select_admin_import', 4), |
| 74 | 'type' => MENU_LOCAL_TASK, |
| 75 | 'file' => 'hierarchical_select.admin.inc', |
| 76 | ); |
| 77 | |
| 78 | return $items; |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Implementation of hook_form_alter(). |
| 83 | */ |
| 84 | function hierarchical_select_form_alter(&$form, &$form_state, $form_id) { |
| 85 | if (_hierarchical_select_form_has_hierarchical_select($form)) { |
| 86 | $form['#after_build'][] = 'hierarchical_select_after_build'; |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * Implementation of hook_elements(). |
| 92 | */ |
| 93 | function hierarchical_select_elements() { |
| 94 | $type['hierarchical_select'] = array( |
| 95 | '#input' => TRUE, |
| 96 | '#process' => array('hierarchical_select_process'), |
| 97 | '#config' => array( |
| 98 | 'module' => 'some_module', |
| 99 | 'params' => array(), |
| 100 | 'save_lineage' => 0, |
| 101 | 'enforce_deepest' => 0, |
| 102 | 'entity_count' => 0, |
| 103 | 'require_entity' => 0, |
| 104 | 'resizable' => 1, |
| 105 | 'level_labels' => array( |
| 106 | 'status' => 0, |
| 107 | 'labels' => array(), |
| 108 | ), |
| 109 | 'dropbox' => array( |
| 110 | 'status' => 0, |
| 111 | 'title' => t('All selections'), |
| 112 | 'limit' => 0, |
| 113 | 'reset_hs' => 1, |
| 114 | ), |
| 115 | 'editability' => array( |
| 116 | 'status' => 0, |
| 117 | 'item_types' => array(), |
| 118 | 'allowed_levels' => array(), |
| 119 | 'allow_new_levels' => 0, |
| 120 | 'max_levels' => 3, |
| 121 | ), |
| 122 | 'animation_delay' => variable_get('hierarchical_select_animation_delay', 400), |
| 123 | 'special_items' => array(), |
| 124 | 'render_flat_select' => 0, |
| 125 | 'path' => 'hierarchical_select_json', |
| 126 | ), |
| 127 | '#default_value' => -1, |
| 128 | ); |
| 129 | return $type; |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * Implementation of hook_requirements(). |
| 134 | */ |
| 135 | function hierarchical_select_requirements($phase) { |
| 136 | $requirements = array(); |
| 137 | |
| 138 | if ($phase == 'runtime') { |
| 139 | // Check if all hook_update_n() hooks have been executed. |
| 140 | require_once('includes/install.inc'); |
| 141 | drupal_load_updates(); |
| 142 | $updates = drupal_get_schema_versions('hierarchical_select'); |
| 143 | $current = drupal_get_installed_schema_version('hierarchical_select'); |
| 144 | |
| 145 | $up_to_date = (end($updates) == $current); |
| 146 | |
| 147 | $hierarchical_select_weight = db_result(db_query("SELECT weight FROM {system} WHERE type = 'module' AND name = 'hierarchical_select'")); |
| 148 | $core_overriding_modules = array('hs_book', 'hs_menu', 'hs_taxonomy'); |
| 149 | $path_errors = array(); |
| 150 | foreach ($core_overriding_modules as $module) { |
| 151 | $filename = db_result(db_query("SELECT filename FROM {system} WHERE type = 'module' AND name = '%s'", $module)); |
| 152 | if (strpos($filename, 'modules/') === 0) { |
| 153 | $module_info = drupal_parse_info_file(dirname($filename) ."/$module.info"); |
| 154 | $path_errors[] = t('!module', array('!module' => $module_info['name'])); |
| 155 | } |
| 156 | } |
| 157 | $weight_errors = array(); |
| 158 | foreach (module_implements('hierarchical_select_root_level') as $module) { |
| 159 | $weight = db_result(db_query("SELECT weight FROM {system} WHERE name = '%s'", $module)); |
| 160 | if (!($hierarchical_select_weight > $weight)) { |
| 161 | $filename = db_result(db_query("SELECT filename FROM {system} WHERE type = 'module' AND name = '%s'", $module)); |
| 162 | $module_info = drupal_parse_info_file(dirname($filename) ."/$module.info"); |
| 163 | $weight_errors[] = t('!module (!weight)', array('!module' => $module_info['name'], '!weight' => $weight)); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | if ($up_to_date && !count($path_errors) && !count($weight_errors)) { |
| 168 | $value = t('All updates installed. Implementation modules are installed correctly.'); |
| 169 | $description = ''; |
| 170 | $severity = REQUIREMENT_OK; |
| 171 | } |
| 172 | elseif ($path_errors) { |
| 173 | $value = t('Modules incorrectly installed!'); |
| 174 | $description = t( |
| 175 | "The following modules implement Hierarchical Select module for Drupal |
| 176 | core modules, but are installed in the wrong location. They're |
| 177 | installed in core's <code>modules</code> directory, but should be |
| 178 | installed in either the <code>sites/all/modules</code> directory or a |
| 179 | <code>sites/yoursite.com/modules</code> directory" |
| 180 | ) .':'. theme('item_list', $path_errors); |
| 181 | $severity = REQUIREMENT_ERROR; |
| 182 | } |
| 183 | elseif ($weight_errors) { |
| 184 | $value = t('Module weight incorrectly configured!'); |
| 185 | $description = t( |
| 186 | 'The weight of the Hierarchical Select module (!weight) is not |
| 187 | strictly higher than the weight of the following modules', |
| 188 | array('!weight' => $hierarchical_select_weight) |
| 189 | ) .':'. theme('item_list', $weight_errors); |
| 190 | $severity = REQUIREMENT_ERROR; |
| 191 | } |
| 192 | else { |
| 193 | $value = t('Not all updates installed!'); |
| 194 | $description = t('Please run update.php to install the latest updates! |
| 195 | You have installed update !installed_update, but the latest update is |
| 196 | !latest_update!', |
| 197 | array( |
| 198 | '!installed_update' => $current, |
| 199 | '!latest_update' => end($updates), |
| 200 | ) |
| 201 | ); |
| 202 | $severity = REQUIREMENT_ERROR; |
| 203 | } |
| 204 | |
| 205 | $requirements['hierarchical_select'] = array( |
| 206 | 'title' => t('Hierarchical Select'), |
| 207 | 'value' => $value, |
| 208 | 'description' => $description, |
| 209 | 'severity' => $severity, |
| 210 | ); |
| 211 | } |
| 212 | |
| 213 | return $requirements; |
| 214 | } |
| 215 | |
| 216 | /** |
| 217 | * Implementation of hook_theme(). |
| 218 | */ |
| 219 | function hierarchical_select_theme() { |
| 220 | return array( |
| 221 | 'hierarchical_select' => array( |
| 222 | 'file' => 'includes/theme.inc', |
| 223 | 'arguments' => array('element' => NULL), |
| 224 | ), |
| 225 | 'hierarchical_select_selects_container' => array( |
| 226 | 'file' => 'includes/theme.inc', |
| 227 | 'arguments' => array('element' => NULL), |
| 228 | ), |
| 229 | 'hierarchical_select_select' => array( |
| 230 | 'file' => 'includes/theme.inc', |
| 231 | 'arguments' => array('element' => NULL), |
| 232 | ), |
| 233 | 'hierarchical_select_special_option' => array( |
| 234 | 'file' => 'includes/theme.inc', |
| 235 | 'arguments' => array('option' => NULL), |
| 236 | ), |
| 237 | 'hierarchical_select_textfield' => array( |
| 238 | 'file' => 'includes/theme.inc', |
| 239 | 'arguments' => array('element' => NULL), |
| 240 | ), |
| 241 | 'hierarchical_select_dropbox_table' => array( |
| 242 | 'file' => 'includes/theme.inc', |
| 243 | 'arguments' => array('element' => NULL), |
| 244 | ), |
| 245 | 'hierarchical_select_common_config_form_level_labels' => array( |
| 246 | 'file' => 'includes/theme.inc', |
| 247 | 'arguments' => array('form' => NULL), |
| 248 | ), |
| 249 | 'hierarchical_select_common_config_form_editability' => array( |
| 250 | 'file' => 'includes/theme.inc', |
| 251 | 'arguments' => array('form' => NULL), |
| 252 | ), |
| 253 | 'hierarchical_select_selection_as_lineages' => array( |
| 254 | 'file' => 'includes/theme.inc', |
| 255 | 'arguments' => array( |
| 256 | 'selection' => NULL, |
| 257 | 'config' => NULL, |
| 258 | ), |
| 259 | ), |
| 260 | ); |
| 261 | } |
| 262 | |
| 263 | /** |
| 264 | * Implementation of hook_simpletest(). |
| 265 | */ |
| 266 | function hierarchical_select_simpletest() { |
| 267 | $dir = drupal_get_path('module', 'hierarchical_select') .'/tests'; |
| 268 | $tests = file_scan_directory($dir, '\.test$'); |
| 269 | return array_keys($tests); |
| 270 | } |
| 271 | |
| 272 | |
| 273 | //---------------------------------------------------------------------------- |
| 274 | // Menu system callbacks. |
| 275 | |
| 276 | /** |
| 277 | * Wildcard loader for Hierarchical Select config ID's. |
| 278 | */ |
| 279 | function hierarchical_select_config_id_load($config_id) { |
| 280 | $config = variable_get('hs_config_'. $config_id, FALSE); |
| 281 | return ($config !== FALSE) ? $config['config_id'] : FALSE; |
| 282 | } |
| 283 | |
| 284 | /** |
| 285 | * Menu callback; format=text/json; generates and outputs the appropriate HTML. |
| 286 | */ |
| 287 | function hierarchical_select_json() { |
| 288 | // We are returning Javascript, so tell the browser. Ripped from Drupal 6's |
| 289 | // drupal_json() function. |
| 290 | drupal_set_header('Content-Type: text/javascript; charset=utf-8'); |
| 291 | |
| 292 | $hs_form_build_id = $_POST['hs_form_build_id']; |
| 293 | |
| 294 | // Collect all necessary variables. |
| 295 | $cached = cache_get($hs_form_build_id, 'cache'); |
| 296 | $storage = $cached->data; |
| 297 | |
| 298 | // Ensure that the form id in the POST array is the same as the one of the |
| 299 | // stored parameters of the original form. For 99% of the forms, this step |
| 300 | // is not necessary, but when a hierarchical_select form item is inside a |
| 301 | // form in a subform_element in a form, then it is necessary. |
| 302 | $form_id = $_POST['form_id'] = $storage['parameters'][0]; |
| 303 | |
| 304 | if (HS_DEVELOPER_MODE) { |
| 305 | _hierarchical_select_log("form_id: $form_id"); |
| 306 | _hierarchical_select_log("hs_form_build_id: $hs_form_build_id"); |
| 307 | } |
| 308 | |
| 309 | $form_state = &$storage['parameters'][1]; |
| 310 | |
| 311 | // Include the file in which the form definition function lives. |
| 312 | if (!empty($storage['file'])) { |
| 313 | require_once($storage['file']); |
| 314 | } |
| 315 | |
| 316 | // Also include files set in $form_state['form_load_files']. Set by CTools |
| 317 | // Delegator, which is used by Panels (i.e. this is necessary for Panels |
| 318 | // compatibility). |
| 319 | if (isset($form_state['form_load_files'])) { |
| 320 | foreach ($form_state['form_load_files'] as $file) { |
| 321 | require_once './' . $file; |
| 322 | } |
| 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 | |
| 331 | // Render only the relevant part of the form (i.e. the hierarchical_select |
| 332 | // form item that has triggered this AJAX callback). |
| 333 | $hsid = $_POST['hsid']; |
| 334 | $name = $storage['#names'][$hsid]; |
| 335 | $part_of_form = _hierarchical_select_get_form_item($form, $name); |
| 336 | $output = drupal_render($part_of_form); |
| 337 | |
| 338 | // If the user's browser supports the active cache system, then send the |
| 339 | // currently requested hierarchy in an easy-to-manage form. |
| 340 | $cache = array(); |
| 341 | if (isset($_POST['client_supports_caching'])) { |
| 342 | if ($_POST['client_supports_caching'] == 'true') { |
| 343 | $cache = _hierarchical_select_json_convert_hierarchy_to_cache($part_of_form['hierarchy']['#value']); |
| 344 | } |
| 345 | else if ($_POST['client_supports_caching'] == 'false') { |
| 346 | // This indicates that a client-side cache is installed, but not working |
| 347 | // properly. |
| 348 | // TODO: figure out a clean way to notify the administrator. |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | print drupal_to_js(array( |
| 353 | 'cache' => $cache, |
| 354 | 'output' => $output, |
| 355 | 'log' => (isset($part_of_form['log']['#value'])) ? $part_of_form['log']['#value'] : NULL, |
| 356 | )); |
| 357 | exit; |
| 358 | } |
| 359 | |
| 360 | |
| 361 | //---------------------------------------------------------------------------- |
| 362 | // Forms API callbacks. |
| 363 | |
| 364 | /** |
| 365 | * Hierarchical select form element type #process callback. |
| 366 | */ |
| 367 | function hierarchical_select_process($element, $edit, &$form_state, $form) { |
| 368 | if (!is_array($element['#value']) || !isset($element['#value']['hsid'])) { |
| 369 | // The HSID is stored in the session, to allow for multiple Hierarchical |
| 370 | // Select form items on the same page of which at least one is added through |
| 371 | // AHAH. A normal static variable won't do in this case, because then at |
| 372 | // least two Hierarchical Select form items will have HSID 0, because they |
| 373 | // are generated in different requests, both of which will have a first HSID |
| 374 | // of 0. This will then cause problems on the page. |
| 375 | if (!isset($_SESSION['hsid'])) { |
| 376 | $_SESSION['hsid'] = 0; |
| 377 | } |
| 378 | else { |
| 379 | // Let the HSID go from 0 to 99, then start over. Larger numbers are |
| 380 | // pointless: who's going to use more than a hundred Hierarchical Select |
| 381 | // form items on the same page? |
| 382 | $_SESSION['hsid'] = ($_SESSION['hsid'] + 1) % 100; |
| 383 | } |
| 384 | $hsid = $_SESSION['hsid']; |
| 385 | } |
| 386 | else { |
| 387 | $hsid = $element['#value']['hsid']; |
| 388 | } |
| 389 | $element['hsid'] = array('#type' => 'hidden', '#value' => $hsid); |
| 390 | // A hierarchical_select form element expands to multiple items. For example |
| 391 | // $element['hsid'] got set just above. If #value is not an array, then |
| 392 | // form_set_value(), which is called by form_builder() will fail, because it |
| 393 | // assumes that #value is an array, because we are trying to set a child of |
| 394 | // it. |
| 395 | if (!is_array($element['#value'])) { |
| 396 | $element['#value'] = array($element['#value']); |
| 397 | } |
| 398 | |
| 399 | // Store the #name property of each hierarchical_select form item, this is |
| 400 | // necessary to find this form item back in an AJAX callback. |
| 401 | _hierarchical_select_store_name($element, $hsid); |
| 402 | |
| 403 | // Get the config and convert the 'special_items' setting to a more easily |
| 404 | // accessible format. |
| 405 | $config = $element['#config']; |
| 406 | if (isset($config['special_items'])) { |
| 407 | $special_items['exclusive'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_exclusive')); |
| 408 | $special_items['none'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_none')); |
| 409 | } |
| 410 | |
| 411 | // Set up Javascript and add settings specifically for the current |
| 412 | // hierarchical select. |
| 413 | $config = _hierarchical_select_inherit_default_config($element['#config']); |
| 414 | _hierarchical_select_setup_js(); |
| 415 | _hierarchical_select_setup_js($form_state); |
| 416 | $settings = array( |
| 417 | 'HierarchicalSelect' => array( |
| 418 | 'settings' => array( |
| 419 | $hsid => array( |
| 420 | 'animationDelay' => ($config['animation_delay'] == 0) ? (int) variable_get('hierarchical_select_animation_delay', 400) : $config['animation_delay'], |
| 421 | 'cacheId' => $config['module'] .'_'. implode('_', (is_array($config['params'])) ? $config['params'] : array()), |
| 422 | 'renderFlatSelect' => (isset($config['render_flat_select'])) ? (int) $config['render_flat_select'] : 0, |
| 423 | 'createNewItems' => (isset($config['editability']['status'])) ? (int) $config['editability']['status'] : 0, |
| 424 | 'createNewLevels' => (isset($config['editability']['allow_new_levels'])) ? (int) $config['editability']['allow_new_levels'] : 0, |
| 425 | 'resizable' => (isset($config['resizable'])) ? (int) $config['resizable'] : 0, |
| 426 | 'path' => $config['path'], |
| 427 | ), |
| 428 | ), |
| 429 | ) |
| 430 | ); |
| 431 | _hierarchical_select_add_js_settings($settings, $form_state); |
| 432 | |
| 433 | // Basic config validation and diagnostics. |
| 434 | if (HS_DEVELOPER_MODE) { |
| 435 | $diagnostics = array(); |
| 436 | if (!isset($config['module']) || empty($config['module'])) { |
| 437 | $diagnostics[] = t("'module is not set!"); |
| 438 | } |
| 439 | elseif (!module_exists($config['module'])) { |
| 440 | $diagnostics[] = t('the module that should be used (module) is not installed!', array('%module' => $config['module'])); |
| 441 | } |
| 442 | else { |
| 443 | $required_params = module_invoke($config['module'], 'hierarchical_select_params'); |
| 444 | $missing_params = array_diff($required_params, array_keys($config['params'])); |
| 445 | if (!empty($missing_params)) { |
| 446 | $diagnostics[] = t("'params' is missing values for: ") . implode(', ', $missing_params) .'.'; |
| 447 | } |
| 448 | } |
| 449 | $config_id = (isset($config['config_id']) && is_string($config['config_id'])) ? $config['config_id'] : 'none'; |
| 450 | if (empty($diagnostics)) { |
| 451 | _hierarchical_select_log("Config diagnostics (config id: $config_id): no problems found!"); |
| 452 | } |
| 453 | else { |
| 454 | $diagnostics_string = print_r($diagnostics, TRUE); |
| 455 | $message = "Config diagnostics (config id: $config_id): $diagnostics_string"; |
| 456 | _hierarchical_select_log($message); |
| 457 | $element['#type']= 'item'; |
| 458 | $element['#value'] = '<p><span style="color:red;">Fix the indicated errors in the #config property first!</span><br />'. nl2br($message) .'</p>'; |
| 459 | return $element; |
| 460 | } |
| 461 | } |
| 462 | |
| 463 | // Calculate the selections in both the hierarchical select and the dropbox, |
| 464 | // we need these before we can render anything. |
| 465 | list($hs_selection, $db_selection) = _hierarchical_select_process_calculate_selections($element); |
| 466 | |
| 467 | if (HS_DEVELOPER_MODE) { |
| 468 | _hierarchical_select_log("Calculated hierarchical select selection:"); |
| 469 | _hierarchical_select_log($hs_selection); |
| 470 | |
| 471 | if ($config['dropbox']['status']) { |
| 472 | _hierarchical_select_log("Calculated dropbox selection:"); |
| 473 | _hierarchical_select_log($db_selection); |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | // If: |
| 478 | // - the special_items setting has been configured |
| 479 | // - at least one special item has the 'exclusive' property |
| 480 | // - the dropbox is enabled |
| 481 | // then do the necessary processing to make exclusive lineages possible. |
| 482 | if (isset($special_item) && count($special_items['exclusive']) && $config['dropbox']['status']) { |
| 483 | // When the form is first loaded, $db_selection will contain the selection |
| 484 | // that we should check, but in updates, $hs_selection will. |
| 485 | $selection = (!empty($hs_selection)) ? $hs_selection : $db_selection; |
| 486 | |
| 487 | // If the current selection of the hierarchical select matches one of the |
| 488 | // configured exclusive items, then disable the dropbox (to ensure an |
| 489 | // exclusive selection). |
| 490 | $exclusive_item = array_intersect($selection, $special_items['exclusive']); |
| 491 | if (count($exclusive_item)) { |
| 492 | // By also updating the configuration stored in $element, we ensure that |
| 493 | // the validation step, which extracts the configuration again, also gets |
| 494 | // the updated config. |
| 495 | $element['#config']['dropbox']['status'] = 0; |
| 496 | $config = _hierarchical_select_inherit_default_config($element['#config']); |
| 497 | |
| 498 | // Set the hierarchical select to the exclusive item and make the |
| 499 | // dropbox empty. |
| 500 | $hs_selection = array(0 => reset($exclusive_item)); |
| 501 | $db_selection = array(); |
| 502 | } |
| 503 | } |
| 504 | |
| 505 | // Generate the $hierarchy and $dropbox objects using the selections that |
| 506 | // were just calculated. |
| 507 | $dropbox = (!$config['dropbox']['status']) ? FALSE : _hierarchical_select_dropbox_generate($config, $db_selection); |
| 508 | $hierarchy = _hierarchical_select_hierarchy_generate($config, $hs_selection, $element['#required'], $dropbox); |
| 509 | |
| 510 | if (HS_DEVELOPER_MODE) { |
| 511 | _hierarchical_select_log('Generated hierarchy in '. $hierarchy->build_time['total'] .' ms:'); |
| 512 | _hierarchical_select_log($hierarchy); |
| 513 | |
| 514 | if ($config['dropbox']['status']) { |
| 515 | _hierarchical_select_log('Generated dropbox in '. $dropbox->build_time .' ms: '); |
| 516 | _hierarchical_select_log($dropbox); |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | // Store the hierarchy object in the element, we'll need this if the user's |
| 521 | // browser supports the active cache system. |
| 522 | $element['hierarchy'] = array('#type' => 'value', '#value' => $hierarchy); |
| 523 | |
| 524 | |
| 525 | // Ensure that #tree is enabled! |
| 526 | $element['#tree'] = TRUE; |
| 527 | |
| 528 | // If render_flat_select is enabled, render a flat select. |
| 529 | if ($config['render_flat_select']) { |
| 530 | $element['flat_select'] = _hierarchical_select_process_render_flat_select($hierarchy, $dropbox, $config); |
| 531 | } |
| 532 | |
| 533 | // Render the hierarchical select. |
| 534 | $element['hierarchical_select'] = array( |
| 535 | '#theme' => 'hierarchical_select_selects_container', |
| 536 | ); |
| 537 | $element['hierarchical_select']['selects'] = _hierarchical_select_process_render_hs_selects($hsid, $hierarchy); |
| 538 | |
| 539 | // The selects in the hierarchical select should inherit the #size property. |
| 540 | foreach (element_children($element['hierarchical_select']['selects']) as $depth) { |
| 541 | $element['hierarchical_select']['selects'][$depth]['#size'] = isset($element['#size']) ? $element['#size'] : 0; |
| 542 | } |
| 543 | |
| 544 | // Check if a new item is being created. |
| 545 | $creating_new_item = FALSE; |
| 546 | if (isset($element['#value']['hierarchical_select']['selects'])) { |
| 547 | foreach ($element['#value']['hierarchical_select']['selects'] as $depth => $value) { |
| 548 | if ($value == 'create_new_item' && _hierarchical_select_create_new_item_is_allowed($config, $depth)) { |
| 549 | $creating_new_item = TRUE; |
| 550 | |
| 551 | // We want to override the select in which the "create_new_item" |
| 552 | // option was selected and hide all selects after that, if they exist. |
| 553 | for ($i = $depth; $i < count($hierarchy->lineage); $i++) { |
| 554 | unset($element['hierarchical_select']['selects'][$i]); |
| 555 | } |
| 556 | |
| 557 | $element['hierarchical_select']['create_new_item'] = array( |
| 558 | '#prefix' => '<div class="'. str_replace('_', '-', $value) .'">', |
| 559 | '#suffix' => '</div>', |
| 560 | ); |
| 561 | |
| 562 | $item_type_depth = ($value == 'create_new_item') ? $depth : $depth + 1; |
| 563 | $item_type = (!empty($config['editability']['item_types'][$item_type_depth])) ? t($config['editability']['item_types'][$item_type_depth]) : t('item'); |
| 564 | |
| 565 | $element['hierarchical_select']['create_new_item']['input'] = array( |
| 566 | '#type' => 'textfield', |
| 567 | '#size' => 20, |
| 568 | '#maxlength' => 255, |
| 569 | '#default_value' => t('new @item', array('@item' => $item_type)), |
| 570 | '#attributes' => array( |
| 571 | 'title' => t('new @item', array('@item' => $item_type)), |
| 572 | 'class' => 'create-new-item-input' |
| 573 | ), |
| 574 | // Use a #theme callback to prevent the textfield from being wrapped |
| 575 | // in a div. This simplifies the CSS and JS code. |
| 576 | '#theme' => 'hierarchical_select_textfield', |
| 577 | ); |
| 578 | |
| 579 | $element['hierarchical_select']['create_new_item']['create'] = array( |
| 580 | '#type' => 'button', |
| 581 | '#value' => t('Create'), |
| 582 | '#attributes' => array('class' => 'create-new-item-create'), |
| 583 | ); |
| 584 | |
| 585 | $element['hierarchical_select']['create_new_item']['cancel'] = array( |
| 586 | '#type' => 'button', |
| 587 | '#value' => t('Cancel'), |
| 588 | '#attributes' => array('class' => 'create-new-item-cancel'), |
| 589 | ); |
| 590 | } |
| 591 | } |
| 592 | } |
| 593 | |
| 594 | |
| 595 | if ($config['dropbox']['status']) { |
| 596 | if (!$creating_new_item) { |
| 597 | // Append an "Add" button to the selects. |
| 598 | $element['hierarchical_select']['dropbox_add'] = array( |
| 599 | '#type' => 'button', |
| 600 | '#value' => t('Add'), |
| 601 | '#attributes' => array('class' => 'add-to-dropbox'), |
| 602 | ); |
| 603 | } |
| 604 | |
| 605 | if ($config['dropbox']['limit'] > 0) { // Zero as dropbox limit means no limit. |
| 606 | if (count($dropbox->lineages) == $config['dropbox']['limit']) { |
| 607 | $element['dropbox_limit_warning'] = array( |
| 608 | '#value' => t("You've reached the maximal number of items you can select."), |
| 609 | '#prefix' => '<p class="hierarchical-select-dropbox-limit-warning">', |
| 610 | '#suffix' => '</p>', |
| 611 | ); |
| 612 | |
| 613 | // Disable all child form elements of $element['hierarchical_select]. |
| 614 | _hierarchical_select_mark_as_disabled($element['hierarchical_select']); |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | // Add the hidden part of the dropbox. This will be used to store the |
| 619 | // currently selected lineages. |
| 620 | $element['dropbox']['hidden'] = array( |
| 621 | '#prefix' => '<div class="dropbox-hidden">', |
| 622 | '#suffix' => '</div>', |
| 623 | ); |
| 624 | $element['dropbox']['hidden'] = _hierarchical_select_process_render_db_hidden($hsid, $dropbox); |
| 625 | |
| 626 | // Add the dropbox-as-a-table that will be visible to the user. |
| 627 | $element['dropbox']['visible'] = _hierarchical_select_process_render_db_visible($hsid, $dropbox); |
| 628 | } |
| 629 | |
| 630 | // This button and accompanying help text will be hidden when Javascript is |
| 631 | // enabled. |
| 632 | $element['nojs'] = array( |
| 633 | '#prefix' => '<div class="nojs">', |
| 634 | '#suffix' => '</div>', |
| 635 | ); |
| 636 | $element['nojs']['update_button'] = array( |
| 637 | '#type' => 'button', |
| 638 | '#value' => t('Update'), |
| 639 | '#attributes' => array('class' => 'update-button'), |
| 640 | ); |
| 641 | $element['nojs']['update_button_help_text'] = array( |
| 642 | '#value' => _hierarchical_select_nojs_helptext($config['dropbox']['status']), |
| 643 | '#prefix' => '<div class="help-text">', |
| 644 | '#suffix' => '</div>', |
| 645 | ); |
| 646 | |
| 647 | |
| 648 | // Ensure the render order is correct. |
| 649 | $element['hierarchical_select']['#weight'] = 0; |
| 650 | $element['dropbox_limit_warning']['#weight'] = 1; |
| 651 | $element['dropbox']['#weight'] = 2; |
| 652 | $element['nojs']['#weight'] = 3; |
| 653 | |
| 654 | // This prevents values from in $element['#post'] to be used instead of the |
| 655 | // generated default values (#default_value). |
| 656 | // For example: $element['hierarchical_select']['selects']['0']['#default_value'] |
| 657 | // is set to 'label_0' after an "Add" operation. When $element['#post'] is |
| 658 | // NOT unset, the corresponding value in $element['#post'] will be used |
| 659 | // instead of the default value that was set. This is undesired behavior. |
| 660 | if (isset($element['#post'])) { |
| 661 | unset($element['#post']); |
| 662 | } |
| 663 | |
| 664 | // Finally, calculate the return value of this hierarchical_select form |
| 665 | // element. This will be set in _hierarchical_select_validate(). (If we'd |
| 666 | // set it now, it would be overridden again.) |
| 667 | $element['#return_value'] = _hierarchical_select_process_calculate_return_value($hierarchy, ($config['dropbox']['status']) ? $dropbox : FALSE, $config['module'], $config['params'], $config['save_lineage']); |
| 668 | |
| 669 | // Add a validate callback, which will: |
| 670 | // - validate that the dropbox limit was not exceeded. |
| 671 | // - set the return value of this form element. |
| 672 | // Also make sure it is the *first* validate callback. |
| 673 | $element['#element_validate'] = (isset($element['#element_validate'])) ? $element['#element_validate'] : array(); |
| 674 | $element['#element_validate'] = array_merge(array('_hierarchical_select_validate'), $element['#element_validate']); |
| 675 | |
| 676 | if (HS_DEVELOPER_MODE) { |
| 677 | $element['log'] = array('#type' => 'value', '#value' => _hierarchical_select_log(NULL, TRUE)); |
| 678 | $settings = array( |
| 679 | 'HierarchicalSelect' => array( |
| 680 | 'initialLog' => array( |
| 681 | $hsid => $element['log']['#value'], |
| 682 | ), |
| 683 | ), |
| 684 | ); |
| 685 | _hierarchical_select_add_js_settings($settings, $form_state); |
| 686 | } |
| 687 | |
| 688 | // If the form item is marked as disabled, disable all child form items as |
| 689 | // well. |
| 690 | if (isset($element['#disabled']) && $element['#disabled']) { |
| 691 | _hierarchical_select_mark_as_disabled($element); |
| 692 | } |
| 693 | |
| 694 | return $element; |
| 695 | } |
| 696 | |
| 697 | /** |
| 698 | * Hierarchical select form element type #after_build callback. |
| 699 | */ |
| 700 | function hierarchical_select_after_build($form, &$form_state) { |
| 701 | // TRICKY: Pageroute compatibility: avoid that the body of this #after_build |
| 702 | // callback is executed twice. |
| 703 | if (isset($form['hs_form_build_id'])) { |
| 704 | return $form; |
| 705 | } |
| 706 | |
| 707 | $names = _hierarchical_select_store_name(NULL, NULL, TRUE); |
| 708 | |
| 709 | if (!isset($_POST['hs_form_build_id']) && count($names)) { |
| 710 | $parameters = (isset($form['#parameters'])) ? $form['#parameters'] : array(); |
| 711 | $menu_item = menu_get_item(); |
| 712 | |
| 713 | // Collect information in this array, which will be used in dynamic form |
| 714 | // updates, to … |
| 715 | $storage = array( |
| 716 | // … retrieve $form. |
| 717 | 'parameters' => $parameters, |
| 718 | // … determine which part of $form should be rendered. |
| 719 | '#names' => $names, |
| 720 | // … include the file in which the form function lives. |
| 721 | 'file' => $menu_item['file'], |
| 722 | ); |
| 723 | |
| 724 | // 6 hours cache life time for forms should be plenty. |
| 725 | $expire = 21600; |
| 726 | |
| 727 | // Store the information needed for dynamic form updates in the cache, so |
| 728 | // we can retrieve this in our JSON callbacks (to be able to rebuild and |
| 729 | // render part of the form). |
| 730 | $hs_form_build_id = 'hs_form_'. md5(mt_rand()); |
| 731 | cache_set($hs_form_build_id, $storage, 'cache', time() + $expire); |
| 732 | } |
| 733 | elseif (isset($_POST['hs_form_build_id'])) { |
| 734 | // Don't generate a new hs_form_build_id if this is a re-rendering of the |
| 735 | // same form! |
| 736 | $hs_form_build_id = $_POST['hs_form_build_id']; |
| 737 | } |
| 738 | |
| 739 | // Store the hs_form_build_id in a hidden value, so that it gets POSTed. |
| 740 | $form_element = array( |
| 741 | '#type' => 'hidden', |
| 742 | '#value' => $hs_form_build_id, |
| 743 | // We have to set #parents manually because we want to send only |
| 744 | // $form_element through form_builder(), not $form. If we set #parents, |
| 745 | // form_builder() has all info it needs to generate #name and #id. |
| 746 | '#parents' => array('hs_form_build_id'), |
| 747 | ); |
| 748 | $form['hs_form_build_id'] = form_builder($form['form_id']['#value'], $form_element, $form_state); |
| 749 | |
| 750 | // Pass the hs_form_build_id to a custom submit function that will clear |
| 751 | // the associated values from the cache. The _hierarchical_select_submit() |
| 752 | // must be the first submit callback to ensure that it's executed: if a |
| 753 | // submit callback before it performs a drupal_goto(), it won't be called! |
| 754 | $form_state['hs_form_build_id'] = $hs_form_build_id; |
| 755 | $form['#submit'] = (is_array($form['#submit'])) ? $form['#submit'] : array(); |
| 756 | $form['#submit'] = array_merge(array('_hierarchical_select_submit'), $form['#submit']); |
| 757 | |
| 758 | return $form; |
| 759 | } |
| 760 | |
| 761 | /** |
| 762 | * Hierarchical select form element #element_validate callback. |
| 763 | */ |
| 764 | function _hierarchical_select_validate(&$element, &$form_state) { |
| 765 | // If the dropbox is enabled and a dropbox limit is configured, check if |
| 766 | // this limit is not exceeded. |
| 767 | $config = _hierarchical_select_inherit_default_config($element['#config']); |
| 768 | if ($config['dropbox']['status']) { |
| 769 | if ($config['dropbox']['limit'] > 0) { // Zero as dropbox limit means no limit. |
| 770 | // TRICKY: #element_validate is not called upon the initial rendering |
| 771 | // (i.e. it is assumed that the default value is valid). However, |
| 772 | // Hierarchical Select's config can influence the validity (i.e. how |
| 773 | // many selections may be added to the dropbox). This means it's |
| 774 | // possible the user has actually selected too many items without being |
| 775 | // notified of this. |
| 776 | $lineage_count = count($element['#value']['dropbox']['hidden']['lineages_selections']); |
| 777 | if ($lineage_count > $config['dropbox']['limit']) { |
| 778 | // TRICKY: this should propagate the error down to the children, but |
| 779 | // this doesn't seem to happen, since for example the selects of the |
| 780 | // hierarchical select don't get the error class set. Further |
| 781 | // investigation needed. |
| 782 | form_error( |
| 783 | $element, |
| 784 | t("You've selected %lineage-count items, but you're only allowed to select %dropbox-limit items.", |
| 785 | array( |
| 786 | '%lineage-count' => $lineage_count, |
| 787 | '%dropbox-limit' => $config['dropbox']['limit'], |
| 788 | ) |
| 789 | ) |
| 790 | ); |
| 791 | _hierarchical_select_form_set_error_class($element); |
| 792 | } |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | // Set the proper return value. I.e. instead of returning all the values |
| 797 | // that are used for making the hierarchical_select form element type work, |
| 798 | // we pass a flat array of item ids. e.g. for the taxonomy module, this will |
| 799 | // be an array of term ids. If a single item is selected, this will not be |
| 800 | // an array. |
| 801 | // If the form item is disabled, set the default value as the return value, |
| 802 | // because otherwise nothing would be returned (disabled form items are not |
| 803 | // submitted, as described in the HTML standard). |
| 804 | if (isset($element['#disabled']) && $element['#disabled']) { |
| 805 | $element['#return_value'] = $element['#default_value']; |
| 806 | } |
| 807 | |
| 808 | $element['#value'] = $element['#return_value']; |
| 809 | form_set_value($element, $element['#value'], $form_state); |
| 810 | |
| 811 | // We have to check again for errors. This line is taken litterally from |
| 812 | // form.inc, so it works in an identical way. |
| 813 | if ($element['#required'] && (!count($element['#value']) || (is_string($element['#value']) && strlen(trim($element['#value'])) == 0))) { |
| 814 | form_error($element, t('!name field is required.', array('!name' => $element['#title']))); |
| 815 | _hierarchical_select_form_set_error_class($element); |
| 816 | } |
| 817 | } |
| 818 | |
| 819 | /** |
| 820 | * Hierarchical select form element #submit callback. |
| 821 | */ |
| 822 | function _hierarchical_select_submit($form, &$form_state) { |
| 823 | // Delete the stored form information when the form is submitted. |
| 824 | // [VIEWS] TRICKY: when using Views, which uses its own Forms API workflow |
| 825 | // instead of core's, this #submit callback is called even when there is |
| 826 | // nothing really there to submit. So to prevent our cache to be cleared, |
| 827 | // which would result in failing AHAH callbacks, we just prevent it from |
| 828 | // clearing the cache. This results in cache entries that aren't cleared |
| 829 | // immediately upon finishing use of the form, but that's acceptable in the |
| 830 | // end because the cache will be cleared anyway at some point. |
| 831 | if (isset($form_state['hs_form_build_id']) && !isset($form_state['view'])) { |
| 832 | cache_clear_all($form_state['hs_form_build_id'], 'cache'); |
| 833 | } |
| 834 | } |
| 835 | |
| 836 | |
| 837 | //---------------------------------------------------------------------------- |
| 838 | // Forms API #process callback: |
| 839 | // Calculation of hierarchical select and dropbox selection. |
| 840 | |
| 841 | /** |
| 842 | * Get the current (flat) selection of the hierarchical select. |
| 843 | * |
| 844 | * This selection is updatable by the user, because the values are retrieved |
| 845 | * from the selects in $element['hierarchical_select']['selects']. |
| 846 | * |
| 847 | * @param $element |
| 848 | * A hierarchical_select form element. |
| 849 | * @return |
| 850 | * An array (bag) containing the ids of the selected items in the |
| 851 | * hierarchical select. |
| 852 | */ |
| 853 | function _hierarchical_select_process_get_hs_selection($element) { |
| 854 | $hs_selection = array(); |
| 855 | $config = _hierarchical_select_inherit_default_config($element['#config']); |
| 856 | |
| 857 | if (!empty($element['#value']['hierarchical_select']['selects'])) { |
| 858 | if ($config['save_lineage']) { |
| 859 | foreach ($element['#value']['hierarchical_select']['selects'] as $key => $value) { |
| 860 | $hs_selection[] = $value; |
| 861 | } |
| 862 | } |
| 863 | else { |
| 864 | foreach ($element['#value']['hierarchical_select']['selects'] as $key => $value) { |
| 865 | $hs_selection[] = $value; |
| 866 | } |
| 867 | $hs_selection = _hierarchical_select_hierarchy_validate($hs_selection, $config['module'], $config['params']); |
| 868 | |
| 869 | // Get the last valid value. (Only the deepest item gets saved). Make |
| 870 | // sure $hs_selection is an array at all times. |
| 871 | $hs_selection = ($hs_selection != -1) ? array(end($hs_selection)) : array(); |
| 872 | } |
| 873 | } |
| 874 | |
| 875 | return $hs_selection; |
| 876 | } |
| 877 | |
| 878 | /** |
| 879 | * Get the current (flat) selection of the dropbox. |
| 880 | * |
| 881 | * This selection is not updatable by the user, because the values are |
| 882 | * retrieved from the hidden values in |
| 883 | * $element['dropbox']['hidden']['lineages_selections']. This selection can |
| 884 | * only be updated by the server, i.e. when the user clicks the "Add" button. |
| 885 | * But this selection can still be reduced in size if the user has marked |
| 886 | * dropbox entries (lineages) for removal. |
| 887 | * |
| 888 | * @param $element |
| 889 | * A hierarchical_select form element. |
| 890 | * @return |
| 891 | * An array (bag) containing the ids of the selected items in the |
| 892 | * dropbox. |
| 893 | */ |
| 894 | function _hierarchical_select_process_get_db_selection($element) { |
| 895 | $db_selection = array(); |
| 896 | |
| 897 | if (!empty($element['#value']['dropbox']['hidden']['lineages_selections'])) { |
| 898 | // This is only present in #value if at least one "Remove" checkbox was |
| 899 | // checked, so ensure that we're doing something valid. |
| 900 | $remove_from_db_selection = (!isset($element['#value']['dropbox']['visible']['lineages'])) ? array() : array_keys($element['#value']['dropbox']['visible']['lineages']); |
| 901 | |
| 902 | // Add all selections to the dropbox selection, except for the ones that |
| 903 | // are scheduled for removal. |
| 904 | foreach ($element['#value']['dropbox']['hidden']['lineages_selections'] as $x => $selection) { |
| 905 | if (!in_array($x, $remove_from_db_selection)) { |
| 906 | $db_selection = array_merge($db_selection, unserialize($selection)); |
| 907 | } |
| 908 | } |
| 909 | |
| 910 | // Ensure that the last item of each selection that was scheduled for |
| 911 | // removal is completely absent from the dropbox selection. |
| 912 | // In case of a tree with multiple parents, the same item can exist in |
| 913 | // different entries, and thus it would stay in the selection. When the |
| 914 | // server then reconstructs all lineages, the lineage we're removing, will |
| 915 | // also be reconstructed: it will seem as if the removing didn't work! |
| 916 | // This will not break removing dropbox entries for hierarchies without |
| 917 | // multiple parents, since items at the deepest level are always unique to |
| 918 | // that specific lineage. |
| 919 | // Easier explanation at http://drupal.org/node/221210#comment-733715. |
| 920 | foreach ($remove_from_db_selection as $key => $x) { |
| 921 | $item = end(unserialize($element['#value']['dropbox']['hidden']['lineages_selections'][$x])); |
| 922 | $position = array_search($item, $db_selection); |
| 923 | if ($position) { |
| 924 | unset($db_selection[$position]); |
| 925 | } |
| 926 | } |
| 927 | $db_selection = array_unique($db_selection); |
| 928 | } |
| 929 | |
| 930 | return $db_selection; |
| 931 | } |
| 932 | |
| 933 | /** |
| 934 | * Calculates the flat selections of both the hierarchical select and the |
| 935 | * dropbox. |
| 936 | * |
| 937 | * @param $element |
| 938 | * A hierarchical_select form element. |
| 939 | * @return |
| 940 | * An array of the following structure: |
| 941 | * array( |
| 942 | * $hierarchical_select_selection = array(), // Flat list of selected ids. |
| 943 | * $dropbox_selection = array(), |
| 944 | * ) |
| 945 | * with both of the subarrays flat lists of selected ids. The |
| 946 | * _hierarchical_select_hierarchy_generate() and |
| 947 | * _hierarchical_select_dropbox_generate() functions should be applied on |
| 948 | * these respective subarrays. |
| 949 | * |
| 950 | * @see _hierarchical_select_hierarchy_generate() |
| 951 | * @see _hierarchical_select_dropbox_generate() |
| 952 | */ |
| 953 | function _hierarchical_select_process_calculate_selections(&$element) { |
| 954 | $hs_selection = array(); // hierarchical select selection |
| 955 | $db_selection = array(); // dropbox selection |
| 956 | |
| 957 | $config = _hierarchical_select_inherit_default_config($element['#config']); |
| 958 | $dropbox = (bool) $config['dropbox']['status']; |
| 959 | |
| 960 | // When: |
| 961 | // - no data was POSTed |
| 962 | // - or #value is set directly and not by a Hierarchical Select POST (and |
| 963 | // therefor set either manually or by another module), |
| 964 | // then use the value of #default_value, or when available, of #value. |
| 965 | if (!isset($element['#post']) || !isset($element['#value']['hierarchical_select'])) { |
| 966 | $value = (isset($element['#value'])) ? $element['#value'] : $element['#default_value']; |
| 967 | $value = (is_array($value)) ? $value : array($value); |
| 968 | if ($dropbox) { |
| 969 | $db_selection = $value; |
| 970 | } |
| 971 | else { |
| 972 | $hs_selection = $value; |
| 973 | } |
| 974 | } |
| 975 | else { |
| 976 | $op = (isset($element['#post']['op'])) ? $element['#post']['op'] : NULL; |
| 977 | if ($dropbox && $op == t('Add')) { |
| 978 | $hs_selection = _hierarchical_select_process_get_hs_selection($element); |
| 979 | $db_selection = _hierarchical_select_process_get_db_selection($element); |
| 980 | |
| 981 | // Add $hs_selection to $db_selection (automatically filters to keep |
| 982 | // only the unique ones). |
| 983 | $db_selection = array_merge($db_selection, $hs_selection); |
| 984 | |
| 985 | // Only reset $hs_selection if the user has configured it that way. |
| 986 | if ($config['dropbox']['reset_hs']) { |
| 987 | $hs_selection = array(); |
| 988 | } |
| 989 | } |
| 990 | else if ($op == t('Create')) { |
| 991 | // This code handles both the creation of a new item in an existing |
| 992 | // level and the creation of an item that also creates a new level. |
| 993 | |
| 994 | // TODO: http://drupal.org/node/253868 |
| 995 | // TODO: http://drupal.org/node/253869 |
| 996 | |
| 997 | $label = trim($element['#value']['hierarchical_select']['create_new_item']['input']); |
| 998 | $selects = $element['#value']['hierarchical_select']['selects']; |
| 999 | $depth = count($selects); |
| 1000 | $parent = ($depth > 0) ? end($selects) : 0; |
| 1001 | |
| 1002 | // Disallow items with empty labels; allow the user again to create a |
| 1003 | // (proper) new item. |
| 1004 | if (empty($label)) { |
| 1005 | $element['#value']['hierarchical_select']['selects'][count($selects)] = 'create_new_item'; |
| 1006 | } |
| 1007 | // Ensure that this new item will not violate the max_levels and |
| 1008 | // allowed_levels settings. |
| 1009 | else if ( |
| 1010 | (count(module_invoke($config['module'], 'hierarchical_select_children', $parent, $config['params'])) |
| 1011 | || $config['editability']['max_levels'] == 0 |
| 1012 | || $depth < $config['editability']['max_levels'] |
| 1013 | ) |
| 1014 | && |
| 1015 | (_hierarchical_select_create_new_item_is_allowed($config, $depth)) |
| 1016 | ) { |
| 1017 | // Create the new item in the hierarchy and retrieve its value. |
| 1018 | $value = module_invoke($config['module'], 'hierarchical_select_create_item', check_plain($label), $parent, $config['params']); |
| 1019 | |
| 1020 | // Ensure the newly created item will be selected after rendering. |
| 1021 | if ($value) { |
| 1022 | // Pretend there was a select where the "create new item" section |
| 1023 | // was, and assign it the value of the item that was just created. |
| 1024 | $element['#value']['hierarchical_select']['selects'][count($selects)] = $value; |
| 1025 | } |
| 1026 | } |
| 1027 | |
| 1028 | $hs_selection = _hierarchical_select_process_get_hs_selection($element); |
| 1029 | if ($dropbox) { |
| 1030 | $db_selection = _hierarchical_select_process_get_db_selection($element); |
| 1031 | } |
| 1032 | } |
| 1033 | else { |
| 1034 | // This handles the cases of: |
| 1035 | // - $op == t('Update') |
| 1036 | // - $op == t('Cancel') (used when creating a new item or a new level) |
| 1037 | // - any other submit button, e.g. the "Preview" button |
| 1038 | $hs_selection = _hierarchical_select_process_get_hs_selection($element); |
| 1039 | if ($dropbox) { |
| 1040 | $db_selection = _hierarchical_select_process_get_db_selection($element); |
| 1041 | } |
| 1042 | } |
| 1043 | } |
| 1044 | |
| 1045 | // Prevent doubles in either array. |
| 1046 | $hs_selection = array_unique($hs_selection); |
| 1047 | $db_selection = array_unique($db_selection); |
| 1048 | |
| 1049 | return array($hs_selection, $db_selection); |
| 1050 | } |
| 1051 | |
| 1052 | |
| 1053 | //---------------------------------------------------------------------------- |
| 1054 | // Forms API #process callback: |
| 1055 | // Rendering (generation of FAPI code) of hierarchical select and dropbox. |
| 1056 | |
| 1057 | /** |
| 1058 | * Render the selects in the hierarchical select. |
| 1059 | * |
| 1060 | * @param $hsid |
| 1061 | * A hierarchical select id. |
| 1062 | * @param $hierarchy |
| 1063 | * A hierarchy object. |
| 1064 | * @return |
| 1065 | * A structured array for use in the Forms API. |
| 1066 | */ |
| 1067 | function _hierarchical_select_process_render_hs_selects($hsid, $hierarchy) { |
| 1068 | $form['#tree'] = TRUE; |
| 1069 | $form['#prefix'] = '<div class="selects">'; |
| 1070 | $form['#suffix'] = '</div>'; |
| 1071 | |
| 1072 | foreach ($hierarchy->lineage as $depth => $selected_item) { |
| 1073 | $form[$depth] = array( |
| 1074 | '#type' => 'select', |
| 1075 | '#options' => $hierarchy->levels[$depth], |
| 1076 | '#default_value' => $selected_item, |
| 1077 | // Use a #theme callback to prevent the select from being wrapped in a |
| 1078 | // div. This simplifies the CSS and JS code. Also sets a special class |
| 1079 | // on the level label option, if any, to make level label styles |
| 1080 | // possible. |
| 1081 | '#theme' => 'hierarchical_select_select', |
| 1082 | // Add child information. When a child has no children, its |
| 1083 | // corresponding "option" element will be marked as such. |
| 1084 | '#childinfo' => (isset($hierarchy->childinfo[$depth])) ? $hierarchy->childinfo[$depth] : NULL, |
| 1085 | ); |
| 1086 | } |
| 1087 | return $form; |
| 1088 | } |
| 1089 | |
| 1090 | /** |
| 1091 | * Render the hidden part of the dropbox. This part stores the lineages of all |
| 1092 | * selections in the dropbox. |
| 1093 | * |
| 1094 | * @param $hsid |
| 1095 | * A hierarchical select id. |
| 1096 | * @param $dropbox |
| 1097 | * A dropbox object. |
| 1098 | * @return |
| 1099 | * A structured array for use in the Forms API. |
| 1100 | */ |
| 1101 | function _hierarchical_select_process_render_db_hidden($hsid, $dropbox) { |
| 1102 | $element['#tree'] = TRUE; |
| 1103 | |
| 1104 | foreach ($dropbox->lineages_selections as $x => $lineage_selection) { |
| 1105 | $element['lineages_selections'][$x] = array('#type' => 'hidden', '#value' => serialize($lineage_selection)); |
| 1106 | } |
| 1107 | return $element; |
| 1108 | } |
| 1109 | |
| 1110 | /** |
| 1111 | * Render the visible part of the dropbox. |
| 1112 | * |
| 1113 | * @param $hsid |
| 1114 | * A hierarchical select id. |
| 1115 | * @param $dropbox |
| 1116 | * A dropbox object. |
| 1117 | * @return |
| 1118 | * A structured array for use in the Forms API. |
| 1119 | */ |
| 1120 | function _hierarchical_select_process_render_db_visible($hsid, $dropbox) { |
| 1121 | $element['#tree'] = TRUE; |
| 1122 | $element['#theme'] = 'hierarchical_select_dropbox_table'; |
| 1123 | |
| 1124 | |
| 1125 | // This information is necessary for the #theme callback. |
| 1126 | $element['title'] = array('#type' => 'value', '#value' => t($dropbox->title)); |
| 1127 | $element['separator'] = array('#type' => 'value', '#value' => '›'); |
| 1128 | $element['is_empty'] = array('#type' => 'value', '#value' => empty($dropbox->lineages)); |
| 1129 | |
| 1130 | |
| 1131 | if (!empty($dropbox->lineages)) { |
| 1132 | foreach ($dropbox->lineages as $x => $lineage) { |
| 1133 | |
| 1134 | // Store position information for the lineage. This will be used in the |
| 1135 | // #theme callback. |
| 1136 | $element['lineages'][$x] = array( |
| 1137 | '#zebra' => (($x + 1) % 2 == 0) ? 'even' : 'odd', |
| 1138 | '#first' => ($x == 0) ? 'first' : '', |
| 1139 | '#last' => ($x == count($dropbox->lineages) - 1) ? 'last' : '', |
| 1140 | ); |
| 1141 | |
| 1142 | // Create a 'markup' element for each item in the lineage. |
| 1143 | foreach ($lineage as $depth => $item) { |
| 1144 | // The item is selected when save_lineage is enabled (i.e. each item |
| 1145 | // will be selected), or when the item is the last item in the current |
| 1146 | // lineage. |
| 1147 | $is_selected = $dropbox->save_lineage || ($depth == count($lineage) - 1); |
| 1148 | |
| 1149 | $element['lineages'][$x][$depth] = array( |
| 1150 | '#value' => $item['label'], |
| 1151 | '#prefix' => '<span class="dropbox-item'. (($is_selected) ? ' dropbox-selected-item' : '') .'">', |
| 1152 | '#suffix' => '</span>', |
| 1153 | ); |
| 1154 | } |
| 1155 | |
| 1156 | // Finally, create a "Remove" checkbox for the lineage. |
| 1157 | $element['lineages'][$x]['remove'] = array( |
| 1158 | '#type' => 'checkbox', |
| 1159 | '#title' => t('Remove'), |
| 1160 | ); |