<?php
// potential replacement for get_nested_categories() in admin-functions.php

// 09.07.04 Stephanie Booth aka bunnywabbit_ on #wordpress -- http://climbtothestars.org/ for questions or fixes

// I was working on a fix for the broken get_nested_categories() function in WordPress 1.2 when I learnt the bug had already been fixed in CVS. The initial function mistakenly assumes that child ID's will always be higher than parent ID's. The fix in CVS runs a lot of queries (fetches the top-level categories, then runs the query again for the children of each, etc.) This solution uses only one query, but a pretty hairy multi-dimensional array. Code is commented and debugging notes have been added. This code does not work -- I think it just needs a little more debugging.



// this function grabs all the categories and sorts them into a multidimensional array, indexed by parent/self

function get_the_categories($default 0) {
 global 
$post_ID$tablecategories$tablepost2cat$mode$wpdb;

 if (
$post_ID) {
   
$checked_categories $wpdb->get_col("
     SELECT category_id
     FROM  $tablecategories, $tablepost2cat
     WHERE $tablepost2cat.category_id = cat_ID AND $tablepost2cat.post_id = '$post_ID'
     "
);
 } else {
   
$checked_categories[] = $default;
 }

 
$categories $wpdb->get_results("SELECT * FROM $tablecategories ORDER BY category_parent ASC, cat_name ASC");
 
// some array, pretty deep!
 
$result = array();
 
// go through the categories. Beware, one cannot guess who is the parent and who is the child by looking at the cat_ID! The assumption that children are created after parents is not a valid one.
 
foreach($categories as $category) {
      
// to help us remain sane
   
$me $category->cat_ID;
   
$parent $category->category_parent;
   
// this is going to be $result[$parent] -- we'll deal with children later
    
$array_category get_object_vars($category);
    
$array_category['checked'] = in_array($category->cat_ID$checked_categories);
   
$array_category['cat_name'] = stripslashes($category->cat_name);
  
   
// file this one as a child of its parent -- indexing as parent/me just to make it easier to follow
   
$result[$parent][$me] = $array_category;
 
 }

return 
$result;
}

function 
get_nested_categories($default=0)
{

if(!isset(
$result))
{
    
// this is an array of all categories, indexed by parent, but not nested yet.
    
$result=get_the_categories($default);
}

// now that we have everybody in an array, we don't just want to indicate who are the children, but we want to store all their information in the parent array, so that $result[$parent][children] is in fact another multidimensional array with the same structure, which can be parsed by the same function. This means we need to start by catching the children and store them in their parent arrays.

foreach($result as $parent => $other_result)
{
    foreach(
$other_result as $child => $this_cat)
    {
        
// has this child ever been a parent? if it hasn't then, it is a starting-point we want to store
        
        
if(!isset($result[$child]))
        {
            
$real_children[]=$this_cat;
        } 
// end if isset
    
// end foreach other_result as counter
// end foreach result as parent

// now we send the children off to a recursive function which will nest the array elements into each other
$big_nasty_nested_array=nest_the_categories($result$real_children);

return 
$big_nasty_nested_array[0];
}

function 
nest_the_categories($categories$branch_ends$counter=0)
{
print(
"running function nest_the_categories: $counter\n\n");
// idea: start with the branch ends, get the parent, find the other children, complete the parent array. Then call the function again with the new branch ends. Problem is that branch ends are not always the same distance from the tree trunk. We have no notion of "depth", we just know our starting points don't have any children to deal with. But the parents might have children, grand-children, and great-great-grandchildren, for what we know. We will first have to get all the siblings of our branch end, and check that they too are branch ends. If they aren't, we keep them in store for the next function call.

// list the IDs of categories which are branch ends
foreach($branch_ends as $branch_end)
{
    
$branch_list[]=$branch_end['cat_ID'];
    
// DEBUG: script runs fine the first time, but messes up the second. I think the problem is where I indicated it below.
    
$name=$branch_end['cat_name'];
    
$id $branch_end['cat_id'];
    print(
"$name ($id) --  ");
}

// make a copy of our branch ends which we will modify and pass along to the next function call
$new_branch_ends=$branch_ends;

// take our branch ends one by one

foreach($branch_ends as $key => $child)
{
    
// take the parent
    
$parent=$child['category_parent'];
    
    
// grab the other siblings one by one and check that they are all in $branch_ends
    
foreach($categories[$parent] as $sibling)
    {
        
// set this
        
$all_branch_ends=TRUE;
        
$sibling_ID=$sibling['cat_ID'];
        if(!
in_array($sibling_ID$branch_list))
        {
            
// if we can't find it, then it isn't a branch end
            
$all_branch_ends=FALSE;
            
//print("Sibling $sibling_ID is not in the branch list!\n");
        
}
        else
        {
        
// print("Sibling $sibling_ID is a branch end. YAY!\n");
        
}
        
    } 
// end foreach $categories[parent]

   // DEBUG added this line to try to identify where the problem is, but it seems to slow the script down so much I haven't had a chance to see it work
    
echo '<pre>'print_r($categories); echo '</pre>';

        
// if they are all branch ends, we can stick our child into the parent array (could put them all in at once, but better safe than sorry, let's keep things under control and not let the kids go running wild)

        
if($all_branch_ends)
        {
            
$child_ID=$child['cat_ID'];
            print(
"$child_ID is being assimilated...\n");
            
// note how neat the multi-dimensional array is
            // DEBUG: I think there is a problem in the next line
            
$categories[$parent][$child_ID]['children'][] = $child;
            
// remove that child from from our branch ends for the next function call
            
unset($new_branch_ends[$key]);
            
// also remove the child from $categories, as we have buried it deep inside the array
            
unset($categories[$child_ID]);
            
            
// if the parent is not yet in $new_branch_ends, add it in there (as all its children are going to be assimilated to it, it will become a new branch end)
            // list current IDs
            
foreach($new_branch_ends as $new_branch_end)
                {
                    
$new_branch_list[]=$new_branch_end['cat_ID'];
                }
            if(!
in_array($parent$branch_list))
            {
                
$new_branch_ends[]=$categories[$parent];
            }
            
            
// use a marker (not elegant, but less risk of error) to indicate when we have reached top level
            
if($parent=='0')
            {
                
$finished=TRUE;
            }
        }

// end foreach $branch_ends

// if we haven't $finished, call the function again for a round of fun. Otherwise, return the result -- the counter is there to limit the number of loops while debugging
  
if(!$finished && $counter<2)
{
    
// put in a counter so that we don't go into an infinite loop
    
$counter++;
    
nest_the_categories($categories$new_branch_ends$counter);
}
else
{
    print(
$counter);
    return 
$categories;


?>