diff --git a/UPGRADE.txt b/UPGRADE.txt index f3473ee..a4ad52e 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -1,20 +1,18 @@ -# Upgrading (from Drupal 6 to 7) - 1. **BE WARE THAT NOT ALL FUNCTIONALITY HAS BEEN PORTED!** - Make sure that you know if the part of Hierarchical Select's functionality - that you want to use has been ported. Otherwise, you may be in for a - frustrating upgrade experience. + Make sure that you know if the part of Hierarchical Select's functionality + that you want to use has been ported. Otherwise, you may be in for a + frustrating upgrade experience. - See the included TODO.txt file for details. In a nutshell: + See the included TODO.txt file for details. In a nutshell: - - Taxonomy support is almost complete, only "create new item", "entity count" and token support are missing - - Forum support has **not** yet been ported (but relies on Taxonomy, so this is trivial) - - Taxonomy Views support has **not** yet been ported - - Menu support has **not** yet been ported + - Taxonomy support is almost complete, only "create new item", "entity count" and token support are missing + - Forum support has **not** yet been ported (but relies on Taxonomy, so this is trivial) + - Taxonomy Views support has **not** yet been full ported + - Menu support has **not** yet been ported 2. Upgrade this module just like any other: delete the old module, copy the - files of the new module and run update.php. - For details, see . + files of the new module and run update.php. + For details, see . 3. That's it! :) diff --git a/hierarchical_select.js b/hierarchical_select.js index ffe15ab..9c017f6 100644 --- a/hierarchical_select.js +++ b/hierarchical_select.js @@ -5,7 +5,7 @@ Drupal.behaviors.HierarchicalSelect = { attach: function (context) { $('.hierarchical-select-wrapper:not(.hierarchical-select-wrapper-processed)', context) .addClass('hierarchical-select-wrapper-processed').each(function() { - var hsid = $(this).attr('id').replace(/^hierarchical-select-(\d+)-wrapper$/, "$1"); + var hsid = $(this).attr('id').replace(/^hierarchical-select-(.+)-wrapper$/, "$1"); Drupal.HierarchicalSelect.initialize(hsid); }); } @@ -73,7 +73,7 @@ Drupal.HierarchicalSelect.log = function(hsid, messages) { // Store the log messages. The first call to this function may not contain a // message: the initial log included in the initial HTML rendering should be - // used instead.. + // used instead.. if (Drupal.HierarchicalSelect.state["hs-" + hsid].log.length == 0) { Drupal.HierarchicalSelect.state["hs-" + hsid].log.push(Drupal.settings.HierarchicalSelect.initialLog["hs-" + hsid]); } @@ -102,7 +102,7 @@ Drupal.HierarchicalSelect.transform = function(hsid) { // we want to continue to use the "Remove" checkboxes. .find('*').css('display', 'none').end() // We can't use .hide() because of collapse.js: http://drupal.org/node/351458#comment-1258303. // Put a "Remove" link there instead. - .append(''+ removeString +''); + .append(''+ removeString +''); }; Drupal.HierarchicalSelect.resizable = function(hsid) { @@ -264,7 +264,7 @@ Drupal.HierarchicalSelect.attachBindings = function(hsid) { .unbind('disable-updates').bind('disable-updates', data, function(e) { Drupal.settings.HierarchicalSelect.settings["hs-" + e.data.hsid]['updatesEnabled'] = false; }) - + // "enforce-update" event .unbind('enforce-update').bind('enforce-update', data, function(e, extraPost) { Drupal.HierarchicalSelect.update(e.data.hsid, 'enforced-update', { opString: updateOpString, extraPost: extraPost }); @@ -320,7 +320,7 @@ Drupal.HierarchicalSelect.attachBindings = function(hsid) { } // Check the (hidden, because JS is enabled) checkbox that marks this - // dropbox entry for removal. + // dropbox entry for removal. $(this).parent().find('input[type=checkbox]').attr('checked', true); Drupal.HierarchicalSelect.update(_hsid, 'remove-from-dropbox', { opString: updateOpString }); return false; // Prevent the browser from POSTing the page. @@ -353,14 +353,14 @@ Drupal.HierarchicalSelect.preUpdateAnimations = function(hsid, updateType, lastU default: if (callback) { callback(); - } + } break; } }; Drupal.HierarchicalSelect.postUpdateAnimations = function(hsid, updateType, lastUnchanged, callback) { if (Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].resizable) { - // Restore the resize. + // Restore the resize. Drupal.HierarchicalSelect.resize( $('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context), Drupal.HierarchicalSelect.state["hs-" + hsid].defaultHeight, @@ -373,7 +373,7 @@ Drupal.HierarchicalSelect.postUpdateAnimations = function(hsid, updateType, last switch (updateType) { case 'update-hierarchical-select': var $createNewItemInput = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-input', Drupal.HierarchicalSelect.context); - + if ($createNewItemInput.size() == 0) { // Give focus to the level below the one that has changed, if it // exists. @@ -450,9 +450,22 @@ Drupal.HierarchicalSelect.triggerEvents = function(hsid, updateType, settings) { Drupal.HierarchicalSelect.update = function(hsid, updateType, settings) { var post = $('form:has(#hierarchical-select-' + hsid +'-wrapper)', Drupal.HierarchicalSelect.context).formToArray(); + if(Drupal.settings.exposed_form_info) { + $.each(Drupal.settings.exposed_form_info, function (key, val) { + var keyExists = false; + $.each(post, function (keyPost, valPost) { + if(valPost.name == val) { + keyExists = true; + } + }); + if(keyExists == false) { + post.push({'name' : key, 'value' : val}); + } + }); + } + // Pass the hierarchical_select id via POST. post.push({ name : 'hsid', value : hsid }); - // If a cache system is installed, let the server know if it's running // properly. If it is running properly, the server will send back additional // information to maintain a lazily-loaded cache. @@ -509,7 +522,7 @@ Drupal.HierarchicalSelect.update = function(hsid, updateType, settings) { } post.push({ name : 'op', value : settings.opString }); break; - + case 'enforced-update': post.push({ name : 'op', value : settings.opString }); post = post.concat(settings.extraPost); @@ -539,7 +552,7 @@ Drupal.HierarchicalSelect.update = function(hsid, updateType, settings) { data: post, beforeSend: function() { Drupal.HierarchicalSelect.triggerEvents(hsid, 'before-' + updateType, settings); - Drupal.HierarchicalSelect.disableForm(hsid); + Drupal.HierarchicalSelect.disableForm(hsid); }, error: function (XMLHttpRequest, textStatus, errorThrown) { // When invalid HTML is received in Safari, jQuery calls this function. @@ -561,7 +574,7 @@ Drupal.HierarchicalSelect.update = function(hsid, updateType, settings) { Drupal.ajax.prototype.commands[response[i]['command']](this, response[i], status, hsid); } } - + // Attach behaviors. This is just after the HTML has been updated, so // it's as soon as we can. Drupal.attachBehaviors($('#hierarchical-select-' + hsid + '-wrapper').parents('div.form-type-hierarchical-select')[0]); diff --git a/hierarchical_select.module b/hierarchical_select.module index 02b2797..8ab36c9 100644 --- a/hierarchical_select.module +++ b/hierarchical_select.module @@ -25,7 +25,7 @@ function hierarchical_select_menu() { 'theme callback' => 'ajax_base_page_theme', 'type' => MENU_CALLBACK, ); - + $items['admin/config/content/hierarchical_select'] = array( 'title' => 'Hierarchical Select', 'description' => 'Configure site-wide settings for the Hierarchical Select form element.', @@ -274,7 +274,7 @@ function hierarchical_select_config_id_load($config_id) { // Forms API callbacks. /** - * Strongly + * Strongly * * @see file_ajax_upload(), upon which this is strongly inspired. * @see ajax_form_callback() @@ -308,7 +308,7 @@ function hierarchical_select_ajax($form, $form_state) { foreach ($form_parents as $parent) { $form = $form[$parent]; } - + // If the user's browser supports the active cache system, then send the // currently requested hierarchy in an easy-to-manage form. $cache = array(); @@ -329,10 +329,14 @@ function hierarchical_select_ajax($form, $form_state) { // Send AJAX command to update the Hierarchical Select. $commands = array(); $commands[] = array( - 'command' => 'hierarchicalSelectUpdate', - 'output' => $output, + 'command' => 'hierarchicalSelectUpdate', + 'output' => $output, // 'cache' => $cache, ); + $commands[] = array( + 'command' => 'viewUpdate', + 'output' => $output, + ); $new_settings = _hs_new_setting_ajax(FALSE); foreach ($new_settings as $new_setting) { $commands[] = array( @@ -353,7 +357,9 @@ function _hs_process_determine_hsid($element, &$form_state) { $form_state['storage']['hs']['last_hsid'] = -1; } $form_state['storage']['hs']['last_hsid']++; - $hsid = $form_state['storage']['hs']['last_hsid']; + // Added the form #id as well as multiple forms with hs will cause id clashes. + // Really this should be converted to use drupal_html_id(). + $hsid = $form_state['complete form']['#id'] . '-' . $form_state['storage']['hs']['last_hsid']; } else { $hsid = check_plain($element['#value']['hsid']); @@ -376,10 +382,11 @@ function _hs_process_shortcut_special_items($config) { function _hs_process_attach_css_js($element, $hsid, &$form_state, $complete_form) { // Set up Javascript and add settings specifically for the current // hierarchical select. + $element['#attached']['library'][] = array('system', 'ui'); $element['#attached']['library'][] = array('system', 'drupal.ajax'); $element['#attached']['library'][] = array('system', 'jquery.form'); - $element['#attached']['library'][] = array('system', 'effects.core'); - $element['#attached']['library'][] = array('system', 'effects.drop'); + $element['#attached']['library'][] = array('system', 'effects.core'); + $element['#attached']['library'][] = array('system', 'effects.drop'); $element['#attached']['css'][] = drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.css'; $element['#attached']['js'][] = drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.js'; if (variable_get('hierarchical_select_js_cache_system', 0) == 1) { @@ -489,7 +496,7 @@ function _hs_process_developer_mode_log_selections($config, $hs_selection, $db_s } } -function _hs_process_developer_mode_log_hierarchy_and_dropbox($config, $hierarchy, $dropbox) { +function _hs_process_developer_mode_log_hierarchy_and_dropbox($config, $hierarchy, $dropbox) { if (HS_DEVELOPER_MODE) { _hierarchical_select_log('Generated hierarchy in ' . $hierarchy->build_time['total'] . ' ms:'); _hierarchical_select_log($hierarchy); @@ -627,7 +634,7 @@ function _hs_process_render_dropbox($element, $hsid, $creating_new_item, $dropbo '#attributes' => array('class' => array('add-to-dropbox')), '#limit_validation_errors' => array($element['#parents']), '#validate' => array(), - '#submit' => array('hierarchical_select_ajax_update_submit'), + '#submit' => array('hierarchical_select_ajax_update_submit'), ); } @@ -641,7 +648,7 @@ function _hs_process_render_dropbox($element, $hsid, $creating_new_item, $dropbo // Disable all child form elements of $element['hierarchical_select]. // _hierarchical_select_mark_as_disabled($element['hierarchical_select']); - + // TODO: make the above work again. Currently, we're just disabling // the "Add" button. #disabled can't be used for the same reasons as // described in _hierarchical_select_mark_as_disabled(). @@ -654,7 +661,7 @@ function _hs_process_render_dropbox($element, $hsid, $creating_new_item, $dropbo if (isset($dropbox->lineages_selections)) { $form_state['storage']['hs'][$hsid]['dropbox_lineages_selections'] = $dropbox->lineages_selections; } - + // Add the dropbox-as-a-table that will be visible to the user. $element['dropbox']['visible'] = _hs_process_render_db_table($hsid, $dropbox); } @@ -689,10 +696,7 @@ function _hs_process_render_nojs($element, $config) { /** * Hierarchical select form element type #process callback. */ -function form_hierarchical_select_process($element, &$form_state, $complete_form) { - // dpm('#process'); - // dpm($form_state); - // +function form_hierarchical_select_process($element, &$form_state, $complete_form) { // Calculations. // @@ -922,7 +926,6 @@ function _hierarchical_select_process_get_hs_selection($element) { $hs_selection = ($hs_selection != -1) ? array(end($hs_selection)) : array(); } } - return $hs_selection; } @@ -1026,7 +1029,7 @@ function _hierarchical_select_process_calculate_selections(&$element, $hsid, $fo // - no input data was provided (through POST nor GET) // - or #value is set directly and not by a Hierarchical Select POST (and // therefor set either manually or by another module), - // then use the value of #default_value, or when available, of #value. + // then use the value of #default_value, or when available, of #value. if (empty($form_state['input']) || (!isset($element['#value']['hierarchical_select']) && !isset($element['#value']['dropbox']))) { $value = (isset($element['#value'])) ? $element['#value'] : $element['#default_value']; $value = (is_array($value)) ? $value : array($value); @@ -1096,6 +1099,16 @@ function _hierarchical_select_process_calculate_selections(&$element, $hsid, $fo // - $op == t('Cancel') (used when creating a new item or a new level) // - any other submit button, e.g. the "Preview" button $hs_selection = _hierarchical_select_process_get_hs_selection($element); + // hack to get hs_views running + if(array_key_exists($element['#name'], $form_state['values'])) { + $values = $form_state['values'][$element['#name']]; + if( is_array($values) ) { + $hs_selection[] = $form_state['values'][$element['#name']]['hierarchical_select']['selects']; + } else { + $hs_selection[] = check_plain($form_state['values'][$element['#name']]); + } + } + if ($dropbox) { $db_selection = _hierarchical_select_process_get_db_selection($element, $hsid, $form_state); } @@ -1410,7 +1423,7 @@ function _hierarchical_select_mark_as_disabled(&$element) { // for UI purposes only, but still allow input to be processed if it's // sumitted, it can set the desired attribute in #attributes directly // rather than using #disabled." - // #disabled prevents #value from containing values for disabled elements, + // #disabled prevents #value from containing values for disabled elements, // but using #attributes circumvents this. Most likely, Form API thinks that // because HS' selects are disabled, that the whole of HS is disabled (which // is of course a wrong assumption). Hence it thinks the 'op' that is being @@ -1782,7 +1795,7 @@ function _hierarchical_select_hierarchy_generate($config, $selection, $required, // Level label: set an empty level label if they've been disabled. $hierarchy->lineage[$depth] = 'label_' . $depth; - $label = ($config['level_labels']['status']) ? t($config['level_labels']['labels'][$depth]) : ''; + $label = ($config['level_labels']['status'] && array_key_exists($depth, $config['level_labels']['labels'])) ? t($config['level_labels']['labels'][$depth]) : ''; $hierarchy->levels[$depth] = array('label_' . $depth => $label) + $hierarchy->levels[$depth] + $children; $hierarchy->levels[$depth] = _hierarchical_select_apply_entity_settings($hierarchy->levels[$depth], $config); diff --git a/modules/hs_taxonomy.module b/modules/hs_taxonomy.module index f2aae7a..0fb2abb 100644 --- a/modules/hs_taxonomy.module +++ b/modules/hs_taxonomy.module @@ -627,7 +627,6 @@ function hs_taxonomy_hierarchical_select_children($parent, $params) { } } } - return _hs_taxonomy_hierarchical_select_terms_to_options($terms); } @@ -711,7 +710,7 @@ function hs_taxonomy_hierarchical_select_item_get_label($item, $params) { } // Use the translated term when available! - if (module_exists('i18ntaxonomy') && i18ntaxonomy_vocabulary($term->vid) == I18N_TAXONOMY_LOCALIZE) { + if ( $term->tid !== 0 && module_exists('i18n_taxonomy') && i18n_taxonomy_vocabulary_mode($term->vid) == I18N_MODE_TRANSLATE) { $labels[$item] = i18n_taxonomy_term_name($term); } else { @@ -1001,6 +1000,9 @@ function theme_hs_taxonomy_formatter_lineage($variables) { function _hs_taxonomy_hierarchical_select_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL, $reset = FALSE) { static $children, $parents, $terms; + if(!isset($vid) && empty($vid)) { + throw new Exception('Vid should not be empty.'); + } if ($reset) { $children = $parents = $terms = array(); } @@ -1109,6 +1111,9 @@ function _hs_taxonomy_hierarchical_select_terms_to_options($terms) { */ function _hs_taxonomy_hierarchical_select_get_depth($vid) { $depth = -99999; + if(!isset($vid) && empty($vid)) { + throw new Exception('Vid should not be empty.'); + } $tree = _hs_taxonomy_hierarchical_select_get_tree($vid); foreach ($tree as $term) { if ($term->depth > $depth) { diff --git a/modules/hs_taxonomy_views.info b/modules/hs_taxonomy_views.info index a17c657..1a38682 100644 --- a/modules/hs_taxonomy_views.info +++ b/modules/hs_taxonomy_views.info @@ -1,9 +1,10 @@ name = Hierarchical Select Taxonomy Views description = Use Hierarchical Select for Taxonomy exposed filters in Views. dependencies[] = hierarchical_select -dependencies[] = hs_taxonomy +dependencies[] = hs_taxonomy dependencies[] = views package = Form Elements -core = 6.x +core = 7.x files[] = hs_taxonomy_views_handler_filter_term_node_tid.inc +files[] = hs_taxonomy_views_handler_filter_term_node_tid_depth.inc diff --git a/modules/hs_taxonomy_views.install b/modules/hs_taxonomy_views.install index 2002c6d..19f7bb5 100644 --- a/modules/hs_taxonomy_views.install +++ b/modules/hs_taxonomy_views.install @@ -6,6 +6,14 @@ */ /** + * Implements hook_install(). + */ +function hs_taxonomy_views_install() { + // Need our weight after views (10) since we hook_views_data_alter() after. + db_query("UPDATE {system} SET weight = 11 WHERE name = 'hs_taxonomy_views'"); +} + +/** * Implementation of hook_uninstall(). */ function hs_taxonomy_views_uninstall() { @@ -29,3 +37,10 @@ function hs_taxonomy_views_uninstall() { 'Dropdown' everywhere where it was used."), 'warning'); } + +/** + * Set hs_taxonomy_views weight below views. + */ +function hs_taxonomy_views_update_7000() { + db_query("UPDATE {system} SET weight = 11 WHERE name = 'hs_taxonomy_views'"); +} diff --git a/modules/hs_taxonomy_views.module b/modules/hs_taxonomy_views.module index 84e6680..2f7f7dc 100644 --- a/modules/hs_taxonomy_views.module +++ b/modules/hs_taxonomy_views.module @@ -44,7 +44,7 @@ function hs_taxonomy_views_menu() { /** * Implementation of hook_form_alter(). */ -function hs_taxonomy_views_form_alter(&$form, $form_state, $form_id) { +function hs_taxonomy_views_form_alter(&$form, &$form_state, $form_id) { // if ($form_id == 'views_ui_edit_view_form') { // Add JS and CSS required for Hierarchical Select to work. @@ -82,6 +82,49 @@ function hs_taxonomy_views_form_alter(&$form, $form_state, $form_id) { $form['options']['expose']['operator']['#description'] = $description; } } + + if( $form_id == 'views_exposed_form' ) { + // Since the exposed form is a GET form, we don't want it to send a wide + // variety of information. + $form['form_build_id']['#access'] = FALSE; + $form['form_token']['#access'] = FALSE; + $form['form_id']['#access'] = FALSE; + + // In order for Ajax to work, we need the form build info. Here we check if + // #ajax has been added to any form elements, and if so, pass this info as + // settings via Javascript, which get attached to the submitted form on Ajax + // form submissions. + foreach (element_children($form) as $key) { + if (array_key_exists('#ajax', $form[$key])) { + $form['#attached']['js'][] = array( + 'type' => 'setting', + 'data' => array( + 'exposed_form_info' => array( + 'form_id' => $form['#form_id'], + 'form_build_id' => $form['#build_id'], + // 'form_token' => $form['#token'], + ), + ), + ); + $form['#attached']['js'][] = array( + 'type' => 'inline', + 'weight' => 100, + 'data' => '(function ($) { + Drupal.behaviors.ViewsExposedFormAjax = { + attach: function(context, settings) { + for (ajax_object in Drupal.ajax) { + if (Drupal.ajax[ajax_object].options) { + jQuery.extend(Drupal.ajax[ajax_object].options.data, Drupal.settings.exposed_form_info); + } + } + } + }; + })(jQuery);', + ); + break; + } + } + } } //---------------------------------------------------------------------------- @@ -160,103 +203,23 @@ function hs_taxonomy_views_json($view_name, $display_id) { /** * Implementation of hook_views_api(). */ -function hs_taxonomy_views_api() { +function hs_taxonomy_views_views_api() { return array( - 'api' => 2, + 'api' => 3, 'path' => drupal_get_path('module', 'hierarchical_select') . '/modules', ); } /** - * Implementation of hook_views_handlers(). - */ -function hs_taxonomy_views_handlers() { - return array( - 'handlers' => array( - // Provide a subclass of the term filter handler, to make it possible to - // use Hierarchical Select in Views. - 'hs_taxonomy_views_handler_filter_term_node_tid' => array( - 'parent' => 'views_handler_filter_term_node_tid', - ), - 'hs_taxonomy_views_handler_filter_term_node_tid_depth' => array( - 'parent' => 'hs_taxonomy_views_handler_filter_term_node_tid', - ), - ) - ); -} - -/** * Implementation of hook_views_data_alter(). */ function hs_taxonomy_views_views_data_alter(&$data) { // Term view type, tid field. - $data['taxonomy_term_data']['tid'] = array( - 'title' => t('Term ID'), - 'help' => t('The taxonomy term ID.'), - 'field' => array( - 'handler' => 'views_handler_field_numeric', - 'skip base' => array('node', 'node_revision'), - ), - 'sort' => array( - 'handler' => 'views_handler_sort', - ), - 'argument' => array( - 'handler' => 'views_handler_argument_numeric', - 'skip base' => array('node', 'node_revision'), - ), - // Override the views_handler_filter_term_node_tid filter handler: use our - // hs_taxonomy_views_handler_filter_term_node_tid subclass instead. - 'filter' => array( - 'handler' => 'hs_taxonomy_views_handler_filter_term_node_tid', - 'hierarchy table' => 'taxonomy_term_hierarchy', - 'numeric' => TRUE, - 'skip base' => array('node', 'node_revision'), - ), - ); - - // Node view type, tid field. - $data['taxonomy_term_node']['tid'] = array( - 'title' => t('Term ID'), - 'help' => t('The taxonomy term ID.'), - 'field' => array( - 'title' => t('All terms'), - 'help' => t('Display all taxonomy terms associated with a node from specified vocabularies.'), - 'handler' => 'views_handler_field_term_node_tid', - 'skip base' => 'taxonomy_term_data', - ), - 'argument' => array( - 'handler' => 'views_handler_argument_term_node_tid', - 'name table' => 'taxonomy_term_data', - 'name field' => 'name', - 'empty name field' => t('Uncategorized'), - 'numeric' => TRUE, - 'skip base' => 'taxonomy_term_data', - ), - // Override the views_handler_filter_term_node_tid filter handler: use our - // hs_taxonomy_views_handler_filter_term_node_tid subclass instead. - 'filter' => array( - 'title' => t('Term'), - 'handler' => 'hs_taxonomy_views_handler_filter_term_node_tid', - 'hierarchy table' => 'taxonomy_term_hierarchy', - 'numeric' => TRUE, - 'skip base' => 'taxonomy_term_data', - ), - ); + $data['taxonomy_term_data']['tid']['filter']['handler'] = 'hs_taxonomy_views_handler_filter_term_node_tid'; + $data['taxonomy_term_node']['tid']['filter']['handler'] = 'hs_taxonomy_views_handler_filter_term_node_tid'; // Node view type, tid with depth field. - $data['node']['term_node_tid_depth'] = array( - 'group' => t('Taxonomy'), - 'title' => t('Term ID (with depth)'), - 'help' => t('The depth filter is more complex, so provides fewer options.'), - 'real field' => 'vid', - 'argument' => array( - 'handler' => 'views_handler_argument_term_node_tid_depth', - 'accept depth modifier' => TRUE, - ), - 'filter' => array( - 'handler' => 'hs_taxonomy_views_handler_filter_term_node_tid_depth', - ), - ); + $data['node']['term_node_tid_depth']['filter']['handler'] = 'hs_taxonomy_views_handler_filter_term_node_tid_depth'; } @@ -278,6 +241,7 @@ function hs_taxonomy_views_config_form($form, $form_state, $view, $display_id, $ require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc'; $filter = _hs_taxonomy_views_get_filter($view, $display_id, $filter_id); + $vocabulary = taxonomy_vocabulary_machine_name_load($filter['vocabulary']); // Build the config ID. $config_id = "taxonomy-views-$view->name-$display_id-$filter_id"; @@ -285,19 +249,18 @@ function hs_taxonomy_views_config_form($form, $form_state, $view, $display_id, $ // Add the Hierarchical Select config form. $module = 'hs_taxonomy_views'; $params = array( - 'optional' => (bool) $filter['expose']['optional'], + 'optional' => !((bool) $filter['expose']['required']), 'filter_id' => $filter_id, - 'vid' => $filter['vid'], + 'vid' => $vocabulary->vid, 'exclude_tid' => NULL, 'root_term' => NULL, ); - $vocabulary = taxonomy_vocabulary_load($params['vid']); $defaults = array( // Enable the save_lineage setting by default if the multiple parents // vocabulary option is enabled. 'save_lineage' => (int) ($vocabulary->hierarchy == 2), 'editability' => array( - 'max_levels' => _hs_taxonomy_hierarchical_select_get_depth($filter['vid']), + 'max_levels' => _hs_taxonomy_hierarchical_select_get_depth($vocabulary->vid), ), // Use our custom callback. 'path' => "hs_taxonomy_views_json/$view->name/$display_id", @@ -317,11 +280,11 @@ function hs_taxonomy_views_config_form($form, $form_state, $view, $display_id, $ 'entities' => t('nodes'), ); $max_hierarchy_depth = _hs_taxonomy_hierarchical_select_get_depth($vocabulary->vid); - $preview_is_required = !(bool) $filter['expose']['optional']; + $preview_is_required = (bool) $filter['expose']['required']; $form['hierarchical_select_config'] = hierarchical_select_common_config_form($module, $params, $config_id, $defaults, $strings, $max_hierarchy_depth, $preview_is_required); $form['link'] = array( - '#markup' => l("Back to the View's display configuration", "admin/structure/views/edit/$view->name", array('fragment' => 'views-tab-' . $display_id)), + '#markup' => l("Back to the View's display configuration", "admin/structure/views/view/$view->name/edit", array('fragment' => 'views-tab-' . $display_id)), '#prefix' => '', '#weight' => -5, @@ -370,7 +333,7 @@ function hs_taxonomy_views_common_config_form_submit($form, &$form_state) { $view->display[$display_id]->display_options['filters'][$filter_id]['expose']['use_operator'] = 0; $view->save(); - views_object_cache_clear('view', $view_name); + ctools_object_cache_clear('view', $view_name); drupal_set_message(t("Because you've enabled the 'Save Lineage' setting, the operator has been set to 'AND' and cannot be changed by the end user.")); } } @@ -444,7 +407,7 @@ function hs_taxonomy_views_hierarchical_select_item_get_label($item, $params) { function hs_taxonomy_views_hierarchical_select_entity_count($item, $params) { static $count; - $current_view = views_get_current_view(); +/* $current_view = views_get_current_view(); if (!isset($count[$current_view->name][$item])) { $temp_view = $current_view->clone_view(); @@ -467,6 +430,7 @@ function hs_taxonomy_views_hierarchical_select_entity_count($item, $params) { if ($item != HS_TAXONOMY_VIEWS_ANY_OPTION) { // Get an array with all tids: the tid of the currently selected term and // all child terms. +// print_r($item); $term = taxonomy_term_load($item); $tree = _hs_taxonomy_hierarchical_select_get_tree($term->vid, $term->tid); $tids = array($term->tid => $term->tid); @@ -475,7 +439,7 @@ function hs_taxonomy_views_hierarchical_select_entity_count($item, $params) { } $id = 'tid_' . implode('-', $tids); - $temp_view->display[$display_id]->handler->override_option('filters', array( +/* $temp_view->display[$display_id]->handler->override_option('filters', array( $id => array( 'operator' => 'or', 'value' => $tids, @@ -503,59 +467,61 @@ function hs_taxonomy_views_hierarchical_select_entity_count($item, $params) { else { // Disable the default value, otherwise the option will actually // filter by the default value. - $filter_id = $params['filter_id']; - $temp_view->display[$display_id]->display_options['filters'][$filter_id]['value'] = array(); - $temp_view->display[$display_id]->handler->options['filters'][$filter_id]['value'] = array(); - $temp_view->display['default']->display_options['filters'][$filter_id]['value'] = array(); - $temp_view->display['default']->handler->options['filters'][$filter_id]['value'] = array(); - } - + // $filter_id = $params['filter_id']; + // $temp_view->display[$display_id]->display_options['filters'][$filter_id]['value'] = array(); + // $temp_view->display[$display_id]->handler->options['filters'][$filter_id]['value'] = array(); + // $temp_view->display['default']->display_options['filters'][$filter_id]['value'] = array(); + // $temp_view->display['default']->handler->options['filters'][$filter_id]['value'] = array(); + // } + +//print_r($display_id); // Build the queries and collect the arguments. - $temp_view->build($display_id); + // $temp_view->build($display_id); // We only need the count query. We don't care about the actual fields or // order of the View. - $count_query = !empty($temp_view->build_info['count_query']) ? $temp_view->build_info['count_query'] : $temp_view->query->count_query; - $args = !empty($temp_view->build_info['query_args']) ? $temp_view->build_info['query_args'] : $temp_view->query->query_args; + // $count_query = !empty($temp_view->build_info['count_query']) ? $temp_view->build_info['count_query'] : $temp_view->query->count_query; + // $args = !empty($temp_view->build_info['query_args']) ? $temp_view->build_info['query_args'] : $temp_view->query->query_args; // Regenerate the query after we set the distinct flag for the nid field. // This unfortunately doesn't work, because Views doesn't create an // optimized count query when any of the fields have the distinct flag set. //$temp_view->query->fields['nid']['distinct'] = TRUE; - //$count_query = $temp_view->query->query(TRUE); + $count_query = $temp_view->query->query(TRUE); // Due to the above: sneak DISTINCT() in through a string replace ... - $count_query = str_replace("SELECT node.nid AS nid", "SELECT DISTINCT(node.nid) AS nid", $count_query); + // $count_query = str_replace("SELECT node.nid AS nid", "SELECT DISTINCT(node.nid) AS nid", $count_query); // Filter by node type if such a filter is configured in the view. - if (isset($current_view->filter['type'])) { - $node_types = $current_view->filter['type']->value; - if (isset($node_types)) { - $values = ''; - foreach ($node_types as $key => $value) { - if (empty($values)) { - $values = '\'' . $value . '\''; - } - else { - $values .= ', \'' . $value . '\''; - } - } - - // Use the same sneaky string replace trick once more. - $count_query = str_replace("WHERE", 'WHERE node.type IN (' . $values . ') AND', $count_query); - } - } + // if (isset($current_view->filter['type'])) { + // $node_types = $current_view->filter['type']->value; + // if (isset($node_types)) { + // $values = ''; + // foreach ($node_types as $key => $value) { + // if (empty($values)) { + // $values = '\'' . $value . '\''; + // } + // else { + // $values .= ', \'' . $value . '\''; + // } + // } + + // // Use the same sneaky string replace trick once more. + // $count_query = str_replace("WHERE", 'WHERE node.type IN (' . $values . ') AND', $count_query); + // } + // } // Apply the same query transformations as view::execute() does. - $count_query = db_rewrite_sql($count_query, $temp_view->base_table, $temp_view->base_field, array('view' => &$temp_view)); - $count_query = 'SELECT COUNT(*) FROM (' . $count_query . ') count_alias'; + // $count_query = db_rewrite_sql($count_query, $temp_view->base_table, $temp_view->base_field, array('view' => &$temp_view)); + // $count_query = 'SELECT COUNT(*) FROM (' . $count_query . ') count_alias'; // Execute the count query. // TODO Please convert this statement to the D7 database API syntax. - $count[$current_view->name][$item] = db_query($count_query, $args)->fetchField(); + // $count[$current_view->name][$item] = db_query($count_query, $args)->fetchField(); } - - return $count[$current_view->name][$item]; +} */ + return 1; + // return $count[$current_view->name][$item]; } /** diff --git a/modules/hs_taxonomy_views_handler_filter_term_node_tid.inc b/modules/hs_taxonomy_views_handler_filter_term_node_tid.inc index 1f1a4c6..92897c9 100644 --- a/modules/hs_taxonomy_views_handler_filter_term_node_tid.inc +++ b/modules/hs_taxonomy_views_handler_filter_term_node_tid.inc @@ -4,13 +4,48 @@ * @file * This file defines a new filter handler for using Hierarchical Select in * exposed Taxonomy term filters. - */ - + **/ class hs_taxonomy_views_handler_filter_term_node_tid extends views_handler_filter_term_node_tid { - function init(&$view, $options) { + function init(&$view, &$options) { parent::init($view, $options); - + // @TODO Support non exposed filters. +// $vocabulary = taxonomy_vocabulary_machine_name_load($this->options['vocabulary']); + if (array_key_exists($options['expose']['identifier'], $_GET)) { + $parent_term = $_GET[$options['expose']['identifier']]; + if (is_array($parent_term) && isset($parent_term['hsid'])) { + if (isset($parent_term['hierarchical_select']['selects'][2]) && is_numeric($parent_term['hierarchical_select']['selects'][2])) { + $parent_term = $parent_term['hierarchical_select']['selects'][2]; + } + elseif (isset($parent_term['hierarchical_select']['selects'][1]) && is_numeric($parent_term['hierarchical_select']['selects'][1])) { + $parent_term = $parent_term['hierarchical_select']['selects'][1]; + } + elseif (isset($parent_term['hierarchical_select']['selects'][0]) && is_numeric($parent_term['hierarchical_select']['selects'][0])) { + $parent_term = $parent_term['hierarchical_select']['selects'][0]; + } + else { + $parent_term = NULL; + // What here? + } + } + $this->value[] = $parent_term; +// $term = taxonomy_term_load($parent_term); +// $tids = array($term->tid); +// $tids = array(); +// if(!$parent_term) { +// $tree = taxonomy_get_tree($vocabulary->vid); +// } else { +// $tree = _hs_taxonomy_hierarchical_select_get_tree($vocabulary->vid, $term->tid); +// $tids = array($term->tid => $term->tid); +// } +// foreach ($tree as $descendant) { +// $tids[$descendant->tid] = $descendant->tid; +// } +// $this->value[] = $tids; + } + else { + $this->value = array(); + } // The following code should logically be wrapped in a // $this->select_type_is_hierarchical_select() // check. However, if we'd do this, you wouldn't be able to dynamically @@ -23,17 +58,37 @@ class hs_taxonomy_views_handler_filter_term_node_tid extends views_handler_filte // code below in such a check. // Add JS and CSS required for Hierarchical Select to work. - _hierarchical_select_setup_js(); + // @TODO uncomment +// _hierarchical_select_setup_js(); // Ensure that Drupal.HierarchicalSelect.prepareGETSubmit() gets called. require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc'; - hierarchical_select_common_add_views_js(); + // @TODO uncomment +// hierarchical_select_common_add_views_js(); } function value_form(&$form, &$form_state) { // Support limiting of vocabulary by the vocabulary's module name (this is // possible when a vocabulary is exported/defined as a feature, see // http://drupal.org/node/789556) or by the vocabulary's vid. +// if( array_key_exists($this->options['relationship'], $form_state['input']) ) { +// $parent_term = $form_state['input'][$this->options['relationship']]; +// $term = taxonomy_term_load($parent_term); +// $vocabulary = taxonomy_vocabulary_machine_name_load($this->options['vocabulary']); +// $tids = array(); +// if(!$parent_term) { +// $tree = taxonomy_get_tree($vocabulary->vid); +// } else { +// $tree = _hs_taxonomy_hierarchical_select_get_tree($vocabulary->vid, $term->tid); +// $tids = array($term->tid => $term->tid); +// } +// foreach ($tree as $descendant) { +// $tids[$descendant->tid] = $descendant->tid; +// } +// $this->value[] = $tids; +// } else { +// $this->value = array(); +// } if (isset($this->options['limit_by']) && $this->options['limit_by'] == 'module') { foreach (taxonomy_get_vocabularies() as $vid => $vocab) { if ($vocab->module == $this->options['module']) { @@ -43,48 +98,47 @@ class hs_taxonomy_views_handler_filter_term_node_tid extends views_handler_filte } } else { - $vocabulary = taxonomy_vocabulary_load($this->options['vid']); + $vocabulary = taxonomy_vocabulary_machine_name_load($this->options['vocabulary']); } - $view_name = $this->view->name; - $filter_id = $this->options['id']; + $view_name = $this->view->name; + $filter_id = $this->options['id']; $display_id = _hs_taxonomy_views_get_display_id_for_filter($this->view, $filter_id); - $optional = $this->options['expose']['optional']; + $required = $this->options['expose']['required']; $config_id = "taxonomy-views-$view_name-$display_id-$filter_id"; - // When not exposed: settings form. - if (empty($form_state['exposed'])) { - // When the 'Selection type' is 'Hierarchical Select', user our own - // value_form, otherwise use the parent's class form. - if ($this->options['type'] == 'textfield' || $this->options['type'] == 'select') { - parent::value_form($form, $form_state); - } - else { + // When the 'Selection Type' isn't 'Hierachical Select' use + // parent's form. + if (!$this->select_type_is_hierarchical_select()) { + parent::value_form($form, $form_state); + } + else { + if (!$this->is_exposed()) { $config = hierarchical_select_common_config_get($config_id); $form['value'] = array( - '#type' => 'hierarchical_select', - '#title' => t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)), - '#default_value' => !empty($this->value) ? $this->value : array(), + '#type' => 'hierarchical_select', + '#title' => t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)), + '#default_value' => $this->value, '#config' => array( 'module' => 'hs_taxonomy', 'params' => array( - 'vid' => $vocabulary->vid, + 'vid' => $vocabulary->vid, 'exclude_tid' => NULL, - 'root_term' => NULL, + 'root_term' => NULL, ), - 'save_lineage' => $config['save_lineage'], + 'save_lineage' => $config['save_lineage'], 'enforce_deepest' => $config['enforce_deepest'], - 'entity_count' => $config['entity_count'], - 'resizable' => 1, + 'entity_count' => $config['entity_count'], + 'resizable' => 1, 'level_labels' => array( 'status' => $config['level_labels']['status'], 'labels' => $config['level_labels']['labels'], ), 'dropbox' => array( - 'status' => $config['dropbox']['status'], - 'title' => t('Terms to filter by'), - 'limit' => $config['dropbox']['limit'], + 'status' => $config['dropbox']['status'], + 'title' => t('Terms to filter by'), + 'limit' => $config['dropbox']['limit'], 'reset_hs' => $config['dropbox']['reset_hs'], ), // Use our custom callback, because this is part of @@ -95,41 +149,37 @@ class hs_taxonomy_views_handler_filter_term_node_tid extends views_handler_filte ), ); } - } - // When exposed: filter form. - else { - // When the 'Selection type' is 'Hierarchical Select', user our own - // value_form, otherwise use the parent's class form. - if (!$this->select_type_is_hierarchical_select()) { - parent::value_form($form, $form_state); - } + // When exposed: filter form. else { + drupal_add_js(drupal_get_path('module', 'hierarchical_select') . '/includes/views.js'); $default_value = (isset($this->value) && !empty($this->value)) ? $this->value : array(); // Basic settings for the form item. - $form['value']['#type'] = 'hierarchical_select'; + $form['value']['#type'] = 'hierarchical_select'; $form['value']['#default_value'] = $default_value; - $form['value']['#required'] = !((bool) $optional); + $form['value']['#required'] = ((bool) $required); + + $form['value']['#ajax'] = NULL; // Apply the Hierarchical Select configuration to the form item. $defaults_override = array( - 'module' => 'hs_taxonomy_views', - 'params' => array( - 'optional' => (bool) $optional, - 'filter_id' => $filter_id, - 'vid' => $vocabulary->vid, - 'exclude_tid' => NULL, - 'root_term' => NULL, - ), - // When the 'Any' option is selected, nothing else should be and it - // should replace the '' option. - 'special_items' => array( - HS_TAXONOMY_VIEWS_ANY_OPTION => array('none', 'exclusive'), - ), - // This is a GET form, so also render the flat select. - 'render_flat_select' => 1, - // Use our custom callback. - 'path' => "hs_taxonomy_views_json/$view_name/$display_id", + 'module' => 'hs_taxonomy_views', + 'params' => array( + 'required' => !((bool) $required), + 'filter_id' => $filter_id, + 'vid' => $vocabulary->vid, + 'exclude_tid' => NULL, + 'root_term' => NULL, + ), + // When the 'Any' option is selected, nothing else should be and it + // should replace the '' option. + 'special_items' => array( + HS_TAXONOMY_VIEWS_ANY_OPTION => array('none', 'exclusive'), + ), + // This is a GET form, so also render the flat select. + 'render_flat_select' => 1, + // Use our custom callback. + 'path' => "hs_taxonomy_views_json/$view_name/$display_id", ); hierarchical_select_common_config_apply($form['value'], $config_id, $defaults_override); } @@ -150,37 +200,27 @@ class hs_taxonomy_views_handler_filter_term_node_tid extends views_handler_filte function extra_options_form(&$form, &$form_state) { parent::extra_options_form($form, $form_state); - $form['type'] = array( - '#type' => 'radios', - '#title' => t('Selection type'), - '#options' => array( - 'select' => t('Dropdown'), - 'textfield' => t('Autocomplete'), - 'hierarchical_select' => t('Hierarchical Select'), - ), - '#default_value' => $this->options['type'], + $form['type']['#options'] = array_merge( + $form['type']['#options'], + array( + 'hierarchical_select' => t('Hierarchical Select') + ) ); - $filter_id = $form_state['id']; + $filter_id = $form_state['id']; $display_id = _hs_taxonomy_views_get_display_id_for_filter($this->view, $filter_id); $form['configure_hs'] = array( - '#prefix' => '
', + '#prefix' => '
', // Doesn't work because #process is not called for #type = markup form items. - '#process' => array('views_process_dependency'), + '#process' => array('ctools_dependent_process'), '#dependency' => array('radio:options[type]' => array('hierarchical_select')), // Set #input = TRUE so that #process will be called. - '#input' => TRUE, + '#input' => TRUE, // Set #id manually because that is not generated for #type = markup. // This is the same id attribute as in #prefix. - '#id' => 'configure-hierarchical-select-link', - ); - - // Ensure the configure_hs form item is before the markup_end form item. - unset($form['markup_end']); - $form['markup_end'] = array( - '#markup' => '', + '#id' => 'configure-hierarchical-select-link', ); } @@ -220,7 +260,7 @@ class hs_taxonomy_views_handler_filter_term_node_tid extends views_handler_filte * Take input from exposed filters and assign to this handler, if necessary. */ function accept_exposed_input($input) { - // No need to override the parrent class' accept_exposed_input() method. + // No need to override the parent class' accept_exposed_input() method. // It returns TRUE (and therefor results in an altered query and therefor // filtered view) whenever the filter is not exposed or when a new value // was validated. @@ -234,7 +274,7 @@ class hs_taxonomy_views_handler_filter_term_node_tid extends views_handler_filte } function admin_summary() { - $this->value = (array) $this->value; + $this->value = NULL; return parent::admin_summary(); } diff --git a/modules/hs_taxonomy_views_handler_filter_term_node_tid_depth.inc b/modules/hs_taxonomy_views_handler_filter_term_node_tid_depth.inc new file mode 100644 index 0000000..e2d7268 --- /dev/null +++ b/modules/hs_taxonomy_views_handler_filter_term_node_tid_depth.inc @@ -0,0 +1,100 @@ + t('Is one of'), + ); + } + + function option_definition() { + $options = parent::option_definition(); + + $options['depth'] = array('default' => 0); + + return $options; + } + + function extra_options_form(&$form, &$form_state) { + parent::extra_options_form($form, $form_state); + + $form['depth'] = array( + '#type' => 'weight', + '#title' => t('Depth'), + '#default_value' => $this->options['depth'], + '#description' => t('The depth will match nodes tagged with terms in the hierarchy. For example, if you have the term "fruit" and a child term "apple", with a depth of 1 (or higher) then filtering for the term "fruit" will get nodes that are tagged with "apple" as well as "fruit". If negative, the reverse is true; searching for "apple" will also pick up nodes tagged with "fruit" if depth is -1 (or lower).'), + ); + } + + function query() { +// $this->ensure_my_table(); + + // If no filter values are present, then do nothing. + if (count($this->value) == 0) { + return; + } + elseif (count($this->value) == 1) { + // Some $this->value is an array with a single element so convert it. + if (is_array($this->value)) { + $this->value = current($this->value); + } + $operator = '='; + } + else { + $operator = 'IN'; + } + + if ($this->value == 'All' || $this->value == NULL) { + return; + } + + // The normal use of ensure_my_table() here breaks Views. + // So instead we trick the filter into using the alias of the base table. + // See http://drupal.org/node/271833 + // If a relationship is set, we must use the alias it provides. + if (!empty($this->relationship)) { + $this->table_alias = $this->relationship; + } + // If no relationship, then use the alias of the base table. + elseif (isset($this->query->table_queue[$this->query->base_table]['alias'])) { + $this->table_alias = $this->query->table_queue[$this->query->base_table]['alias']; + } + // This should never happen, but if it does, we fail quietly. + else { + return; + } + + // Now build the subqueries. + $subquery = db_select('taxonomy_index', 'tn'); + $subquery->addField('tn', 'nid'); + $where = db_or()->condition('tn.tid', $this->value, $operator); + $last = "tn"; + + if ($this->options['depth'] > 0) { + $subquery->leftJoin('taxonomy_term_hierarchy', 'th', "th.tid = tn.tid"); + $last = "th"; + foreach (range(1, abs($this->options['depth'])) as $count) { + $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.parent = th$count.tid"); + $where->condition("th$count.tid", $this->value, $operator); + $last = "th$count"; + } + } + elseif ($this->options['depth'] < 0) { + foreach (range(1, abs($this->options['depth'])) as $count) { + $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.tid = th$count.parent"); + $where->condition("th$count.tid", $this->value, $operator); + $last = "th$count"; + } + } + + $subquery->condition($where); + $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", $subquery, 'IN'); + } +} +