3 * Table Definition for mtrack_ticket
5 class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php';
7 class Pman_MTrack_DataObjects_Mtrack_ticket extends DB_DataObject
10 /* the code below is auto generated do not remove the above tag */
12 public $__table = 'mtrack_ticket'; // table name
13 public $id; // int(11) not_null primary_key auto_increment
14 public $summary; // blob(-1) blob
15 public $description; // blob(-1) blob
16 public $changelog; // blob(-1) blob
17 public $created; // int(11) not_null
18 public $updated; // int(11) not_null
19 public $owner_id; // int(11) not_null
20 public $developer_id; // int(11) not_null
22 public $priority_id; // int(11) not_null
24 public $severity_id; // int(11) not_null
25 public $classification_id; // int(11) not_null
26 public $resolution_id; // int(11) not_null
27 public $cc; // blob(65535) blob
28 public $status; // int(11) not_null
29 public $estimated; // real(22)
30 public $spent; // real(22)
31 public $x_fieldname; // blob(65535) blob
35 public $act_now; // a field to flag as ready to go.
36 // we only allow 3 of these to
37 // be active at the same time..
39 /* the code above is auto generated do not remove the tag below */
41 //checkPerm('S'/'E'/'A', $authuser) - can
42 function checkPerm($perm, $au)
44 $pr = $this->project();
45 // var_dump($pr ? $pr->name : false); var_dump($perm);
47 if (($perm == 'S') && (!$pr || ($pr->code == '*PUBLIC'))) {
53 // admins with all project rights.
54 if ($au->hasPerm('Core.Projects_All', 'S')) {
58 $pd = DB_DataObject::factory('ProjectDirectory');
59 $pd->user_id = $au->id;
60 $pd->project_id = $this->project_id;
61 $pd->whereAdd("role != ''");
72 if (!$this->project_id) {
75 $p = DB_DataObject::factory('Projects');
76 $p->get($this->project_id);
85 $p = DB_DataObject::factory('core_enum');
86 $p->get($this->status);
93 if (!$this->resolution_id) {
96 $p = DB_DataObject::factory('core_enum');
97 $p->get($this->resolution_id);
102 function classification()
104 if (!$this->classification_id) {
107 $p = DB_DataObject::factory('core_enum');
108 $p->get($this->classification_id);
115 if (!$this->developer_id) {
118 $p = DB_DataObject::factory('Person');
119 $p->get($this->developer_id);
124 function applyFilters($q, $au, $roo)
126 if (isset($q['query']['ticket_change'])) {
127 return $this->ticketChangeOptions($roo,$q, $au);
130 $tn = $this->tableName();
132 // then we only show global projects..
133 // DB_DAtaObject::DebugLevel(1);
134 $this->whereAdd(" join_project_id_id.code = '*PUBLIC' ");
135 } else if ($au->hasPerm('Core.Projects_All', 'S')) {
136 // // if we are admin?? should we show all..
139 //DB_DataObject::debugLevel(1);
140 // restrict to any projects that the user is involved in..
141 $pd = DB_DataObject::factory('ProjectDirectory');
142 $pd->person_id = $au->id;
143 $pd->whereAdd("role != ''");
146 $this->whereAdd(" join_project_id_id.code = '*PUBLIC' ");
148 // they can do what they like on all projects.
153 $ar = $pd->fetchAll('project_id');
154 $this->whereAddIn("join_project_id_id.id", $ar, 'int');
161 if ($au && isset($q['_options_for'])) {
162 $this->optionsForProject($roo, $q['_options_for']);
163 $this->jerr("somethings missing");
169 $jira_ref = "(SELECT jira_key
172 WHERE on_table='mtrack_ticket' AND on_id = {$tn}.id LIMIT 1)";
175 if (empty($q['_distinct'])) {
176 $tn = $this->tableName();
177 $this->selectAdd("$jira_ref as jira_ref ");
182 if (!empty($q['query']['search'])) {
184 $sq = $this->escape($q['query']['search']);
186 if (is_numeric($q['query']['search'])) {
187 $cond = "OR {$tn}.id = {$q['query']['search']}";
190 {$tn}.summary like '%{$sq}%'
192 {$tn}.description like '%{$sq}%'
194 $jira_ref like '%{$sq}%'
198 if (is_numeric($q['query']['search'])) {
199 // allow quick lookup by number..
206 if (!empty($q['_developer'])) {
207 switch ($q['_developer']) {
209 $this->whereAdd("mtrack_ticket.developer_id = {$au->id} ");
217 if (!empty($q['query']['viewtype'])) {
219 switch ($q['query']['viewtype']) {
221 // hide "on hold" and closed..
222 $this->whereAdd("join_status_id.name NOT IN('closed', 'on hold') OR {$tn}.status = 0");
223 $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
227 $this->whereAdd("join_status_id.name = 'on hold' ");
228 $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
232 $this->whereAdd("mtrack_ticket.developer_id = {$au->id} ");
233 $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
237 $this->whereAdd("join_status_id.name != 'closed' OR {$tn}.status = 0 ");
238 $this->whereAdd("mtrack_ticket.developer_id = {$au->id} ");
244 $this->whereAdd("join_status_id.name ='closed'");
248 case 'active-or-recent':
250 (join_status_id.name != 'closed' OR {$tn}.status = 0)
252 (join_status_id.name = 'closed' AND join_updated.changedate > NOW() - 1 MONTH )
254 $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
258 $this->whereAdd("join_status_id.name IN ('resolved', 'needs review' )");
262 $this->whereAdd("join_status_id.name IN ('in progress' )");
266 $this->whereAdd("1=1");
270 $this->whereAdd("1=0");
278 if (!empty($q['_orderby_self_alloc '])) {
279 $auid = $au ? (int) $au->id : 0;
281 IF(developer_id = $auid , 1, 0) as is_self_alloc
283 $this->orderBy('is_self_alloc DESC');
288 if (!empty($q['query']['include_id'])) {
289 $this->whereAdd("{$tn}.id = " . ((int) $q['query']['include_id'] ), 'OR');
293 /// only show unposted..
298 function ticketChangeOptions($roo, $q, $au)
300 $et = DB_DataObject::Factory('core_enum');
302 $et->whereAddIn('etype', array(
311 // the end result of this:
316 ticketstate_[me] ????
318 ticketstate_[... everything else except close & new ...]
319 .. eg. open, reopened..
321 // these actually close the ticket...
322 // so the do not appear if the ticket is closed...
329 if (empty($q['query']['ticket_change'])) {
330 $roo->jerr("no ticket!");
333 // find out what state the ticket is in.
334 $t = DB_DataObject::Factory('mtrack_ticket');
336 if (strpos($q['query']['ticket_change'], ',') > 0) {
338 $t->status_name = 'xx';
342 $t->get($q['query']['ticket_change']);
345 if (!$et->checkPerm('S', $au)) {
346 $roo->jerr("permssion denied to query state of ticket");
351 //$this->debugLevel(1);
356 IF( id = {$t->status} , 0, 10000) +
357 IF( etype='ticketstate'
358 AND name='assigned' , 10, 0) +
359 IF( etype='ticketstate' AND
360 name !='closed' AND name != 'new' , 100, 0) +
361 IF( etype='ticketstate' , 0 , 1000) +
370 switch($t->status_name) {
373 // do not show closed..
375 ( CONCAT(etype , '.', name) != 'ticketstate.closed')
377 ( CONCAT(etype , '.', name) != 'ticketstate.reopened')
384 ( CONCAT(etype , '.', name) = 'ticketstate.reopened')
386 core_enum.id = {$t->status}
392 ( CONCAT(etype , '.', name) != 'ticketstate.closed')
394 ( CONCAT(etype , '.', name) != 'ticketstate.new')
396 ( CONCAT(etype , '.', name) != 'ticketstate.open')
403 ( CONCAT(etype , '.', name) != 'ticketstate.closed')
405 ( CONCAT(etype , '.', name) != 'ticketstate.new')
407 ( CONCAT(etype , '.', name) != 'ticketstate.reopened')
416 $et->orderBy("ts_order ASC");
418 $roo->jdata( $et->fetchAll(false,false,'toArray'));
425 *-- return false to let system do default sort code.
428 function applySort($au, $sortcol, $direction)
431 $direction = $direction == 'DESC' ? 'DESC' : 'ASC';
433 $tn = $this->tableName();
438 $this->orderBy("$sortcol $direction");
444 when join_status_id.display_name = 'in progress' then 1
445 when join_status_id.display_name = 'resolved' then -1
448 (case when join_milestone_id_id.duedate is null then 1 else 0 end) ASC,
449 join_milestone_id_id.duedate $direction ,
450 {$tn}.seqid $direction ,
451 join_milestone_id_id.name $direction
458 (case when join_milestone_id_id.duedate is null then 1 else 0 end) ASC,
459 join_milestone_id_id.duedate ASC ,
460 join_severity_id_id.seqid ASC,
461 join_priority_id_id.seqid ASC,
462 join_classification_id_id.seqid ASC,
463 join_updated_id.changedate DESC
469 when join_status_id.display_name = 'in progress' then 1
470 when join_status_id.display_name = 'resolved' then -1
473 join_priority_id_id.seqid $direction, {$tn}.seqid ASC
477 // this should favour in_progress, and disfavour resolved
481 when join_status_id.display_name = 'in progress' then 1
482 when join_status_id.display_name = 'resolved' then -1
485 join_priority_id_id.seqid ASC,
498 (case when join_milestone_id_id.duedate is null then 1 else 0 end),
499 join_milestone_id_id.duedate ASC ,
500 join_severity_id_id.seqid ASC,
501 join_priority_id_id.seqid ASC,
502 join_classification_id_id.seqid ASC,
503 join_updated_id.changedate DESC
517 switch ($this->status) {
524 function descriptionToHtml() /* depreciated */
526 // return MTrack_Wiki::format_to_html($this->description);
527 return htmlspecialchars($this->description);
532 function toEmailCalcAddress($changes, $person)
534 $ff = HTML_FlexyFramework::get();
537 $headers['To'] = '"'. addslashes($person->name). '" <' . $person->email .'>';
538 $headers['From'] = $ff->MTrackWeb['email_address']; // could be the user who made the change...
540 //if (count($from) > 1) {
542 // array_shift($from);
543 // foreach ($from as $email) {
544 // $rep[] = make_email($email[0], $email[1]);
547 $headers['Reply-To'] = $ff->MTrackWeb['email_address'];
553 $i = DB_DataObject::factory('Images');
554 return $i->gather($this);
557 function toEventName()
559 return $this->summary;
564 function toEmail($person, $lasttime)
566 $ff = HTML_FlexyFramework::get(); // used to work out url to show..
568 $CS = DB_DataObject::factory('mtrack_change');
569 $changes = $CS->changesSince($this,$lasttime);
570 $headers = $this->toEmailCalcAddress($changes, $person);
573 'MIME-Version' => '1.0',
574 'Content-Type' => 'text/plain; charset="UTF-8"',
575 'Content-Transfer-Encoding' => 'quoted-printable',
579 $mid = $this->id . '@' . php_uname('n');
581 $p = $this->project();
583 $subj = "[" . $p->code . ' - ' . $p->name . "] ";
585 $headers['X-mtrack-project-list'] = $p->code;
588 $headers['Subject'] = sprintf("%s#%s %s (%s %s)",
589 $subj, $this->id, $this->summary, $this->status_name, $this->classification_id_name);
591 $owner = $this->owner_id ? $this->owner_id_name : 'nobody';
594 sprintf("%s/Ticket/%s\n\n", $ff->MTrackWeb['url'], $this->id) .
595 sprintf("#%s: %s (%s %s)\n", $this->id, $this->summary, $this->status_name, $this->classification_id_name) .
596 sprintf("Responsible: %s (%s / %s)\n", $owner, $this->priority_id_name, $this->severity_id_name) .
598 sprintf("\nDescription:\n%s\n", wordwrap($this->description,80, "\n")) .
599 //sprintf("Milestone: %s\n", join(', ', $T->getMilestones()));
600 //sprintf("Component: %s\n", join(', ', $T->getComponents()));
603 // Display changed fields grouped by the person that last changed them
604 //$who_changed = array();
605 //foreach ($field_changers as $field => $who) {
606 // $who_changed[$who][] = $field;
609 foreach ($changes as $change) {
611 $body .= "\n{$change->changedate}: Change by {$change->person_id_name}\n";
612 $body .= str_repeat('-', 80) . "\n";
613 if (!empty($change->reason)) {
614 $body.= wordwrap($change->reason, 80, "\n") ."\n\n";
616 $ar = $change->cachedAudit();
618 foreach($ar as $audit) {
620 switch($audit->field()) {
622 continue; //??? ignore?
635 if ($audit->oldvalue == $audit->value) {
636 continue; // no change?!?!?
638 $oldvalue = $audit->oldvalue($change);
639 $value = $audit->value($change);
640 $field = $audit->field();
642 $field = preg_replace('/_id$/', '', $field);
643 $field = str_replace('/_/', ' ', $field);
644 $lb = strpos($oldvalue,"\n") > -1 || strpos($value,"\n") > -1 ? "\n" : '';
646 if (!strlen($oldvalue)) {
647 $body .= "Set {$field} to: {$lb}{$value}\n";
651 if (!strlen($value)) {
652 $body .= "Removed {$field} - was: {$lb}{$oldvalue}\n";
655 $body .= "Changed {$field} from : {$lb}{$oldvalue} {$lb}-> {$lb}{$value}\n";
661 $body .= sprintf("\n\n%s/Ticket/%s\n\n", $ff->MTrackWeb['url'], $this->id);
665 'headers' => $headers
672 function toEventString()
674 return '#'.$this->id . ' ' . $this->summary;
678 var $_mtrack_change = false;
680 function beforeUpdate($old, $req, $roo)
682 if (!empty($req['reason'])) {
683 $ch = DB_DataObject::factory('mtrack_change');
684 $ch->beginChange($this, $req['reason']);
685 $this->_mtrack_change= $ch;
687 // used by gitlive close ticket...
688 if (!empty($req['status_name']) && empty($req['status'])) {
689 $n = DB_DataObject::Factory('core_enum');
690 $n->name = $req['status_name'];
691 $n->whereAddIn('etype', array('ticketstate','resolution'),'string');
692 if (!$n->find(true)) {
693 $roo->jerr("invalid status name");
695 $this->status = $n->id;
698 if ($old->status != $this->status) {
699 // status has been changed. - check to see if it's really a resolution.
700 $n = DB_DataObject::Factory('core_enum');
701 if (!$this->status || !$n->get($this->status)) {
702 $roo->jerr("status set invalid");
704 if ($n->etype != 'ticketstate') {
705 if ($n->etype != 'resolution') {
706 $roo->jerr("status set invalid");
708 $this->resolution_id = $n->id;
709 $n = DB_DataObject::Factory('core_enum');
710 $n->etype = 'ticketstate';
711 $n->get('name', 'closed'); // no error checking!?!
712 $this->status = $n->id;
720 if (isset($req['_reorder'])) {
721 $old->updateSequence($req['_reorder']);
722 $roo->jok("UPDATED");
728 function beforeInsert($req, $roo)
731 if (isset($req['_bulk_update'])) {
732 $this->bulkUpdate($req,$roo);
736 if (!$this->status) {
737 $n = DB_DataObject::Factory('core_enum');
738 $n->etype = 'ticketstate';
739 $n->get('name', 'new'); // no error checking!?!
740 $this->status = $n->id;
745 function bulkUpdate($req, $roo)
747 if (empty($req['_bulk_update'])) {
748 $roo->jerr("invalid tickets");
751 $t = DB_DAtaObject::Factory('mtrack_ticket');
753 $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
754 $all = $t->fetchAll();
756 foreach($req as $k=>$v) {
762 foreach($all as $t) {
765 $t->beforeUpdate($old, $cg, $roo);
767 $t->onUpdate($old,$req, $roo);
770 $t = DB_DAtaObject::Factory('mtrack_ticket');
772 $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
773 $roo->jok($t->fetchAll(false,false,'toArray'));
784 function onUpdate($old,$req, $roo)
786 //print_r($old); print_r($this);
789 $ch = $this->_mtrack_change;
791 $ch = DB_DataObject::factory('mtrack_change');
792 $ch->beginChange($this, "Changed");
793 $this->_mtrack_change = $ch;
796 $n = $ch->add($this, $old);
797 if (!$n && !$this->_mtrack_change) {
798 //die("deleting change");
799 $ch->delete(); // do not record the change..
803 $this->updated = $ch->id;
814 $ch = DB_DataObject::factory('mtrack_change');
815 $ch->get($this->updated ? $this->updated : $this->created);
821 function toRooSingleArray($au, $req)
824 return $this->toArray();
827 $p = $this->factory('core_enum');
828 $p->etype='priority';
829 $p->get('name', 'normal');
831 $s = $this->factory('core_enum');
832 $s->etype='severity';
833 $s->get('name', 'normal');
835 $c = $this->factory('core_enum');
836 $c->etype='classification';
837 $c->get('name', 'enhancement');
839 $this->severity_id = $s->id;
840 $this->classification_id = $c->id;
841 $this->priority_id = $p->id;
842 $ret = $this->toArray();
843 $ret = array_merge($ret, $p->toArray('priority_id_%s'));
844 $ret = array_merge($ret, $c->toArray('classification_id_%s'));
845 $ret = array_merge($ret, $s->toArray('severity_id_%s'));
851 // http://roojs.com/admin.php/Roo/Mtrack_ticket?_post=1&id=733&_reorder=0
852 function updateSequence($tbefore)
854 $tn = $this->tableName();
855 $t = DB_DataObject::factory('mtrack_ticket');
858 //$t->project_id = $this->project_id;
861 // we still apply to closed tickets????
862 //$t->whereAdd("join_status_id.name != 'closed'");
863 $t->applySort(false, 'old_style', false);
865 $old_seqid = $this->seqid;
868 // add a seq id to everything.
869 $t->selectAdd("{$tn}.id as id");
870 $ids = $t->fetchAll('id');
872 $t_all = DB_DataObject::factory('mtrack_ticket');
873 $t_all->selectAdd('max(seqid) as seqid');
876 $start = empty($t_all->seqid) ? 1 : $t_all->seqid;
878 foreach($ids as $i => $id) {
879 $tt->query("UPDATE {$tn} SET seqid = " . ($start + $i) ." WHERE id = {$id}");
881 $t = DB_DataObject::Factory('mtrack_ticket');
883 $old_seqid = $t->seqid;
889 $before = DB_DataObject::factory('mtrack_ticket');
890 $before->get($tbefore);
891 $before_seq_id = $before->seqid;
894 $t = DB_DataObject::factory('mtrack_ticket');
897 /// move everything down.
898 $t->query("UPDATE {$tn} SET
901 seqid > {$before_seq_id}
906 // puts this one in the gap..
907 $t->query("UPDATE {$tn} SET seqid = ". ( $before_seq_id + 1) . " WHERE id = {$this->id}");
909 // now there is a gap where this used to be..
912 $t->query("UPDATE {$tn} SET
920 function toRooArray($q) {
921 if (empty($q['_future_schedule'])) {
922 return $this->toArray();
924 if (!$this->developer_id) {
927 $add = $this->toCalEvent();
928 if (isset($q['filter'])) {
929 foreach($q['filter'] as $k=>$v) {
933 if (!isset($add[$k])) {
934 $roo->jerr('invalid filter ' .$k);
936 if ($v != $add[$k]) {
947 // only run this once..
948 function toCalEvent()
950 //secho '<PRE>';print_r($this);exit;
951 static $cdata = array();
954 if (empty($cdata[$this->developer_id])) {
955 $start = $this->availStart(date('Y-m-d H:i:s'));
957 $start = $this->availStart($cdata[$this->developer_id]);
960 $end = $this->availEnd($start, $this->estimated ? $this->estimated : 1);
961 $cdata[$this->developer_id]= $end;
963 $ret = array_merge($this->toArray(), array(
964 'start_dt' => $start,
967 'title' => '#' . $this->id . ' ' .$this->developer_id_name . ' : ' . $this->project_id_name,
968 'description' => $this->summary,
976 function availStart($dt)
978 //give a date time.. see if we should schedule it today?
979 $st = strtotime($dt);
981 if (date('H', $st) < 11) {
982 $st = strtotime(date('Y-m-d 10:00:00',$st));
984 if (date('H', $st) > 17) {
985 if(date('N',$st) > 4) {
986 // it's friday... (5) +3 , sat (6) +2 sun(7) +1
987 $days = 8 - date('N',$st);
988 $st = strtotime(date('Y-m-d',$st).' + ' . $days . ' DAY');
991 $st = strtotime(date('Y-m-d',$st).' + 1 DAY');
993 $st = strtotime(date('Y-m-d 10:00:00',$st));
994 return date('Y-m-d H:i:s', $st);
996 // otherwise this time is OK...
999 function availEnd($start, $hours) {
1002 for ($i = 0; $i < ceil($hours ); $i++) {
1003 $et = $this->availStart(date('Y-m-d H:i:s', strtotime($et . ' + 1 HOUR')));
1009 function optionsForProject($roo,$pid)
1012 $m = DB_DataObject::factory('mtrack_milestone');
1013 $m->project_id = $pid;
1015 $m->selectAdd('id,name as name, name as display_name');
1016 $m->orderBy('startdate ASC');
1017 $ret['milestone'] = $m->fetchAll(false,false,'toArray');
1019 $mm = DB_DataObject::factory('core_enum');
1020 $mm->orderBy('seqid ASC');
1024 $m->etype = 'priority';
1025 $ret['priority'] = $m->fetchAll(false,false,'toArray');
1028 $m->etype = 'severity';
1029 $ret['severity'] = $m->fetchAll(false,false,'toArray');
1032 $m->etype = 'classification';
1033 $ret['classification'] = $m->fetchAll(false,false,'toArray');
1037 $m = DB_DataObject::factory('core_person');
1038 $m->company_id = $roo->company->id;
1040 $m->selectAdd("id,name as name, concat(name ,' <', email , '>') as display_name");
1042 $m->applyFilters(array(
1044 'project_id' => $pid,
1045 'role' => 'DEVELOPER'
1047 ),$roo->authUser, $roo);
1049 $m->orderBy('name ASC');
1050 $ret['developer'] = $m->fetchAll(false,false,'toArray');
1051 $ret['authuser_id'] = "".$roo->authUser->id; //string...