Oops, the question-mark feature wasn't marking Resolved bugs that had
[wvapps.git] / gracefultavi / macros / Sched.php
blob7876726820d38e555594eeb9081485dba0854e48
1 <?php
2 global $sch_user;
3 global $sch_start;
4 global $sch_load;
5 // FIXME: sch_curday, sch_elapsed_curday shouldn't be global
6 // The day we are currently processing, in days since the epoch
7 global $sch_curday;
8 // The date up to which we think the user has done work (in days since the
9 // epoch).
10 global $sch_elapsed_curday;
11 // Array of bugs already processed from FogBugz (to avoid duplicates)
12 global $sch_got_bug;
13 // Array of bugs the user has explicitly scheduled
14 global $sch_manual_bugs;
15 // True if we've already processed all finished bugs from FogBugz
16 global $sch_did_all_done;
17 // Array of bugs with unknown fixfor targets (so they can be added to the
18 // database when a [[Sched MILESTONE/FIXFOR/RELEASE]] is encountered)
19 global $sch_unknown_fixfor;
20 // Bug database handle
21 global $bug_h;
22 global $SchedServer;
23 global $SchedUser;
24 global $SchedPass;
25 global $SchedName;
27 // Contains lists of complete and incomplete bugs, one per Fixfor.
28 // $sch_bug_lists[fixfor-name] is an array with two elements, "incomplete" and
29 // "complete". Each of these is itself an array of bugs.
30 global $sch_bug_lists;
31 // Working lists of complete and incomplete bugs (we don't know the Fixfor yet)
32 // FIXME: Merge into one list of Bug objects (a BugTable?)
33 global $sch_cur_incomplete_bugs;
34 global $sch_cur_complete_bugs;
37 function bug_init()
39 global $bug_h;
40 global $SchedServer;
41 global $SchedUser;
42 global $SchedPass;
43 global $SchedName;
45 if (!$bug_h)
47 $bug_h = mysql_connect($SchedServer, $SchedUser, $SchedPass);
48 mysql_select_db($SchedName, $bug_h);
53 // This function could maybe be inlined
54 function bug_person($user)
56 global $sch_db;
58 $p = $sch_db->person->first_prefix("email", "$user@");
59 return $p->ix;
63 // This function could maybe be inlined
64 function bug_duedate($fixfor)
66 global $sch_db;
68 $d = $sch_db->fixfor->first("name", $fixfor);
69 return $d->date;
73 // find the last FixFor entry due sooner than or on the same day as
74 // $fixforname or $enddate, whichever is later. Either one can be
75 // undefined. If both are undefined or there are no FixFors at all,
76 // returns undef.
77 function bug_last_fixfor($fixforname, $enddate)
79 global $sch_db;
80 if ($enddate)
81 $last_fix = $sch_db->fixfor->last_before($enddate);
82 $f = $sch_db->fixfor->first("name", $fixforname);
84 if (!$last_fix)
85 $last_fix = $f;
86 if (!$f)
87 $f = $last_fix;
88 if ($last_fix && $f)
89 $last_fix = $f->due_before($last_fix) ? $last_fix : $f;
91 return $last_fix;
95 // return a list of all ACTIVE bugs due by the given fixfor/enddate, whichever
96 // is *later*.
98 // Returns an array:
99 // bugid -> (title,OrigEst,CurrEst,Elapsed,FixForDate)
100 function bug_unfinished_list($user, $fixforname, $enddate)
102 global $sch_db;
103 $personid = bug_person($user);
104 $last_fix = bug_last_fixfor($fixforname, $enddate);
105 $a = array();
107 foreach ($sch_db->estimate->a as $e)
109 if ($e->isdone()
110 || $e->assignto->ix != $personid
111 || ($last_fix && !$e->task->fixfor->due_before($last_fix))
112 || $e->task->fixfor->name != $fixforname)
113 continue;
115 $a[$e->id] =
116 array($e->id, $e->nice_title(),
117 $e->est_orig(), $e->est_curr(),
118 $e->est_elapsed(),
119 $e->task->fixfor->date);
122 return $a;
126 // return a list of all RESOLVED bugs that were resolved by this user before
127 // the given fixfor *and* (if given) the given enddate.
129 // The list is in order of least-to-most-recently-resolved.
131 // Returns an array:
132 // bugid -> (title,OrigEst,CurrEst,Elapsed,FixForDate)
133 function bug_finished_list($user, $fixfor, $startdate, $enddate)
135 global $sch_db;
137 // We want to get all bugs *resolved by* the user after the given start
138 // date. The bugs are probably no longer assigned to that user.
139 $personid = bug_person($user);
141 $last_fix = bug_last_fixfor($fixforname, $enddate);
142 $a = array();
144 $xstartdate = str_replace("/", "-", $startdate);
146 foreach ($sch_db->estimate->a as $e)
148 if (!$e->isdone()
149 || $e->resolvedate < $xstartdate
150 || ($e->est_curr()==0 && $e->est_elapsed()==0)
151 || ($last_fix && !$e->task->fixfor->due_before($last_fix)))
152 continue;
154 $a[$e->id] =
155 array($e->id, $e->nice_title(),
156 $e->est_orig(), $e->est_curr(), $e->est_elapsed(),
157 $e->task->fixfor->date);
160 return $a;
164 // FIXME: Return a Bug object from this
165 function bug_get($bugid)
167 global $sch_db;
168 global $bug_h;
170 $b = $sch_db->bug->first("ix", $bugid);
172 if ($b)
174 // $b->status doesn't give what we want. Can't wait to return Bug
175 // objects.
176 if ($b->isresolved())
177 $mystatus = "Resolved";
178 else
179 $mystatus = "ACTIVE";
181 return array($b->name, $b->origest, $b->currest, $b->elapsed,
182 $mystatus, $b->fixfor->name, $b->priority);
184 else
186 bug_init();
187 $result = mysql_query("select sTitle,hrsOrigEst,hrsCurrEst,hrsElapsed, " .
188 " sStatus,sFixFor,ixPriority " .
189 "from Bug as b, Status as s, FixFor as f " .
190 "where s.ixStatus=b.ixStatus " .
191 " and f.ixFixFor=b.ixFixFor " .
192 " and ixBug=" . ($bugid+0),
193 $bug_h);
194 $row = mysql_fetch_row($result);
196 if (!$row)
197 return array($bugid, 0, 0, 0);
198 else
199 return $row;
204 // This function should become obsolete as everything switches to using bug
205 // objects that make their own links
206 function bug_link($bugid)
208 return "<a href='http://nits/FogBUGZ3/?$bugid'>$bugid</a>";
212 // This function should become obsolete as everything switches to using bug
213 // objects that have their own titles
214 function bug_title($bugid)
216 $bug = bug_get($bugid);
217 return $bug[0];
221 // This function could maybe be inlined
222 function bug_milestone_realname($name)
224 global $sch_db;
226 $f = $sch_db->fixfor->first_prefix("name", $name);
227 return $f->name;
231 function bug_set_release($fixfor, $dt)
233 global $bug_h;
234 bug_init();
236 $query = "delete from schedulator.Milestone " .
237 " where sMilestone='$fixfor' and nSub=0";
238 $result = mysql_query($query, $bug_h);
240 $query = "insert into schedulator.Milestone " .
241 " (sMilestone, nSub, dtDue) " .
242 " values ('$fixfor', 0, '$dt')";
243 $result = mysql_query($query, $bug_h);
247 function bug_set_milestones($fixfor, $dates)
249 global $bug_h;
250 bug_init();
252 $query = "delete from schedulator.Milestone " .
253 " where sMilestone='$fixfor' and nSub>0";
254 $result = mysql_query($query, $bug_h);
255 if (!$result)
256 print mysql_error($bug_h);
258 $n = 1;
259 foreach ($dates as $d)
261 $query = "insert into schedulator.Milestone " .
262 " (sMilestone, nSub, dtDue) " .
263 " values ('$fixfor', $n, '$d')";
264 $result = mysql_query($query, $bug_h);
265 if (!$result)
266 print mysql_error($bug_h);
267 $n++;
272 function bug_get_milestones($fixfor)
274 global $bug_h;
275 bug_init();
277 $query = "select distinct dtDue from schedulator.Milestone " .
278 " where sMilestone='$fixfor' and nSub>0 order by dtDue";
279 $result = mysql_query($query, $bug_h);
280 if (!$result)
281 print mysql_error($bug_h);
283 $a = array();
284 while ($row = mysql_fetch_row($result))
285 $a[] = $row[0];
286 return $a;
289 // Format of $t:
290 // task, subtask, hrsOrigEst, hrsCurrEst, hrsElapsed, dtDue,
291 // fDone, fResolved, ixPriority
292 function bug_add_task($user, $fixfor, $t)
294 global $bug_h;
295 bug_init();
297 $task = mysql_escape_string($t[0]);
298 $subtask = mysql_escape_string($t[1]);
299 $remain = $t[3]-$t[4];
300 $query = "insert into schedulator.Task " .
301 " (sPerson, sFixFor, " .
302 " sTask, sSubTask, " .
303 " hrsOrigEst, hrsCurrEst, hrsElapsed, hrsRemain, " .
304 " dtDue, fDone, fResolved, ixPriority) " .
305 " values ('$user', '$fixfor', \"$task\", \"$subtask\", " .
306 " $t[2], $t[3], $t[4], $remain, " .
307 " '$t[5]', $t[6], '$t[7]', '$t[8]')";
308 $result = mysql_query($query, $bug_h);
309 if (!$result)
310 print 'x-' . mysql_error($bug_h);
314 function bug_add_tasks($user, $fixfor, $tasks)
316 foreach ($tasks as $t)
317 bug_add_task($user, $fixfor, $t);
321 // Put all bugs assigned to "-Needs Volunteer-", "-Tech Support-", etc into
322 // the Schedulator database.
323 function bug_add_volunteer_tasks()
325 global $bug_h;
326 bug_init();
328 $query = "delete from schedulator.Task where sPerson like '-%-'";
329 $result = mysql_query($query, $bug_h);
330 if (!$result)
331 print mysql_error($bug_h);
333 $query = "select sFullName, sFixFor, ixBug, sTitle, " .
334 " b.ixStatus, ixPriority " .
335 " from Bug as b, Person as p, FixFor as f, Status as s " .
336 " where p.ixPerson=b.ixPersonAssignedTo " .
337 " and f.ixFixFor=b.ixFixFor " .
338 " and s.ixStatus=b.ixStatus " .
339 " and s.sStatus='ACTIVE' " .
340 " and sFullName like '-%-' ";
341 $result = mysql_query($query, $bug_h);
342 if (!$result)
343 print mysql_error($bug_h);
345 while ($row = mysql_fetch_row($result))
347 $person = "-???-";
348 $fixfor = $row[1];
349 $bugid = $row[2];
350 $title = $row[3];
351 $resolved = ($row[4] != 1);
352 $priority = $row[5];
353 $info = array($bugid, $title, 1000, 1000, 0, '2099/9/9', 0,
354 $resolved, $priority);
355 bug_add_task($person, $fixfor, $info);
360 function bug_query($query)
362 global $bug_h;
363 bug_init();
364 $result = mysql_query($query, $bug_h);
365 if (!$result)
366 print mysql_error($bug_h);
367 return $result;
371 function bug_onerow($query)
373 return mysql_fetch_row(bug_query($query));
377 function bug_start_user($user)
379 // mark old schedulator tasks for this user as invalid
380 $query = "update schedulator.Task set fValid=0 where sPerson='$user'";
381 bug_query($query);
385 function bug_finish_user($user)
387 $query = "delete from schedulator.Task " .
388 "where (fValid=0 and sPerson='$user') or sPerson=''";
389 bug_query($query);
393 // Returns an array of all FixFors the given user has active bugs for, or has
394 // ever fixed a bug for, ordered by the due date of the fixfors.
395 function bug_get_fixfors($user)
397 global $bug_h;
398 bug_init();
400 // We want to get all bugs *resolved by* the user after the given start
401 // date. The bugs are probably no longer assigned to that user.
402 $personid = bug_person($user);
404 $query = "select distinct sFixFor, " .
405 " ifnull(f.dt, '2099/9/9') as sortdate " .
406 " from Bug as b, BugEvent as e, FixFor as f, Person as p " .
407 " where p.ixPerson = $personid " .
408 " and p.ixPerson = e.ixPerson " .
409 " and e.ixBug = b.ixBug " .
410 " and b.ixFixFor = f.ixFixFor " .
411 " order by sortdate";
412 //print "(($query))<p>";
413 $result = mysql_query($query, $bug_h);
414 if (!$result)
415 print mysql_error($bug_h);
417 $a = array();
419 while ($row = mysql_fetch_row($result))
421 //print "((Adding fixfor $row[0] to array))<br>\n";
422 $a[] = $row[0];
425 return $a;
429 function sch_today()
431 // we mostly use GMT for our work, but if they want today, they want
432 // the local "today"
433 $dat = strftime("%Y/%m/%d", time());
434 $today = sch_parse_day($dat);
435 //print("(today=$dat:$today/" . sch_format_day($today) . ")");
436 return $today;
440 // Given a day in "yyyy/mm/dd" or "yyyy-mm-dd" format, return the next
441 // Schedulator-day in days since the epoch.
442 // FIXME: Actually returns working-days since the epoch, approximately equal
443 // to days_since_epoch * 4/7.
444 function sch_parse_day($day)
446 if ($day == '')
447 return 0;
448 else if (preg_match(",(....)[/-](..)[/-](..),", $day, $a))
450 $year = $a[1];
451 $month = $a[2];
452 $day = $a[3];
454 // FIXME: Insane math ahoy
455 $stamp = mktime(12,0,0, $month, $day, $year);
456 $spl = localtime($stamp);
457 //print("(day-$year/$month/$day:$stamp:$spl[6])<br>\n");
458 if ($spl[6] == 0) // sunday
459 $stamp += 24*60*60;
460 else if ($spl[6] == 6) // saturday
461 $stamp += 2*24*60*60;
462 else if ($spl[6] == 5) // friday
463 $stamp += 3*24*60*60;
465 $stamp -= 4*24*60*60; // the epoch was a Thursday. Skip to Monday.
466 $days = floor($stamp/24/60/60);
467 $weeks = floor($days/7);
468 $days -= $weeks*7;
469 //printf("(days/weeks:$days/$weeks)");
470 return $weeks*4 + $days;
472 else
474 print("(INVALID DATE:'$day')");
475 return 0;
480 // Given a value in days since the epoch, return a formatted string in the
481 // form "yyyy/mm/dd", suitable for display.
482 // FIXME: This actually takes a value in "working days since the epoch".
483 // Must fix this.
484 function sch_format_day($day)
486 if (!$day)
487 return '';
489 //print "sch_format_day: day='$day'; ";
490 // convert working days to "real" days
491 // FIXME: Insane math ahoy. Should at least divide by days_worked_per_week
492 $weeks = floor($day/4);
493 $days = $day - $weeks*4;
494 $frac = $days - floor($days);
495 //print "(weeks/days/frac:$weeks/$days/$frac)";
496 $stamp = ($weeks*7 + floor($days)) * 24*60*60;
497 $stamp += 4*24*60*60; // the epoch was a Thursday
498 $stamp += 12*60*60; // php timezone handling is insane
500 $ret = strftime("%Y/%m/%d", $stamp);// . sprintf("+%.1f", $frac);
501 //print "(ret:$ret)<br>\n";
502 return $ret;
506 function sch_add_hours($day, $hours)
508 global $sch_load;
510 //if ($hours < 0)
511 // return $day; // never subtract hours from the date!
513 return $day + ($hours * $sch_load) / 8.0;
517 // Given a time in the format "4h", "3 days", "30 min" (etc), returns the time
518 // in hours.
519 function sch_parse_period($str)
521 //return "($str)";
522 if (preg_match('/([0-9]+) *(d|day|days) *([0-9]+) *(h|hr|hrs|hour|hours)$/', $str, $out))
523 return $out[1] * 8.0 + $out[3];
524 elseif (preg_match('/([0-9.]+) *(h|hr|hrs|hour|hours)$/', $str, $out))
525 return $out[1]+0.0;
526 else if (preg_match('/([0-9.]+) *(d|day|days)$/', $str, $out))
527 return $out[1] * 8.0;
528 else if (preg_match('/([0-9.]+) *(min|minutes)$/', $str, $out))
529 return $out[1] / 60.0;
530 else
531 return $str + 0.0;
535 // FIXME: This gets returned in a non-standard format (4.4d instead of 4d3h)
536 function _sch_period($hours)
538 //return $hours;
539 if (!$hours)
540 return "";
541 else if ($hours < 9)
542 return sprintf("%dh", $hours);
543 else if ($hours < 10*8.0)
544 return sprintf("%.1fd", $hours/8);
545 else
546 return sprintf("%dd", $hours/8);
550 // Return the given time in hours formatted into a suitable denomination
551 // (hours, days), suitable for inclusion directly into HTML.
552 function sch_period($hours)
554 if ($hours < -1)
555 return "<font color=red>-" . _sch_period(-$hours) . "</font>";
556 else
557 return _sch_period($hours);
560 function sch_fullline($text)
562 return "<tr><td colspan=7>$text</td></tr>";
566 function sch_warning($text)
568 return sch_fullline("<font color=red>&nbsp;&nbsp;"
569 . "** WARNING: $text **"
570 . "</font>");
574 function sch_genline($feat, $task, $orig, $curr, $elapsed, $left, $due)
576 $ret = "<tr>";
578 $junk1 = $junk2 = '';
579 if ($left == "done")
581 $junk1 = "<strike><font color=gray><i>";
582 $junk2 = "</i></font></strike>";
585 if ($task)
586 $ret .= "<td>$junk1$feat$junk2</td><td>$junk1$task$junk2</td>";
587 else
588 $ret .= "<td colspan=2>$junk1$feat$junk2</td>";
589 return $ret . "<td>$junk1" .
590 join("$junk2</td><td>$junk1",
591 array($orig, $curr, $elapsed, $left, $due)) .
592 "$junk2</td></tr>";
596 function sch_line($feat, $task, $orig, $curr, $elapsed, $remain, $done,
597 $allow_red)
599 global $sch_curday, $sch_elapsed_curday;
601 if (preg_match('/^[0-9]+$/', $feat))
602 $xfeat = bug_link($feat);
603 else
604 $xfeat = $feat;
606 if ($done)
607 $sremain = 'done';
608 else if (!$remain)
609 $sremain = '';
610 else
611 $sremain = sch_period($remain);
613 $today = sch_today();
614 // FIXME: Insane math?
615 $was_over_elapsed = ($sch_elapsed_curday - 4 > $today);
617 // All unfinished bugs have had their elapsed time already accounted for
618 if ($done)
620 $sch_curday = sch_add_hours($sch_curday, $curr);
621 $sch_elapsed_curday = sch_add_hours($sch_elapsed_curday, $elapsed);
623 else
625 $sch_curday = sch_add_hours($sch_curday, $curr - $elapsed);
626 //$ret .= sch_fullline("Ignoring $elapsed elapsed hours for bug $feat");
629 $due = sch_format_day($sch_curday);
631 if ($allow_red && (!$curr || $remain) && !$done
632 && floor($sch_curday) < floor($today))
633 $due = "<font color=red>$due</font>";
635 $ret .= sch_genline($xfeat, $task,
636 sch_period($orig), sch_period($curr),
637 sch_period($elapsed), $sremain,
638 $due);
639 //$ret .= sch_fullline("gork: $was_over_elapsed $sch_elapsed_curday $sch_curday $today");
640 if (!$was_over_elapsed && $sch_elapsed_curday - 4 > $today)
641 $ret .= sch_warning("The START time, plus the time so far in your " .
642 "ELAPSED column, puts you into the future! " .
643 "Either your START date is wrong, or some of " .
644 "your elapsed hours are wrong, or you're working " .
645 "too hard.");
646 return $ret;
650 function sch_bug($feat, $task, $_orig, $_curr, $_elapsed, $done, $fixfor)
652 global $sch_user, $sch_curday, $sch_need_extraline;
653 global $sch_got_bug, $sch_unknown_fixfor;
654 global $sch_load;
656 // FIXME: Use Bug object, check its LoadFactor member
657 if ($feat == "LOADFACTOR")
659 if ($task != $sch_load)
660 $ret .= sch_fullline("(Load factor = $task)");
661 $sch_load = $task;
662 return $ret;
665 $task = htmlentities($task, ENT_QUOTES);
667 if (!$done)
668 $done = 0;
669 else if ($done == -1)
671 $notdone = 1;
672 $done = 0;
675 if ($sch_need_extraline)
677 $ret .= sch_fullline('&nbsp;');
678 $sch_need_extraline = 0;
681 // $fixfor = ''; // now provided as a parameter...
682 if (preg_match('/^[0-9]+$/', $feat))
684 $bug = bug_get($feat);
685 // $bug = (title origest currest elapsed status fixfor priority)
687 if (!$task) $task = $bug[0];
688 if (!strcmp($_orig,'')) $_orig = $bug[1];
689 if (!strcmp($_curr,'')) $_curr = $bug[2];
690 if (!strcmp($_elapsed,'')) $_elapsed = $bug[3];
691 $resolved = ($bug[4] != 'ACTIVE');
692 if (!$done && !$notdone && $resolved) $done = 1;
693 if ((!$done && $_curr != $_elapsed)
694 || ($bug[4] != 'ACTIVE'))
696 // already listed as not done, *or* really done in fogbugz:
697 // never need to auto-import this bug again.
698 $sch_got_bug[$feat] = 1;
700 $fixfor = $bug[5];
701 $priority = $bug[6];
704 $orig = sch_parse_period($_orig);
705 $curr = sch_parse_period($_curr);
706 $elapsed = sch_parse_period($_elapsed);
708 $remain = $curr - $elapsed;
710 if (!$remain && $curr)
711 $done = 1;
713 $ret .= sch_line($feat, $task, $orig, $curr, $elapsed, $remain, $done,
714 true);
715 if ($done && $curr != $elapsed)
716 $ret .= sch_warning("This bug is done, but elapsed time is different " .
717 "from the current estimate. You know " .
718 "how long it really took, so make your estimate " .
719 "accurate.");
720 else if ($curr < $elapsed)
721 $ret .= sch_warning("This bug's current estimate is less than the " .
722 "elapsed time so far. Update your estimate!");
723 $buga = array($feat, $task, $orig, $curr, $elapsed,
724 sch_format_day($sch_curday), $done, $resolved, $priority);
725 if ($fixfor)
726 bug_add_task($sch_user, $fixfor, $buga);
727 else
728 $sch_unknown_fixfor[] = $buga;
729 return $ret;
733 // Get a list of all FogBugz assigned to the user, and insert them into the
734 // buglists for the correct milestones
735 function sch_add_all_fogbugz($user)
737 global $sch_bug_lists;
738 global $sch_cur_complete_bugs;
739 global $sch_cur_incomplete_bugs;
741 $fixfors = bug_get_fixfors($user);
743 foreach ($fixfors as $fixfor)
745 sch_extrabugs($user, $fixfor, '', false);
748 return $ret;
752 // FIXME: This function can be cleaned up an awful lot
753 // Handle all finished and optionally all unfinished bugs for the given
754 // FixFor. If no FixFor given, do all bugs.
755 function sch_extrabugs($user, $fixfor, $enddate, $only_done)
757 global $sch_got_bug, $sch_start, $sch_did_all_done;
758 global $sch_bug_lists;
759 global $sch_manual_bugs;
761 $ret .= sch_warning("Extrabugs for $fixfor ($enddate) ($only_done)");
763 $start = sch_format_day($sch_start);
764 $today = sch_today();
765 $fixfor_in_past = ($enddate && sch_parse_day($enddate) < $today);
767 $bugs1 = array();
768 $bugs2 = array();
770 $a = bug_finished_list($user, $fixfor, $start, $enddate);
772 // handle done bugs
773 foreach ($a as $idx => $bug)
775 $bugid = array_shift($bug);
776 $zeroest = (abs($bug[2]) <= 0.01 && abs($bug[2]) >= 0.0001);
777 $done = ($bug[2] && $bug[3] == $bug[2]);
778 if (!$done)
779 $ret .= sch_warning("Weird1: I don't know if bug #$bugid is done!");
781 #print "(done_bug:$idx:$bugid:$done:$zeroest)";
782 if ($sch_got_bug[$bugid] || $sch_manual_bugs[$bugid])
783 continue;
784 #print "!";
786 if ($zeroest)
788 // the bug is done, but with a zero estimate; probably a
789 // duplicate, wontfix, or something. Skip it.
790 $sch_got_bug[$bugid] = 1;
791 continue;
793 #print "/";
795 $bugarr = array($bugid, $bug[0], $bug[1], $bug[2], $bug[3]);
796 $sch_bug_lists[$fixfor]["complete"][] = $bugarr;
798 $sch_got_bug[$bugid] = 1; # definitely done now
801 // handle unfinished bugs
802 if (!$only_done)
804 $ua = bug_unfinished_list($user, $fixfor, $enddate);
805 foreach ($ua as $idx => $bug)
807 $bugid = array_shift($bug);
808 $zeroest = (abs($bug[2]) <= 0.01 && abs($bug[2]) >= 0.0001);
809 $done = ($bug[2] && $bug[3] == $bug[2]);
811 if ($sch_got_bug[$bugid])
812 continue;
814 if ($done) // not actually done - adjust estimate!
816 // $ret .= sch_warning("Weird2: I don't know if bug #$bugid is done!");
817 $bug[2] += 0.0001;
820 if (0 && $zeroest)
822 // the bug is done, but with a zero estimate; probably a
823 // duplicate, wontfix, or something. Skip it.
824 $sch_got_bug[$bugid] = 1;
825 continue;
828 if ($fixfor_in_past)
830 // if this release is in the past, but it's not fixed yet,
831 // the bug must not *actually* be for this milestone. Skip
832 // it now, and add it into the next release or at the end of
833 // the schedule.
834 continue;
837 if ($sch_manual_bugs[$bugid])
839 // The bug was already manually listed, so don't mess with it.
840 continue;
843 $bugarr = array($bugid, $bug[0], $bug[1], $bug[2], $bug[3]);
844 $sch_bug_lists[$fixfor]["incomplete"][] = $bugarr;
846 $sch_got_bug[$bugid] = 1;
850 // print "done: " . count($sch_bug_lists[$fixfor]["complete"]) . "<br>\n";
852 return $ret;
855 // Output all unprocessed completed bugs from both FogBugz and the bug lists
856 function sch_all_done($user)
858 global $sch_did_all_done;
859 global $sch_bug_lists;
860 global $sch_load;
862 $oldload = $sch_load;
864 if (!$sch_did_all_done)
866 // Can't just use foreach ($sch_bug_lists as $milestone => $type)
867 // because the foreach would clobber the internal array pointers
868 $milestones = array_keys($sch_bug_lists);
869 foreach ($milestones as $milestone)
871 $type = $sch_bug_lists[$milestone];
872 // print("Adding " . count($type["complete"]) . " done bugs for $milestone<br>\n");
873 if (is_array($type["complete"]) && count($type["complete"]) > 0)
875 foreach ($type["complete"] as $bug)
877 //print("Adding done bug $bug[0]<br>\n");
878 $ret .= sch_bug($bug[0], $bug[1], $bug[2], $bug[3],
879 $bug[4], 1, $milestone);
883 //unset($sch_bug_lists[$milestone]["complete"]);
886 // Display the MAGIC line after all done bugs, but it already has all
887 // relevant loadfactors accounted for.
888 $extra = sch_elapsed_time_unfinished($user);
889 // Fudge to get it to display if 0
890 if ($extra == 0)
891 $extra = 0.001;
892 $extra = $extra / $oldload;
893 $ret .= sch_line("MAGIC", "Time elapsed on unfinished bugs " .
894 "listed below",
895 $extra, $extra, $extra, 0, true, false);
897 $sch_did_all_done = true;
900 // Running through all these bugs can mess with our load factor, make sure
901 // to reset it
902 // FIXME: Should use LoadFactor member of Bug object instead of LOADFACTOR
903 // "bugs"
904 if ($sch_load != $oldload)
905 $ret .= sch_bug("LOADFACTOR", $oldload, "", "", "", 0, "");
907 return $ret;
911 // FIXME: This function doesn't make all that much sense any more. I think it
912 // now just outputs the MILESTONE line and the blank line. Fair enough, but
913 // could be simpler.
914 function sch_milestone($descr, $name, $due)
916 global $sch_user, $sch_load, $sch_curday, $sch_need_extraline;
918 // if no due date was given, make it the day after the last bug finished.
919 if (!$due)
920 $due = sch_format_day($sch_curday+1);
922 $today = sch_today();
923 $old_curday = $sch_curday;
925 $newday = sch_parse_day($due);
926 $xdue = sch_format_day($newday);
927 // FIXME: Insane math
928 $slip = ($newday-$sch_curday)*8 / $sch_load;
929 //$ret .= sch_line("SLIPPAGE (to $xdue)", "", 0,$slip,0,$slip, 0, true);
930 $done = $newday < $today;
931 // FIXME: If current day is past the milestone's release date, don't show
932 // the "current" column in red. See sch_period().
933 $ret .= sch_genline("<b>$descr: $name ($xdue)</b>", '',
934 '', sch_period($slip), '',
935 $done ? "done" : sch_period($slip),
936 '');
937 $sch_need_extraline = 1;
939 $sch_curday = $old_curday; // slippage doesn't actually take time... right?
941 return $ret;
944 // Count up the elapsed time in hours on all unfinished bugs, taking
945 // loadfactors into account
946 function sch_elapsed_time_unfinished($user)
948 global $sch_bug_lists, $sch_cur_incomplete_bugs;
950 $elapsed = 0;
951 $local_loadfactor = 1;
953 $milestones = array_keys($sch_bug_lists);
954 foreach ($milestones as $milestone)
956 $inc = $sch_bug_lists[$milestone]["incomplete"];
957 if (is_array($inc))
958 foreach($inc as $bug)
960 // FIXME: Should use LoadFactor member of Bug object instead
961 // of LOADFACTOR "bugs"
962 if ($bug[0] == "LOADFACTOR")
963 $local_loadfactor = $bug[1];
964 else if ($bug[4] > 0)
965 $elapsed += sch_parse_period($bug[4]) * $local_loadfactor;
969 return $elapsed;
973 // Returns true if the given milestone has at least one incomplete bug (that
974 // isn't a LoadFactor)
975 // FIXME: This function might not be needed with the new flat buglist. Let's
976 // hope so, because it'll have to be rewritten.
977 function sch_has_inc_bug($milestone)
979 global $sch_bug_lists;
980 if (is_array($sch_bug_lists[$milestone]["incomplete"]))
982 $bugids = array_keys($sch_bug_lists[$milestone]["incomplete"]);
983 foreach ($bugids as $bugid)
985 $bug = $sch_bug_lists[$milestone]["incomplete"][$bugid];
986 if ($bug[0] != "LOADFACTOR")
987 return 1;
990 return 0;
994 // Given a list of bugs, go through it and output the HTML for them. msname
995 // is the name of the current milestone, and extra_due is an array of extra
996 // due dates that should be listed as they occur while the bugs are output.
997 // extra_due will have any output extra due dates removed from it.
998 function sch_output_buglist($buglist, $msname, &$extra_due, $done)
1000 global $sch_did_all_done, $sch_need_extraline;
1001 global $sch_curday;
1003 if (is_array($buglist))
1005 $next_fixfor = array_shift($extra_due);
1006 $fixfor_day = sch_parse_day($next_fixfor);
1007 foreach($buglist as $bug)
1009 $bug_line = sch_bug($bug[0], $bug[1], $bug[2], $bug[3],
1010 $bug[4], $done, $msname);
1011 while ($sch_curday > $fixfor_day && !is_null($next_fixfor))
1013 $ret .= sch_milestone("ZeroBugBounce", $msname, $next_fixfor);
1014 $next_fixfor = array_shift($extra_due);
1015 if (!is_null($next_fixfor))
1016 $fixfor_day = sch_parse_day($next_fixfor);
1018 if ($sch_need_extraline)
1020 $ret .= sch_fullline('&nbsp;');
1021 $sch_need_extraline = 0;
1023 $ret .= $bug_line;
1026 if (!is_null($next_fixfor))
1027 array_unshift($extra_due, $next_fixfor);
1030 return $ret;
1034 // Output the HTML for all the bugs in the given milestone. $buglists is an
1035 // array containing two subarrays, $buglists["incomplete"] and
1036 // $buglists["complete"]
1037 function sch_output_milestone($user, $milestone, $msname, $msdue)
1039 global $sch_did_all_done, $sch_need_extraline;
1040 global $sch_bug_lists;
1041 global $sch_curday;
1043 $buglists = $sch_bug_lists[$milestone];
1045 //$num_incomplete = count($buglists["incomplete"]);
1046 // $ret .= sch_fullline("Processing $num_incomplete incomplete bugs for milestone $milestone");
1048 $extra_due = bug_get_milestones($msname);
1050 $has_incomplete = sch_has_inc_bug($milestone);
1051 // If we have an incomplete bug, make sure to list all completed bugs
1052 if (!$sch_did_all_done && $has_incomplete)
1054 // $ret .= sch_fullline("Found incomplete bug, adding all completed ".
1055 // "bugs first");
1056 $ret .= sch_all_done($user);
1059 if (!$sch_did_all_done && is_array($buglists["complete"]))
1060 $ret .= sch_output_buglist($buglists["complete"], $msname, $extra_due, 0);
1062 if ($has_incomplete && is_array($buglists["incomplete"]))
1063 $ret .= sch_output_buglist($buglists["incomplete"], $msname, $extra_due, -1);
1065 // Clear out bugs we've already listed, so we can find all unlisted
1066 // bugs later.
1067 $sch_bug_lists[$milestone]["complete"] = array();
1068 $sch_bug_lists[$milestone]["incomplete"] = array();
1070 foreach ($extra_due as $next_fixfor)
1072 $ret .= sch_milestone("ZeroBugBounce", $msname, $next_fixfor);
1073 $next_fixfor++;
1076 if ($msdue)
1077 $ret .= sch_milestone("RELEASE", $msname, $msdue);
1079 return $ret;
1083 // Output an HTML schedule based on the current list of bugs
1084 function sch_create($user)
1086 global $sch_start;
1087 global $sch_bug_lists;
1088 global $sch_cur_complete_bugs;
1089 global $sch_cur_incomplete_bugs;
1090 global $sch_load;
1092 $ret .= "<table border=0 width='95%'>\n";
1093 $ret .= "<tr><th>" .
1094 join("</th><th>", array("Task", "Subtask", "Orig", "Curr",
1095 "Done", "Left", "Due")) .
1096 "</th></tr>\n";
1097 $ret .= sch_line("START", "", 0,0,0,0, 0, false);
1098 if ($sch_start > sch_today())
1099 $ret .= sch_warning("START date is in the future!");
1101 sch_add_all_fogbugz($user);
1103 // Shove the remaining current list of incomplete bugs onto the pile,
1104 // even though we don't know its target name
1105 $cur_list_name = "-Undecided-";
1106 sch_merge_cur_bugs($cur_list_name);
1108 // make sure to print initial load factor
1109 $tmpload = $sch_load;
1110 $sch_load = 1;
1111 $ret .= sch_bug("LOADFACTOR", $tmpload, "", "", "", 0, "");
1113 $fixfors = array();
1115 $milestones = array_keys($sch_bug_lists);
1116 foreach ($milestones as $milestone)
1118 $msnames[$milestone] = bug_milestone_realname($milestone);
1119 $msdue = bug_duedate($msnames[$milestone]);
1120 // FIXME: It's a bit gross hard-coding this date, but PHP doesn't have
1121 // a "uasort()" to do user-defined sorting based on array values so
1122 // otherwise milestones with no release date (-Undecided- and
1123 // -Wishlist- in particular) will sort at the start, which is exactly
1124 // wrong.
1125 if ($msdue == "")
1126 $msdue = "2099-09-09 00:00:00";
1127 $fixfors[$milestone] = $msdue;
1130 // List milestones in order of release date
1131 asort($fixfors);
1133 // FIXME: foreach milestone (and bugbounce), iterate over manual and
1134 // FogBugz buglists until no more bugs targeted for that milestone
1135 foreach ($fixfors as $milestone => $msdue)
1137 $ret .= sch_output_milestone($user, $milestone, $msnames[$milestone],
1138 $msdue);
1141 $ret .= sch_line("END", "", 0,0,0,0, 0, true);
1142 $ret .= "</table>";
1144 return $ret;
1148 // FIXME: This function should no longer be required. At worst, it should
1149 // apply a FixFor to the Bug objects in the list, and then throw them onto the
1150 // big flat list.
1151 function sch_merge_cur_bugs($fixfor)
1153 global $sch_cur_incomplete_bugs;
1154 global $sch_cur_complete_bugs;
1156 sch_list_merge($fixfor, "complete", $sch_cur_complete_bugs);
1157 sch_list_merge($fixfor, "incomplete", $sch_cur_incomplete_bugs);
1158 $sch_cur_incomplete_bugs = array();
1159 $sch_cur_complete_bugs = array();
1163 function sch_list_merge($fixfor, $sect, $arr)
1165 global $sch_bug_lists;
1167 if (!is_array($sch_bug_lists[$fixfor])) {
1168 $sch_bug_lists[$fixfor][$sect] = $arr;
1170 else
1171 $sch_bug_lists[$fixfor][$sect] =
1172 array_merge($sch_bug_lists[$fixfor][$sect], $arr);
1176 // FIXME: LoadFactors will not be separate "bugs" in the future.
1177 function sch_switch_loadfactor($load)
1179 global $sch_cur_incomplete_bugs;
1180 global $sch_cur_complete_bugs;
1181 global $sch_load;
1183 $loadbug = array("LOADFACTOR", $load, "", "", "");
1185 // Mark where the load factor changed for both the complete and
1186 // incomplete bug lists, so we can display something sane.
1187 $sch_cur_incomplete_bugs[] = $loadbug;
1188 $sch_cur_complete_bugs[] = $loadbug;
1190 $sch_load = $load;
1191 //$ret .= "Setting loadfactor to $sch_load<br>\n";
1195 function sch_next_milestone($fixfor)
1197 global $bug_h;
1198 bug_init();
1199 $ret = "";
1201 $query = "select dtDue " .
1202 " from schedulator.Milestone " .
1203 " where dtDue > now() and sMilestone='$fixfor' " .
1204 " order by dtDue limit 1 ";
1205 $result = mysql_query($query, $bug_h);
1206 $row = mysql_fetch_row($result);
1207 if (count($row) >= 1)
1208 return $row[0];
1209 else
1210 return "";
1214 function sch_month_out($str, $count)
1216 $months = array("Jan", "Feb", "Mar", "Apr", "May", "Jun",
1217 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
1218 $simple = $months[substr($str, 5, 2) - 1];
1219 return "<th class='year' colspan=$count>$simple</th>";
1223 function sch_dateclass($due, $daystobounce)
1225 $colclass = "";
1226 $daysleft = (strtotime($due) - time()) / 24 / 60 / 60;
1227 if ($daysleft < -7)
1228 $colclass = "superlate";
1229 else if ($daysleft < -2)
1230 $colclass = "late";
1231 else if ($daystobounce - $daysleft < 0)
1232 $colclass = "superlate";
1233 else if ($daystobounce - $daysleft < ($daystobounce / 2.5))
1234 $colclass = "late";
1235 return $colclass;
1239 function sch_summary($fixfor)
1241 global $bug_h;
1242 bug_init();
1243 $ret = "";
1245 $allpeople = array();
1246 $result = mysql_query("select distinct sPerson from schedulator.Task " .
1247 " where fValid=1 and fDone=0 " .
1248 " and sPerson not like '-%-'" .
1249 " order by sPerson ",
1250 $bug_h);
1251 while ($result && $row = mysql_fetch_row($result))
1253 $person = $row[0];
1254 $schedname = strtoupper(substr($person, 0, 1))
1255 . substr($person, 1) . "Schedule";
1257 $allpeople[$person] =
1258 "<a href='index.php?$schedname' title=\"$person's schedulator\">" .
1259 "$person</a>";
1262 $query = "select dtDue, sPerson, sTask, sSubTask, " .
1263 " fResolved, ixPriority, hrsCurrEst " .
1264 " from schedulator.Task " .
1265 " where fValid=1 and sFixFor='$fixfor' and fDone=0 " .
1266 " order by dtDue, ixPriority, sTask, sSubTask ";
1267 $result = mysql_query($query, $bug_h);
1269 $dates = array();
1270 $bugs = array();
1272 while ($row = mysql_fetch_row($result))
1274 $due = $row[0];
1275 $person = $row[1];
1276 $task = $row[2];
1277 $subtask = $row[3];
1278 $resolved = $row[4];
1279 $priority = $row[5];
1280 $currest = $row[6];
1282 # $nicedue = ereg_replace("-", " ", $due);
1283 $nicedue = ereg_replace("(....)-(..)-(..)", "\\3", $due);
1284 $dates[$due] = $nicedue;
1285 $last_date = $due;
1287 $bugs[$person][$due][] = array
1288 ("task" => $task,
1289 "subtask" => $subtask,
1290 "resolved" => $resolved,
1291 "priority" => $priority,
1292 "currest" => $currest);
1293 unset($allpeople[$person]);
1296 $nextbounce = sch_next_milestone($fixfor);
1297 if ($nextbounce)
1299 $next_str = " (Scheduled for $nextbounce)";
1300 $daystobounce = (strtotime($nextbounce) - time()) / 24 / 60 / 60;
1302 else
1304 $next_str = " (No bounces scheduled)";
1305 $daystobounce = 365;
1308 $ret .= "<h1>Schedulator Summary for '$fixfor'</h1>\n";
1309 $ret .= "<p>Predicted Bounce: $last_date$next_str</p>\n";
1311 $ret .= <<<EOF
1313 <style type='text/css'>
1314 /* headings */
1315 table.schedsum th {
1316 font-weight:normal; margin:0pt; text-align:left;
1319 /* headings except topleft corner */
1320 table.schedsum th.year,th.day,th.person {
1321 background: lightgray
1324 /* typical font size */
1325 table.schedsum th.day,td.bugs {
1326 font-size: 8pt;
1329 /* cell backgrounds */
1330 table.schedsum td.superlate {
1331 background: #ee9999;
1333 table.schedsum td.late {
1334 background: #f0f0c0
1337 /* normal vs. late vs. super-late bugs */
1338 table.schedsum a {
1339 text-decoration: none;
1341 table.schedsum a.superlate:link,a.superlate:visited {
1342 color: yellow; background: #aa0000
1344 table.schedsum a.late:link,a.late:visited {
1345 color: red; background: yellow
1348 /* resolved vs. unresolved bugs */
1349 table.schedsum a.resolved:link,a.resolved:visited {
1350 font-style: italic;
1352 table.schedsum a.unresolved:link,a.unresolved:visited {
1353 border-style: solid; border-width: 1px;
1356 /* bug priorities */
1357 table.schedsum a.pri1,a.pri2 {
1358 font-size: 150%;
1360 table.schedsum a.pri3 {
1361 font-size: 125%;
1363 table.schedsum a.pri6,a.pri7 {
1364 font-size: 80%;
1367 /* mouseovers on bugs */
1368 table.schedsum a:hover:link,a:hover:visited,span:hover {
1369 color: white; background: green;
1371 </style>
1372 EOF;
1374 $date_list = array_keys($dates);
1375 sort($date_list);
1377 $person_list = array_keys($bugs);
1378 sort($person_list);
1379 $rowcount = 2 + count($person_list);
1381 $ret .= "<table class='schedsum'>\n";
1383 $ret .= "<tr class='schedsum'><th></th>";
1384 $lastyear = 0;
1385 $yearcount = 0;
1386 foreach ($date_list as $due)
1388 $year = ereg_replace("(....)-(..)-(..)", "\\1", $due);
1389 if (!$lastyear) $lastyear = $year;
1390 if ($year != $lastyear)
1392 $ret .= "<th class='year' colspan=$yearcount>$lastyear</th>";
1393 $lastyear = $year;
1394 $yearcount = 0;
1396 $yearcount++;
1398 $ret .= "<th class='year' colspan=$yearcount>$lastyear</th>";
1399 $ret .= "</tr>\n";
1401 $ret .= "<tr class='schedsum'><th></th>";
1402 $lastmonth = 0;
1403 $monthcount = 0;
1404 foreach ($date_list as $due)
1406 $month = ereg_replace("(....)-(..)-(..)", "\\1 \\2", $due);
1407 if (!$lastmonth) $lastmonth = $month;
1408 if ($month != $lastmonth)
1410 $ret .= sch_month_out($lastmonth, $monthcount);
1411 $lastmonth = $month;
1412 $monthcount = 0;
1414 $monthcount++;
1416 $ret .= sch_month_out($lastmonth, $monthcount);
1417 $ret .= "</tr>\n";
1419 $ret .= "<tr><th></th>";
1420 foreach ($date_list as $due)
1422 $day = ereg_replace("(....)-(..)-(..)", "\\3", $due);
1423 $colclass = sch_dateclass($due, $daystobounce);
1424 $ret .= "<th class='day $colclass'>" .
1425 $day .
1426 "</th>\n";
1428 $ret .= "</tr>\n";
1430 foreach ($person_list as $person)
1432 $schedname = strtoupper(substr($person, 0, 1))
1433 . substr($person, 1) . "Schedule";
1435 $ret .= "<tr><th class='person'>" .
1436 "<a href='index.php?$schedname' title=\"$person's schedulator\">" .
1437 "$person</a></th>";
1438 foreach ($date_list as $due)
1440 $dateclass = sch_dateclass($due, $daystobounce);
1441 $n = 0;
1442 $num_unresolved = 0;
1443 $v = "";
1444 if (is_array($bugs[$person][$due]))
1446 foreach ($bugs[$person][$due] as $bug)
1448 $n++;
1449 $task = $bug["task"];
1450 $subtask = $bug["subtask"];
1451 $pri = $bug["priority"];
1452 $priclass = "pri$pri";
1453 $isbug = (($task + 0)."" == $task);
1454 if ($bug["resolved"] || !$isbug)
1455 $bugclass = "resolved $priclass";
1456 else
1458 $bugclass = "unresolved $priclass $dateclass";
1459 $num_unresolved++;
1461 if ($isbug)
1462 $v .= "<a class='$bugclass' " .
1463 "href='http://nits/fogbugz3?$task' " .
1464 "title='Bug $task: $subtask'>$pri</a>";
1465 else
1466 $v .= "<span class='bugclass' " .
1467 "title=\"$task: $subtask\">$pri</span>";
1468 if ($bug["currest"] < 0.05)
1469 $v .= "<sup>?</sup>";
1470 $v .= " ";
1474 if ($v == "" || $num_unresolved==0)
1475 $colclass = "";
1476 else
1477 $colclass = $dateclass;
1479 $ret .= "<td class='bugs $colclass'>$v</td>";
1481 $ret .= "</tr>\n";
1484 $ret .= "</table>\n";
1486 $ret .= "<p><b>Done for this release:</b> " .
1487 join(", ", array_values($allpeople)) .
1488 "</p>\n";
1490 return $ret;
1494 class Macro_Sched
1496 var $pagestore;
1498 function parse($args, $page)
1500 global $sch_start, $sch_curday, $sch_elapsed_curday;
1501 global $sch_user, $sch_load;
1502 global $sch_unknown_fixfor;
1503 global $sch_bug_lists;
1504 global $sch_cur_complete_bugs;
1505 global $sch_cur_incomplete_bugs;
1506 global $sch_got_bug, $sch_manual_bugs;
1507 global $sch_db;
1509 $ret = "";
1511 if (!preg_match_all('/"[^"]*"|[^ \t]+/', $args, $words))
1512 return "regmatch failed!\n";
1513 $words = $words[0]; // don't know why I have to do this, exactly...
1515 foreach ($words as $key => $value)
1517 if (preg_match('/^"(.*)"$/', $value, $result))
1518 $words[$key] = $result[1];
1521 if (0)
1523 $ret .= "Hello: ";
1524 foreach ($words as $word)
1525 $ret .= "($word) ";
1526 $ret .= "<br>\n";
1529 if ($words[0] == "START")
1531 // Initialize bug lists
1532 $sch_bug_lists = array();
1533 $sch_cur_incomplete_bugs = array();
1534 $sch_cur_complete_bugs = array();
1535 $sch_unknown_fixfor = array();
1537 $sch_user = $words[1];
1538 $sch_start = $sch_curday = $sch_elapsed_curday
1539 = sch_parse_day($words[2]);
1540 $sch_unknown_fixfor = array();
1541 $sch_load = 0.1;
1542 sch_switch_loadfactor(1.0);
1544 $sch_db = new FogTables($sch_user, -1);
1546 bug_start_user($sch_user);
1548 else if ($words[0] == "LOADFACTOR")
1550 $loadtmp = $words[1] + 0.0;
1551 if ($loadtmp < 0.1)
1552 $ret .= "(INVALID LOAD FACTOR:'$words[1]')";
1554 sch_switch_loadfactor($loadtmp);
1556 else if ($words[0] == "FIXFOR")
1558 // We now know where the current lists of bugs go
1559 sch_merge_cur_bugs($words[1]);
1561 // Carry over the current LoadFactor
1562 // FIXME: Make this a function or something
1563 // FIXME: Instead of adding a LoadFactor "bug", just set the
1564 // LoadFactor global and set the LoadFactor we'll use for FogBugz
1565 // targeted at this milestone somehow.
1566 $loadbug = array("LOADFACTOR", $sch_load, "", "", "");
1567 $sch_cur_incomplete_bugs[] = $loadbug;
1568 $sch_cur_complete_bugs[] = $loadbug;
1570 else if ($words[0] == "SETBOUNCE")
1572 $cmd = array_shift($words);
1573 $msname = bug_milestone_realname(array_shift($words));
1574 bug_set_milestones($msname, $words);
1576 else if ($words[0] == "MILESTONE" || $words[0] == "RELEASE"
1577 || $words[0] == "BOUNCE")
1579 $msname = bug_milestone_realname($words[1]);
1580 $msdue = $words[2];
1582 // We now know where the current lists of bugs go
1583 sch_merge_cur_bugs($msname);
1585 // Carry over the current LoadFactor
1586 // FIXME: Make this a function or something
1587 // FIXME: Instead of adding a LoadFactor "bug", just set the
1588 // LoadFactor global and set the LoadFactor we'll use for FogBugz
1589 // targeted at this milestone somehow.
1590 $loadbug = array("LOADFACTOR", $sch_load, "", "", "");
1591 $sch_cur_incomplete_bugs[] = $loadbug;
1592 $sch_cur_complete_bugs[] = $loadbug;
1594 bug_set_release($msname, $msdue);
1595 bug_add_tasks($sch_user, $msname, $sch_unknown_fixfor);
1596 $sch_unknown_fixfor = array();
1598 else if ($words[0] == "END")
1600 bug_add_tasks($sch_user, 'UNKNOWN', $sch_unknown_fixfor);
1601 bug_finish_user($sch_user);
1602 bug_add_volunteer_tasks();
1604 $ret .= sch_create($sch_user);
1606 else if ($words[0] == "SUMMARY")
1608 $fixfor = $words[1];
1609 $ret .= sch_summary($fixfor);
1611 else
1613 $bugid = $words[0];
1614 $task = $words[1];
1615 $est = $words[2];
1616 $elapsed = $words[3];
1618 $force_done = ($est && $est==$elapsed);
1620 $fixfor = '';
1621 $orig = $est;
1622 $done = $force_done;
1623 if (preg_match('/^[0-9]+$/', $bugid))
1625 // title, origest, currest, elapsed, status, fixfor
1626 $bugdata = bug_get($bugid);
1627 if (!$task) $task = $bugdata[0];
1628 $orig = $bugdata[1];
1629 if (!strcmp($est,'')) $est = $bugdata[2];
1630 if (!strcmp($elapsed,'')) $elapsed = $bugdata[3];
1631 if (!$done && $bugdata[4] != 'ACTIVE') $done = 1;
1632 if ((!$done && $curr != $elapsed) || ($bugdata[4] != 'ACTIVE'))
1634 // already listed as not done, *or* really done in fogbugz:
1635 // never need to auto-import this bug again.
1636 $sch_got_bug[$feat] = 1;
1638 $fixfor = $bugdata[5];
1639 $sch_manual_bugs[$bugid] = 1;
1642 if (!$elapsed) $elapsed = 0;
1644 // Make an array entry to put into bug lists
1645 // FIXME: Make a Bug object, set its done status and loadfactor
1646 $bug = array($bugid, $task, $orig, $est, $elapsed);
1648 if (!$force_done && !$done)
1650 $sch_cur_incomplete_bugs[] = $bug;
1651 // $ret .= "Adding bug ($bug[0], $bug[1], $bug[2], $bug[3], $bug[4], $bug[5]) to incomplete buglist<br>\n";
1653 else
1655 $sch_cur_complete_bugs[] = $bug;
1656 // $ret .= "Adding bug ($bug[0], $bug[1], $bug[2], $bug[3], $bug[4], $bug[5]) to completed buglist<br>\n";
1660 return $ret;
1664 return 1;