Index: themes/default/tpl/header.tpl
===================================================================
--- themes/default/tpl/header.tpl	(revision 1699)
+++ themes/default/tpl/header.tpl	(working copy)
@@ -24,11 +24,7 @@
 <td class="header" align="left" width="80%"><img src="{$smarty.env.imgs.logo}" width="200" height="48" alt="Issue Tracker" /></td>
 <td class="header" align="center" width="20%">
 {if !empty($smarty.session.userid)}
-{if (is_employee($smarty.session.userid)
-and (permission_check("status_manager")
-or permission_check("category_manager")
-or permission_check("product_manager")))
-and $smarty.get.module != "help"}
+{if (is_manager($smarty.session.userid)) and $smarty.get.module != "help"}
 <a href="?module=admin"><img src="{$smarty.env.imgs.system}" alt="Administration" border="0" />Administration</a>
 {else}
 &nbsp;
Index: themes/default/tpl/leftnav.tpl
===================================================================
--- themes/default/tpl/leftnav.tpl	(revision 1699)
+++ themes/default/tpl/leftnav.tpl	(working copy)
@@ -24,6 +24,17 @@
 {/foreach}
 <tr><td>&nbsp;</td></tr>
 <tr><td>You are logged in as {username id=$smarty.session.userid}</td></tr>
+<tr><td>
+{if !empty($individual_grades[$smarty.session.userid])}
+Indiv.: {$individual_grades[$smarty.session.userid]}%
+<br>
+{/if}
+Group: {$group_grade}%
+{if !empty($final_grades[$smarty.session.userid])}
+<br>
+Overall: {$final_grades[$smarty.session.userid]}%
+{/if}
+</td></tr>
 {closenavtable}
 {if is_array($pmenus)}
 <br />
Index: themes/default/tpl/issues/view.tpl
===================================================================
--- themes/default/tpl/issues/view.tpl	(revision 1738)
+++ themes/default/tpl/issues/view.tpl	(working copy)
@@ -99,7 +99,7 @@
 <tr>
 <td class="label" width="20%" align="right" valign="top">Problem:</td>
 <td class="data" width="80%">
-{if issue_priv($smarty.get.issueid,"technician") && ($issue.opened_by == $smarty.session.userid || is_manager())}
+{if status($issue.status) != 'Closed' && (issue_priv($smarty.get.iseueid,"technician") && ($issue.opened_by == $smarty.session.userid || is_manager()))}
 <form method="post" action="?module=issues&action=view&issueid={$smarty.get.issueid}">
 <textarea cols="60" rows="10" name="problem">
 {$issue.problem|stripslashes}
Index: index.php
===================================================================
--- index.php	(revision 1738)
+++ index.php	(working copy)
@@ -102,6 +102,26 @@
     $leftnav_menu["Logout"] = "?logout=true";
     $pmenus = user_menu($_SESSION['userid']);
 
+    // To allow for performance scores to be shown in the left nav panel
+		{
+			// Group 1 is the developers
+			$group = 1;
+
+			global $milestones_to_date;
+			$category = array_search(
+			  $milestones_to_date[count($milestones_to_date)-1],
+				group_categories(1));
+
+      $_POST['gid'] = $group;
+      $_POST['category'] = $category;
+      $_POST['sdate'] = null;
+      $_POST['edate'] = null;
+
+			initialize_post_variables_for_performance_score();
+
+			compute_performance_information($group,null,null,$category);
+		}
+
     $smarty->assign('leftnav_menu',$leftnav_menu);
     $smarty->assign('pmenus',$pmenus);
     $smarty->assign('rightnav_menu',$rightnav_menu);
Index: TODO
===================================================================
--- TODO	(revision 1699)
+++ TODO	(working copy)
@@ -1 +1,3 @@
 - Add milestone filtering to issues lists for group, my owned, and my open.
