--- taxonomy.module.my 2005-05-25 08:02:07.900573032 +0200 +++ taxonomy.module 2005-05-25 12:05:23.755286456 +0200 @@ -902,6 +902,54 @@ function _taxonomy_prepare_insert($data, return "($result)"; } + +// Here it gets complicated... +function _taxonomy_prepare_tids($tids, $depth) { + $t = array( + "max" => 0, // max number of tids in an AND group + "descendants" => array(), + "OR" => array(), + "AND" => array() + ); + + if ($depth === 'all') + $depth = NULL; + + foreach ($tids as $index => $and_tids) { + $tids_nr = count($and_tids); + if ($tids_nr > $t["max"]) + $t["max"] = $tids_nr; + + foreach ($and_tids as $i => $tid) { + $bare_tid = ltrim($tid, '!'); + $tid_nin = ($bare_tid == $tid ? "IN" : "NOT IN"); // nin == in/not in + + if (! isset($t["descendants"][$bare_tid])) { + // Get descendants + $term = taxonomy_get_term($bare_tid); + $tree = taxonomy_get_tree($term->vid, $bare_tid, -1, $depth); + $t["descendants"][$bare_tid] = array_merge(array($bare_tid), array_map('_taxonomy_get_tid_from_term', $tree)); + } + + if ($tids_nr == 1) { + // Only 1 tid in AND group - let's put it (with it's descendants) directly into OR tids + $t["OR"][$tid_nin] = array_merge($t["OR"][$tid_nin], $t["descendants"][$bare_tid]); + break; // next $and_group + } + + // More then 1 tid in the AND group - add the tid to this group's IN/NOT INT group (but only it, without descendants) + $t["AND"][$index][$tid_nin] = array_merge($t["AND"][$index][$tid_nin], array($bare_tid)); + } + } + + return $t; +} + +// $op = "IN" or "NOT IN" +function _taxonomy_make_sql($tids, $index, $op) { + return "tn{$index}.tid $op (". implode(',', $tids) .')'; +} + /** * Finds all nodes that match selected taxonomy conditions. * @@ -918,42 +966,49 @@ function _taxonomy_prepare_insert($data, * @return * A resource identifier pointing to the query results. */ -function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE) { - if (count($tids) > 0) { - // For each term ID, generate an array of descendant term IDs to the right depth. - $descendant_tids = array(); - if ($depth === 'all') { - $depth = NULL; - } - foreach ($tids as $index => $tid) { - $term = taxonomy_get_term($tid); - $tree = taxonomy_get_tree($term->vid, $tid, -1, $depth); - $descendant_tids[] = array_merge(array($tid), array_map('_taxonomy_get_tid_from_term', $tree)); - } - - if ($operator == 'or') { - $str_tids = implode(',', call_user_func_array('array_merge', $descendant_tids)); - $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid IN ('. $str_tids .') AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'; - $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid IN ('. $str_tids .') AND n.status = 1'; - } - else { - $joins = ''; - $wheres = ''; - foreach ($descendant_tids as $index => $tids) { - $joins .= ' INNER JOIN {term_node} tn'. $index .' ON n.nid = tn'. $index .'.nid'; - $wheres .= ' AND tn'. $index .'.tid IN ('. implode(',', $tids) .')'; - } - $sql = 'SELECT n.nid, n.sticky, n.title, n.created FROM {node} n '. $joins .' WHERE n.status = 1 '. $wheres .' ORDER BY n.sticky DESC, n.created DESC'; - $sql_count = 'SELECT COUNT(n.nid) FROM {node} n '. $joins .' WHERE n.status = 1 ' . $wheres; - } - $sql = db_rewrite_sql($sql); - $sql_count = db_rewrite_sql($sql_count); - if ($pager) { - $result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count); - } - else { - $result = db_query_range($sql, 0, 15); - } +function taxonomy_select_nodes($tids = array(), $depth = 0, $pager = TRUE) { + if (count($tids) <= 0) { + return NULL; + } + + $tids = _taxonomy_prepare_tids($tids, $depth); + $max_and_tids = $tids["max"]; + $or_tids = $tids["OR"]; + $and_tids = $tids["AND"]; + $descendants = $tids["descendants"]; + + $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM {node} n'; + $sql_count = 'SELECT DISTINCT(n.nid) FROM {node} n'; + for ($i = 0; $i < $max_and_tids; $i++) { + $sql_body .= ' INNER JOIN {term_node} tn'. $i .' ON n.nid = tn'. $i .'.nid'; + } + $sql_body .= ' WHERE n.status = 1 AND (0=1'; + if ($or_tids["IN"]) $sql_body .= ' OR '. _taxonomy_make_sql($or_tids["IN"], 0, "IN"); + if ($or_tids["NOT IN"]) $sql_body .= ' OR '. _taxonomy_make_sql($or_tids["NOT IN"], 0, "NOT IN"); + + // This will construct sql for AND groups, e.g. if we specified 1,2+3,4,!5 this should produce something like + // OR (1=1 AND tn0.tid IN (1) AND tn1.tid IN (2)) OR (1=1 AND tn0.tid IN (3) AND tn1.tid IN (4) AND tn2.tid NOT IN (5)) + foreach ($and_tids as $i => $and_group) { + $sql_group = ''; $index = 0; + foreach ($and_group["IN"] as $j => $tid) { + $sql_group .= ' AND '. _taxonomy_make_sql($descendants[$tid], $index++, "IN"); + } + foreach ($and_group["NOT IN"] as $j => $tid) { + $sql_group .= ' AND '. _taxonomy_make_sql($descendants[$tid], $index++, "NOT IN"); + } + $sql_body .= ' OR (1=1'. $sql_group .')'; + } + + $sql .= $sql_body .') ORDER BY n.sticky DESC, n.created DESC'; + $sql_count .= $sql_body .')'; + + $sql = db_rewrite_sql($sql); + $sql_count = db_rewrite_sql($sql_count); + if ($pager) { + $result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count); + } + else { + $result = db_query_range($sql, 0, 15); } return $result; @@ -1000,64 +1055,28 @@ function taxonomy_nodeapi($node, $op, $a * Menu callback; displays all nodes associated with a term. */ function taxonomy_term_page($str_tids = '', $depth = 0, $op = 'page') { - if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) { - $operator = 'or'; - // The '+' character in a query string may be parsed as ' '. - $tids = preg_split('/[+ ]/', $str_tids); - } - else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) { - $operator = 'and'; - $tids = explode(',', $str_tids); - } - else { + if(! preg_match('/^(!?[0-9]+[,+])*!?[0-9]+$/', $str_tids)) drupal_not_found(); - } - + // Needed for '+' to show up in RSS discovery URLs $rss_tids = urlencode($str_tids); - if (function_exists('taxonomy_access')) { - $access_tids = array(); - foreach($tids as $tid) { - if (taxonomy_access('view', $tid)) { - $access_tids[] = $tid; - } - } - if ($access_tids) { - $tids = $access_tids; - } - else { - drupal_access_denied(); - return; - } + // Split first by OR (least important), then AND and then NOT + // E.g. for 1,2+!3 this will create + // array( array ("1", "2"), + // array ("!3") ); + // The '+' character in a query string may be parsed as ' '. + foreach (preg_split('/[+ ]/', $str_tids) as $and_tids) { + $tids[] = explode(',', $and_tids); } if ($tids) { - // Build title: - $result = db_query('SELECT name FROM {term_data} WHERE tid IN (%s)', implode(',', $tids)); - $names = array(); - while ($term = db_fetch_object($result)) { - $names[] = $term->name; - } - + $names = TRUE; if ($names) { - drupal_set_title($title = implode(', ', $names)); switch ($op) { case 'page': - // Build breadcrumb based on first hierarchy of first term: - $current->tid = $tids[0]; - $breadcrumbs = array(array('path' => $_GET['q'])); - while ($parents = taxonomy_get_parents($current->tid)) { - $current = array_shift($parents); - $breadcrumbs[] = array('path' => 'taxonomy/term/'. $current->tid, 'title' => $current->name); - } - $breadcrumbs = array_reverse($breadcrumbs); - menu_set_location($breadcrumbs); - - drupal_set_html_head(''); - - $output = taxonomy_render_nodes(taxonomy_select_nodes($tids, $operator, $depth, TRUE)); + $output = taxonomy_render_nodes(taxonomy_select_nodes($tids, $depth, TRUE)); $output .= theme('xml_icon', url("taxonomy/term/$rss_tids/$depth/feed")); print theme('page', $output); break; @@ -1068,7 +1087,7 @@ function taxonomy_term_page($str_tids = $channel['title'] = variable_get('site_name', 'drupal') .' - '. $title; $channel['description'] = $term->description; - $result = taxonomy_select_nodes($tids, $operator, $depth, FALSE); + $result = taxonomy_select_nodes($tids, $depth, FALSE); node_feed($result, $channel); break; default: