id = $parent->tid; break; case VOCABINDEX_VOC: $parent = taxonomy_vocabulary_load($id); $parent->id = $parent->vid; } return $parent; } /** * Cache a VI. * * @param $data * Type: any; The data to cache. * @param $type * Type: constant; VOCABINDEX_TERM or VOCABINDEX_VOC. * @param $id * Type: integer; The VID or TID of the list to return. * @param $view * Type: constant; View. Necessary for the cached vocabulary lists as they * can be viewed by blocks and pages with different views. * @param $letter * Type: string; The letter of the alphabetical list to set the cache for. */ function vocabindex_cache_set($data, $type, $id, $view, $letter = NULL) { $lifetime = variable_get('vocabindex_cache_lifetime', 60); if ($lifetime > 0) { $expire = $lifetime * 60 + mktime(); $letter = !is_null($letter) ? '_' . htmlentities($letter, ENT_NOQUOTES, 'UTF-8') : NULL; cache_set('vocabindex_' . $type . '_' . $id . '_' . $view . $letter, $data, 'cache', $expire); } } /** * Return a cached VI. * * @param $type * Type: constant; VOCABINDEX_TERM or VOCABINDEX_VOC. * @param $id * Type: integer; The VID or TID of the list to return. * @param $view * Type: constant; view. Necessary for the cached vocabulary lists as they * can be viewed by blocks and pages with different views. * @param $letter * Type: string; The letter of the alphabetical list to get the cache for. * * @return * Type: any; The cached data. */ function vocabindex_cache_get($type, $id, $view, $letter = NULL) { $lifetime = variable_get('vocabindex_cache_lifetime', 60); if ($lifetime > 0) { $letter = !is_null($letter) ? '_' . htmlentities($letter, ENT_NOQUOTES, 'UTF-8') : NULL; return cache_get('vocabindex_' . $type . '_' . $id . '_' . $view . $letter, 'cache'); } } /** * Load the proper path to a term. * * Either the default taxonomy path or an alias. * * @param $tid * Type: integer; The TID of the term to load the path for. * * @return * Type: string; The path at which the term can be viewed. */ function vocabindex_term_get_path($tid) { return drupal_get_path_alias(url('taxonomy/term/' . $tid)); } /** * Converts a numerical view value to a string. * * Conversion needs to be done to provide a textual view representation for * easy use in CSS files. * * @param $view * Type: integer; The numerical representation of the view. VOCABINDEX_VIEW_FLAT, * VOCABINDEX_VIEW_ALPHABETICAL or VOCABINDEX_VIEW_TREE * * @return * Type: string; The textual representation of the given view. */ function vocabindex_convert_view($view) { $strings[VOCABINDEX_VIEW_FLAT] = 'flat'; $strings[VOCABINDEX_VIEW_TREE] = 'tree'; $strings[VOCABINDEX_VIEW_ALPHABETICAL] = 'alphabetical'; return $strings[$view]; } /** * Set the letter for which an alphabetical list is requested. * * @param $value * Type: string; The letter in question. * * @return * Type: any; The $value parameter is being returned if it's a string, * otherwise FALSE is being returned. */ function vocabindex_letter_set($value = NULL) { static $letter = NULL; if (is_null($letter) && !is_null($value)) { $letter = $value; } return $letter; } /** * Get the letter for which an alphabetical index is requested. * * @param $tree * Type: array; A taxonomy tree. Only necessary for the first call. * * @return * Type: string; The letter in question. */ function vocabindex_letter_get($tree = array()) { static $letter = NULL; if (is_null($letter)) { $letter = vocabindex_letter_set(); if ($letter == '') { $letters = vocabindex_letters_get($tree); $letter = $letters[0]; } } return $letter; } /** * Get all first letters from the term names of a given tree. * * @param $tree * Type: array; The taxonomy tree containing the terms. * * @return * Type: array; The array has been sorted by value alphabetically. */ function vocabindex_letters_get($tree) { // Caching the letters won't be a problem as alphabetical indices are only available to pages static $letters = array(); if (empty($letters)) { $transliteration = variable_get('vocabindex_transliteration', FALSE); // Get all first letters from the term names for ($i = 0, $len_i = count($tree); $i < $len_i; $i++) { $term = $tree[$i]; $letter = drupal_strtolower(drupal_substr($term->name, 0, 1)); if ($transliteration == TRUE) { $letter = vocabindex_transliterate($letter); } $letters[] = $letter; } $letters = array_values(array_unique($letters)); if ($transliteration == FALSE) { // Sort the letters $letters_transliterated = array(); for ($i = 0, $len_i = count($letters); $i < $len_i; $i++) { $letter = $letters[$i]; // Group letters by their transliterated equivalent $letters_transliterated[vocabindex_transliterate($letter)][] = $letter; } // Sort the groups ksort($letters_transliterated); $letters = array(); // Sort the letters from every single group and merge them with the main array of letters. foreach ($letters_transliterated as &$letter) { sort($letter); $letters = array_merge($letters, $letter); } } } return $letters; } /** * Display a Vocabulary Index page * * @param $id * Type: integer; The TID or the VID to generate the index page for. * @param $type * Type: constant; Either VOCABINDEX_VOC or VOCABINDEX_TERM. * @param $letter * Type: string; The letter to display the terms for. Only needed for alphabetical indices. * * @return string A fully rendered index page */ function vocabindex_view_page($id, $type, $letter = '') { vocabindex_letter_set($letter); $parent = vocabindex_parent_load($id, $type); $vis = vocabindex_index_load(VOCABINDEX_VI_VID, $parent->vid); $vi = $vis[VOCABINDEX_VI_PAGE]; $output = vocabindex_index_view($id, $type, $vi, $parent); return theme('vocabindex_page', check_plain($parent->description), $output->content, $output->pager); } /** * Display a Vocabulary Index block * * @param $id * Type: integer; The VID to generate the Index Block for. * * @return * Type: string; The block's content: a rendered index. */ function vocabindex_view_block($vid) { $vis = vocabindex_index_load(VOCABINDEX_VI_VID, $vid); $vi = $vis[VOCABINDEX_VI_BLOCK]; $output = vocabindex_index_view($vid, VOCABINDEX_VOC, $vi, $vi); return $output->content; } /** * View an index. * * @param $id * Type: integer; The TID or the VID to generate the index page for. * @param $type * Type: string; Either 'term' or 'vocab'. * @param $vi * Type: object; The VI the current index belongs to. * @param $parent * Type: object; The object of the vocab or the term under which to generate the tree. * * @return * Type: string; A fully rendered index. */ function vocabindex_index_view($id, $type, $vi, $parent) { // Only add the stylesheet if the site administrator wants it. if (variable_get('vocabindex_stylesheet', TRUE)) { drupal_add_css(drupal_get_path('module', 'vocabindex') . '/vocabindex.css', 'module', 'screen', FALSE); } // Only use Javascript if the site administrator wants it and if this is a tree view if (variable_get('vocabindex_javascript', TRUE) && $vi->view == VOCABINDEX_VIEW_TREE) { drupal_add_js(drupal_get_path('module', 'vocabindex') . '/vocabindex.js'); } // Check for cached data $cached_data = vocabindex_cache_get($type, $parent->id, $vi->view, vocabindex_letter_get()); // There is no cached data, so render the output from scratch if ($cached_data == 0) { // Check whether it's a term index or not. If so, set the term's TID as $parent for taxonomy_get_tree(). Do not confuse with the $parent argument of vocabindex_index_view(). $pid = $type == VOCABINDEX_TERM ? $parent->tid : 0; $tree = taxonomy_get_tree($parent->vid, $pid, -1, $vi->depth); // Display an error message if there are no terms if (count($tree) == 0) { $output->content = t('There are no terms to display.'); } // We do have terms, so render them else { for ($i = 0, $l = count($tree); $i < $l; $i++) { $term = &$tree[$i]; // Calculate node count if ($vi->node_count == 1) { $term->node_count = taxonomy_term_count_nodes($term->tid); } else { $term->node_count = -1; } } $children = vocabindex_get_children($tree); // Render the list-items if ($vi->view == VOCABINDEX_VIEW_TREE) { $list_items = _vocabindex_list_render_tree($tree, $children, $vi); } elseif ($vi->view == VOCABINDEX_VIEW_FLAT) { $list_items = _vocabindex_list_render_flat($tree, $children); } elseif ($vi->view == VOCABINDEX_VIEW_ALPHABETICAL) { $list_items = _vocabindex_list_render_alphabetical($tree, $children, $vi); $output->pager = _vocabindex_pager(vocabindex_letters_get($tree), $vi->path); } // Render the list and the page $view = vocabindex_convert_view($vi->view); $output->content = theme('vocabindex_list', $list_items, $view); } vocabindex_cache_set($output, $type, $id, $vi->view, vocabindex_letter_get()); } else { $output = $cached_data->data; } return $output; } /** * Render a flat list. * * @param $tree * Type: array; A taxonomy tree. * @param $children * Type: array; The vocabindex_get_children() output for this particular tree. * * @return * Type: string; The fully rendered list. */ function _vocabindex_list_render_flat($tree, $children) { // Filter out all children $tree_buffer = array(); for ($i = 0, $len_i = count($tree); $i < $len_i; $i++) { if ($tree[$i]->depth == 0) { $tree_buffer[] = $tree[$i]; } } $tree = $tree_buffer; // Loop through all the terms from the list and render them $list_items = ''; for ($i = 0, $len_i = count($tree); $i < $len_i; $i++) { $term = $tree[$i]; $zebra = ($i % 2 == 0 ? 'even' : 'odd'); $url = vocabindex_term_get_path($term->tid); $parent = isset($children[$term->tid]) ? 'parent' : NULL; // Calculate the list item's position. Necessary because not all browsers have implemented the :first and :last pseudo-classes $position = ''; if ($i == 0) { $position = 'first'; } if ($i == ($len_i - 1)) { $position .= ' last'; } $list_items .= theme('vocabindex_list_item', $url, check_plain($term->name), check_plain($term->description), $zebra, $parent, NULL, $term->node_count, $position); } return $list_items; } /** * Render a tree * * @param $tree * Type: array; A taxonomy tree. * @param $children * Type: array; The vocabindex_get_children() output for this particular tree. * @param $vi * Type: object; The VI this list belongs to. * * @return * Type: string; The fully rendered list. */ function _vocabindex_list_render_tree($tree, $children, $vi) { //Prepare the tree: Use the TIDs as keys $tree_buffer = array(); for ($i = 0, $len_i = count($tree); $i < $len_i; $i++) { $term = $tree[$i]; $tree_buffer[$term->tid] = $term; } $tree = $tree_buffer; return _vocabindex_list_render_tree_walker($tree, $children, $vi); } /** * Helper function for _vocabindex_list_render_tree() * * Walks through the tree and renders each term * * @param $tree * Type: array; The term tree. It is a taxonomy tree modified by _vocabindex_list_render_tree(). * @param $children * Type: array; The vocabindex_get_children() output for this particular tree. * @param $vi * Type: object; The VI this list belongs to. * @param $pid * Type: integer; The ID of the term to generate the children for. Internal use only. * @param $zebra_i * Type: integer; Incrementor used for calculating the zebra value. Internal use only. * * @return * Type: string; The fully rendered list. */ function _vocabindex_list_render_tree_walker($tree, $children, $vi, $pid = 0, $zebra_i = 0) { $view = vocabindex_convert_view($vi->view); $list_items = ''; for ($i = 0, $len_i = count($children[$pid]); $i < $len_i; $i++) { $term = $tree[$children[$pid][$i]]; $zebra_i++; $term_children = NULL; $parent = NULL; if (array_key_exists($term->tid, $children)) { $term_children = _vocabindex_list_render_tree_walker($tree, $children, $vi, $term->tid, $zebra_i); $term_children = theme('vocabindex_list', $term_children, $view); $parent = 'parent'; } // Render the current term $url = vocabindex_term_get_path($term->tid); $zebra = ($zebra_i % 2 == 0 ? 'even' : 'odd'); //Calculate the list item's position. Necessary because not all browsers have implemented the :first and :last pseudo-classes $position = ''; if ($i == 0) { $position = 'first'; } if ($i == ($len_i - 1)) { $position .= ' last'; } $list_items .= theme('vocabindex_list_item', $url, check_plain($term->name), check_plain($term->description), $zebra, $parent, $term_children, $term->node_count, $position); } return $list_items; } /** * Render an alphabetical list. * * @param $tree * Type: array; A taxonomy tree. * @param $children * Type: array; The vocabindex_get_children() output for this particular tree. * @param $vi * Type: object; The VI this list belongs to. */ function _vocabindex_list_render_alphabetical($tree, $children, $vi) { $letter = vocabindex_letter_get($tree); $tree_buffer = array(); for ($i = 0, $len_i = count($tree); $i < $len_i; $i++) { $term = $tree[$i]; // Reset depth to prevent terms with depth != 0 from not being displayed due to the depth check in _vocabindex_list_render_flat() $term->depth = 0; // NOTE: Implement transliteration $term_letter = drupal_strtolower(drupal_substr($term->name, 0, 1)); if (variable_get('vocabindex_transliteration', FALSE) == TRUE) { $term_letter = vocabindex_transliterate($term_letter); } if ($letter == $term_letter) { $tree_buffer[] = $term; } } $tree = $tree_buffer; // Throw a 404 if there are no terms matching this letter if (count($tree) == 0) { drupal_not_found(); exit; } return _vocabindex_list_render_flat($tree, array()); } /** * Render a pager for an alphabetical index. * * @param $letters * Type: array; The available letters to create a pager for. * * @return * Type: string; A fully rendered pager. */ function _vocabindex_pager($letters, $path) { $letter_active = vocabindex_letter_get(); $letters_rendered = array(); for ($i = 0, $len_i = count($letters); $i < $len_i; $i++) { $letter = $letters[$i]; $active = $letter == $letter_active ? 'active' : ''; $letters_rendered[] = theme('vocabindex_pager_letter', $letter, $path . '/' . $letter, $active); } $pager = implode(theme('vocabindex_pager_separator'), $letters_rendered); return theme('vocabindex_pager', $pager); }