1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
4 require_once 'MTrack/Captcha.php';
7 require_once 'MTrackWeb.php';
8 class MTrackWeb_Ticket extends MTrackWeb
15 var $tid = 0; // or the MD5 rep.
17 var $template = 'ticket.html';
23 //require_once 'MTrack/ACL.php';
24 // MTrackACL::requireAllRights('Browser', 'read');
32 require_once 'MTrack/DataObjects/Userinfo.php';
33 $this->authUser = MTrack_DataObjects_Userinfo::get(MTrackAuth::whoami());
35 $this->id = $pi ? $pi: (isset($_GET['id']) ? $_GET['id'] : 0);
36 $this->id = $this->id == 'new' ? 0 : $this->id;
42 $this->issue = new MTrackIssue;
43 $this->issue->priority = 'normal';
45 $this->issue = (strlen($this->id) == 32) ?
46 MTrackIssue::loadById($this->id) :
47 MTrackIssue::loadByNSIdent($this->id);
50 throw new Exception("Invalid ticket $this->id");
53 $this->tid = $this->id ? $this->issue->tid : 0;
56 $this->issue->augmentFormFields($this->fieldset());
59 $this->preview = false;
60 $this->error = array();
65 $this->issue->milestoneURL = $this->baseURL.'/milestone.php'; // fix me later..
67 $this->showEditBar = false;
69 if ($this->editable && $this->id != 'new' && !$this->preview) {
70 $this->showEditBar = true;
73 $this->initEditForm();
79 function post($pi=0) // handle the post...
84 if (isset($_POST['cancel'])) {
85 header("Location: {$$this->baseURL}/Ticket/$this->issue->nsident");
89 if (!MTrack_Captcha::check('ticket')) {
90 $this->error[] = "CAPTCHA failed, please try again";
92 $this->preview = isset($_POST['preview']) ? true : false;
97 MTrackACL::requireAllRights("Tickets", 'create');
99 MTrackACL::requireAllRights("ticket:" . $this->issue->tid, 'modify');
101 } catch (Exception $e) {
102 $this->error[] = $e->getMessage();
106 $comment = empty($_POST['comment']) ? '' : $_POST['comment'];
109 if (!strlen($comment)) {
110 $comment = $_POST['summary'];
113 $CS = MTrackChangeset::begin("ticket:X", $comment);
114 } catch (Exception $e) {
115 $this->error[] = $e->getMessage();
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();
128 switch($db->getAttribute(PDO::ATTR_DRIVER_NAME)) {
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+$'";
136 $max = "select max(cast(nsident as UNSIGNED)) + 1 from tickets";
140 $max = 'select max(cast(nsident as integer)) + 1 from tickets';
146 list($this->issue->nsident) = MTrackDB::q($max)->fetchAll(PDO::FETCH_COLUMN, 0);
147 if ($this->issue->nsident === null) {
148 $this->issue->nsident = 1;
152 if (isset($_POST['action']) && !$this->preview) {
153 $act= explode('_', $_POST['action'] , 2);
154 //var_dump($act);exit;
159 $this->issue->reOpen();
162 $this->issue->resolution = 'fixed';
163 $this->issue->close();
164 $_POST['estimated'] = $this->issue->estimated;
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';
178 //$this->issue->resolution = $_POST['resolution'];
179 $this->issue->resolution = $act[1];
180 $this->issue->close();
181 $_POST['estimated'] = $this->issue->estimated;
185 $this->issue->status = $act[1];
201 $this->issue->applyPOSTData($_POST);
205 foreach ($fields as $fieldname) {
206 if (isset($_POST[$fieldname]) && strlen($_POST[$fieldname])) {
207 $this->issue->$fieldname = $_POST[$fieldname];
209 $this->issue->$fieldname = null;
213 $kw = $this->issue->getKeywords();
214 $kill = array_values($kw);
215 foreach (preg_split('/[ \t,]+/', $_POST['keywords']) as $w) {
219 $x = array_search($w, $kw);
221 $k = MTrackKeyword::loadByWord($w);
223 $k = new MTrackKeyword;
227 $this->issue->assocKeyword($k);
229 $w = array_search($w, $kill);
235 foreach ($kill as $w) {
236 $this->issue->dissocKeyword($w);
239 $ms = $this->issue->getMilestones();
241 if (isset($_POST['milestone']) && is_array($_POST['milestone'])) {
242 foreach ($_POST['milestone'] as $mid) {
243 $this->issue->assocMilestone($mid);
247 foreach ($kill as $mid) {
248 $this->issue->dissocMilestone($mid);
251 $ms = $this->issue->getComponents();
253 if (isset($_POST['component']) && is_array($_POST['component'])) {
254 foreach ($_POST['component'] as $mid) {
255 $this->issue->assocComponent($mid);
259 foreach ($kill as $mid) {
260 $this->issue->dissocComponent($mid);
263 if (!empty($_POST['comment'])) {
264 $this->issue->addComment($_POST['comment']);
267 $this->issue->addEffort(
268 empty($_POST['spent']) ? 0 : $_POST['spent'],
269 empty($_POST['estimate']) ? 0 : $_POST['estimate']
272 if (!count($this->error)) {
274 $this->issue->save($CS);
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);
282 if ($this->id == 'new') {
283 MTrackWatch::watch_object('ticket', $this->issue->tid, MTrackAuth::whoami());
287 $CS->setObject("ticket:" . $this->issue->tid);
288 } catch (Exception $e) {
289 $this->error[] = $e->getMessage();
293 if (!count($this->error)) {
294 if (!empty($_FILES['attachments'])) {
295 require_once 'MTrack/Attachment.php';
296 foreach ($_FILES['attachments']['name'] as $fileid => $name) {
298 MTrackAttachment::add("ticket:{$this->issue->tid}",
299 $_FILES['attachments']['tmp_name'][$fileid],
300 $_FILES['attachments']['name'][$fileid],
306 if (!count($this->error) && $this->id != 'new') {
307 require_once 'MTrack/Attachment.php';
308 MTrackAttachment::process_delete("ticket:{$this->issue->tid}", $CS);
311 if (isset($_POST['apply']) && !count($this->error)) {
313 header("Location: {$this->baseURL}/Ticket/{$this->issue->nsident}");
318 function fieldset() // depreciated... eventually...
322 "description" => array(
323 "label" => "Full description",
331 "Properties" => array(
332 "milestone" => array(
333 "label" => "Milestone",
334 "type" => "multiselect",
336 "component" => array(
337 "label" => "Component",
338 "type" => "multiselect",
340 "classification" => array(
341 "label" => "Classification",
345 "label" => "Priority",
349 "label" => "Severity",
353 "label" => "Keywords",
356 "changelog" => array(
357 "label" => "ChangeLog (customer visible)",
362 # "condition" => $this->issue->status == 'closed'
365 "Resources" => array(
367 "label" => "Responsible",
370 "estimated" => array(
371 "label" => "Estimated Hours",
375 "label" => "Spent Hours",
387 function initEditForm($params = array())
389 require_once 'HTML/Template/Flexy/Element.php';
390 require_once 'HTML/Template/Flexy/Factory.php';
391 $this->elements = array();
393 require_once 'MTrack/Classification.php';
394 require_once 'MTrack/Priority.php';
395 require_once 'MTrack/Severity.php';
396 require_once 'MTrack/Resolution.php';
398 foreach(array( 'classification', 'priority', 'severity', 'resolution' ) as $c) {
401 $ar = $C->enumerate();
402 $this->elements[$c] = new HTML_Template_Flexy_Element('select');
403 $this->elements[$c]->setOptions($C->enumerate());
411 select c.compid, c.name, p.name
413 left join components_by_project cbp on (c.compid = cbp.compid)
414 left join projects p on (cbp.projid = p.projid)
420 foreach ($q->fetchAll(PDO::FETCH_NUM) as $row) {
422 $r[$row[0]] = strlen($row[2]) ? $row[1] . " ($row[2])" : $row[1];
427 $this->elements['component[]'] = new HTML_Template_Flexy_Element('select');
428 $this->elements['component[]']->setOptions($r);
430 $ar = $this->issue->getComponents();
431 $this->elements['component[]']->setValue(array_keys($ar));
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];
442 foreach ($this->issue->getMilestones() as $mid => $name) {
443 if (!isset($r[$mid])) {
447 $this->elements['milestone[]'] = new HTML_Template_Flexy_Element('select');
448 $this->elements['milestone[]']->setOptions($r);
453 // FIXME: workflow should be able to influence this list of users
455 $inactiveusers = array();
456 require_once 'MTrack/DataObjects/Userinfo.php';
457 $users = MTrack_DataObjects_Userinfo::selectList(array(''=>'nobody'));
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;
464 $this->elements['owner'] = new HTML_Template_Flexy_Element('select');
465 $this->elements['owner']->setOptions($users);
469 // keywords -- in toArray...
473 $this->change_status = array();
474 $this->resolve_status = array();
477 // for coder's they can only change this ticke to certian states
480 // Nasty - I really do not like the acl code in this ...
481 require_once 'MTrack/TicketState.php';
483 $ST = new MTrackTicketState;
484 $ST = $ST->enumerate();
486 unset($ST['closed']);
487 unset($ST[$this->issue->status]);
489 $this->change_status = empty($ST) ? array() : array_keys($ST);
491 $ac = MTrackAuth::getUserClass($this->authUser->userid);
492 //var_dump($ac);exit;
493 // KLUDGE! - remove later...
494 if ($ac == 'admin') {
496 $this->resolve_status= array('fixed');
497 $R = new MTrackResolution;
498 $resolutions = $R->enumerate();
499 unset($resolutions['fixed']);
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 );
507 // $html .= mtrack_radio('action', 'reopen', $_POST['action']);
508 // $html .= " <label for='reopen'>Reopen ticket</label><br>\n";
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);
523 if ($this->id == 'new' || empty($this->id)) {
524 MTrackACL::requireAllRights("Tickets", 'create');
525 $this->editable = MTrackACL::hasAllRights("Tickets", 'create');
529 MTrackACL::requireAllRights("ticket:" . $this->issue->tid, 'read');
530 $this->editable = MTrackACL::hasAllRights("ticket:" . $this->issue->tid, 'modify');
536 return MTrack_Captcha::emit('ticket');