diff -urp --strip-trailing-cr ../category/category_display/category_display.module ./category_display/category_display.module --- ../category/category_display/category_display.module 2009-06-08 21:46:26.000000000 +0200 +++ ./category_display/category_display.module 2009-06-26 01:52:25.000000000 +0200 @@ -321,14 +321,19 @@ function category_display_category_legac * The display settings for the specified node. */ function category_display_get_container($nid) { - $result = db_query("SELECT * FROM {category_display} WHERE cid = %d", $nid); + $cache_key = 'cdisp_get_cont'; + $container = category_cache_op('get', $nid, $cache_key); + if (!isset($container)) { - if ($container = db_fetch_object($result)) { - return $container; - } - else { - return (object) _category_display_defaults(); + $result = db_query("SELECT * FROM {category_display} WHERE cid = %d", $nid); + $container = db_fetch_object($result); + if (empty($container)) { + $container = (object) _category_display_defaults(); + } + + category_cache_op('set', $nid, $cache_key, $container); } + return $container; } /** @@ -351,6 +356,7 @@ function category_display_save_container $status = SAVED_UPDATED; } + category_cache_op('flush', $node->nid); return $status; } @@ -363,6 +369,7 @@ function category_display_save_container */ function category_display_del_container($nid) { db_query('DELETE FROM {category_display} WHERE cid = %d', $nid); + category_cache_op('flush', $nid); } /** @@ -509,85 +516,93 @@ function category_display_next($book_lin * Format the menu links for the child pages of the current page. */ function category_display_children($book_link, $container_info) { - $book_link_orig = $book_link; + $cache_key = 'cdisp_children'. $container_info->cid .':'. $book_link['mlid']; + $output = category_cache_op('get', 0, $cache_key); + if (!isset($output)) { - // If the TOC has more than one level, then we need to find a menu item with - // a specific depth. For example, if we want the TOC to be five levels deep, - // then we need to find a menu item at the fifth level of the hierarchy (or - // as close to it as possible). This menu item then gets passed to - // category_menu_tree_all_data(), so that it retrieves a menu hierarchy - // with the depth we need. - if ($container_info->toc_depth != 0 && isset($book_link)) { - if ($container_info->toc_depth == -1 || $container_info->toc_depth > MENU_MAX_DEPTH) { - $container_info->toc_depth = MENU_MAX_DEPTH; - } - $container_info->toc_depth += $book_link['depth']; - if ($book_link_new = _category_display_children_recurse($book_link, $container_info)) { - $book_link = $book_link_new; + $book_link_orig = $book_link; + + // If the TOC has more than one level, then we need to find a menu item with + // a specific depth. For example, if we want the TOC to be five levels deep, + // then we need to find a menu item at the fifth level of the hierarchy (or + // as close to it as possible). This menu item then gets passed to + // category_menu_tree_all_data(), so that it retrieves a menu hierarchy + // with the depth we need. + if ($container_info->toc_depth != 0 && isset($book_link)) { + if ($container_info->toc_depth == -1 || $container_info->toc_depth > MENU_MAX_DEPTH) { + $container_info->toc_depth = MENU_MAX_DEPTH; + } + $container_info->toc_depth += $book_link['depth']; + if ($book_link_new = _category_display_children_recurse($book_link, $container_info)) { + $book_link = $book_link_new; + } } - } - $book_link = $book_link_orig; + $book_link = $book_link_orig; - $tree = array(); + $tree = array(); - if (isset($book_link)) { - $tree = category_menu_tree_all_data($book_link_orig['menu_name'], $book_link, $container_info->toc_depth); - } - // When no menu link is provided to this function, we're generating a TOC - // for a hidden container. This is typically useful for things such as a site - // map. All of the below code is for the special case of hidden containers - // that have no menu link. - else { - $children = category_get_children($container_info->cid); - $containers_map = array(); - if (!empty($children)) { - foreach (array_keys($children) as $child_cid) { - $child_cnid = !empty($children[$child_cid]->cnid) ? $children[$child_cid]->cnid : $child_cid; - $child_menu = category_menu_get_container($child_cnid); - $child_display = category_display_get_container($child_cnid); - $child_menu_map = _category_display_children_get_menu_map($children[$child_cid], $child_cnid); - - if ($container_info->toc_depth != 0) { - if ($container_info->toc_depth == -1 || $container_info->toc_depth > MENU_MAX_DEPTH) { - $container_info->toc_depth = MENU_MAX_DEPTH; + if (isset($book_link)) { + $tree = category_menu_tree_all_data($book_link_orig['menu_name'], $book_link, $container_info->toc_depth); + } + // When no menu link is provided to this function, we're generating a TOC + // for a hidden container. This is typically useful for things such as a site + // map. All of the below code is for the special case of hidden containers + // that have no menu link. + else { + $children = category_get_children($container_info->cid); + $containers_map = array(); + if (!empty($children)) { + foreach (array_keys($children) as $child_cid) { + $child_cnid = !empty($children[$child_cid]->cnid) ? $children[$child_cid]->cnid : $child_cid; + $child_menu = category_menu_get_container($child_cnid); + $child_display = category_display_get_container($child_cnid); + $child_menu_map = _category_display_children_get_menu_map($children[$child_cid], $child_cnid); + + if ($container_info->toc_depth != 0) { + if ($container_info->toc_depth == -1 || $container_info->toc_depth > MENU_MAX_DEPTH) { + $container_info->toc_depth = MENU_MAX_DEPTH; + } + $container_info->toc_depth += $child_menu_map['depth']; + $container_info->toc_depth--; + if ($child_menu_map_new = _category_display_children_recurse($child_menu_map, $container_info, 1, TRUE)) { + $child_menu_map = $child_menu_map_new; + } } - $container_info->toc_depth += $child_menu_map['depth']; - $container_info->toc_depth--; - if ($child_menu_map_new = _category_display_children_recurse($child_menu_map, $container_info, 1, TRUE)) { - $child_menu_map = $child_menu_map_new; + else { + $container_info->toc_depth = $child_menu_map['depth']; + $container_info->toc_depth--; } - } - else { - $container_info->toc_depth = $child_menu_map['depth']; - $container_info->toc_depth--; - } - if ($child_menu->links_for_cats && !isset($containers_map[$child_cnid])) { - $tree_new = category_menu_tree_all_data('category-menu-toc-'. $child_cnid, $child_menu_map, $container_info->toc_depth); - _category_display_children_prune_tagged_content($tree_new, $container_info); - $tree += $tree_new; - $containers_map[$child_cnid] = TRUE; + if ($child_menu->links_for_cats && !isset($containers_map[$child_cnid])) { + $tree_new = category_menu_tree_all_data('category-menu-toc-'. $child_cnid, $child_menu_map, $container_info->toc_depth); + _category_display_children_prune_tagged_content($tree_new, $container_info); + $tree += $tree_new; + $containers_map[$child_cnid] = TRUE; + } } - } - if (!$container_info->toc_depth) { - foreach (array_keys($tree) as $key) { - unset($tree[$key]['below']); + if (!$container_info->toc_depth) { + foreach (array_keys($tree) as $key) { + unset($tree[$key]['below']); + } } } } - } - // We've retrieved a menu hierarchy with the desired depth. Now we set the - // menu link back to what it was originally, and we reduce the hierarchy so - // that it doesn't include the parents of the current page, or any menu links - // for assigned content. - if (isset($book_link)) { - $tree = _category_display_children_reduce($tree, $book_link, $container_info); - } + // We've retrieved a menu hierarchy with the desired depth. Now we set the + // menu link back to what it was originally, and we reduce the hierarchy so + // that it doesn't include the parents of the current page, or any menu links + // for assigned content. + if (isset($book_link)) { + $tree = _category_display_children_reduce($tree, $book_link, $container_info); + } - return $tree ? ($container_info->toc_nodecount ? category_display_menu_tree_output($tree) : menu_tree_output($tree)) : ''; + $output = $tree ? ($container_info->toc_nodecount ? category_display_menu_tree_output($tree) : menu_tree_output($tree)) : ''; + + category_cache_op('set', 0, $cache_key, $output); + } + return $output; } /** @@ -688,7 +703,7 @@ function _category_display_children_prun preg_match('/^node\/(\d+)$/', $tree[$key]['link']['link_path'], $matches); if (!empty($matches[1]) && $type = db_result(db_query("SELECT type FROM {node} WHERE nid = %d", $matches[1]))) { $behavior = variable_get('category_behavior_'. $type, 0); - if (empty($behavior) && db_result(db_query("SELECT n.nid FROM {node} n INNER JOIN {category_node} cn ON n.nid = cn.nid WHERE n.type = '%s'", $type))) { + if (empty($behavior)) { $include_link = FALSE; } } diff -urp --strip-trailing-cr ../category/category.inc ./category.inc --- ../category/category.inc 2008-07-21 11:30:48.000000000 +0200 +++ ./category.inc 2009-06-28 17:58:59.000000000 +0200 @@ -18,31 +18,38 @@ * If set, return only those containers associated with this node type. */ function category_get_containers($type = NULL) { - $type_sql = $type ? " WHERE nt.type = '%s'" : ""; - $result = db_query(db_rewrite_sql("SELECT n.nid, n.title, cn.*, c.weight, c.depth, n.status, nt.type FROM {category_cont} cn INNER JOIN {category} c ON cn.cid = c.cid INNER JOIN {node} n ON cn.cid = n.nid LEFT JOIN {category_cont_node_types} nt ON cn.cid = nt.cid". $type_sql ." ORDER BY c.weight, n.title", 'n', 'nid'), $type); + $cache_key = 'c_get_conts'. $type; + $containers = category_cache_op('get', 0, $cache_key); + if (!isset($containers)) { - $containers = array(); - $node_types = array(); - while ($container = db_fetch_object($result)) { - // If no node types are associated with a container, the LEFT JOIN will - // return a NULL value for type. - if (isset($container->type)) { - $node_types[$container->cid][$container->type] = $container->type; - unset($container->type); - $container->nodes = $node_types[$container->cid]; - } - elseif (!isset($container->nodes)) { - $container->nodes = array(); - } - - if (empty($container->admin_title)) { - $container->admin_title = ''; - $container->has_admin_title = FALSE; - } - else { - $container->has_admin_title = TRUE; + $type_sql = $type ? " WHERE nt.type = '%s'" : ""; + $result = db_query(db_rewrite_sql("SELECT n.nid, n.title, cn.*, c.weight, c.depth, n.status, nt.type FROM {category_cont} cn INNER JOIN {category} c ON cn.cid = c.cid INNER JOIN {node} n ON cn.cid = n.nid LEFT JOIN {category_cont_node_types} nt ON cn.cid = nt.cid". $type_sql ." ORDER BY c.weight, n.title", 'n', 'nid'), $type); + + $containers = array(); + $node_types = array(); + while ($container = db_fetch_object($result)) { + // If no node types are associated with a container, the LEFT JOIN will + // return a NULL value for type. + if (isset($container->type)) { + $node_types[$container->cid][$container->type] = $container->type; + unset($container->type); + $container->nodes = $node_types[$container->cid]; + } + elseif (!isset($container->nodes)) { + $container->nodes = array(); + } + + if (empty($container->admin_title)) { + $container->admin_title = ''; + $container->has_admin_title = FALSE; + } + else { + $container->has_admin_title = TRUE; + } + $containers[$container->cid] = $container; } - $containers[$container->cid] = $container; + + category_cache_op('set', 0, $cache_key, $containers); } return $containers; @@ -52,10 +59,17 @@ function category_get_containers($type = * Find all categories associated to the given node, within one container. */ function category_node_get_categories_by_container($node, $cnid, $key = 'cid') { - $result = db_query(db_rewrite_sql('SELECT c.*, n.title FROM {category} c INNER JOIN {category_node} r ON c.cid = r.cid INNER JOIN {node} n ON c.cid = n.nid WHERE c.cnid = %d AND r.vid = %d AND n.status = 1 ORDER BY c.weight'), $cnid, $node->vid); - $categories = array(); - while ($category = db_fetch_object($result)) { - $categories[$category->$key] = $category; + $cache_key = 'c_node_get_cats_by_cont'. $node->vid .':'. $cnid .':'. $key; + $categories = category_cache_op('get', $node->nid, $cache_key); + if (!isset($categories)) { + + $result = db_query(db_rewrite_sql('SELECT c.*, n.title FROM {category} c INNER JOIN {category_node} r ON c.cid = r.cid INNER JOIN {node} n ON c.cid = n.nid WHERE c.cnid = %d AND r.vid = %d AND n.status = 1 ORDER BY c.weight'), $cnid, $node->vid); + $categories = array(); + while ($category = db_fetch_object($result)) { + $categories[$category->$key] = $category; + } + + category_cache_op('set', $node->nid, $cache_key, $categories); } return $categories; } @@ -63,20 +77,22 @@ function category_node_get_categories_by /** * Find all categories associated to the given node, ordered by container and category weight. */ -function category_node_get_categories($node, $key = 'cid', $reset = FALSE) { - static $categories = array(); +function category_node_get_categories($node, $key = 'cid') { + $cache_key = 'cat_node_get_cats'. $node->vid .':'. $key; + $categories = category_cache_op('get', $node->nid, $cache_key); + if (!isset($categories)) { - if (!isset($categories[$node->vid]) || $reset) { - $categories[$node->vid] = array(); + $categories = array(); $result = db_query(db_rewrite_sql('SELECT n.nid, nr.teaser AS description, r.vid AS node_id, c.*, n.title FROM {category} c INNER JOIN {category_node} r ON c.cid = r.cid INNER JOIN {category} cn ON c.cnid = cn.cid INNER JOIN {node} n ON c.cid = n.nid INNER JOIN {node_revisions} nr ON n.vid = nr.vid INNER JOIN {node} cnn ON cn.cid = cnn.nid WHERE n.status = 1 AND r.vid = %d ORDER BY cn.weight, cnn.title, c.weight, n.title', 'n', 'nid'), $node->vid); while ($category = db_fetch_object($result)) { - $categories[$node->vid][$category->$key] = $category; + $categories[$category->$key] = $category; } - } - return $categories[$node->vid]; + category_cache_op('set', $node->nid, $cache_key, $categories); + } + return $categories; } /** @@ -84,10 +100,17 @@ function category_node_get_categories($n */ function category_get_related($cid, $key = 'cid') { if ($cid) { - $result = db_query(db_rewrite_sql('SELECT c.*, n.title, cid1, cid2 FROM {category_relation}, {category} c, {node} n WHERE (c.cid = cid1 OR c.cid = cid2) AND (cid1 = %d OR cid2 = %d) AND c.cid = n.nid AND c.cid != %d AND n.status = 1 ORDER BY c.weight, n.title'), $cid, $cid, $cid); - $related = array(); - while ($category = db_fetch_object($result)) { - $related[$category->$key] = $category; + $cache_key = 'cat_get_related'. $key; + $related = category_cache_op('get', $cid, $cache_key); + if (!isset($related)) { + + $result = db_query(db_rewrite_sql('SELECT c.*, n.title, cid1, cid2 FROM {category_relation}, {category} c, {node} n WHERE (c.cid = cid1 OR c.cid = cid2) AND (cid1 = %d OR cid2 = %d) AND c.cid = n.nid AND c.cid != %d AND n.status = 1 ORDER BY c.weight, n.title'), $cid, $cid, $cid); + $related = array(); + while ($category = db_fetch_object($result)) { + $related[$category->$key] = $category; + } + + category_cache_op('set', $cid, $cache_key, $related); } return $related; } @@ -100,33 +123,26 @@ function category_get_related($cid, $key * Find all parents of a given category ID. * This function will return the parents in an indexed array sorted by the parents' weight and title. */ -function category_get_parents($cid, $key = 'cid', $distant = TRUE, $reset = FALSE) { - static $parents, $distant_parents; - +function category_get_parents($cid, $key = 'cid', $distant = TRUE) { if (!$cid) { return array(); } - if (($distant && !isset($distant_parents)) || (!$distant && !isset($parents)) || $reset) { + $cache_key = 'cat_get_parents'. ($distant ? 'd' : 'n'); + $parents = category_cache_op('get', 0, $cache_key); + if (!isset($parents)) { + $distant_sql = $distant ? '' : ' AND (c.cnid = cc.cnid OR c.cnid = 0)'; $result = db_query(db_rewrite_sql('SELECT c.*, h.cid AS child, n.title, cn.admin_title FROM {category} c INNER JOIN {category_hierarchy} h ON c.cid = h.parent INNER JOIN {node} n ON c.cid = n.nid LEFT JOIN {category_cont} cn ON c.cid = cn.cid INNER JOIN {category} cc ON h.cid = cc.cid WHERE n.status = 1'. $distant_sql .' ORDER BY c.weight, n.title'), $cid); - if ($distant) { - $distant_parents = array(); - } - else { - $parents = array(); - } + $parents = array(); while ($parent = db_fetch_object($result)) { if (empty($parent->admin_title)) { $parent->admin_title = $parent->title; } - if ($distant) { - $distant_parents[$parent->child][$parent->$key] = $parent; - } - else { - $parents[$parent->child][$parent->$key] = $parent; - } + $parents[$parent->child][$parent->$key] = $parent; } + + category_cache_op('set', 0, $cache_key, $parents); } $no_parent = new StdClass(); @@ -137,7 +153,7 @@ function category_get_parents($cid, $key $no_parent->title = ''; $no_parent->admin_title = ''; $no_parent->child = $cid; - return $distant ? (isset($distant_parents[$cid]) ? $distant_parents[$cid] : array(0 => $no_parent)) : (isset($parents[$cid]) ? $parents[$cid] : array(0 => $no_parent)); + return isset($parents[$cid]) ? $parents[$cid] : array(0 => $no_parent); } /** @@ -159,26 +175,37 @@ function category_get_parents_all($cid, /** * Find all children of a category ID. */ -function category_get_children($cid, $cnid = 0, $key = 'cid', $reset = FALSE) { - static $children; +function category_get_children($cid, $cnid = 0, $key = 'cid') { if ($cnid) { - $result = db_query(db_rewrite_sql('SELECT c.*, n.title FROM {category} c INNER JOIN {category_hierarchy} h ON c.cid = h.cid INNER JOIN {node} n ON c.cid = n.nid WHERE h.parent = %d AND n.status = 1 AND c.cnid = %d ORDER BY c.weight, n.title'), $cid, $cnid); + $cache_key = 'cat_get_children_cn'. $cnid .':'. $key; + $children = category_cache_op('get', $cid, $cache_key); + if (!isset($children)) { + + $result = db_query(db_rewrite_sql('SELECT c.*, n.title FROM {category} c INNER JOIN {category_hierarchy} h ON c.cid = h.cid INNER JOIN {node} n ON c.cid = n.nid WHERE h.parent = %d AND n.status = 1 AND c.cnid = %d ORDER BY c.weight, n.title'), $cid, $cnid); + + $children = array(); + while ($category = db_fetch_object($result)) { + $children[$category->$key] = $category; + } - $results = array(); - while ($category = db_fetch_object($result)) { - $results[$category->$key] = $category; + category_cache_op('set', $cid, $cache_key, $children); } - - return $results; + return $children; } - elseif (!isset($children) || $reset) { + + $cache_key = 'cat_get_children_c0'. $key; + $children = category_cache_op('get', 0, $cache_key); + if (!isset($children)) { + $result = db_query(db_rewrite_sql('SELECT c.*, h.parent, n.title FROM {category} c INNER JOIN {category_hierarchy} h ON c.cid = h.cid INNER JOIN {node} n ON c.cid = n.nid WHERE n.status = 1 ORDER BY c.weight, n.title'), $cid); $children = array(); while ($category = db_fetch_object($result)) { $children[$category->parent][$category->$key] = $category; } + + category_cache_op('set', 0, $cache_key, $children); } return isset($children[$cid]) ? $children[$cid] : array(); @@ -209,8 +236,6 @@ function category_get_children($cid, $cn * to have "depth" and "parents" attributes in addition to its normal ones. */ function category_get_tree($cnid, $parent = NULL, $depth = -1, $max_depth = NULL, $distant = FALSE) { - static $children, $parents, $categories; - if (empty($cnid) && $depth == -1) { $distant = TRUE; } @@ -222,10 +247,20 @@ function category_get_tree($cnid, $paren // We cache trees, so it's not CPU-intensive to call get_tree() on a category // and its children, too. - if (!isset($children[$cnid])) { + $cache_key = 'cat_get_tree'. $depth .':'. ($distant ? 'd' : 'n'); + $data = category_cache_op('get', $cnid, $cache_key); + if (isset($data)) { + $children = $data['children']; + $categories = $data['categories']; + $parents = $data['parents']; + } + else { + $distant_sql = ($distant) ? '' : 'AND c.cnid = %d '; - $children[$cnid] = array(); + $children = array(); + $categories = array(); + $parents = array(); $result = db_query(db_rewrite_sql('SELECT c.cid, c.cnid, c.weight, c.depth, h.parent, n.title, cn.admin_title FROM {category} c INNER JOIN {category_hierarchy} h ON c.cid = h.cid INNER JOIN {node} n ON c.cid = n.nid LEFT JOIN {category_cont} cn ON c.cid = cn.cid LEFT JOIN {category} c2 ON h.parent = c2.cid WHERE (c2.cid IS NOT NULL OR h.parent = 0) AND n.status = 1 '. $distant_sql .'ORDER BY c.weight, n.title', 'n', 'nid'), $cnid); while ($category = db_fetch_object($result)) { @@ -236,29 +271,30 @@ function category_get_tree($cnid, $paren } } - $children[$cnid][$category->parent][] = $category->cid; - $categories[$cnid][$category->cid] = $category; - $parents[$cnid][$category->cid][] = $category->parent; + $children[$category->parent][] = $category->cid; + $categories[$category->cid] = $category; + $parents[$category->cid][] = $category->parent; } + category_cache_op('set', $cnid, $cache_key, array('children' => $children, 'categories' => $categories, 'parents' => $parents)); } - $max_depth = (is_null($max_depth) && isset($children[$cnid])) ? count($children[$cnid]) : $max_depth; + $max_depth = (is_null($max_depth) && !empty($children)) ? count($children) : $max_depth; $tree = array(); - if (!empty($children[$cnid]) && !empty($children[$cnid][$parent])) { - foreach ($children[$cnid][$parent] as $child) { + if (!empty($children) && !empty($children[$parent])) { + foreach ($children[$parent] as $child) { if ($max_depth > $depth) { - $cat = drupal_clone($categories[$cnid][$child]); + $cat = drupal_clone($categories[$child]); $cat->depth = $depth; // The "parent" attribute is not useful, as it would show one parent only. unset($cat->parent); - $cat->parents = $parents[$cnid][$child]; + $cat->parents = $parents[$child]; if (!$cat->cnid && empty($cat->admin_title)) { $cat->admin_title = $cat->title; } $tree[] = $cat; - if (!empty($children[$cnid][$child])) { + if (!empty($children[$child])) { $tree = array_merge($tree, category_get_tree($cnid, $child, $depth, $max_depth, $distant)); } } @@ -273,10 +309,17 @@ function category_get_tree($cnid, $paren */ function category_get_synonyms($cid) { if ($cid) { - $synonyms = array(); - $result = db_query('SELECT name FROM {category_synonym} WHERE cid = %d', $cid); - while ($synonym = db_fetch_array($result)) { - $synonyms[] = $synonym['name']; + $cache_key = 'cat_get_synonyms'; + $synonyms = category_cache_op('get', $cid, $cache_key); + if (!isset($synonyms)) { + + $synonyms = array(); + $result = db_query('SELECT name FROM {category_synonym} WHERE cid = %d', $cid); + while ($synonym = db_fetch_array($result)) { + $synonyms[] = $synonym['name']; + } + + category_cache_op('set', $cid, $cache_key, $synonyms); } return $synonyms; } @@ -296,9 +339,9 @@ function category_get_synonym_root($syno * Given a category id, count the number of published nodes in it. */ function category_category_count_nodes($cid, $type = 0) { - static $count; - - if (!isset($count[$type])) { + $cache_key = 'cat_cat_count_nodes'. $type; + $count = category_cache_op('get', 0, $cache_key); + if (!isset($count)) { // $type == 0 always evaluates true if $type is a string if (is_numeric($type)) { $result = db_query(db_rewrite_sql('SELECT c.cid, COUNT(n.nid) AS cnt FROM {category_node} c INNER JOIN {node} n ON c.nid = n.nid WHERE n.status = 1 GROUP BY c.cid')); @@ -307,27 +350,29 @@ function category_category_count_nodes($ $result = db_query(db_rewrite_sql("SELECT c.cid, COUNT(n.nid) AS cnt FROM {category_node} c INNER JOIN {node} n ON c.nid = n.nid WHERE n.status = 1 AND n.type = '%s' GROUP BY c.cid"), $type); } while ($category = db_fetch_object($result)) { - $count[$type][$category->cid] = $category->cnt; + $count[$category->cid] = $category->cnt; } + category_cache_op('set', 0, $cache_key, $count); } $children_count = 0; foreach (_category_category_children($cid) as $c) { $children_count += category_category_count_nodes($c, $type); } - return $children_count + (isset($count[$type][$cid]) ? $count[$type][$cid] : 0); + return $children_count + (isset($count[$cid]) ? $count[$cid] : 0); } /** * Helper for category_category_count_nodes(). */ function _category_category_children($cid) { - static $children; - + $cache_key = 'cat_cat_childr'; + $children = category_cache_op('get', 0, $cache_key); if (!isset($children)) { $result = db_query('SELECT cid, parent FROM {category_hierarchy}'); while ($category = db_fetch_object($result)) { $children[$category->parent][] = $category->cid; } + category_cache_op('set', 0, $cache_key, $children); } return isset($children[$cid]) ? $children[$cid] : array(); } @@ -362,15 +407,15 @@ function category_get_category_by_name($ * * @return * The container object with all of its metadata, if exists, NULL otherwise. - * Results are statically cached. + * Results are cached. */ function category_get_container($cnid) { - static $containers = array(); - - if (!isset($containers[$cnid])) { + $cache_key = 'cat_get_cont'; + $container = category_cache_op('get', $cnid, $cache_key); + if (!isset($container)) { // Initialize so if this container does not exist, we have // that cached, and we will not try to load this later. - $containers[$cnid] = FALSE; + $container = FALSE; // Try to load the data and fill up the object. $result = db_query(db_rewrite_sql('SELECT n.nid, cn.*, n.title, c.weight, c.depth, ct.type, c.weight AS cont_weight, c.depth AS cont_depth FROM {category} c INNER JOIN {category_cont} cn ON c.cid = cn.cid INNER JOIN {node} n ON c.cid = n.nid LEFT JOIN {category_cont_node_types} ct ON c.cid = ct.cid WHERE c.cid = %d ORDER BY c.weight, n.title', 'n', 'nid'), $cnid); $node_types = array(); @@ -392,12 +437,13 @@ function category_get_container($cnid) { $cont->has_admin_title = TRUE; } - $containers[$cnid] = $cont; + $container = $cont; } + category_cache_op('set', $cnid, $cache_key, $container); } // Return NULL if this vocabulary does not exist. - return (isset($containers[$cnid]) ? $containers[$cnid] : NULL); + return (!empty($container) ? $container : NULL); } /** @@ -405,20 +451,22 @@ function category_get_container($cnid) { * * @param $cid * The node/category ID of the category to be retrieved. - * @param $reset - * Whether or not to reset the cache of categories. Default is FALSE. * * @return * Populated category object if the category is found, NULL otherwise. */ -function category_get_category($cid, $reset = FALSE) { - static $categories = array(); - - if (!isset($categories[$cid])) { - $categories[$cid] = db_fetch_object(db_query('SELECT n.nid, n.title, c.* FROM {category} c INNER JOIN {node} n ON c.cid = n.nid WHERE cid = %d', $cid)); +function category_get_category($cid) { + $cache_key = 'cat_get_cat'; + $category = category_cache_op('get', $cid, $cache_key); + if (!isset($category)) { + $category = db_fetch_object(db_query('SELECT n.nid, n.title, c.* FROM {category} c INNER JOIN {node} n ON c.cid = n.nid WHERE cid = %d', $cid)); + if (empty($category)) { + $category = FALSE; + } + category_cache_op('set', $cid, $cache_key, $category); } - return (isset($categories[$cid]) ? $categories[$cid] : NULL); + return (!empty($category) ? $category : NULL); } /** @@ -490,7 +538,7 @@ function category_select_nodes($cids = a /** * Accepts the result of a pager_query() call, such as that performed by * category_select_nodes(), and formats each node along with a pager. -*/ + */ function category_render_nodes($result, $category_display) { $output = ''; $has_rows = FALSE; @@ -534,3 +582,88 @@ function category_categories_parse_strin } return $categories; } + +/** + * Central caching function for all kinds of node-related category data, + * so that all data for a given node may be loaded by single query from + * cache table into static variable, and then returned by various API + * functions without any queries needed. The cache is stored per-node, + * and flushed only when given node is saved or deleted. The $nid of '0' + * is used for global summary-data, and flushed on every node save. + * While saving a category or container, we flush the whole cache, as + * this affects the whole hiearchy, and happens rarely. This should not + * be used for data affected by other factors (such as current user's + * permissions), unless the condition is incorporated into the cache key. + * + * @param op + * Operation to be performed - either 'get', 'set', or 'flush'. + * + * @param nid + * Node id to handle data for. 0 is general data, 'all' flushes all. + * + * @param key + * Name of node-data subset to work with. (Usually an API function identificator.) + * + * @param data + * Data for the 'set' operation (will get added to the existing per-node record). + * + * @return + * Cached data, or NULL if none. + */ +function category_cache_op($op, $nid, $key = '', $data = NULL) { + static $cache; + if (!isset($cache)) { + $cache = array('n' => array(), 'g' => array()); + } + + // Flush cache if requested + if ($op == 'flush') { + if ($nid === 'all') { + // Total flush: Empty {cache_category} and static cache entirely + cache_clear_all('*', 'cache_category', TRUE); + $cache = array('n' => array(), 'g' => array()); + } + else { + // Node flush: Delete entries for given node + cache_clear_all('n'. $nid, 'cache_category'); + unset($cache['n'][$nid]); + // Delete also all entries for global data, because we don't really + // know how these might be affected. + cache_clear_all('g', 'cache_category', TRUE); + $cache['g'] = array(); + } + return; + } + + // For global data, we use the $key instead of $nid as identificator, for + // better granularity of data loading (as these are larger pieces). Both + // node/global parts of the cache are separated, to allow for all-global + // flush. + $part = ($nid == 0) ? 'g' : 'n'; + $id = ($nid == 0) ? $key : $nid; + + // Load existing record, if not available in static variable already. + if (!isset($cache[$part][$id])) { + $entry = cache_get($part . $id, 'cache_category'); + if ($entry) { + $cache[$part][$id] = $entry->data; + } + else { + $cache[$part][$id] = array(); + } + } + + // Save new data if required. + if ($op == 'set') { + // Store serialized data, to ensure a fresh copy being always returned from + // static cache, without any references to the original (which might be + // further changed, after it got set to the cache). This is to avoid issues + // with PHP5 always passing and assigning objects as references, and/or with + // any nested by-reference elements. + $cache[$part][$id][$key] = serialize($data); + cache_set($part . $id, $cache[$part][$id], 'cache_category'); + } + + // Return data for given node/key combination. + return isset($cache[$part][$id][$key]) ? unserialize($cache[$part][$id][$key]) : NULL; +} diff -urp --strip-trailing-cr ../category/category.install ./category.install --- ../category/category.install 2009-06-08 21:46:41.000000000 +0200 +++ ./category.install 2009-06-26 21:54:02.000000000 +0200 @@ -379,6 +379,9 @@ function category_schema() { ), ); + $schema['cache_category'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_category']['description'] = 'Cache table for category module, to store various preprocessed data, both per-node and global.'; + return $schema; } @@ -511,3 +514,48 @@ function category_update_6002() { return $ret; } + +/** + * Add cache table. + */ +function category_update_6003() { + $ret = array(); + + // This is verbatim from system module (minus descriptions), to avoid call to + // drupal_get_schema_unprocessed() which may change in future, per schema/updates + // guidelines. + $schema = array( + 'fields' => array( + 'cid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => ''), + 'data' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big'), + 'expire' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0), + 'headers' => array( + 'type' => 'text', + 'not null' => FALSE), + 'serialized' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0) + ), + 'indexes' => array('expire' => array('expire')), + 'primary key' => array('cid'), + ); + + db_create_table($ret, 'cache_category', $schema); + return $ret; +} diff -urp --strip-trailing-cr ../category/category_menu/category_menu.module ./category_menu/category_menu.module --- ../category/category_menu/category_menu.module 2009-06-21 17:25:42.000000000 +0200 +++ ./category_menu/category_menu.module 2009-06-28 18:03:30.000000000 +0200 @@ -142,6 +142,7 @@ function category_menu_menu_link_alter(& $node = db_fetch_array(db_query("SELECT * FROM {category_menu_map} WHERE mlid = %d", $item['mlid'])); if (!empty($node['nid'])) { db_query("UPDATE {category} SET weight = %d WHERE cid = %d", $item['weight'], $node['nid']); + category_cache_op('flush', 'all'); } } } @@ -581,23 +582,29 @@ function category_menu_category_legacy($ * The menu settings for the specified node. */ function category_menu_get_container($nid) { - $result = db_query("SELECT * FROM {category_menu} WHERE cid = %d", $nid); + $cache_key = 'cmenu_get_cont'; + $container = category_cache_op('get', $nid, $cache_key); + if (!isset($container)) { - if ($container = db_fetch_object($result)) { - $links_for_nodes = array(); - foreach (array_keys(node_get_types('names')) as $type) { - $links_setting = variable_get('category_menu_links_'. $type, 0); - if (!empty($links_setting) && $links_setting == $nid) { - $links_for_nodes[$type] = $type; + $result = db_query("SELECT * FROM {category_menu} WHERE cid = %d", $nid); + + if ($container = db_fetch_object($result)) { + $links_for_nodes = array(); + foreach (array_keys(node_get_types('names')) as $type) { + $links_setting = variable_get('category_menu_links_'. $type, 0); + if (!empty($links_setting) && $links_setting == $nid) { + $links_for_nodes[$type] = $type; + } } + $container->links_for_nodes = $links_for_nodes; + } + else { + $container = (object) array(); } - $container->links_for_nodes = $links_for_nodes; - return $container; - } - else { - return (object) array(); + category_cache_op('set', $nid, $cache_key, $container); } + return $container; } /** @@ -632,6 +639,7 @@ function category_menu_save_container(&$ } } + category_cache_op('flush', 'all'); return $status; } @@ -651,6 +659,7 @@ function category_menu_del_container($ni variable_del('category_menu_links_'. $type); } } + category_cache_op('flush', 'all'); } /** @@ -671,12 +680,20 @@ function category_menu_map_load($mlid) { * during hook_nodeapi('load'). */ function category_menu_map_load_by_nid($nid) { - if ($item = db_fetch_array(db_query('SELECT m.*, cmm.nid, ml.* FROM {menu_links} ml INNER JOIN {category_menu_map} cmm ON cmm.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE cmm.nid = %d', $nid))) { + $cache_key = 'cmenu_map_load_nid'; + $item = category_cache_op('get', $nid, $cache_key); + if (!isset($item)) { - $item['options'] = unserialize($item['options']); - return $item; + if ($item = db_fetch_array(db_query('SELECT m.*, cmm.nid, ml.* FROM {menu_links} ml INNER JOIN {category_menu_map} cmm ON cmm.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE cmm.nid = %d', $nid))) { + $item['options'] = unserialize($item['options']); + } + else { + $item = FALSE; + } + + category_cache_op('set', $nid, $cache_key, $item); } - return FALSE; + return $item; } /** @@ -830,7 +847,8 @@ function category_menu_map_save($node) { * by the category hierarchy), or 0 if no suitable menu link was found. */ function _category_menu_map_save_category($nid) { - $parents = category_get_parents($nid, 'cid', TRUE, TRUE); + category_cache_op('flush', 'all'); + $parents = category_get_parents($nid, 'cid', TRUE); $parent = reset($parents); if (empty($parent->cid)) { @@ -922,72 +940,83 @@ function category_menu_build_active_trai * children of bottom-level menu items. */ function category_menu_tree_all_data($menu_name, &$item, $max_depth = NULL, $cache_prefix = 'category-menu-') { - // Start with a regular tree, based on the specified item, containing - // items local to the $menu_name menu. - $tree = _category_menu_tree_all_data($menu_name, $item, $cache_prefix, $max_depth); - - // Next, find top-level items with defined parents, and extend the tree - // to include such parents and their ancestors. - $parent_link = reset($tree); - $parent_link_key = key($tree); - while ($parent_link['link']['plid']) { - $item['has_children'] = 1; - $ancestor_link = menu_link_load($parent_link['link']['plid']); - if ($ancestor_link['menu_name'] != $menu_name) { - $tree = _category_menu_tree_all_data($ancestor_link['menu_name'], $ancestor_link, $cache_prefix); - _category_menu_tree_above($tree, array( - 'key' => $parent_link_key, - 'value' => $parent_link, - )); + // Cache key employs current user's roles, to cover access check performed + // inside _category_menu_tree_all_data(). (TODO: Check whether that check is + // necessary, considering these are our own generated menu items.) + global $user; + $cache_key = 'cmenu_tree_all_data'. $menu_name .':'. $item[$mlid] .':'. $max_depth .':'. implode(':', $user->roles); + $tree = category_cache_op('get', 0, $cache_key); + if (!isset($tree)) { + + // Start with a regular tree, based on the specified item, containing + // items local to the $menu_name menu. + $tree = _category_menu_tree_all_data($menu_name, $item, $cache_prefix, $max_depth); + + // Next, find top-level items with defined parents, and extend the tree + // to include such parents and their ancestors. + $parent_link = reset($tree); + $parent_link_key = key($tree); + while ($parent_link['link']['plid']) { + $item['has_children'] = 1; + $ancestor_link = menu_link_load($parent_link['link']['plid']); + if ($ancestor_link['menu_name'] != $menu_name) { + $tree = _category_menu_tree_all_data($ancestor_link['menu_name'], $ancestor_link, $cache_prefix); + _category_menu_tree_above($tree, array( + 'key' => $parent_link_key, + 'value' => $parent_link, + )); - $parent_link = reset($tree); - $parent_link_key = key($tree); - } - else { - $parent_link['link']['plid'] = 0; - } - }; + $parent_link = reset($tree); + $parent_link_key = key($tree); + } + else { + $parent_link['link']['plid'] = 0; + } + }; - $query_nodetypes1 = ''; - $query_nodetypes2 = ''; - $args = array($item['mlid'], $menu_name); - if ($category = db_fetch_object(db_query('SELECT c.* FROM {category} c INNER JOIN {category_menu_map} cmm ON c.cid = cmm.nid WHERE cmm.mlid = %d', $item['mlid']))) { - $cnid = !empty($category->cnid) ? $category->cnid : $category->cid; - $node_types = array(); - $query_nodetypes = db_query('SELECT type FROM {category_cont_node_types} WHERE cid = %d', $cnid); + $query_nodetypes1 = ''; + $query_nodetypes2 = ''; + $args = array($item['mlid'], $menu_name); + if ($category = db_fetch_object(db_query('SELECT c.* FROM {category} c INNER JOIN {category_menu_map} cmm ON c.cid = cmm.nid WHERE cmm.mlid = %d', $item['mlid']))) { + $cnid = !empty($category->cnid) ? $category->cnid : $category->cid; + $node_types = array(); + $query_nodetypes = db_query('SELECT type FROM {category_cont_node_types} WHERE cid = %d', $cnid); - while ($node_type = db_fetch_object($query_nodetypes)) { - $node_types[] = 'category-menu-toc-'. $node_type->type; - } + while ($node_type = db_fetch_object($query_nodetypes)) { + $node_types[] = 'category-menu-toc-'. $node_type->type; + } - if (!empty($node_types)) { - $query_nodetypes1 = ' LEFT JOIN {category_menu_map} cmm ON ml.mlid = cmm.mlid LEFT JOIN {node} n ON cmm.nid = n.nid'; + if (!empty($node_types)) { + $query_nodetypes1 = ' LEFT JOIN {category_menu_map} cmm ON ml.mlid = cmm.mlid LEFT JOIN {node} n ON cmm.nid = n.nid'; - $placeholders = db_placeholders($node_types, 'varchar'); - $query_nodetypes2 = ' AND ml.menu_name NOT IN('. $placeholders .')'; - $args = array_merge($args, $node_types); + $placeholders = db_placeholders($node_types, 'varchar'); + $query_nodetypes2 = ' AND ml.menu_name NOT IN('. $placeholders .')'; + $args = array_merge($args, $node_types); + } } - } - $query = "SELECT m.*, ml.* FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path". $query_nodetypes1 ." WHERE ml.plid = %d AND ml.menu_name != '%s'". $query_nodetypes2; + $query = "SELECT m.*, ml.* FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path". $query_nodetypes1 ." WHERE ml.plid = %d AND ml.menu_name != '%s'". $query_nodetypes2; - if (isset($max_depth)) { - $query .= ' AND ml.depth < %d'; - $args[] = $max_depth - 1; - } - // Finally, find distant children with the specified item as their parent, - // and extend the tree to include such children and their descendants. - $children = db_query($query, $args); - $child_tree_prev = array(); + if (isset($max_depth)) { + $query .= ' AND ml.depth < %d'; + $args[] = $max_depth - 1; + } + // Finally, find distant children with the specified item as their parent, + // and extend the tree to include such children and their descendants. + $children = db_query($query, $args); + $child_tree_prev = array(); - while ($child_link = db_fetch_array($children)) { - $item['has_children'] = 1; - _menu_link_translate($child_link); - $child_tree = _category_menu_tree_all_data($child_link['menu_name'], $child_link, $cache_prefix); - if (!empty($child_tree) && $child_tree != $child_tree_prev) { - _category_menu_tree_below($tree, $child_tree, $item); - $child_tree_prev = $child_tree; + while ($child_link = db_fetch_array($children)) { + $item['has_children'] = 1; + _menu_link_translate($child_link); + $child_tree = _category_menu_tree_all_data($child_link['menu_name'], $child_link, $cache_prefix); + if (!empty($child_tree) && $child_tree != $child_tree_prev) { + _category_menu_tree_below($tree, $child_tree, $item); + $child_tree_prev = $child_tree; + } } + + category_cache_op('set', 0, $cache_key, $tree); } return $tree; diff -urp --strip-trailing-cr ../category/category.module ./category.module --- ../category/category.module 2009-06-21 18:42:02.000000000 +0200 +++ ./category.module 2009-06-28 16:25:03.000000000 +0200 @@ -37,6 +37,13 @@ function category_theme() { } /** + * Implementation of hook_flush_caches() + */ +function category_flush_caches() { + return array('cache_category'); +} + +/** * Implementation of hook_link(). * * This hook is extended with $type = 'categories' to allow themes to @@ -816,6 +823,7 @@ function category_node_save($node, $cate */ function category_node_delete($node) { db_query('DELETE FROM {category_node} WHERE nid = %d', $node->nid); + category_cache_op('flush', $node->nid); } /** @@ -823,6 +831,7 @@ function category_node_delete($node) { */ function category_node_delete_revision($node) { db_query('DELETE FROM {category_node} WHERE vid = %d', $node->vid); + category_cache_op('flush', $node->nid); } /** @@ -1082,6 +1091,7 @@ function category_save_container(&$node) } } + category_cache_op('flush', 'all'); return $status; } @@ -1111,6 +1121,7 @@ function category_del_container($cid) { } } + category_cache_op('flush', 'all'); return SAVED_DELETED; } @@ -1226,6 +1237,7 @@ function category_save_category(&$node) } } + category_cache_op('flush', 'all'); return $status; } @@ -1244,6 +1256,7 @@ function category_del_category($cid) { db_query('DELETE FROM {category_synonym} WHERE cid = %d', $cid); db_query('DELETE FROM {category_node} WHERE cid = %d', $cid); + category_cache_op('flush', 'all'); return SAVED_DELETED; } @@ -1692,7 +1705,6 @@ function category_form_category_submit($ * Rendered node listing, or FALSE if no listing should be displayed. */ function category_node_listing($node) { - $nodes = category_select_nodes(array($node->category['cid']), 'or', $node->category['depth'], TRUE, TRUE); $is_container = empty($node->category['cnid']); $show_nodes = FALSE; $category_display = NULL; @@ -1733,6 +1745,7 @@ function category_node_listing($node) { return category_views_render_nodes($node); } else { + $nodes = category_select_nodes(array($node->category['cid']), 'or', $node->category['depth'], TRUE, TRUE); return category_render_nodes($nodes, $category_display); } } diff -urp --strip-trailing-cr ../category/contrib/category_views/category_views.module ./contrib/category_views/category_views.module --- ../category/contrib/category_views/category_views.module 2009-06-08 20:50:03.000000000 +0200 +++ ./contrib/category_views/category_views.module 2009-06-26 01:15:51.000000000 +0200 @@ -26,17 +26,23 @@ function category_views_nodeapi(&$node, if (!empty($behavior)) { // This is a container or category. Load the view settings from database. // No category information is available in the $node object yet. - if ($behavior === 'container') { - $result = db_query('SELECT * FROM {category_views} WHERE cid = %d', $node->nid); - } - else { - $result = db_query('SELECT v.* FROM {category_views} v INNER JOIN {category} c ON v.cid = c.cnid WHERE c.cid = %d', $node->nid); + $cache_key = 'cview_node_load'; + $output = category_cache_op('get', $node->nid, $cache_key); + if (!isset($output)) { + if ($behavior === 'container') { + $result = db_query('SELECT * FROM {category_views} WHERE cid = %d', $node->nid); + } + else { + $result = db_query('SELECT v.* FROM {category_views} v INNER JOIN {category} c ON v.cid = c.cnid WHERE c.cid = %d', $node->nid); + } + $settings = db_fetch_array($result); + $output = array( + 'view_for_cats' => $settings['view_name'], + 'view_display' => array('cont' => $settings['display_cont'], 'cat' => $settings['display_cat']), + ); + category_cache_op('set', $node->nid, $cache_key, $output); } - $settings = db_fetch_array($result); - return array( - 'view_for_cats' => $settings['view_name'], - 'view_display' => array('cont' => $settings['display_cont'], 'cat' => $settings['display_cat']), - ); + return $output; } break;