+- Change issue updates so that the "update" button doesn't update the summary
+  and problem description separately from the other stuff.
Index: modules/groups/edit.groups.php
===================================================================
--- modules/groups/edit.groups.php	(revision 1699)
+++ modules/groups/edit.groups.php	(working copy)
@@ -38,8 +38,8 @@
 		"field"		=> "product",
 		"gfunc"		=> "group_products",
 		"func"		=> "product",
-		"single"	=> "product",
-		"plural"	=> "products"
+		"single"	=> "module",
+		"plural"	=> "modules"
 	),
 	/* }}} */
 	/* {{{ Statuses */
Index: modules/announce/new.announce.php
===================================================================
--- modules/announce/new.announce.php	(revision 1699)
+++ modules/announce/new.announce.php	(working copy)
@@ -10,7 +10,7 @@
   exit;
 }
 
-if (!permission_check("create_announcments")) {
+if (!permission_check("create_announcements")) {
 	redirect("?module=announce");
 }
 
Index: modules/issues/group.issues.php
===================================================================
--- modules/issues/group.issues.php	(revision 1699)
+++ modules/issues/group.issues.php	(working copy)
@@ -42,12 +42,23 @@
 		$_GET['sort'] = $_SESSION['sort'];
 	}
 
-  // Hack for now to filter out unwanted milestone stuff. Uncomment these
-  // lines and comment out the following line.
-//  $categories = array('Milestone 4', 'Milestone 5', 'Wishlist');
-//  $rows = group_issues_by_category($_GET['gid'],$categories);
-  $rows = group_issues($_GET['gid']);
+  // Filter out previous milestone issues
+	{
+		$sql  = "SELECT cid,category ";
+		$sql .= "FROM categories ";
+		$sql .= "ORDER BY category";
+		$c = $dbi->fetch_all($sql,"array");
+		foreach ($c as $category) {
+				$categories[$category['cid']] = $category['category'];
+		}
 
+		global $milestones_to_date;
+		$categories = array_diff($categories,array_slice($milestones_to_date,0,-1));
+		$rows = group_issues_by_category($_GET['gid'],$categories);
+	}
+
+//  $rows = group_issues($_GET['gid']);
+
   foreach ($rows as $key => $val)
   {
     $rows[$key]['assigned_to'] = explode(",",$rows[$key]['assigned_to']);
Index: modules/reports/hooks/funcs.php
===================================================================
--- modules/reports/hooks/funcs.php	(revision 1699)
+++ modules/reports/hooks/funcs.php	(working copy)
@@ -190,4 +190,305 @@
   return str_replace(_PATH_."/",_URL_,$filename);
 }
 /* }}} */
