import
[web.mtrack] / web / milestone.php
1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
3
4 include '../inc/common.php';
5 $pi = urldecode(mtrack_get_pathinfo());
6
7 function parse_date_string($str)
8 {
9   if (!strlen($str)) {
10     return null;
11   }
12   return MTrackDB::unixtime(strtotime($str));
13 }
14
15 if ($_GET['new'] == 1 || $_GET['edit'] == 1) {
16   $error = null;
17
18   if ($_SERVER['REQUEST_METHOD'] == 'POST') {
19     if (isset($_POST['cancel'])) {
20       header("Location: {$ABSWEB}roadmap.php");
21       exit;
22     }
23
24     if ($_GET['new'] == 1) {
25       MTrackACL::requireAllRights("Roadmap", 'create');
26       $ms = new MTrackMilestone;
27     } else {
28       MTrackACL::requireAllRights("Roadmap", 'modify');
29       $ms = MTrackMilestone::loadById($_POST['mid']);
30     }
31
32     if (strlen($_POST['name'])) {
33       $ms->name = $_POST['name'];
34       $ms->description = $_POST['desc'];
35
36       $pmid = (int)$_POST['pmid'];
37       if ($pmid > 0) {
38         $pm = MTrackMilestone::loadById($pmid);
39         if (!$pm) {
40           $error = "There is no milestone with a parent of $pmid";
41         } else {
42           $ms->pmid = $pmid;
43         }
44       } else {
45         $ms->pmid = null;
46       }
47
48       $ms->duedate = parse_date_string($_POST['duedate']);
49       $ms->startdate = parse_date_string($_POST['startdate']);
50
51       $compdate = parse_date_string($_POST['compdate']);
52       if ($ms->completed === null && $compdate !== null) {
53         $description = "$ms->name completed";
54       } else {
55         $description = $ms->description;
56       }
57       $ms->completed = $compdate;
58
59       $other = MTrackMilestone::loadByName($_POST['name']);
60       if ($other && ($_GET['new'] == 1 || $ms->mid != $other->mid)) {
61         $error = "a milestone named \"$ms->name\" already exists";
62       } else if ($error === null) {
63         $CS = MTrackChangeset::begin("milestone:$ms->name", $description);
64         $ms->save($CS);
65
66         if ($pmid < 1 && $_POST['additers'] == 'on') {
67           /* add children for iterations (not allowed for milestones
68            * that are themselves a child of another */
69           $start = strtotime($ms->startdate);
70           $end = strtotime($ms->duedate);
71           $days = (int)$_POST['iterduration'];
72
73           $n = 1;
74           $link = rawurlencode($ms->name);
75           while ($start < $end) {
76             $kid = new MTrackMilestone;
77             $kid->name = $ms->name . " ($n)";
78             $kid->description = "Iteration $n of [milestone:$link]";
79             $kid->startdate = MTrackDB::unixtime($start);
80             $due = strtotime("+$days day", $start);
81             if ($due > $end) {
82               $due = $end;
83             }
84             $kid->duedate = MTrackDB::unixtime($due);
85             $kid->pmid = $ms->mid;
86
87             $kid->save($CS);
88
89             $start = strtotime("+1 day", $due);
90             $n++;
91           }
92         }
93
94         if ($ms->completed !== null && $_POST['compmilestone'] != '') {
95           $TM = MTrackMilestone::loadById($_POST['compmilestone']);
96           foreach (MTrackDB::q("select t.tid from ticket_milestones tm left join tickets t on (tm.tid = t.tid) where mid = ? and status != 'closed'", $ms->mid)->fetchAll(PDO::FETCH_COLUMN, 0) as $tid) {
97             $T = MTrackIssue::loadById($tid);
98             $T->dissocMilestone($ms);
99             $T->assocMilestone($TM);
100             $T->addComment("$ms->name completed, moving ticket to $TM->name");
101             $T->save($CS);
102           }
103         }
104
105         $CS->commit();
106         header("Location: {$ABSWEB}milestone.php/$ms->name");
107         exit;
108       }
109     }
110     var_export($_POST);
111   } else if (strlen($pi)) {
112     MTrackACL::requireAllRights("Roadmap", 'modify');
113     $ms = MTrackMilestone::loadByName($pi);
114   } else {
115     MTrackACL::requireAllRights("Roadmap", 'create');
116     $ms = new MTrackMilestone;
117   }
118   mtrack_head($_GET['new'] == 1 ? "New Milestone" : "Edit Milestone");
119
120   if ($error) {
121     $error = htmlentities($error, ENT_QUOTES, 'utf-8');
122     echo <<<HTML
123 <div class='ui-state-error ui-corner-all'>
124     <span class='ui-icon ui-icon-alert'></span> $error
125 </div>
126
127 HTML;
128   }
129
130   $name = htmlentities($ms->name, ENT_COMPAT, 'utf-8');
131   $desc = htmlentities($ms->description, ENT_COMPAT, 'utf-8');
132   
133   if ($ms->duedate) {
134     $duedate = date('m/d/y', strtotime($ms->duedate));
135   } else {
136     $duedate = '';
137   }
138   if ($ms->startdate) {
139     $startdate = date('m/d/y', strtotime($ms->startdate));
140   } else {
141     $startdate = '';
142   }
143
144   if ($ms->completed != null) {
145     $compdate = date('m/d/y', strtotime($ms->completed));
146   } else {
147     $compdate = null;
148   }
149
150   if ($_GET['new'] == 1) {
151     echo "<h1>New Milestone</h1>";
152     $save = 'Add';
153   } else {
154     echo "<h1>Edit Milestone</h1>";
155     $save = 'Save';
156   }
157
158   echo <<<HTML
159 <form method='post'>
160 <input type='hidden' name='mid' value='{$ms->mid}'>
161 <div class='field'>
162   <label>Name of the milestone:</label><br>
163   <input type='text' id='name' name='name' size='32' value='$name'>
164 </div>
165 HTML;
166
167   $kids = MTrackDB::q('select name from milestones where pmid = ?', $ms->mid)->fetchAll(PDO::FETCH_COLUMN, 0);
168   if (count($kids)) {
169
170     echo <<<HTML
171 <div class='field'>
172   <label>Children:</label> <em>Effort expended against the following milestones is also counted towards the burndown of this milestone</em><br>
173 HTML;
174
175     foreach ($kids as $name) {
176       echo "<a href='{$ABSWEB}milestone.php/$name'>$name</a><br>\n";
177     }
178
179     echo "</div>\n";
180   
181   } else {
182
183     $parents = array();
184     foreach (MTrackDB::q('select mid, name from milestones where
185         pmid is null and ((deleted != 1 and mid != ? and completed is null)
186         or (mid = ?))
187         order by name',
188         $ms->mid, $ms->pmid)->fetchAll(PDO::FETCH_ASSOC) as $row) {
189       $parents[$row['mid']] = $row['name'];
190     }
191     $parents[''] = '(none)';
192     $parent = mtrack_select_box('pmid', $parents, $ms->pmid);
193
194
195     echo <<<HTML
196 <div class='field'>
197   <label>Parent:</label> <em>Effort expended against a milestone is also counted towards the burndown of its parent</em><br>
198   $parent
199 </div>
200 HTML;
201   }
202
203   $open_milestones = MTrackMilestone::enumMilestones();
204   $open_milestones[''] = '(none)';
205
206   $compmilestone = mtrack_select_box('compmilestone', $open_milestones);
207
208   echo <<<HTML
209 <fieldset>
210   <legend>Schedule</legend>
211   <div class='field'>
212     <label>Start:<br>
213       <input type='text' id='startdate' name='startdate' size='0'
214         value='$startdate' class='dateinput'>
215       <em>Format: MM/DD/YY</em>
216     </label>
217   </div>
218   <div class='field'>
219     <label>Due:<br>
220       <input type='text' id='duedate' name='duedate' size='0'
221         value='$duedate' class='dateinput'>
222       <em>Format: MM/DD/YY</em>
223     </label>
224   </div>
225   <br>
226   <div class='field'>
227     <label>
228       Completed:<br>
229       <input type='text' id='compdate' name='compdate'
230         size='0' value='$compdate' class='dateinput'>
231       <em>Format: MM/DD/YY</em>
232     </label><br>
233     <em>Re-target open tickets to milestone:</em> $compmilestone
234   </div>
235 HTML;
236
237   if (count($kids) == 0 && !$ms->pmid) {
238     echo <<<HTML
239   <br>
240   <div class='field'>
241     <label>
242       <input type='checkbox' id='additers' name='additers'>
243       Add child milestones for iteration tracking<br>
244       <em>Iteration duration of
245       <input type='text' id='iterduration' name='iterduration'
246         size='3' value='7'>
247       days</em>
248     </label>
249   </div>
250 HTML;
251   }
252
253   echo <<<HTML
254 </fieldset>
255 <div class='field'>
256   <fieldset class='iefix'>
257     <label for='desc'>Description</label><br/>
258     <em>By default, the milestone summary will display a burndown chart
259       as though you had added <tt>[[BurnDown(milestone=name,width=50%,height=150)]]</tt> into the description field below.<br>
260       If you wish to change the size and position of the chart, explicitly
261       enter the burndown macro in the description field.<br>
262       To turn off the burndown for this milestone, enter <tt>[[BurnDown()]]</tt> in the description field.
263     </em>
264     <textarea id='desc' name='desc' class='code wiki' rows='10' cols='78'>$desc</textarea>
265   </fieldset>
266 </div>
267 <div class='buttons'>
268   <button type='submit' name='save'>$save Milestone</button>
269   <button type='submit' name='cancel'>Cancel</button>
270 </div>
271 </form>
272 <script type='text/javascript'>
273 $(document).ready(function() {
274   $('#name').focus();
275   $('.dateinput').datepicker({
276     // minDate: 0,
277     dateFormat: 'mm/dd/yy'
278   });
279 });
280 </script>
281 HTML;
282 } else if (strlen($pi)) {
283
284   mtrack_head($pi);
285 echo <<<HTML
286 <div style="float:right">
287 <button onclick="document.location.href='{$ABSWEB}milestone.php/$pi?edit=1';return false;">Edit Milestone</button>
288 </div>
289 HTML;
290
291   echo MTrackMilestone::macro_MilestoneSummary($pi);
292
293   $kids = MTrackDB::q('select name from milestones where pmid = 
294     (select mid from milestones where name = ?)', $pi)
295     ->fetchAll(PDO::FETCH_ASSOC);
296   if (count($kids)) {
297     echo "<h2>Related milestones:</h2>";
298     foreach ($kids as $row) {
299       echo MTrackMilestone::macro_MilestoneSummary($row['name']);
300     }
301   } 
302
303 }  else {
304   throw new Exception("no such milestone $pi");
305 }
306
307 mtrack_foot();