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($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..
208 if (!empty($q['query']['viewtype'])) {
210 switch ($q['query']['viewtype']) {
212 // hide "on hold" and closed..
213 $this->whereAdd("join_status_id.name NOT IN('closed', 'on hold') OR {$tn}.status = 0");
214 $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
218 $this->whereAdd("join_status_id.name = 'on hold' ");
219 $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
223 $this->whereAdd("mtrack_ticket.developer_id = {$au->id} ");
224 $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
228 $this->whereAdd("join_status_id.name != 'closed' OR {$tn}.status = 0 ");
229 $this->whereAdd("mtrack_ticket.developer_id = {$au->id} ");
235 $this->whereAdd("join_status_id.name ='closed'");
239 case 'active-or-recent':
241 (join_status_id.name != 'closed' OR {$tn}.status = 0)
243 (join_status_id.name = 'closed' AND join_updated.changedate > NOW() - 1 MONTH )
245 $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
249 $this->whereAdd("join_status_id.name IN ('resolved', 'needs review' )");
253 $this->whereAdd("join_status_id.name IN ('in progress' )");
257 $this->whereAdd("1=1");
261 $this->whereAdd("1=0");
269 if (!empty($q['_orderby_self_alloc '])) {
270 $auid = $au ? (int) $au->id : 0;
272 IF(developer_id = $auid , 1, 0) as is_self_alloc
274 $this->orderBy('is_self_alloc DESC');
279 if (!empty($q['query']['include_id'])) {
280 $this->whereAdd("{$tn}.id = " . ((int) $q['query']['include_id'] ), 'OR');
284 /// only show unposted..
289 function ticketChangeOptions($roo, $q, $au)
291 $et = DB_DataObject::Factory('core_enum');
293 $et->whereAddIn('etype', array(
302 // the end result of this:
307 ticketstate_[me] ????
309 ticketstate_[... everything else except close & new ...]
310 .. eg. open, reopened..
312 // these actually close the ticket...
313 // so the do not appear if the ticket is closed...
320 if (empty($q['query']['ticket_change'])) {
321 $roo->jerr("no ticket!");
324 // find out what state the ticket is in.
325 $t = DB_DataObject::Factory('mtrack_ticket');
327 if (strpos($q['query']['ticket_change'], ',') > 0) {
329 $t->status_name = 'xx';
333 $t->get($q['query']['ticket_change']);
336 if (!$et->checkPerm('S', $au)) {
337 $roo->jerr("permssion denied to query state of ticket");
342 //$this->debugLevel(1);
347 IF( id = {$t->status} , 0, 10000) +
348 IF( etype='ticketstate'
349 AND name='assigned' , 10, 0) +
350 IF( etype='ticketstate' AND
351 name !='closed' AND name != 'new' , 100, 0) +
352 IF( etype='ticketstate' , 0 , 1000) +
361 switch($t->status_name) {
364 // do not show closed..
366 ( CONCAT(etype , '.', name) != 'ticketstate.closed')
368 ( CONCAT(etype , '.', name) != 'ticketstate.reopened')
375 ( CONCAT(etype , '.', name) = 'ticketstate.reopened')
377 core_enum.id = {$t->status}
383 ( CONCAT(etype , '.', name) != 'ticketstate.closed')
385 ( CONCAT(etype , '.', name) != 'ticketstate.new')
387 ( CONCAT(etype , '.', name) != 'ticketstate.open')
394 ( CONCAT(etype , '.', name) != 'ticketstate.closed')
396 ( CONCAT(etype , '.', name) != 'ticketstate.new')
398 ( CONCAT(etype , '.', name) != 'ticketstate.reopened')
407 $et->orderBy("ts_order ASC");
409 $roo->jdata( $et->fetchAll(false,false,'toArray'));
416 *-- return false to let system do default sort code.
419 function applySort($au, $sortcol, $direction)
422 $direction = $direction == 'DESC' ? 'DESC' : 'ASC';
424 $tn = $this->tableName();
429 $this->orderBy("$sortcol $direction");
435 when join_status_id.display_name = 'in progress' then 1
436 when join_status_id.display_name = 'resolved' then -1
439 (case when join_milestone_id_id.duedate is null then 1 else 0 end) ASC,
440 join_milestone_id_id.duedate $direction ,
441 {$tn}.seqid $direction ,
442 join_milestone_id_id.name $direction
449 (case when join_milestone_id_id.duedate is null then 1 else 0 end) ASC,
450 join_milestone_id_id.duedate ASC ,
451 join_severity_id_id.seqid ASC,
452 join_priority_id_id.seqid ASC,
453 join_classification_id_id.seqid ASC,
454 join_updated_id.changedate DESC
460 when join_status_id.display_name = 'in progress' then 1
461 when join_status_id.display_name = 'resolved' then -1
464 join_priority_id_id.seqid $direction, {$tn}.seqid ASC
468 // this should favour in_progress, and disfavour resolved
472 when join_status_id.display_name = 'in progress' then 1
473 when join_status_id.display_name = 'resolved' then -1
476 join_priority_id_id.seqid ASC,
489 (case when join_milestone_id_id.duedate is null then 1 else 0 end),
490 join_milestone_id_id.duedate ASC ,
491 join_severity_id_id.seqid ASC,
492 join_priority_id_id.seqid ASC,
493 join_classification_id_id.seqid ASC,
494 join_updated_id.changedate DESC
508 switch ($this->status) {
515 function descriptionToHtml() /* depreciated */
517 // return MTrack_Wiki::format_to_html($this->description);
518 return htmlspecialchars($this->description);
523 function toEmailCalcAddress($changes, $person)
525 $ff = HTML_FlexyFramework::get();
528 $headers['To'] = '"'. addslashes($person->name). '" <' . $person->email .'>';
529 $headers['From'] = $ff->MTrackWeb['email_address']; // could be the user who made the change...
531 //if (count($from) > 1) {
533 // array_shift($from);
534 // foreach ($from as $email) {
535 // $rep[] = make_email($email[0], $email[1]);
538 $headers['Reply-To'] = $ff->MTrackWeb['email_address'];
544 $i = DB_DataObject::factory('Images');
545 return $i->gather($this);
548 function toEventName()
550 return $this->summary;
555 function toEmail($person, $lasttime)
557 $ff = HTML_FlexyFramework::get(); // used to work out url to show..
559 $CS = DB_DataObject::factory('mtrack_change');
560 $changes = $CS->changesSince($this,$lasttime);
561 $headers = $this->toEmailCalcAddress($changes, $person);
564 'MIME-Version' => '1.0',
565 'Content-Type' => 'text/plain; charset="UTF-8"',
566 'Content-Transfer-Encoding' => 'quoted-printable',
570 $mid = $this->id . '@' . php_uname('n');
572 $p = $this->project();
574 $subj = "[" . $p->code . ' - ' . $p->name . "] ";
576 $headers['X-mtrack-project-list'] = $p->code;
579 $headers['Subject'] = sprintf("%s#%s %s (%s %s)",
580 $subj, $this->id, $this->summary, $this->status_name, $this->classification_id_name);
582 $owner = $this->owner_id ? $this->owner_id_name : 'nobody';
585 sprintf("%s/Ticket/%s\n\n", $ff->MTrackWeb['url'], $this->id) .
586 sprintf("#%s: %s (%s %s)\n", $this->id, $this->summary, $this->status_name, $this->classification_id_name) .
587 sprintf("Responsible: %s (%s / %s)\n", $owner, $this->priority_id_name, $this->severity_id_name) .
589 sprintf("\nDescription:\n%s\n", wordwrap($this->description,80, "\n")) .
590 //sprintf("Milestone: %s\n", join(', ', $T->getMilestones()));
591 //sprintf("Component: %s\n", join(', ', $T->getComponents()));
594 // Display changed fields grouped by the person that last changed them
595 //$who_changed = array();
596 //foreach ($field_changers as $field => $who) {
597 // $who_changed[$who][] = $field;
600 foreach ($changes as $change) {
602 $body .= "\n{$change->changedate}: Change by {$change->person_id_name}\n";
603 $body .= str_repeat('-', 80) . "\n";
604 if (!empty($change->reason)) {
605 $body.= wordwrap($change->reason, 80, "\n") ."\n\n";
607 $ar = $change->cachedAudit();
609 foreach($ar as $audit) {
611 switch($audit->field()) {
613 continue; //??? ignore?
626 if ($audit->oldvalue == $audit->value) {
627 continue; // no change?!?!?
629 $oldvalue = $audit->oldvalue($change);
630 $value = $audit->value($change);
631 $field = $audit->field();
633 $field = preg_replace('/_id$/', '', $field);
634 $field = str_replace('/_/', ' ', $field);
635 $lb = strpos($oldvalue,"\n") > -1 || strpos($value,"\n") > -1 ? "\n" : '';
637 if (!strlen($oldvalue)) {
638 $body .= "Set {$field} to: {$lb}{$value}\n";
642 if (!strlen($value)) {
643 $body .= "Removed {$field} - was: {$lb}{$oldvalue}\n";
646 $body .= "Changed {$field} from : {$lb}{$oldvalue} {$lb}-> {$lb}{$value}\n";
652 $body .= sprintf("\n\n%s/Ticket/%s\n\n", $ff->MTrackWeb['url'], $this->id);
656 'headers' => $headers
663 function toEventString()
665 return '#'.$this->id . ' ' . $this->summary;
669 var $_mtrack_change = false;
671 function beforeUpdate($old, $req, $roo)
673 if (!empty($req['reason'])) {
674 $ch = DB_DataObject::factory('mtrack_change');
675 $ch->beginChange($this, $req['reason']);
676 $this->_mtrack_change= $ch;
678 // used by gitlive close ticket...
679 if (!empty($req['status_name']) && empty($req['status'])) {
680 $n = DB_DataObject::Factory('core_enum');
681 $n->name = $req['status_name'];
682 $n->whereAddIn('etype', array('ticketstate','resolution'),'string');
683 if (!$n->find(true)) {
684 $roo->jerr("invalid status name");
686 $this->status = $n->id;
689 if ($old->status != $this->status) {
690 // status has been changed. - check to see if it's really a resolution.
691 $n = DB_DataObject::Factory('core_enum');
692 if (!$this->status || !$n->get($this->status)) {
693 $roo->jerr("status set invalid");
695 if ($n->etype != 'ticketstate') {
696 if ($n->etype != 'resolution') {
697 $roo->jerr("status set invalid");
699 $this->resolution_id = $n->id;
700 $n = DB_DataObject::Factory('core_enum');
701 $n->etype = 'ticketstate';
702 $n->get('name', 'closed'); // no error checking!?!
703 $this->status = $n->id;
711 if (isset($req['_reorder'])) {
712 $old->updateSequence($req['_reorder']);
713 $roo->jok("UPDATED");
719 function beforeInsert($req, $roo)
722 if (isset($req['_bulk_update'])) {
723 $this->bulkUpdate($req,$roo);
727 if (!$this->status) {
728 $n = DB_DataObject::Factory('core_enum');
729 $n->etype = 'ticketstate';
730 $n->get('name', 'new'); // no error checking!?!
731 $this->status = $n->id;
736 function bulkUpdate($req, $roo)
738 if (empty($req['_bulk_update'])) {
739 $roo->jerr("invalid tickets");
742 $t = DB_DAtaObject::Factory('mtrack_ticket');
744 $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
745 $all = $t->fetchAll();
747 foreach($req as $k=>$v) {
753 foreach($all as $t) {
756 $t->beforeUpdate($old, $cg, $roo);
758 $t->onUpdate($old,$req, $roo);
761 $t = DB_DAtaObject::Factory('mtrack_ticket');
763 $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
764 $roo->jok($t->fetchAll(false,false,'toArray'));
775 function onUpdate($old,$req, $roo)
777 //print_r($old); print_r($this);
780 $ch = $this->_mtrack_change;
782 $ch = DB_DataObject::factory('mtrack_change');
783 $ch->beginChange($this, "Changed");
784 $this->_mtrack_change = $ch;
787 $n = $ch->add($this, $old);
788 if (!$n && !$this->_mtrack_change) {
789 //die("deleting change");
790 $ch->delete(); // do not record the change..
794 $this->updated = $ch->id;
805 $ch = DB_DataObject::factory('mtrack_change');
806 $ch->get($this->updated ? $this->updated : $this->created);
812 function toRooSingleArray($au, $req)
815 return $this->toArray();
818 $p = $this->factory('core_enum');
819 $p->etype='priority';
820 $p->get('name', 'normal');
822 $s = $this->factory('core_enum');
823 $s->etype='severity';
824 $s->get('name', 'normal');
826 $c = $this->factory('core_enum');
827 $c->etype='classification';
828 $c->get('name', 'enhancement');
830 $this->severity_id = $s->id;
831 $this->classification_id = $c->id;
832 $this->priority_id = $p->id;
833 $ret = $this->toArray();
834 $ret = array_merge($ret, $p->toArray('priority_id_%s'));
835 $ret = array_merge($ret, $c->toArray('classification_id_%s'));
836 $ret = array_merge($ret, $s->toArray('severity_id_%s'));
842 // http://roojs.com/admin.php/Roo/Mtrack_ticket?_post=1&id=733&_reorder=0
843 function updateSequence($tbefore)
845 $tn = $this->tableName();
846 $t = DB_DataObject::factory('mtrack_ticket');
849 //$t->project_id = $this->project_id;
852 // we still apply to closed tickets????
853 //$t->whereAdd("join_status_id.name != 'closed'");
854 $t->applySort(false, 'old_style', false);
856 $old_seqid = $this->seqid;
859 // add a seq id to everything.
860 $t->selectAdd("{$tn}.id as id");
861 $ids = $t->fetchAll('id');
863 $t_all = DB_DataObject::factory('mtrack_ticket');
864 $t_all->selectAdd('max(seqid) as seqid');
867 $start = empty($t_all->seqid) ? 1 : $t_all->seqid;
869 foreach($ids as $i => $id) {
870 $tt->query("UPDATE {$tn} SET seqid = " . ($start + $i) ." WHERE id = {$id}");
872 $t = DB_DataObject::Factory('mtrack_ticket');
874 $old_seqid = $t->seqid;
880 $before = DB_DataObject::factory('mtrack_ticket');
881 $before->get($tbefore);
882 $before_seq_id = $before->seqid;
885 $t = DB_DataObject::factory('mtrack_ticket');
888 /// move everything down.
889 $t->query("UPDATE {$tn} SET
892 seqid > {$before_seq_id}
897 // puts this one in the gap..
898 $t->query("UPDATE {$tn} SET seqid = ". ( $before_seq_id + 1) . " WHERE id = {$this->id}");
900 // now there is a gap where this used to be..
903 $t->query("UPDATE {$tn} SET
911 function toRooArray($q) {
912 if (empty($q['_future_schedule'])) {
913 return $this->toArray();
915 if (!$this->developer_id) {
918 $add = $this->toCalEvent();
919 if (isset($q['filter'])) {
920 foreach($q['filter'] as $k=>$v) {
924 if (!isset($add[$k])) {
925 $roo->jerr('invalid filter ' .$k);
927 if ($v != $add[$k]) {
938 // only run this once..
939 function toCalEvent()
941 //secho '<PRE>';print_r($this);exit;
942 static $cdata = array();
945 if (empty($cdata[$this->developer_id])) {
946 $start = $this->availStart(date('Y-m-d H:i:s'));
948 $start = $this->availStart($cdata[$this->developer_id]);
951 $end = $this->availEnd($start, $this->estimated ? $this->estimated : 1);
952 $cdata[$this->developer_id]= $end;
954 $ret = array_merge($this->toArray(), array(
955 'start_dt' => $start,
958 'title' => '#' . $this->id . ' ' .$this->developer_id_name . ' : ' . $this->project_id_name,
959 'description' => $this->summary,
967 function availStart($dt)
969 //give a date time.. see if we should schedule it today?
970 $st = strtotime($dt);
972 if (date('H', $st) < 11) {
973 $st = strtotime(date('Y-m-d 10:00:00',$st));
975 if (date('H', $st) > 17) {
976 if(date('N',$st) > 4) {
977 // it's friday... (5) +3 , sat (6) +2 sun(7) +1
978 $days = 8 - date('N',$st);
979 $st = strtotime(date('Y-m-d',$st).' + ' . $days . ' DAY');
982 $st = strtotime(date('Y-m-d',$st).' + 1 DAY');
984 $st = strtotime(date('Y-m-d 10:00:00',$st));
985 return date('Y-m-d H:i:s', $st);
987 // otherwise this time is OK...
990 function availEnd($start, $hours) {
993 for ($i = 0; $i < ceil($hours ); $i++) {
994 $et = $this->availStart(date('Y-m-d H:i:s', strtotime($et . ' + 1 HOUR')));
1001 function optionsForProject($roo,$pid)
1004 $m = DB_DataObject::factory('mtrack_milestone');
1005 $m->project_id = $pid;
1006 $m->orderBy('startdate ASC');
1007 $ret['milestone'] = $m->fetchAll(false,false,'toArray');
1009 $mm = DB_DataObject::factory('core_enum');
1010 $mm->orderBy('seqid ASC');
1014 $m->etype = 'priority';
1015 $ret['priority'] = $m->fetchAll(false,false,'toArray');
1018 $m->etype = 'severity';
1019 $ret['severity'] = $m->fetchAll(false,false,'toArray');
1022 $m->etype = 'classification';
1023 $ret['classification'] = $m->fetchAll(false,false,'toArray');
1025 $m = DB_DataObject::factory('core_person');
1027 $m->applyFilters(array(
1029 'project_id' => $pid,
1030 'role' => 'DEVELOPER'
1034 $m->orderBy('name ASC');
1035 $ret['developer'] = $m->fetchAll(false,false,'toArray');
1042 query[project_id]: 3116
1043 query[role]: DEVELOPER