final move of files
[web.mtrack] / MTrackWeb / Ticket.php
1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
3  
4 require_once 'MTrack/Captcha.php';
5
6  
7 require_once 'MTrackWeb.php';
8 class MTrackWeb_Ticket extends MTrackWeb 
9 {
10     var $id; // 0 = new
11     var $issue;
12     var $preview;
13     var $error;
14     var $editable;
15     var $tid = 0; // or the MD5 rep.
16     
17     var $template = 'ticket.html';
18     
19     
20      function getAuth() 
21     {
22         parent::getAuth();
23         //require_once 'MTrack/ACL.php';
24      //   MTrackACL::requireAllRights('Browser', 'read');
25         return true;
26   
27     }
28     
29     function get($pi= 0)
30     {
31     
32         require_once 'MTrack/DataObjects/Userinfo.php';
33         $this->authUser = MTrack_DataObjects_Userinfo::get(MTrackAuth::whoami());
34         
35         $this->id = $pi ?  $pi: (isset($_GET['id']) ? $_GET['id'] : 0);
36         $this->id  = $this->id  == 'new' ? 0 : $this->id;
37         
38         
39     
40         // -- load issue..
41
42         $this->issue = new MTrackIssue;
43         $this->issue->priority = 'normal';
44         if ($this->id) {
45             $this->issue = (strlen($this->id) == 32)  ? 
46                 MTrackIssue::loadById($this->id) : 
47                 MTrackIssue::loadByNSIdent($this->id);
48         }
49         if (!$this->issue) {
50             throw new Exception("Invalid ticket $this->id");
51         }
52         
53         $this->tid = $this->id  ? $this->issue->tid : 0;
54         
55         
56         $this->issue->augmentFormFields($this->fieldset());
57
58
59         $this->preview = false;
60         $this->error = array();
61        
62         $this->rights();
63         
64          
65         $this->issue->milestoneURL = $this->baseURL.'/milestone.php'; // fix me later..
66     
67         $this->showEditBar = false;
68          
69         if ($this->editable && $this->id != 'new' && !$this->preview) {
70             $this->showEditBar = true;
71         }
72          
73         $this->initEditForm();
74  
75         
76       
77     }
78    
79     function post($pi=0) // handle the post...
80     {
81      
82        $this->get($pi);
83        
84         if (isset($_POST['cancel'])) {
85             header("Location: {$$this->baseURL}/Ticket/$this->issue->nsident");
86             exit;
87           }
88           
89           if (!MTrack_Captcha::check('ticket')) {
90             $this->error[] = "CAPTCHA failed, please try again";
91           }
92           $this->preview = isset($_POST['preview']) ? true : false;
93
94           $comment = '';
95           try {
96             if (!$this->id) {
97               MTrackACL::requireAllRights("Tickets", 'create');
98             } else {
99               MTrackACL::requireAllRights("ticket:" . $this->issue->tid, 'modify');
100             }
101           } catch (Exception $e) {
102             $this->error[] = $e->getMessage();
103           }
104           
105           if (!$this->id) {
106             $comment = empty($_POST['comment']) ? '' : $_POST['comment'];
107           }
108           
109           if (!strlen($comment)) {
110             $comment = $_POST['summary'];
111           }
112           try {
113             $CS = MTrackChangeset::begin("ticket:X", $comment);
114           } catch (Exception $e) {
115             $this->error[] = $e->getMessage();
116             $CS = null;
117           }
118           if (!$this->id) {
119             // compute next id number.
120             // We don't use auto-number, because we allow for importing multiple
121             // projects with their own ticket sequence.
122             // During "normal" user-driven operation, we do want plain old id numbers
123             // so we compute it here, under a transaction
124             $db = MTrackDB::get();
125             
126             
127             
128             switch($db->getAttribute(PDO::ATTR_DRIVER_NAME)) {
129                 case 'pgsql':
130                     // Some versions of postgres don't like that we have "abc123" for
131                     // identifiers, so match on the bigest number nsident fields only
132                     $max = "select max(cast(nsident as integer)) + 1 from tickets where nsident ~ '^\\\\d+$'";
133                     break;
134                 
135                 case 'mysql':
136                     $max = "select max(cast(nsident as UNSIGNED)) + 1 from tickets";
137                     break;
138                 
139                 default:
140                     $max = 'select max(cast(nsident as integer)) + 1 from tickets';   
141                     break;
142             }
143             
144             
145              
146             list($this->issue->nsident) = MTrackDB::q($max)->fetchAll(PDO::FETCH_COLUMN, 0);
147             if ($this->issue->nsident === null) {
148               $this->issue->nsident = 1;
149             }
150           }
151
152           if (isset($_POST['action']) && !$this->preview) {
153             $act= explode('_', $_POST['action'] , 2);
154             //var_dump($act);exit;
155             switch ($act[0]) {
156               case 'leave':
157                 break;
158               case 'reopen':
159                 $this->issue->reOpen();
160                 break;
161               case 'fixed':
162                 $this->issue->resolution = 'fixed';
163                 $this->issue->close();
164                 $_POST['estimated'] = $this->issue->estimated;
165                 break;
166                 
167               
168               case 'accept':
169                 // will be applied to the issue further down
170                 $_POST['owner'] = MTrackAuth::whoami();
171                 if ($this->issue->status == 'new') {
172                   $this->issue->status = 'open';
173                 }
174                 break;
175                 
176                 
177               case 'resolve':
178                 //$this->issue->resolution = $_POST['resolution'];
179                 $this->issue->resolution = $act[1];
180                 $this->issue->close();
181                 $_POST['estimated'] = $this->issue->estimated;
182                 break;  
183                 
184               case 'change':
185                 $this->issue->status = $act[1];
186                 break;
187             }
188           }
189
190           $fields = array(
191             'summary',
192             'description',
193             'classification',
194             'priority',
195             'severity',
196             'changelog',
197             'owner',
198             'cc',
199           );
200
201           $this->issue->applyPOSTData($_POST);
202
203          
204           
205           foreach ($fields as $fieldname) {
206             if (isset($_POST[$fieldname]) && strlen($_POST[$fieldname])) {
207               $this->issue->$fieldname = $_POST[$fieldname];
208             } else {
209               $this->issue->$fieldname = null;
210             }
211           }
212
213           $kw = $this->issue->getKeywords();
214           $kill = array_values($kw);
215           foreach (preg_split('/[ \t,]+/', $_POST['keywords']) as $w) {
216             if (!strlen($w)) {
217               continue;
218             }
219             $x = array_search($w, $kw);
220             if ($x === false) {
221               $k = MTrackKeyword::loadByWord($w);
222               if ($k === null) {
223                 $k = new MTrackKeyword;
224                 $k->keyword = $w;
225                 $k->save($CS);
226               }
227               $this->issue->assocKeyword($k);
228             } else {
229               $w = array_search($w, $kill);
230               if ($w !== false) {
231                 unset($kill[$w]);
232               }
233             }
234           }
235           foreach ($kill as $w) {
236             $this->issue->dissocKeyword($w);
237           }
238
239           $ms = $this->issue->getMilestones();
240           $kill = $ms;
241           if (isset($_POST['milestone']) && is_array($_POST['milestone'])) {
242             foreach ($_POST['milestone'] as $mid) {
243               $this->issue->assocMilestone($mid);
244               unset($kill[$mid]);
245             }
246           }
247           foreach ($kill as $mid) {
248             $this->issue->dissocMilestone($mid);
249           }
250
251           $ms = $this->issue->getComponents();
252           $kill = $ms;
253           if (isset($_POST['component']) && is_array($_POST['component'])) {
254             foreach ($_POST['component'] as $mid) {
255               $this->issue->assocComponent($mid);
256               unset($kill[$mid]);
257             }
258           }
259           foreach ($kill as $mid) {
260             $this->issue->dissocComponent($mid);
261           }
262           
263             if (!empty($_POST['comment'])) {
264                $this->issue->addComment($_POST['comment']);
265             }
266           
267           $this->issue->addEffort(
268             empty($_POST['spent']) ? 0 : $_POST['spent'], 
269             empty($_POST['estimate']) ? 0 : $_POST['estimate']
270         );
271
272           if (!count($this->error)) {
273             try {
274               $this->issue->save($CS);
275               
276               // make sure everyone is watching it!!!!
277                 if($this->issue->owner && $this->issue->tid) {
278                   // make sure owner is tracking it...
279                     MTrackWatch::watch_object('ticket', $this->issue->tid,  $this->issue->owner);
280                 }
281                 
282                 if ($this->id == 'new') {
283                     MTrackWatch::watch_object('ticket', $this->issue->tid,  MTrackAuth::whoami());
284                 }
285               
286               
287               $CS->setObject("ticket:" . $this->issue->tid);
288             } catch (Exception $e) {
289               $this->error[] = $e->getMessage();
290             }
291         }
292
293         if (!count($this->error)) {
294             if (!empty($_FILES['attachments'])) {
295                 require_once 'MTrack/Attachment.php';
296                 foreach ($_FILES['attachments']['name'] as $fileid => $name) {
297                       
298                     MTrackAttachment::add("ticket:{$this->issue->tid}",
299                         $_FILES['attachments']['tmp_name'][$fileid],
300                         $_FILES['attachments']['name'][$fileid],
301                         $CS
302                     );
303                 }
304             }
305         }
306         if (!count($this->error) && $this->id != 'new') {
307             require_once 'MTrack/Attachment.php';
308             MTrackAttachment::process_delete("ticket:{$this->issue->tid}", $CS);
309         }
310
311         if (isset($_POST['apply']) && !count($this->error)) {
312           $CS->commit();
313           header("Location: {$this->baseURL}/Ticket/{$this->issue->nsident}");
314           exit;
315         }
316     }
317      
318     function fieldset() // depreciated... eventually...
319     {
320         return array(
321             array(
322               "description" => array(
323                 "label" => "Full description",
324                 "ownrow" => true,
325                 "type" => "wiki",
326                 "rows" => 10,
327                 "cols" => 78,
328                 "editonly" => true,
329                 ),
330               ),
331             "Properties" => array(
332               "milestone" => array(
333                 "label" => "Milestone",
334                 "type" => "multiselect",
335                 ),
336               "component" => array(
337                 "label" => "Component",
338                 "type" => "multiselect",
339                 ),
340               "classification" => array(
341                 "label" => "Classification",
342                 "type" => "select",
343                 ),
344               "priority" => array(
345                 "label" => "Priority",
346                 "type" => "select",
347                 ),
348               "severity" => array(
349                 "label" => "Severity",
350                 "type" => "select",
351                 ),
352               "keywords" => array(
353                   "label" => "Keywords",
354                   "type" => "text",
355                   ),
356               "changelog" => array(
357                   "label" => "ChangeLog (customer visible)",
358                   "type" => "multi",
359                   "ownrow" => true,
360                   "rows" => 5,
361                   "cols" => 78,
362                #   "condition" => $this->issue->status == 'closed'
363                   ),
364               ),
365               "Resources" => array(
366                   "owner" => array(
367                     "label" => "Responsible",
368                     "type" => "select"
369                     ),
370                   "estimated" => array(
371                     "label" => "Estimated Hours",
372                     "type" => "text"
373                     ),
374                   "spent" => array(
375                     "label" => "Spent Hours",
376                     "type" => "text",
377                     "readonly" => true,
378                     ),
379                   "cc" => array(
380                     "label" => "Cc",
381                     "type" => "text"
382                     ),
383                   ),
384               );
385     }
386     
387     function initEditForm($params = array())
388     {
389         require_once 'HTML/Template/Flexy/Element.php';
390         require_once 'HTML/Template/Flexy/Factory.php';
391         $this->elements = array();
392         
393         require_once 'MTrack/Classification.php';
394         require_once 'MTrack/Priority.php';
395         require_once 'MTrack/Severity.php';
396         require_once 'MTrack/Resolution.php';
397         
398         foreach(array( 'classification', 'priority', 'severity', 'resolution' ) as $c)  {
399             $cls = 'Mtrack'. $c;
400             $C = new $cls;
401             $ar = $C->enumerate();
402             $this->elements[$c] = new HTML_Template_Flexy_Element('select');
403             $this->elements[$c]->setOptions($C->enumerate());
404             
405         }
406         
407         
408
409         $r = array();
410         $q = MTrackDB::q('
411             select c.compid, c.name, p.name 
412                 from components c 
413                     left join components_by_project cbp on (c.compid = cbp.compid) 
414                     left join projects p on (cbp.projid = p.projid) 
415                 where 
416                     deleted <> 1 
417                 order by 
418                     c.name');
419             
420         foreach ($q->fetchAll(PDO::FETCH_NUM) as $row) {
421               
422             $r[$row[0]] = strlen($row[2]) ? $row[1] . " ($row[2])" : $row[1];
423             
424         }
425        // print_r($r);
426         
427         $this->elements['component[]'] = new HTML_Template_Flexy_Element('select');
428         $this->elements['component[]']->setOptions($r);
429         // compenets 
430         $ar = $this->issue->getComponents();
431         $this->elements['component[]']->setValue(array_keys($ar));
432         
433         
434         
435         $r = array();
436         foreach (MTrackDB::q(
437                 'select mid, name from milestones where deleted <> 1
438                 and completed is null order by (case when duedate is null then 1 else 0 end), duedate, name'
439                 )->fetchAll(PDO::FETCH_NUM) as $row) {
440             $r[$row[0]] = $row[1];
441         }
442         foreach ($this->issue->getMilestones() as $mid => $name) {
443             if (!isset($r[$mid])) {
444               $r[$mid] = $name;
445             }
446         }
447         $this->elements['milestone[]'] = new HTML_Template_Flexy_Element('select');
448         $this->elements['milestone[]']->setOptions($r);
449  
450  
451  
452  
453           // FIXME: workflow should be able to influence this list of users
454         $users = array();
455         $inactiveusers = array();
456         require_once 'MTrack/DataObjects/Userinfo.php';
457         $users = MTrack_DataObjects_Userinfo::selectList(array(''=>'nobody'));
458          
459           // last ditch to have it show the right info
460         if (!isset($users[$this->issue->owner])) {
461             $users[$this->issue->owner] = $this->issue->owner;
462         }
463         
464         $this->elements['owner'] = new HTML_Template_Flexy_Element('select');
465         $this->elements['owner']->setOptions($users);
466         
467         
468         
469         // keywords -- in toArray...
470         // milestone 
471           
472           
473           $this->change_status = array();
474           $this->resolve_status = array();
475           if ($this->id) {
476          
477             // for coder's they can only change this ticke to certian states
478             
479             //print_r($groups);
480             // Nasty - I really do not like the acl code in this ...
481             require_once 'MTrack/TicketState.php';
482             
483             $ST = new MTrackTicketState;
484             $ST = $ST->enumerate();
485             //print_r($ST);
486             unset($ST['closed']);
487             unset($ST[$this->issue->status]);
488            
489             $this->change_status = empty($ST) ? array() : array_keys($ST);
490             
491             $ac = MTrackAuth::getUserClass($this->authUser->userid);
492             //var_dump($ac);exit;
493 // KLUDGE! - remove later...
494             if ($ac == 'admin') {
495                 
496                 $this->resolve_status= array('fixed');
497                 $R = new MTrackResolution;
498                 $resolutions = $R->enumerate();
499                 unset($resolutions['fixed']);
500                 
501                 $this->resolve_status= array_keys($resolutions);
502                 array_unshift($this->resolve_status, 'fixed');
503                // $html .= $this->mtrack_chg_status('action', 'resolve', 'Resolve as:', 'resolution', $resolutions, $this->issue );
504             } 
505
506            // } else {
507            //   $html .= mtrack_radio('action', 'reopen', $_POST['action']);
508            //   $html .= " <label for='reopen'>Reopen ticket</label><br>\n";
509            // }
510            
511         }
512         $this->elements = HTML_Template_Flexy_Factory::fromArray($this->issue->toArray(), $this->elements);
513         if (!empty($_POST)) {
514             $this->elements = HTML_Template_Flexy_Factory::fromArray($_POST, $this->elements);
515         }
516         
517         
518
519           
520     }
521    
522     function rights() {
523         if ($this->id == 'new' || empty($this->id)) {
524             MTrackACL::requireAllRights("Tickets", 'create');
525             $this->editable = MTrackACL::hasAllRights("Tickets", 'create');
526             return;
527         } 
528         
529         MTrackACL::requireAllRights("ticket:" . $this->issue->tid, 'read');
530         $this->editable = MTrackACL::hasAllRights("ticket:" . $this->issue->tid, 'modify');
531   
532     }
533
534     function captcha()
535     {
536         return MTrack_Captcha::emit('ticket');
537     }
538  
539  
540     function eq($a,$b) {
541         return $a == $b;
542     }
543     
544 }