Index: question/upgrade.php
===================================================================
RCS file: /cvsroot/moodle/moodle/question/upgrade.php,v
retrieving revision 1.12.2.2
diff -u -r1.12.2.2 upgrade.php
--- question/upgrade.php	7 May 2008 13:35:54 -0000	1.12.2.2
+++ question/upgrade.php	26 Aug 2008 08:36:24 -0000
@@ -169,23 +169,36 @@
     return $tofix;
 }
 
-function question_category_next_parent_in($contextid, $question_categories, $id){
+function question_category_next_parent_in($contextid, $question_categories, $id, $already_seen = array()){
+    // Recursively look for an ancestor category of the given category that
+    // belongs to context $contextid. (In a lot of cases, the parent will be 
+    // the one.) If there is none, return 0, meaning the top level.
+    $already_seen[] = $id;
+
     $nextparent = $question_categories[$id]->parent;
-    if ($nextparent == 0){
+    if ($nextparent == 0) {
+        // Hit the top level, we are done.
+        return 0;
+    } else if (!array_key_exists($nextparent, $question_categories)) {
+        // The category hierarchy must have been screwed up before, in that
+        // we have run out of categories to search, but without reaching the
+        // top level. Repair the situation by returning 0, meaning top level.
         return 0;
-    } elseif (!array_key_exists($nextparent, $question_categories)){
-        //finished searching up the category hierarchy. For some reason
-        //the top level items is not 0. We'll return 0 though.
+    } else if (in_array($nextparent, $already_seen)) {
+        // The category hierarchy must have been screwed up before, in that
+        // we have just found a loop in the category 'tree'. That should,
+        // of course, be impossible, but it did acutally happen in at least once.
+        // Repair the situation by returning 0, meaning top level.
         return 0;
-    } elseif ($contextid == $question_categories[$nextparent]->contextid){
+    } else if ($contextid == $question_categories[$nextparent]->contextid) {
+        // Found a suitable category, we are done.
         return $nextparent;
     } else {
-        //parent is not in the same context look further up.
-        return question_category_next_parent_in($contextid, $question_categories, $nextparent);
+        // The immediate parent is not in the same context, so look further up.
+        return question_category_next_parent_in($contextid, $question_categories, $nextparent, $already_seen);
     }
 }
 
-
 /**
  * Check that either category parent is 0 or a category shared in the same context.
  * Fix any categories to point to grand or grand grand parent etc in the same context or 0.
@@ -196,7 +209,7 @@
     foreach ($question_categories as $id => $category){
         $newparents[$id] = question_category_next_parent_in($category->contextid, $question_categories, $id);
     }
-    foreach (array_Keys($question_categories) as $id){
+    foreach (array_keys($question_categories) as $id){
         $question_categories[$id]->parent = $newparents[$id];
     }
     return $question_categories;
@@ -343,7 +356,7 @@
 function question_fix_random_question_parents() {
     global $CFG;
     return execute_sql('UPDATE ' . $CFG->prefix . 'question SET parent = id ' .
-    		"WHERE qtype = 'random' AND parent <> id");
+            "WHERE qtype = 'random' AND parent <> id");
 }
 
 ?>