+
+/* {{{ Function: get_group_info */
+/**
+ * Computes developers and non-developers that should be included in the team
+ * computation of performance scores.
+ *
+ * @param $gid group id
+ */
+function get_group_info($gid)
+{
+  global $dbi;
+  global $managers_are_group_members;
+
+  // Determine group members and non-group members
+  $sql =<<<END
+  SELECT gu.userid
+  FROM group_users gu
+  WHERE gu.gid = $gid
+END;
+
+  $temp_group_members = $dbi->fetch_all($sql);
+
+  unset($non_group_members,$group_members);
+
+  foreach ($temp_group_members as $userid) {
+    if (!$managers_are_group_members && is_manager($userid) ||
+        is_admin($userid) || !user_active($userid))
+      $non_group_members[] = $userid;
+    else
+      $group_members[] = $userid;
+  }
+
+  $group_info["group_members"] = $group_members;
+  $group_info["non_group_members"] = $non_group_members;
+
+  return $group_info;
+}
+/* }}} */
+
+/* {{{ Function: get_issues */
+/**
+ * Computes the set of issues active for the group between the given dates
+ * computation of performance scores.
+ *
+ * @param $gid group id
+ * @param $sdate start date
+ * @param $sdate end date
+ */
+function get_issues($gid,$sdate,$edate)
+{
+	global $dbi;
+
+  $sql  = "SELECT g.cid ";
+  $sql .= "FROM group_categories g ";
+  $sql .= "WHERE g.gid='$gid' ";
+  $categories = $dbi->fetch_all($sql);
+
+  $sql =<<<END
+  SELECT i.closed,i.issueid,i.summary,i.severity,i.priority,i.iseverity,s.status,ig.assigned_to,i.points,p.product
+  FROM issues i,issue_groups ig,statuses s,products p
+  WHERE i.issueid = ig.issueid 
+  AND ig.gid = $gid 
+  AND i.status = s.sid 
+  AND p.pid = i.product 
+END;
+
+  $sql .= "AND i.category IN (" . implode(',',$categories) . ") \n";
+
+  if ($_POST['category'] != '')
+    $sql .= "\nAND i.category = " . $_POST['category'] . " ";
+
+  $sql .= $_POST['use_date'] == "on" ? "AND i.opened BETWEEN '$sdate' AND '$edate' " : "";
+
+  $issues = $dbi->fetch_all($sql,"array");
+
+  foreach ($issues as $i => $issue)
+  {
+    if (is_null($issues[$i]["difficulty"]))
+      $issues[$i]["difficulty"] = 0;
+    if (is_null($issues[$i]["priority"]))
+      $issues[$i]["priority"] = 0;
+    if (is_null($issues[$i]["iseverity"]))
+      $issues[$i]["iseverity"] = 0;
+
+    $issues[$i]["assigned_to"] = explode(",",$issues[$i]["assigned_to"]);
+
+    $issues[$i]["points_per_person"] =
+      round($issues[$i]["points"] / count($issues[$i]["assigned_to"]),2);
+  }
+
+  return $issues;
+}
+/* }}} */
+
+function compare_usernames($ar1,$ar2)
+{
+  if (username($ar1) < username($ar2))
+    return -1;
+  if (username($ar1) == username($ar2))
+    return 0;
+  if (username($ar1) > username($ar2))
+    return 1;
+}
+
+
+function compute_performance_information($gid,$sdate = null,$edate = null,$category = null) {
+	global $smarty;
+
+	$percent_group = $_POST['percent_group'];
+	$percent_individual = $_POST['percent_individual'];
+
+  $smarty->assign('percent_group', $percent_group);
+  $smarty->assign('percent_individual', $percent_individual);
+
+  $smarty->assign('gid', $gid);
+
+  $issues = get_issues($gid,$sdate,$edate);
+  $smarty->assign('issues',$issues);
+
+  $member_info = $_POST['member_info_' . $gid];
+
+  $smarty->assign('group_members',$member_info['group_members']);
+  $smarty->assign('non_group_members',$member_info['non_group_members']);
+
+  $bonus_issues = array();
+
+  // Extract bonuses from the issues
+  foreach ($issues as $i => $issue) {
+    if ($issue["product"] == "Bonus")
+    {
+      $bonus_issues[] = $issue;
+      unset($issues[$i]);
+    }
+  }
+
+  // Reindex
+  $issues = array_values($issues);
+
+  // Group and total points
+  $total_points = 0;
+  $group_completed_points = 0;
+  $group_invalid_points = 0;
+
+  foreach ($issues as $issue) {
+    if ($issue["status"] == "Invalid")
+      $group_invalid_points += $issue["points"];
+
+    $total_points += $issue["points"];
+
+    if ($issue["status"] == "Closed" &&
+        ($_POST['use_date'] != "on" || $issue['closed'] < $edate))
+      $group_completed_points += $issue["points"];
+  }
+
+  $smarty->assign('group_invalid_points',$group_invalid_points);
+  $smarty->assign('group_completed_points',$group_completed_points);
+  $smarty->assign('total_points',$total_points);
+
+
+  // Group grade
+  if ($total_points-$group_invalid_points != 0) {
+    $group_grade =
+      ($group_completed_points) / ($total_points - $group_invalid_points) * 100;
+  } else {
+    $group_grade = 100;
+  }
+
+  $smarty->assign('group_grade', round($group_grade, 0));
+
+
+  // Second compute the total earnable points
+    $non_group_member_points = 0;
+
+    // Points assigned to non-group-members
+    foreach ($issues as $issue) {
+      // Be careful not to double-count invalid tasks assigned to non-members
+      if ($issue["status"] == "Invalid")
+        continue;
+
+      foreach ($issue["assigned_to"] as $person) {
+        if (in_array($person,$member_info["non_group_members"]))
+        {
+          $non_group_member_points +=
+            $issue["points"] / count($issue["assigned_to"]);
+        }
+      }
+    }
+
+    $smarty->assign('non_group_member_points',$non_group_member_points);
+
+    // Total earnable points. (Bonus points already subtracted.)
+    $total_earnable_points =
+      $total_points - $non_group_member_points - $group_invalid_points;
+    $smarty->assign('total_earnable_points', $total_earnable_points);
+
+
+    // Number of people
+    $number_of_non_group_members = count($member_info["non_group_members"]);
+    $number_of_group_members = count($member_info["group_members"]);
+    $number_of_people = $number_of_group_members + $number_of_non_group_members;
+
+    // Unadjusted average per person
+    $smarty->assign('number_of_group_members', $number_of_group_members);
+
+    $points_per_group_member = $total_earnable_points / $number_of_group_members;
+
+    $smarty->assign('points_per_group_member', round($points_per_group_member,2));
+
+
+    foreach ($member_info["group_members"] as $member) {
+      $earned_points[$member] = 0;
+      $bonus_points[$member] = 0;
+
+      foreach ($issues as $issue) {
+        if (in_array($member,$issue["assigned_to"]) &&
+            $issue["status"] == "Closed" &&
+            ($_POST['use_date'] != "on" || $issue['closed'] < $edate))
+        {
+          $earned_points[$member] +=
+            $issue["points"] / count($issue["assigned_to"]);
+        }
+      }
+
+      foreach ($bonus_issues as $bonus_issue) {
+        if (in_array($member,$bonus_issue["assigned_to"]) &&
+            $bonus_issue["status"] == "Closed" &&
+            ($_POST['use_date'] != "on" || $bonus_issue['closed'] < $edate))
+        {
+          $bonus_points[$member] +=
+            $bonus_issue["points"] / count($bonus_issue["assigned_to"]);
+        }
+      }
+    }
+
+    $smarty->assign('earned_points', $earned_points);
+    $smarty->assign('bonus_points', $bonus_points);
+
+
+  // Individual grades
+    foreach ($earned_points as $member => $member_earned_points)
+    {
+      $member_bonus_points = $bonus_points[$member];
+
+      $total_points = $member_earned_points + $member_bonus_points;
+
+      if ($total_points > $points_per_group_member)
+        $individual_grades[$member] = 100;
+      else
+      {
+        if ($total_points > $points_per_group_member)
+          $individual_grades[$member] = 100;
+        elseif ($points_per_group_member == 0)
+          $individual_grades[$member] = 100;
+        else
+          $individual_grades[$member] =
+            $total_points / $points_per_group_member * 100;
+      }
+
+      $rounded_individual_grades[$member] = round($individual_grades[$member],0);
+    }
+
+    uksort($rounded_individual_grades, "compare_usernames");
+
+    $smarty->assign('individual_grades', $rounded_individual_grades);
+
+  // Final grades
+    foreach ($individual_grades as $group_member => $individual_grade)
+    {
+      // 50% Group grade, 50% Individual grade
+      $final_grades[$group_member] = 
+        $group_grade * $percent_group/100 +
+        $individual_grade * $percent_individual/100;
+      $rounded_final_grades[$group_member] = round($final_grades[$group_member],0);
+    }
+
+    uksort($rounded_final_grades, "compare_usernames");
+
+    $smarty->assign('final_grades', $rounded_final_grades);
+}
+
+function initialize_post_variables_for_performance_score() {
+	global $performance_score_group_percentage;
+	global $performance_score_individual_percentage;
+
+	if (empty($_POST['percent_group']))
+		$_POST['percent_group'] = $performance_score_group_percentage;
+
+	if (empty($_POST['percent_individual']))
+		$_POST['percent_individual'] = $performance_score_individual_percentage;
+
+   if (is_array($_POST['gid'])) {
+     foreach ($_POST['gid'] as $gid) {
+	    if (empty($_POST['member_info_' . $gid]))
+			  $_POST['member_info_' . $gid] = get_group_info($gid);
+		}
+	} else {
+	  if (empty($_POST['member_info_' . $_POST['gid']]))
+			$_POST['member_info_' . $_POST['gid']] = get_group_info($_POST['gid']);
+	}
+}
+
 ?>
