taxonomy_edge.module in Taxonomy Edge 6
Same filename and directory in other branches
Selecting all children of a given taxonomy term can be a pain. This module makes it easier to do this, by maintaining a complete list of edges for each term using the adjecency matrix graph theory.
Example of getting all children from tid:14
SELECT e.tid FROM term_edge e WHERE e.parent = 14;
@todo Fix concurrency issue for queue rebuild and full tree rebuild (lock_may_be_available() inside transaction)
See also
File
taxonomy_edge.moduleView source
<?php
/**
* @file
*
* Selecting all children of a given taxonomy term can be a pain.
* This module makes it easier to do this, by maintaining a complete list of
* edges for each term using the adjecency matrix graph theory.
*
* Example of getting all children from tid:14
*
* SELECT e.tid
* FROM term_edge e
* WHERE e.parent = 14;
*
* @todo Fix concurrency issue for queue rebuild and full tree rebuild (lock_may_be_available() inside transaction)
*
* @see README.txt
*/
/**
* Fail safe for avoiding infite loops when rebuilding edges.
*/
define('TAXONOMY_EDGE_MAX_DEPTH', 100);
/**
* Default value for realtime building of tree.
*/
define('TAXONOMY_EDGE_BUILD_REALTIME', TRUE);
/**
* Default value for static caching.
*/
define('TAXONOMY_EDGE_STATIC_CACHING', FALSE);
/**
* Default value for optimized tree.
*/
define('TAXONOMY_EDGE_OPTIMIZED_GET_TREE', TRUE);
/**
* Default value for optimized tree.
*/
define('TAXONOMY_EDGE_USE_CUSTOM_TRANSACTIONS', FALSE);
// ---------- HOOKS ----------
/**
* Implements hook_init().
*/
function taxonomy_edge_init() {
if (!empty($_SESSION['taxonomy_edge_rebuild_module_enabled'])) {
unset($_SESSION['taxonomy_edge_rebuild_module_enabled']);
module_load_include('rebuild.inc', 'taxonomy_edge');
$vids = array();
foreach (taxonomy_get_vocabularies() as $vocabulary) {
$vids[] = $vocabulary->vid;
}
taxonomy_edge_rebuild_all_batch($vids);
batch_process();
}
}
/**
* Implements hook_help().
*/
function taxonomy_edge_help($section) {
switch ($section) {
case 'admin/help#taxonomy_edge':
// Return a line-break version of the module README.txt
return filter_filter('process', 1, NULL, file_get_contents(dirname(__FILE__) . "/README.txt"));
}
}
/**
* Implements hook_perm().
*/
function taxonomy_edge_perm() {
return array(
'administer taxonomy edge',
);
}
/**
* Implements hook_menu().
*/
function taxonomy_edge_menu() {
$items = array();
// Bring /level and /all back into taxonomy pages and feeds
$items['taxonomy/term/%'] = array(
'title' => 'Taxonomy term',
'page callback' => 'taxonomy_edge_term_page',
'page arguments' => array(
2,
),
'access arguments' => array(
'access content',
),
'type' => MENU_CALLBACK,
'file' => 'taxonomy_edge.pages.inc',
);
// settings page
$items['admin/content/taxonomy/edge'] = array(
'title' => 'Edge',
'description' => 'Administer taxonomy edges',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'taxonomy_edge_settings_form',
),
'access arguments' => array(
'administer taxonomy edge',
),
'type' => MENU_LOCAL_TASK,
'file' => 'taxonomy_edge.admin.inc',
);
$items['admin/content/taxonomy/rebuild/%'] = array(
'title' => 'Rebuild edges',
'description' => 'Rebuild taxonomy edges',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'taxonomy_edge_rebuild_page_confirm',
4,
5,
),
'access arguments' => array(
'administer taxonomy edge',
),
'type' => MENU_CALLBACK,
'file' => 'taxonomy_edge.admin.inc',
);
return $items;
}
/**
* Implements hook_cron().
*
* Rebuild sorted tree if invalid
*/
function taxonomy_edge_cron() {
module_load_include('rebuild.inc', 'taxonomy_edge');
foreach (taxonomy_get_vocabularies() as $vocabulary) {
if (!taxonomy_edge_is_order_valid($vocabulary->vid)) {
$context = array();
taxonomy_edge_rebuild_order($vocabulary->vid, $context);
taxonomy_edge_rebuild_finished($context['success'], $context['results'], array());
}
}
}
/**
* Implements hook_cronapi().
* Regularly rebuild the edge list and resort trees.
*/
function taxonomy_edge_cronapi($op, $job = NULL) {
switch ($op) {
case 'list':
return array(
'taxonomy_edge_cron_rebuild' => 'Rebuild taxonomy edges',
'taxonomy_edge_cron' => 'Resort invalid trees',
);
case 'rule':
switch ($job) {
case 'taxonomy_edge_cron_rebuild':
return '0 2 * * *';
case 'taxonomy_edge_cron':
return '* * * * *';
}
case 'settings':
switch ($job) {
case 'taxonomy_edge_cron_rebuild':
return array(
'enabled' => FALSE,
);
case 'taxonomy_edge_cron':
return array(
'enabled' => TRUE,
);
}
}
}
/**
* Implements hook_cron_queue_info().
*/
function taxonomy_edge_cron_queue_info() {
$queues['taxonomy_edge'] = array(
'worker callback' => 'taxonomy_edge_process_queue_item',
'time' => 60,
);
return $queues;
}
/**
* Implements hook_taxonomy_vocabulary_insert().
*/
function taxonomy_edge_taxonomy_vocabulary_insert($vocabulary) {
taxonomy_edge_taxonomy_vocabulary_delete($vocabulary);
db_query("INSERT INTO {term_edge} (vid) VALUES(%d)", $vocabulary->vid);
}
/**
* Implements hook_taxonomy_vocabulary_delete().
*/
function taxonomy_edge_taxonomy_vocabulary_delete($vocabulary) {
db_query("DELETE FROM {term_edge} WHERE vid = %d", $vocabulary->vid);
}
/**
* Implements hook_taxonomy_term_insert().
*/
function taxonomy_edge_taxonomy_term_insert($term) {
if (taxonomy_edge_is_build_realtime($term->vid)) {
return _taxonomy_edge_taxonomy_term_insert($term);
}
else {
_taxonomy_edge_taxonomy_term_queue($term, 'insert');
}
}
/**
* Implements hook_taxonomy_term_update().
*/
function taxonomy_edge_taxonomy_term_update($term) {
if (taxonomy_edge_is_build_realtime($term->vid)) {
return _taxonomy_edge_taxonomy_term_update($term);
}
else {
_taxonomy_edge_taxonomy_term_queue($term, 'update');
}
}
/**
* Implements hook_taxonomy_term_delete().
*/
function taxonomy_edge_taxonomy_term_delete($term) {
if (taxonomy_edge_is_build_realtime($term->vid)) {
return _taxonomy_edge_taxonomy_term_delete($term);
}
else {
_taxonomy_edge_taxonomy_term_queue($term, 'delete');
}
}
/**
* Implementation of hook_taxonomy().
*
* Maintain edges upon taxonomy manipulation
*/
function taxonomy_edge_taxonomy($op, $type, $array = NULL) {
switch ($type) {
case 'term':
$term = (object) $array;
switch ($op) {
case 'insert':
taxonomy_edge_taxonomy_term_insert($term);
break;
case 'update':
taxonomy_edge_taxonomy_term_update($term);
break;
case 'delete':
taxonomy_edge_taxonomy_term_delete($term);
break;
}
break;
case 'vocabulary':
$vocabulary = (object) $array;
switch ($op) {
case 'insert':
taxonomy_edge_taxonomy_vocabulary_insert($vocabulary);
break;
case 'delete':
taxonomy_edge_taxonomy_vocabulary_delete($vocabulary);
break;
}
break;
}
}
/**
* Hook into the overview of vocabularies to provide rebuild actions.
*/
function taxonomy_edge_form_taxonomy_overview_vocabularies_alter(&$form, &$form_state) {
if (user_access('administer taxonomy edge')) {
foreach (taxonomy_get_vocabularies() as $vocabulary) {
$form[$vocabulary->vid]['rebuild_edges'] = array(
'#value' => l('rebuild edges', "admin/content/taxonomy/rebuild/edges/{$vocabulary->vid}"),
);
$form[$vocabulary->vid]['rebuild_order'] = array(
'#value' => l('rebuild order', "admin/content/taxonomy/rebuild/order/{$vocabulary->vid}"),
);
$form[$vocabulary->vid]['rebuild_all'] = array(
'#value' => l('rebuild everything', "admin/content/taxonomy/rebuild/all/{$vocabulary->vid}"),
);
}
$form['#theme'] = 'taxonomy_edge_overview_vocabularies';
}
}
/**
* Implements hook_theme().
*/
function taxonomy_edge_theme() {
return array(
'taxonomy_edge_overview_vocabularies' => array(
'arguments' => array(
'form' => array(),
),
'file' => 'taxonomy_edge.theme.inc',
),
);
}
// ---------- HANDLERS ----------
/**
* Cron queue worker
* Process edge for a queued term.
* @param integer $vid
* Vocabulary ID
*/
function taxonomy_edge_process_queue_item($vid) {
if (lock_acquire('taxonomy_edge_rebuild_edges_' . $vid)) {
drupal_queue_include();
$queue = DrupalQueue::get('taxonomy_edge_items_' . $vid, TRUE);
$max = 1000;
while ($max-- > 0 && ($item = $queue
->claimItem())) {
$term = $item->data;
switch ($term->operation) {
case 'insert':
_taxonomy_edge_taxonomy_term_insert($term);
break;
case 'update':
_taxonomy_edge_taxonomy_term_update($term);
break;
case 'delete':
_taxonomy_edge_taxonomy_term_delete($term);
break;
}
$queue
->deleteItem($item);
}
lock_release('taxonomy_edge_rebuild_edges_' . $vid);
}
}
/**
* Rebuild edges
*/
function taxonomy_edge_cron_rebuild() {
module_load_include('rebuild.inc', 'taxonomy_edge');
foreach (taxonomy_get_vocabularies() as $vocabulary) {
$context = array();
taxonomy_edge_rebuild_edges($vocabulary->vid, $context);
taxonomy_edge_rebuild_finished($context['success'], $context['results'], array());
}
}
// ---------- PRIVATE HELPER FUNCTIONS ----------
/**
* Delete subtree
*
* @param $vid
* Vocabulary ID
* @param $tids
* Array of term ids to delete subtree from
* @param $type
* Type of delete
* 0: Delete entire subtree
* 1: Delete ancestors (detach children from parents in $tids)
*/
function _taxonomy_edge_delete_subtree($vid, $tids, $type = 0) {
// @fixme MySQL can not use EXISTS or IN without temp tables when deleting.
// SQLite cannot perform join delete.
// MySQL and Postgres differ in joined delete syntax.
// Use optimized joined delete for MySQL and subquery for others.
// With MySQL 5.6 we might be able to switch to a subquery method only.
global $db_type;
$type = $type == 0 ? '' : 'AND e2.distance > e.distance';
$args = $tids;
$args[] = $vid;
$args[] = $vid;
$placeholders = db_placeholders($tids);
switch ($db_type) {
case 'mysql':
case 'mysqli':
db_query("DELETE e2.*\n FROM {term_edge} e\n INNER JOIN {term_edge} e2 ON e2.tid = e.tid\n WHERE e.parent IN ({$placeholders})\n {$type}\n AND e.vid = %d AND e2.vid = %d\n ", $args);
break;
default:
db_query("DELETE FROM\n {term_edge} WHERE eid IN (\n SELECT e2.eid\n FROM {term_edge} e\n INNER JOIN {term_edge} e2 ON e2.tid = e.tid\n WHERE e.parent IN ({$placeholders})\n {$type}\n AND e.vid = %d AND e2.vid = %d\n )", $args);
break;
}
}
/**
* Detach children from parents in $tids (delete ancestors)
*/
function _taxonomy_edge_detach_subtree($vid, $tids) {
return _taxonomy_edge_delete_subtree($vid, $tids, 1);
}
/**
* Attach path and children to new parents
*
* @param $vid
* Vocabulary ID
* @param $tid
* Term ID
* @param $parents
* Term ID of new parents
*/
function _taxonomy_edge_attach_subtree($vid, $tid, $parents) {
// Build new parents
$args = $parents;
array_unshift($args, $vid);
$args[] = $tid;
$args[] = $vid;
$args[] = $vid;
$placeholders = db_placeholders($parents);
db_query("INSERT INTO {term_edge} (vid, tid, parent, distance)\n SELECT %d, e2.tid, e.parent, e.distance + e2.distance + 1 AS distance\n FROM {term_edge} e\n INNER JOIN {term_edge} e2 ON e.tid IN ({$placeholders}) AND e2.parent = %d\n WHERE e.vid = %d AND e2.vid = %d\n ", $args);
taxonomy_edge_invalidate_order($vid);
}
/**
* Detach path and children from current parent and attach to new parents
*
* @param $vid
* Vocabulary ID
* @param $tid
* Term ID
* @param $parents
* Term ID of new parents
*/
function _taxonomy_edge_move_subtree($vid, $tid, $parents) {
_taxonomy_edge_detach_subtree($vid, array(
$tid,
));
_taxonomy_edge_attach_subtree($vid, $tid, $parents);
}
/**
* Insert a term into the edge tree.
*
* @param type $term
*/
function _taxonomy_edge_taxonomy_term_insert($term) {
$vocabulary = taxonomy_vocabulary_load($term->vid);
// Derive proper parents.
$parents = _taxonomy_edge_unify_parents($term->parent);
if ($term->tid > 0) {
$tx = _taxonomy_edge_db_transaction();
db_query("INSERT INTO {term_edge} (vid, tid, parent) VALUES(%d, %d, %d)", $term->vid, $term->tid, $term->tid);
$args = $parents;
array_unshift($args, $term->tid);
array_unshift($args, $term->vid);
$args[] = $term->vid;
$placeholders = db_placeholders($parents);
db_query("INSERT INTO {term_edge} (vid, tid, parent, distance)\n SELECT %d AS vid, %d AS tid, e.parent, e.distance + 1 AS distance\n FROM {term_edge} e\n WHERE e.tid IN ({$placeholders})\n AND e.vid = %d\n ", $args);
taxonomy_edge_invalidate_order($term->vid);
}
else {
watchdog('taxonomy_edge', 'Invalid term-id (%tid) received', array(
'%tid' => $term->tid,
), WATCHDOG_ERROR);
}
}
/**
* Update a term in the edge tree.
*
* @param type $term
*/
function _taxonomy_edge_taxonomy_term_update($term) {
$tx = _taxonomy_edge_db_transaction();
// Invalidate sorted tree in case of name/weight change
if (isset($term->original) && ($term->original->name != $term->name || $term->original->weight != $term->weight)) {
taxonomy_edge_invalidate_order($term->vid);
}
if (!isset($term->parent)) {
// Parent not set, no need to update hierarchy.
return;
}
// Derive proper parents.
$parents = _taxonomy_edge_unify_parents($term->parent);
_taxonomy_edge_move_subtree($term->vid, $term->tid, $parents);
}
/**
* Delete a term from the edge tree.
*
* * @param $tid
* Term ID.
*/
function _taxonomy_edge_taxonomy_term_delete($term) {
db_query("DELETE FROM {term_edge} WHERE vid = %d AND tid = %d", $term->vid, $term->tid);
}
/**
* Queue an operation for the edge tree.
*
* @param object $term
* Term object
* @param string $op
* insert, update or delete
*/
function _taxonomy_edge_taxonomy_term_queue($term, $op) {
// Wait for rebuild to clear queue and initiate snapshot of term_hierarchy
if (lock_may_be_available('taxonomy_edge_rebuild_edges_' . $term->vid)) {
drupal_queue_include();
$queue = DrupalQueue::get('taxonomy_edge_items_' . $term->vid, TRUE);
$term->operation = $op;
$queue
->createItem($term);
$queue = DrupalQueue::get('taxonomy_edge', TRUE);
$queue
->createItem($term->vid);
}
}
/**
* Unify parents
*
* @param mixed $parents
* @return array
* Flattened array of parent term IDs
*/
function _taxonomy_edge_unify_parents($parents) {
$parents = is_array($parents) ? $parents : array(
$parents,
);
$new_parents = array();
foreach ($parents as $parent) {
if (is_array($parent)) {
foreach ($parent as $new) {
$new_parents[] = $new;
}
}
else {
$new_parents[] = $parent;
}
}
return $new_parents;
}
/**
* Generate query for ordering by dynamically compiled path
*
* @param $tid
* Term ID to generate dynamic path from.
* @return
* String containing query.
*/
function _taxonomy_edge_generate_term_path_query($tid) {
$args = func_get_args();
global $db_type;
switch ($db_type) {
case 'pgsql':
return "(SELECT array_to_string(array_agg(((tpx.weight + 1500) || tpx.name || ' ' || tpx.tid)), ' ') FROM (SELECT tpd.* FROM {term_edge} tpe INNER JOIN {term_data} tpd ON tpd.tid = tpe.parent WHERE tpe.tid = {$tid} ORDER BY tpe.distance DESC) tpx)";
case 'mysql':
case 'mysqli':
return "(SELECT GROUP_CONCAT(tpd.weight + 1500, tpd.name, ' ', tpd.tid ORDER BY tpe.distance DESC SEPARATOR ' ') FROM {term_edge} tpe INNER JOIN {term_data} tpd ON tpd.tid = tpe.parent WHERE tpe.tid = {$tid})";
case 'sqlite':
return "(SELECT GROUP_CONCAT(val, ' ') FROM (SELECT (tpd.weight + 1500) || tpd.name || ' ' || tpd.tid AS val FROM {term_edge} tpe INNER JOIN {term_data} tpd ON tpd.tid = tpe.parent WHERE tpe.tid = {$tid} ORDER BY tpe.distance DESC))";
case 'sqlsrv':
return "(SELECT RTRIM((\n SELECT CAST(tpd.weight + 1500 as nvarchar) + tpd.name + ' ' + CAST(tpd.tid as nvarchar) + ' '\n FROM {term_edge} tpe\n JOIN {term_data} tpd ON tpd.tid = tpe.parent\n WHERE tpe.tid = {$tid}\n ORDER BY tpe.distance DESC\n FOR XML PATH ('')))\n )";
default:
}
}
/**
* Transaction handler wrapper
*/
function _taxonomy_edge_db_transaction() {
if (function_exists('db_start_transaction')) {
return new TaxonomyEdgeDatabaseTransaction('db_start_transaction', 'db_end_transaction');
}
elseif (variable_get('taxonomy_edge_use_custom_transactions', TAXONOMY_EDGE_USE_CUSTOM_TRANSACTIONS)) {
return new TaxonomyEdgeDatabaseTransaction();
}
}
/**
* Class for simulating DBTNG transactions
*/
class TaxonomyEdgeDatabaseTransaction {
private $end;
private static $counter = 0;
function __construct($begin = NULL, $end = NULL) {
if ($begin && $end) {
$this->end = $end;
call_user_func($begin);
}
else {
self::begin();
}
}
function __destruct() {
$this
->commit();
}
private static function begin() {
if (self::$counter == 0) {
db_query('BEGIN');
}
self::$counter++;
}
private static function end($commit = TRUE) {
self::$counter--;
if (self::$counter == 0) {
if ($commit) {
db_query('COMMIT');
}
else {
db_query('ROLLBACK');
}
}
}
function commit() {
if ($this->end) {
return call_user_func($this->end, TRUE);
}
else {
return self::end(TRUE);
}
}
function rollback() {
if ($this->end) {
return call_user_func($this->end, FALSE);
}
else {
return self::end(FALSE);
}
}
}
// ---------- PUBLIC HELPER FUNCTIONS ----------
/**
* Get max depth of a tree..
*
* @param $vid
* Vocabulary ID
* @param $parent (optional)
* Parent to max depth of
* @return
* Max depth (distance)
*/
function taxonomy_edge_get_max_depth($vid, $parent = 0) {
return db_result(db_query("SELECT MAX(distance) FROM {term_edge} WHERE vid = %d AND parent = %d", $vid, $parent));
}
/**
* Checks if it's possible to build an edge realtime.
*
* @return boolean
* TRUE if possible, FALSE if not.
*/
function taxonomy_edge_is_build_realtime($vid) {
if (!module_exists('drupal_queue')) {
// Only realtime build is supported, if Drupal Queue is missing
return TRUE;
}
if (variable_get('taxonomy_edge_build_realtime', TAXONOMY_EDGE_BUILD_REALTIME)) {
drupal_queue_include();
$queue = DrupalQueue::get('taxonomy_edge_items_' . $vid, TRUE);
if ($queue
->numberOfItems()) {
// Don't build realtime, if there are still items left in the queue.
return FALSE;
}
if (!lock_may_be_available('taxonomy_edge_rebuild_edges_' . $vid)) {
// Don't build realtime, if entire tree rebuild is in progress.
return FALSE;
}
return TRUE;
}
return FALSE;
}
/**
* Check if our sorted tree is still valid
*/
function taxonomy_edge_is_order_valid($vid, $reset = FALSE) {
static $valid_orders = array();
if ($reset || !isset($valid_orders[$vid])) {
$valid_orders[$vid] = db_result(db_query_range("SELECT 1 FROM {term_edge_order} WHERE vid = %d", -$vid, 0, 1));
}
return $valid_orders[$vid];
}
/**
* Check if our sorted tree is still valid
*/
function taxonomy_edge_is_order_invalid($reset = FALSE) {
static $invalid;
if ($reset || !isset($valid)) {
$invalid = db_result(db_query_range("SELECT 1\n FROM {vocabulary} v\n LEFT JOIN {term_edge_order} o ON o.vid = -v.vid AND o.vid < 0\n WHERE o.oid IS NULL\n ", 0, 1));
}
return $invalid;
}
/**
* Invalidate order for a vocabulary
*/
function taxonomy_edge_invalidate_order($vid) {
db_query("DELETE FROM {term_edge_order} WHERE vid = %d", -$vid);
}
/**
* Get parent from edge list.
*
* @param $tid
* term id to get parent from.
* @return array
* array of term ids.
*/
function taxonomy_edge_get_parents($tid) {
$result = db_query("SELECT e.distance, e.parent\n FROM {term_data} d\n INNER JOIN {term_edge} e ON e.parent = d.tid\n WHERE e.tid = %d\n AND e.tid > 0\n AND e.distance > 0\n ORDER BY e.distance\n ", $tid);
$parents = array();
while ($row = db_fetch_object($result)) {
$parents[$row->distance] = $row->parent;
}
return $parents;
}
/**
* Get top term ids.
*
* @param $tid
* Term ID to get top term ID from.
* @return array
* Top term IDs.
*/
function taxonomy_edge_get_top_tids($tid) {
$result = db_query("SELECT DISTINCT e2.parent\n FROM {term_edge} e\n JOIN {term_edge} e2 ON e2.tid = e.tid AND e2.distance = e.distance - 1 AND e2.parent <> e.parent\n WHERE e.tid = %d\n AND e.parent = 0\n AND e.vid = e2.vid\n ", $tid);
$toptids = array();
while ($row = db_fetch_object($result)) {
$toptids[$row->parent] = $row->parent;
}
return $toptids;
}
/**
* Get top term id.
*
* @param $tid
* Term ID to get top term ID from.
* @return int
* Top term ID.
*/
function taxonomy_edge_get_top_tid($tid) {
$tids = taxonomy_edge_get_top_tids(array(
$tid,
));
return reset($tids);
}
// ---------- CORE OVERRIDES ----------
/**
* BC wrappers after function name changes
*/
function taxonomy_edge_taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
return taxonomy_edge_get_tree($vid, $parent, $max_depth);
}
function taxonomy_edge_taxonomy_term_count_nodes($tid, $type = 0) {
return taxonomy_edge_term_count_nodes($tid, $type);
}
function taxonomy_edge_taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC, n.nid DESC') {
return taxonomy_edge_select_nodes($tids, $operator, $depth, $pager, $order);
}
/**
* Reimplementation of taxonomy_get_tree().
* Limit db fetch to only specified parent.
* @see taxonomy_get_tree()
*/
function taxonomy_edge_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
// @todo Use regular taxonomy_get_tree if realtime build is disabled,
// as this function might be unreliable.
module_load_include('core.inc', 'taxonomy_edge');
// Use optimized version if possible
if (variable_get('taxonomy_edge_optimized_get_tree', TAXONOMY_EDGE_OPTIMIZED_GET_TREE)) {
return taxonomy_edge_get_tree_optimized($vid, $parent, $max_depth);
}
else {
return taxonomy_edge_get_tree_generic($vid, $parent, $max_depth);
}
}
/**
* Reimplementation of taxonomy_term_count_nodes().
* Avoid GROUP BY and recursive calls.
* @see taxonomy_term_count_nodes()
*/
function taxonomy_edge_term_count_nodes($tid, $type = 0) {
static $count;
if (!isset($count[$type])) {
// $type == 0 always evaluates TRUE if $type is a string
if (is_numeric($type)) {
$result = db_query("SELECT COUNT(1) AS c FROM node n JOIN term_node tn ON n.vid = tn.vid JOIN term_edge e ON tn.tid = e.tid WHERE n.status = 1 AND e.parent = %d", $tid);
}
else {
$result = db_query("SELECT COUNT(1) AS c FROM node n JOIN term_node tn ON n.vid = tn.vid JOIN term_edge e ON tn.tid = e.tid WHERE n.status = 1 AND e.parent = %d AND n.type = '%s'", $tid, $type);
}
$count[$type][$tid] = db_result($result);
}
return isset($count[$type][$tid]) ? $count[$type][$tid] : 0;
}
/**
* Reimplementation of taxonomy_select_nodes().
* Uses the Taxonomy Edge table instead of cpu/mem (mostly mem) hungry taxonomy_get_tree().
* @note This function also fixes the "broken" default order by from the taxonomy_select_nodes() function,
* by added "n.nid" to the ORDER BY clause. This is ensure consistent results on each query.
*
* @see taxonomy_select_nodes()
*/
function taxonomy_edge_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC, n.nid DESC') {
// Fix broken default order by the Taxonomy Module.
// Ordering without a unique key could result in incosistent result sets,
// depending on which index (if any) the database decides to use for the query.
switch ($order) {
case 'n.sticky DESC, n.created DESC':
$order .= ', n.nid DESC';
break;
case 'n.sticky ASC, n.created ASC':
$order .= ', n.nid ASC';
break;
}
// If possible, use optimize by using GROUP BY instead of DISTINCT.
// This is what the idx_taxonomy_edge index is used for.
$group_by = '';
$distinct = 'DISTINCT(n.nid)';
global $db_type;
if ($db_type == 'mysql' || $db_type == 'mysqli') {
switch ($order) {
case 'n.sticky DESC, n.created DESC, n.nid DESC':
case 'n.sticky ASC, n.created ASC, n.nid ASC':
$group_by = ' GROUP BY n.sticky, n.created, n.nid';
$distinct = 'n.nid';
break;
case 'n.created DESC, n.nid DESC':
case 'n.created ASC, n.nid ASC':
$group_by = ' GROUP BY n.created, n.nid';
$distinct = 'n.nid';
break;
}
}
if (count($tids) > 0) {
if ($operator == 'or') {
$args = $tids;
$placeholders = db_placeholders($args, 'int');
if ($depth === 'all') {
$sql = 'SELECT ' . $distinct . ', n.sticky, n.title, n.created FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid JOIN {term_edge} te ON tn.tid = te.tid WHERE te.parent IN(' . $placeholders . ') AND n.status = 1 ' . $group_by . ' ORDER BY ' . $order;
$sql_count = 'SELECT COUNT(DISTINCT n.nid) FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid JOIN {term_edge} te ON tn.tid = te.tid WHERE te.parent IN(' . $placeholders . ') AND n.status = 1';
}
else {
$sql = 'SELECT ' . $distinct . ', n.sticky, n.title, n.created FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid JOIN {term_edge} te ON tn.tid = te.tid WHERE te.parent IN(' . $placeholders . ') AND te.distance <= %d AND n.status = 1 ' . $group_by . ' ORDER BY ' . $order;
$sql_count = 'SELECT COUNT(DISTINCT n.nid) FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid JOIN {term_edge} te ON tn.tid = te.tid WHERE te.parent IN(' . $placeholders . ') AND te.distance <= %d AND n.status = 1';
$args[] = $depth;
}
}
else {
$join = '';
$args = array();
foreach ($tids as $index => $tid) {
$join .= ' INNER JOIN {term_node} tn' . $index . ' ON n.vid = tn' . $index . '.vid';
$join .= ' INNER JOIN {term_edge} te' . $index . ' ON tn' . $index . '.tid = te' . $index . '.tid AND te' . $index . '.parent = %d';
$args[] = $tid;
if ($depth !== 'all') {
$join .= ' AND te' . $index . '.distance <= %d';
$args[] = $depth;
}
}
$sql = 'SELECT ' . $distinct . ', n.sticky, n.title, n.created FROM {node} n ' . $join . ' WHERE n.status = 1 ' . $group_by . ' ORDER BY ' . $order;
$sql_count = 'SELECT COUNT(DISTINCT n.nid) FROM {node} n ' . $join . ' WHERE n.status = 1';
}
$sql = db_rewrite_sql($sql);
if ($pager) {
$sql_count = db_rewrite_sql($sql_count);
$result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count, $args);
}
else {
$result = db_query_range($sql, $args, 0, variable_get('feed_default_items', 10));
}
}
return $result;
}
Functions
Name | Description |
---|---|
taxonomy_edge_cron | Implements hook_cron(). |
taxonomy_edge_cronapi | Implements hook_cronapi(). Regularly rebuild the edge list and resort trees. |
taxonomy_edge_cron_queue_info | Implements hook_cron_queue_info(). |
taxonomy_edge_cron_rebuild | Rebuild edges |
taxonomy_edge_form_taxonomy_overview_vocabularies_alter | Hook into the overview of vocabularies to provide rebuild actions. |
taxonomy_edge_get_max_depth | Get max depth of a tree.. |
taxonomy_edge_get_parents | Get parent from edge list. |
taxonomy_edge_get_top_tid | Get top term id. |
taxonomy_edge_get_top_tids | Get top term ids. |
taxonomy_edge_get_tree | Reimplementation of taxonomy_get_tree(). Limit db fetch to only specified parent. |
taxonomy_edge_help | Implements hook_help(). |
taxonomy_edge_init | Implements hook_init(). |
taxonomy_edge_invalidate_order | Invalidate order for a vocabulary |
taxonomy_edge_is_build_realtime | Checks if it's possible to build an edge realtime. |
taxonomy_edge_is_order_invalid | Check if our sorted tree is still valid |
taxonomy_edge_is_order_valid | Check if our sorted tree is still valid |
taxonomy_edge_menu | Implements hook_menu(). |
taxonomy_edge_perm | Implements hook_perm(). |
taxonomy_edge_process_queue_item | Cron queue worker Process edge for a queued term. |
taxonomy_edge_select_nodes | Reimplementation of taxonomy_select_nodes(). Uses the Taxonomy Edge table instead of cpu/mem (mostly mem) hungry taxonomy_get_tree(). @note This function also fixes the "broken" default order by from the taxonomy_select_nodes() function, by… |
taxonomy_edge_taxonomy | Implementation of hook_taxonomy(). |
taxonomy_edge_taxonomy_get_tree | BC wrappers after function name changes |
taxonomy_edge_taxonomy_select_nodes | |
taxonomy_edge_taxonomy_term_count_nodes | |
taxonomy_edge_taxonomy_term_delete | Implements hook_taxonomy_term_delete(). |
taxonomy_edge_taxonomy_term_insert | Implements hook_taxonomy_term_insert(). |
taxonomy_edge_taxonomy_term_update | Implements hook_taxonomy_term_update(). |
taxonomy_edge_taxonomy_vocabulary_delete | Implements hook_taxonomy_vocabulary_delete(). |
taxonomy_edge_taxonomy_vocabulary_insert | Implements hook_taxonomy_vocabulary_insert(). |
taxonomy_edge_term_count_nodes | Reimplementation of taxonomy_term_count_nodes(). Avoid GROUP BY and recursive calls. |
taxonomy_edge_theme | Implements hook_theme(). |
_taxonomy_edge_attach_subtree | Attach path and children to new parents |
_taxonomy_edge_db_transaction | Transaction handler wrapper |
_taxonomy_edge_delete_subtree | Delete subtree |
_taxonomy_edge_detach_subtree | Detach children from parents in $tids (delete ancestors) |
_taxonomy_edge_generate_term_path_query | Generate query for ordering by dynamically compiled path |
_taxonomy_edge_move_subtree | Detach path and children from current parent and attach to new parents |
_taxonomy_edge_taxonomy_term_delete | Delete a term from the edge tree. |
_taxonomy_edge_taxonomy_term_insert | Insert a term into the edge tree. |
_taxonomy_edge_taxonomy_term_queue | Queue an operation for the edge tree. |
_taxonomy_edge_taxonomy_term_update | Update a term in the edge tree. |
_taxonomy_edge_unify_parents | Unify parents |
Constants
Name | Description |
---|---|
TAXONOMY_EDGE_BUILD_REALTIME | Default value for realtime building of tree. |
TAXONOMY_EDGE_MAX_DEPTH | Fail safe for avoiding infite loops when rebuilding edges. |
TAXONOMY_EDGE_OPTIMIZED_GET_TREE | Default value for optimized tree. |
TAXONOMY_EDGE_STATIC_CACHING | Default value for static caching. |
TAXONOMY_EDGE_USE_CUSTOM_TRANSACTIONS | Default value for optimized tree. |
Classes
Name | Description |
---|---|
TaxonomyEdgeDatabaseTransaction | Class for simulating DBTNG transactions |