Index: modules/reports/generate.reports.php
===================================================================
--- modules/reports/generate.reports.php	(revision 1699)
+++ modules/reports/generate.reports.php	(working copy)
@@ -56,6 +56,10 @@
     $edate = mktime(0,0,0,$parts[0],$parts[1],$parts[2]);
   }
 
+  // This is an ugly hack. But I need to be able to store the information
+	// required to reliably (re)generate the performance report.
+	initialize_post_variables_for_performance_score();
+
   $saved = serialize($_POST);
   $smarty->assign('saved',$saved);
   $smarty->display("reports/save.tpl");
Index: modules/reports/reports/performance.php
===================================================================
--- modules/reports/reports/performance.php	(revision 1700)
+++ modules/reports/reports/performance.php	(working copy)
@@ -5,259 +5,7 @@
   exit;
 }
 
-// CONFIGURABLE
-$percent_group = 50;
-$percent_individual = 50;
-
-function get_issues($dbi,$gid,$sdate,$edate)
-{
-  $sql  = "SELECT g.cid ";
-  $sql .= "FROM group_categories g ";
-  $sql .= "WHERE g.gid='$gid' ";
-  $categories = $dbi->fetch_all($sql);
-
-  $sql =<<<END
-  SELECT i.closed,i.issueid,i.summary,i.severity,i.priority,i.iseverity,s.status,ig.assigned_to,i.points,p.product
-  FROM issues i,issue_groups ig,statuses s,products p
-  WHERE i.issueid = ig.issueid 
-  AND ig.gid = $gid 
-  AND i.status = s.sid 
-  AND p.pid = i.product 
-END;
-
-  $sql .= "AND i.category IN (" . implode(',',$categories) . ") \n";
-
-  if ($_POST['category'] != '')
-  {
-    $sql .= "\nAND i.category = " . $_POST['category'] . " ";
-  }
-
-  $sql .= $_POST['use_date'] == "on" ? "AND i.opened BETWEEN '$sdate' AND '$edate' " : "";
-
-  $issues = $dbi->fetch_all($sql,"array");
-
-  foreach ($issues as $i => $issue)
-  {
-    if (is_null($issues[$i]["difficulty"]))
-      $issues[$i]["difficulty"] = 0;
-    if (is_null($issues[$i]["priority"]))
-      $issues[$i]["priority"] = 0;
-    if (is_null($issues[$i]["iseverity"]))
-      $issues[$i]["iseverity"] = 0;
-
-    $issues[$i]["assigned_to"] = explode(",",$issues[$i]["assigned_to"]);
-
-    $issues[$i]["points_per_person"] =
-      round($issues[$i]["points"] / count($issues[$i]["assigned_to"]),2);
-  }
-
-  return $issues;
-}
-
-function get_group_info($dbi,$gid)
-{
-  // Determine group members and non-group members
-  $sql =<<<END
-  SELECT gu.userid
-  FROM group_users gu
-  WHERE gu.gid = $gid
-END;
-
-  $temp_group_members = $dbi->fetch_all($sql);
-
-  unset($non_group_members,$group_members);
-
-  foreach ($temp_group_members as $userid) {
-    if (is_manager($userid) || is_admin($userid) || !user_active($userid))
-      $non_group_members[] = $userid;
-    else
-      $group_members[] = $userid;
-  }
-
-  $group_info["group_members"] = $group_members;
-  $group_info["non_group_members"] = $non_group_members;
-
-  return $group_info;
-}
-
 if (in_array("performance",$_POST['options'])) {
-
-  $smarty->assign('percent_group', $percent_group);
-  $smarty->assign('percent_individual', $percent_individual);
-
-  $smarty->assign('gid', $gid);
-
-  $issues = get_issues($dbi,$gid,$sdate,$edate);
-  $smarty->assign('issues',$issues);
-
-  $member_info = get_group_info($dbi,$gid);
-
-  $smarty->assign('group_members',$member_info["group_members"]);
-  $smarty->assign('non_group_members',$member_info["non_group_members"]);
-
-  $bonus_issues = array();
-
-  // Extract bonuses from the issues
-  foreach ($issues as $i => $issue) {
-    if ($issue["product"] == "Bonus")
-    {
-      $bonus_issues[] = $issue;
-      unset($issues[$i]);
-    }
-  }
-
-  // Reindex
-  $issues = array_values($issues);
-
-  // Group and total points
-  $total_points = 0;
-  $group_completed_points = 0;
-  $group_invalid_points = 0;
-
-  foreach ($issues as $issue) {
-    if ($issue["status"] == "Invalid")
-      $group_invalid_points += $issue["points"];
-
-    $total_points += $issue["points"];
-
-    if ($issue["status"] == "Closed" &&
-        ($_POST['use_date'] != "on" || $issue['closed'] < $edate))
-      $group_completed_points += $issue["points"];
-  }
-
-  $smarty->assign('group_invalid_points',$group_invalid_points);
-  $smarty->assign('group_completed_points',$group_completed_points);
-  $smarty->assign('total_points',$total_points);
-
-
-  // Group grade
-  if ($total_points-$group_invalid_points != 0) {
-    $group_grade =
-      ($group_completed_points) / ($total_points - $group_invalid_points) * 100;
-  } else {
-    $group_grade = 100;
-  }
-
-  $smarty->assign('group_grade', round($group_grade, 0));
-
-
-  // Second compute the total earnable points
-    $non_group_member_points = 0;
-
-    // Points assigned to non-group-members
-    foreach ($issues as $issue) {
-      // Be careful not to double-count invalid tasks assigned to non-members
-      if ($issue["status"] == "Invalid")
-        continue;
-
-      foreach ($issue["assigned_to"] as $person) {
-        if (in_array($person,$member_info["non_group_members"]))
-        {
-          $non_group_member_points +=
-            $issue["points"] / count($issue["assigned_to"]);
-        }
-      }
-    }
-
-    $smarty->assign('non_group_member_points',$non_group_member_points);
-
-    // Total earnable points. (Bonus points already subtracted.)
-    $total_earnable_points =
-      $total_points - $non_group_member_points - $group_invalid_points;
-    $smarty->assign('total_earnable_points', $total_earnable_points);
-
-
-    // Number of people
-    $number_of_non_group_members = count($member_info["non_group_members"]);
-    $number_of_group_members = count($member_info["group_members"]);
-    $number_of_people = $number_of_group_members + $number_of_non_group_members;
-
-    // Unadjusted average per person
-    $smarty->assign('number_of_group_members', $number_of_group_members);
-
-    $points_per_group_member = $total_earnable_points / $number_of_group_members;
-
-    $smarty->assign('points_per_group_member', round($points_per_group_member,2));
-
-
-    foreach ($member_info["group_members"] as $member) {
-      $earned_points[$member] = 0;
-      $bonus_points[$member] = 0;
-
-      foreach ($issues as $issue) {
-        if (in_array($member,$issue["assigned_to"]) &&
-            $issue["status"] == "Closed" &&
-            ($_POST['use_date'] != "on" || $issue['closed'] < $edate))
-        {
-          $earned_points[$member] +=
-            $issue["points"] / count($issue["assigned_to"]);
-        }
-      }
-
-      foreach ($bonus_issues as $bonus_issue) {
-        if (in_array($member,$bonus_issue["assigned_to"]) &&
-            $bonus_issue["status"] == "Closed" &&
-            ($_POST['use_date'] != "on" || $bonus_issue['closed'] < $edate))
-        {
-          $bonus_points[$member] +=
-            $bonus_issue["points"] / count($bonus_issue["assigned_to"]);
-        }
-      }
-    }
-
-    $smarty->assign('earned_points', $earned_points);
-    $smarty->assign('bonus_points', $bonus_points);
-
-
-  // Individual grades
-    foreach ($earned_points as $member => $member_earned_points)
-    {
-      $member_bonus_points = $bonus_points[$member];
-
-      $total_points = $member_earned_points + $member_bonus_points;
-
-      if ($total_points > $points_per_group_member)
-        $individual_grades[$member] = 100;
-      else
-      {
-        if ($total_points > $points_per_group_member)
-          $individual_grades[$member] = 100;
-        elseif ($points_per_group_member == 0)
-          $individual_grades[$member] = 100;
-        else
-          $individual_grades[$member] =
-            $total_points / $points_per_group_member * 100;
-      }
-
-      $rounded_individual_grades[$member] = round($individual_grades[$member],0);
-    }
-
-function compare_usernames($ar1,$ar2)
-{
-  if (username($ar1) < username($ar2))
-    return -1;
-  if (username($ar1) == username($ar2))
-    return 0;
-  if (username($ar1) > username($ar2))
-    return 1;
+	compute_performance_information($gid,$sdate,$edate,$category);
 }
-
-    uksort($rounded_individual_grades, "compare_usernames");
-
-    $smarty->assign('individual_grades', $rounded_individual_grades);
-
-  // Final grades
-    foreach ($individual_grades as $group_member => $individual_grade)
-    {
-      // 50% Group grade, 50% Individual grade
-      $final_grades[$group_member] = 
-        $group_grade * $percent_group/100 +
-        $individual_grade * $percent_individual/100;
-      $rounded_final_grades[$group_member] = round($final_grades[$group_member],0);
-    }
-
-    uksort($rounded_final_grades, "compare_usernames");
-
-    $smarty->assign('final_grades', $rounded_final_grades);
-}
 ?>
Index: CHANGES
===================================================================
--- CHANGES	(revision 1738)
+++ CHANGES	(working copy)
@@ -1,3 +1,23 @@
+4.0.4f:
+- The problem description for closed issues now cannot be edited. You have to
+  reopen the issue.
+- Fixed references to products instead of modules in group module management
+  page.
+- Fixed a bug where the admin link on the main page would not appear for
+  managers.
+- Fixed a bug where managers could not create announcements.
+- Added options in config.php to set the relative weights of the group and
+	individual grades. Also added an option to treat managers as members of the
+	group.
+- Saved reports now save the group and individual percentages, as well as the
+	group membership, so that saved reports will be reliable if managers or
+	percentages are modified later.
+- Added an individual and group progress percentage to the left navigation
+	panel.
+- Added a configuration value in config.php for a list of milestones to date.
+	This is used to filter out old issues from the issues list, and to compute
+	the left navigation panel values for the current milestone.
+
 4.0.4e:
 - Fixed a bug in file download that would download some php with the file.
 - Fixed a bug where single quotes would appear quoted in MOTD of the login
