Uncommited changes synced
[Pman.MTrack] / DataObjects / Mtrack_ticket.php
1 <?php
2 /**
3  * Table Definition for mtrack_ticket
4  */
5 class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php';
6
7 class Pman_MTrack_DataObjects_Mtrack_ticket extends DB_DataObject 
8 {
9     ###START_AUTOCODE
10     /* the code below is auto generated do not remove the above tag */
11
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
21
22     public $priority_id;                     // int(11)  not_null
23     
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
32     public $project_id;
33     public $milestone_id;
34     
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..
38     
39     /* the code above is auto generated do not remove the tag below */
40     ###END_AUTOCODE
41     //checkPerm('S'/'E'/'A', $authuser) - can
42     function checkPerm($perm, $au)
43     {
44         $pr = $this->project();
45         // var_dump($pr ? $pr->name : false); var_dump($perm);
46         
47         if (($perm == 'S')  && (!$pr || ($pr->code == '*PUBLIC'))) {
48             return true;
49         }
50         if (!$au) {
51             return false;
52         }
53         // admins with all project rights.
54         if ($au->hasPerm('Core.Projects_All', 'S')) {
55             return true;
56         }
57          
58         $pd = DB_DataObject::factory('ProjectDirectory');
59         $pd->user_id = $au->id;
60         $pd->project_id = $this->project_id;
61         $pd->whereAdd("role != ''");
62         // not        
63         if ($pd->count()) {
64             return true;
65         }
66         return false;
67         
68     }
69     
70     function project()
71     {
72         if (!$this->project_id) {
73             return false;
74         }
75         $p = DB_DataObject::factory('core_project');
76         $p->get($this->project_id);
77         return $p;
78          
79     }
80     function status()
81     {
82         if (!$this->status) {
83             return false;
84         }
85         $p = DB_DataObject::factory('core_enum');
86         $p->get($this->status);
87         return $p;
88          
89     }
90     
91     function resolution()
92     {
93         if (!$this->resolution_id) {
94             return false;
95         }
96         $p = DB_DataObject::factory('core_enum');
97         $p->get($this->resolution_id);
98         return $p;
99          
100     }
101     
102      function classification()
103     {
104         if (!$this->classification_id) {
105             return false;
106         }
107         $p = DB_DataObject::factory('core_enum');
108         $p->get($this->classification_id);
109         return $p;
110          
111     }
112     
113     function developer()
114     {
115         if (!$this->developer_id) {
116             return false;
117         }
118         $p = DB_DataObject::factory('Person');
119         $p->get($this->developer_id);
120         return $p;
121         
122     }
123     
124     function applyFilters($q, $au, $roo)
125     {
126         if (isset($q['query']['ticket_change'])) {
127             return $this->ticketChangeOptions($roo,$q, $au);
128         }
129         
130         $tn = $this->tableName();
131         if (!$au) {
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..
137                
138         } else {
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 != ''");
144             
145             if (!$pd->count()) {
146                 $this->whereAdd(" join_project_id_id.code = '*PUBLIC' ");
147             
148                 // they can do what they like on all projects.
149                 // no op..
150             } else {
151                 
152                 
153                 $ar = $pd->fetchAll('project_id');
154                 $this->whereAddIn("join_project_id_id.id", $ar, 'int');
155                 
156             }
157              
158             
159         }
160         
161         if ($au && isset($q['_options_for'])) {
162             $this->optionsForProject($roo, $q['_options_for']);
163             $this->jerr("somethings missing");
164             
165         }
166         
167         
168         
169         $jira_ref =  "(SELECT jira_key
170                     FROM
171                         mtrack_jira_object
172                     WHERE on_table='mtrack_ticket' AND on_id = {$tn}.id LIMIT 1)";
173           
174         
175         if (empty($q['_distinct'])) {
176             $tn = $this->tableName();
177             $this->selectAdd("$jira_ref as jira_ref ");
178         }    
179           
180           
181         //var_dump($q);exit;
182         if (!empty($q['query']['search'])) {
183             
184             $sq = $this->escape($q['query']['search']);
185             $cond = '';
186             if (is_numeric($q['query']['search'])) {
187                 $cond = "OR {$tn}.id = {$q['query']['search']}";
188             }
189             $this->whereAdd(" 
190                  {$tn}.summary like '%{$sq}%'
191                  OR
192                  {$tn}.description like '%{$sq}%'
193                  OR
194                  $jira_ref like '%{$sq}%'
195                 $cond
196             ");
197             
198             if (is_numeric($q['query']['search'])) {
199                 // allow quick lookup by number..
200                 
201                 return;
202             }
203             
204         }
205             
206         if (!empty($q['_developer'])) {
207             switch ($q['_developer']) {
208                 case 'me':
209                     $this->whereAdd("mtrack_ticket.developer_id = {$au->id} ");
210                     break;
211                 case 'anybody':
212                     break;
213             }
214         }
215         
216         
217         if (!empty($q['query']['viewtype'])) {
218              
219             switch ($q['query']['viewtype']) {
220                 case 'active':
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");
224                     break;
225                 
226                 case 'on-hold':
227                     $this->whereAdd("join_status_id.name = 'on hold' ");
228                     $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
229                     break;
230                 
231                 case 'my':
232                     $this->whereAdd("mtrack_ticket.developer_id = {$au->id} ");
233                     $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
234                     
235                 case 'me':
236                 case 'me-all':
237                     $this->whereAdd("join_status_id.name != 'closed' OR  {$tn}.status  = 0 ");
238                     $this->whereAdd("mtrack_ticket.developer_id = {$au->id} ");
239                     
240                     break;
241                 
242                   
243                 case 'closed':
244                     $this->whereAdd("join_status_id.name ='closed'");
245                     
246                     break;
247                  
248                 case 'active-or-recent':
249                     $this->whereAdd("
250                             (join_status_id.name != 'closed' OR  {$tn}.status  = 0)
251                             OR
252                             (join_status_id.name = 'closed' AND join_updated.changedate > NOW() - 1 MONTH  )
253                             ");
254                     $this->whereAdd("{$tn}.milestone_id = 0 OR join_milestone_id_id.on_hold = 0");
255                     break;
256                 
257                 case 'pending':
258                     $this->whereAdd("join_status_id.name IN ('resolved', 'needs review' )");
259                     break;
260                 
261                 case 'in-progress':
262                     $this->whereAdd("join_status_id.name IN ('in progress' )");
263                     break;
264                     
265                 case 'all': 
266                     $this->whereAdd("1=1");
267                     break;
268                 
269                 default :
270                     $this->whereAdd("1=0");
271                 
272             }
273             
274             
275             
276         }
277         
278         if (!empty($q['_orderby_self_alloc '])) {
279             $auid = $au ? (int) $au->id : 0;
280             $this->selectAdd(" 
281                   IF(developer_id = $auid , 1, 0) as is_self_alloc         
282                              ");
283             $this->orderBy('is_self_alloc DESC');
284             
285             
286         }
287         
288         if (!empty($q['query']['include_id'])) {
289             $this->whereAdd("{$tn}.id = " . ((int) $q['query']['include_id'] ), 'OR');
290         }
291         
292         
293         /// only show unposted..
294         
295     }
296     
297     
298     function ticketChangeOptions($roo, $q, $au)
299     {
300             $et = DB_DataObject::Factory('core_enum');
301             $et->active  = 1;
302             $et->whereAddIn('etype', array(
303                     'ticketstate',
304                     'resolution',
305                     //'classification',
306                     //'severity',
307                     //'priority'
308             ),'string') ;
309            
310             
311             // the end result of this:
312             /*
313                
314                
315                ticketstate_[same]
316                ticketstate_[me] ????
317                
318                ticketstate_[... everything else except close & new ...]
319                   .. eg. open, reopened..
320                
321                // these actually close the ticket...
322                // so the do not appear if the ticket is closed...
323                resolution:
324                     
325              
326              
327             */
328             
329             if (empty($q['query']['ticket_change'])) {
330                 $roo->jerr("no ticket!");
331             }
332             
333             // find out what state the ticket is in.
334             $t = DB_DataObject::Factory('mtrack_ticket');
335             
336             if (strpos($q['query']['ticket_change'], ',')  > 0) {
337                 $t->status = 0;
338                 $t->status_name = 'xx';
339                 
340             } else {
341                 $t->autoJoin();
342                 $t->get($q['query']['ticket_change']);
343             
344             }
345             if (!$et->checkPerm('S', $au)) {
346                 $roo->jerr("permssion denied to query state of ticket");
347             }
348             
349
350             
351             //$this->debugLevel(1);
352             
353             
354             
355             $et->selectAdd("
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) +
362
363                 seqid
364                 AS
365                 ts_order
366                              
367             ");
368             
369             // hide stuff...
370             switch($t->status_name) {
371                 
372                 case 'new':
373                     // do not show closed..
374                     $et->whereAdd("
375                         ( CONCAT(etype , '.', name) != 'ticketstate.closed')
376                         AND
377                          ( CONCAT(etype , '.', name) != 'ticketstate.reopened')
378                     ");
379                     break;
380                   
381                 case 'closed':
382                     // 
383                     $et->whereAdd("
384                                     ( CONCAT(etype , '.', name) = 'ticketstate.reopened')
385                                     OR
386                                     core_enum.id = {$t->status}
387                     ");
388                     break;
389                 
390                 case 'reopened':
391                     $et->whereAdd("
392                              ( CONCAT(etype , '.', name) != 'ticketstate.closed')
393                              AND
394                             ( CONCAT(etype , '.', name) != 'ticketstate.new')
395                              AND
396                            ( CONCAT(etype , '.', name) != 'ticketstate.open')
397                             ");
398                     break;
399
400                 case 'open':
401                 default:
402                     $et->whereAdd("
403                            ( CONCAT(etype , '.', name) != 'ticketstate.closed')
404                              AND
405                             ( CONCAT(etype , '.', name) != 'ticketstate.new')
406                              AND
407                            ( CONCAT(etype , '.', name) != 'ticketstate.reopened')
408                             ");
409                     break;
410                 
411                 
412                    
413             }
414             
415             
416             $et->orderBy("ts_order ASC");
417             
418             $roo->jdata( $et->fetchAll(false,false,'toArray'));
419             exit;
420             
421     }
422     
423     
424     /**
425      *-- return false to let system do default sort code.
426      **/
427     
428     function applySort($au, $sortcol, $direction)
429     {
430         // fixme smarter?
431         $direction = $direction == 'DESC' ? 'DESC' : 'ASC';
432         
433         $tn = $this->tableName();
434         switch( $sortcol ) {
435             
436             case 'id':
437             //..... others..
438                 $this->orderBy("$sortcol $direction");
439                 return;
440             
441             case 'milestone_id':
442                    $this->orderBy("
443                         case
444                             when join_status_id.display_name = 'in progress' then 1
445                             when join_status_id.display_name = 'resolved' then -1
446                             else 0
447                         end DESC,
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
452                        ");
453                   
454                 return;
455                 
456             case 'old_style':
457                 $this->orderBy('
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
464                        ');
465                 return;
466             case 'priority_id':
467                 $this->orderBy("
468                         case
469                             when join_status_id.display_name = 'in progress' then 1
470                             when join_status_id.display_name = 'resolved' then -1
471                             else 0
472                         end DESC,
473                         join_priority_id_id.seqid $direction, {$tn}.seqid ASC
474                     ");
475                 break;
476             default:
477                 // this should favour in_progress, and disfavour resolved
478                 
479                 $this->orderBy("
480                         case
481                             when join_status_id.display_name = 'in progress' then 1
482                             when join_status_id.display_name = 'resolved' then -1
483                             else 0
484                         end DESC,
485                         join_priority_id_id.seqid ASC,
486                         {$tn}.seqid ASC   ");
487                         
488                
489                             
490                 
491                 return;
492         }
493         
494     }
495     
496     /*
497      
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
504     */
505     
506     
507     
508     
509     
510     
511     
512     
513     
514     
515     function isOpen()
516     {
517         switch ($this->status) {
518           case 'closed':
519             return false;
520           default:
521             return true;
522         }
523     }
524     function descriptionToHtml() /* depreciated */
525     {
526        // return MTrack_Wiki::format_to_html($this->description);
527         return htmlspecialchars($this->description);
528     }
529     
530     
531     
532     function toEmailCalcAddress($changes, $person)
533     {
534         $ff = HTML_FlexyFramework::get();
535      
536         
537         $headers['To'] = '"'. addslashes($person->name). '" <' . $person->email .'>';
538         $headers['From'] = $ff->MTrackWeb['email_address']; // could be the user who made the change...
539         
540         //if (count($from) > 1) {
541         //    $rep = array();
542         //    array_shift($from);
543          //       foreach ($from as $email) {
544          // $rep[] = make_email($email[0], $email[1]);
545         // }
546         
547         $headers['Reply-To'] =  $ff->MTrackWeb['email_address'];
548         return $headers;
549     }
550     
551     function images()
552     {
553         $i = DB_DataObject::factory('Images');
554         return $i->gather($this);
555     }
556     
557     function toEventName()
558     {
559         return $this->summary;
560         
561     }
562     
563     
564     function toEmail($person, $lasttime)
565     {
566         $ff = HTML_FlexyFramework::get(); // used to work out url to show..
567         
568         $CS = DB_DataObject::factory('mtrack_change');
569         $changes = $CS->changesSince($this,$lasttime);
570         $headers = $this->toEmailCalcAddress($changes, $person);
571         
572         $headers += array(
573             'MIME-Version' => '1.0',
574             'Content-Type' => 'text/plain; charset="UTF-8"',
575             'Content-Transfer-Encoding' => 'quoted-printable',
576         );
577     
578         
579         $mid = $this->id . '@' . php_uname('n');
580         
581         $p = $this->project();
582          
583         $subj = "[" . $p->code . ' - ' . $p->name . "] ";
584     
585         $headers['X-mtrack-project-list'] = $p->code;
586      
587     
588         $headers['Subject'] = sprintf("%s#%s %s (%s %s)",
589           $subj, $this->id, $this->summary, $this->status_name, $this->classification_id_name);
590     
591         $owner =  $this->owner_id ? $this->owner_id_name : 'nobody';
592     
593         $body = 
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) .
597     
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()));
601             "\n";
602     
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;
607         //}
608         $body.="\n";
609         foreach ($changes as $change) {
610             
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";
615             }
616             $ar = $change->cachedAudit();
617             
618             foreach($ar as $audit) {
619                 
620                 switch($audit->field()) {
621                     case 'id':
622                         continue 2; //??? ignore?
623                     case '@components':
624                         continue 2;
625                    
626                     case '@milestones':
627                         continue 2;
628                      
629                     case '@keywords':
630                         continue 2;
631                    
632                     //case 'commit?'  
633                       
634                     default:
635                         if ($audit->oldvalue == $audit->value) {
636                             continue 2; // no change?!?!?
637                         }
638                         $oldvalue = $audit->oldvalue($change);
639                         $value = $audit->value($change);
640                         $field = $audit->field();
641                         
642                         $field  = preg_replace('/_id$/', '', $field);
643                         $field  = str_replace('/_/', ' ', $field);
644                         $lb = strpos($oldvalue,"\n") > -1 || strpos($value,"\n") > -1 ? "\n" : '';
645                         
646                         if (!strlen($oldvalue)) {
647                             $body .= "Set {$field} to: {$lb}{$value}\n";
648                             continue 2;
649                         }
650                         
651                         if (!strlen($value)) {
652                             $body .= "Removed {$field} - was: {$lb}{$oldvalue}\n";
653                             continue 2;
654                         }
655                         $body .= "Changed {$field} from : {$lb}{$oldvalue} {$lb}-> {$lb}{$value}\n";
656                         continue 2;  
657                 }
658             }
659         }
660         
661         $body .= sprintf("\n\n%s/Ticket/%s\n\n", $ff->MTrackWeb['url'], $this->id);
662         
663         return array(
664             'body' => $body,
665             'headers' => $headers
666         );
667          
668         
669         
670     }
671     
672     function toEventString()
673     {
674         return '#'.$this->id . ' ' . $this->summary;
675         
676     }
677     
678     var $_mtrack_change = false;
679     
680     function beforeUpdate($old, $req, $roo)
681     {
682         
683          if (!empty($req['reason'])) {
684             $ch = DB_DataObject::factory('mtrack_change');
685             $ch->beginChange($this, $req['reason']);
686             $this->_mtrack_change= $ch;
687         }
688         // used by gitlive close ticket...
689         if (!empty($req['status_name']) && empty($req['status'])) {
690             $n = DB_DataObject::Factory('core_enum');
691             $n->name = $req['status_name'];
692             $n->whereAddIn('etype', array('ticketstate','resolution'),'string');
693             if (!$n->find(true)) {
694                 $roo->jerr("invalid status name");
695             }
696             $this->status = $n->id;
697             
698         }
699         if ($old->status != $this->status) {
700             // status has been changed. - check to see if it's really a resolution.
701             $n = DB_DataObject::Factory('core_enum');
702             if (!$this->status || !$n->get($this->status)) {
703                 $roo->jerr("status set invalid");
704             }
705             if ($n->etype != 'ticketstate') {
706                 if ($n->etype != 'resolution') {
707                     $roo->jerr("status set invalid");
708                 }
709                 $this->resolution_id = $n->id;
710                 $n = DB_DataObject::Factory('core_enum');
711                 $n->etype = 'ticketstate';
712                 $n->get('name', 'closed'); // no error checking!?!
713                 $this->status = $n->id;
714             }
715             
716         }
717         
718         
719         
720         
721         if (isset($req['_reorder'])) {
722             $old->updateSequence($req['_reorder']);
723             $roo->jok("UPDATED");
724         }
725         
726         if (isset($req['_before']) || isset($req['_after']))  {
727             $req['_after'] = isset($req['_after']) ? $req['_after'] : 0;
728             $req['_before'] = isset($req['_before']) ? $req['_before'] : 0;
729             $old->updateSequence($req['_before'],$req['_after']);
730             
731             
732             if ($req['_before']) { 
733                 $before = DB_DataObject::factory('mtrack_ticket');
734                 $before->get($req['_before']);
735                 $this->seqid = $before->seqid;
736             } else if ($req['_after']) {
737                 $after = DB_DataObject::factory('mtrack_ticket');
738                 $after->get($req['_after']);
739                 $this->seqid =  $after->seqid+1;
740             } else {
741                 
742                 
743                 
744             }
745         }   
746          
747         
748         
749     }
750     
751     function beforeInsert($req, $roo)
752     {
753         
754         if (isset($req['_bulk_update'])) {
755             $this->bulkUpdate($req,$roo);
756             $this->jerr("oops");
757         }
758         
759         if (!$this->status) {
760             $n = DB_DataObject::Factory('core_enum');
761             $n->etype = 'ticketstate';
762             $n->get('name', 'new'); // no error checking!?!
763             $this->status = $n->id;
764         }
765     }
766     
767     
768     function bulkUpdate($req, $roo)
769     {
770         if (empty($req['_bulk_update'])) {
771             $roo->jerr("invalid tickets");
772         }
773         
774         $t = DB_DAtaObject::Factory('mtrack_ticket');
775         $t->autoJoin();
776         $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
777         $all = $t->fetchAll();
778         
779         foreach($req as $k=>$v) {
780             if (empty($v)) {
781                 continue;
782             }
783             $cg[$k] = $v;
784         }
785         foreach($all as $t) {
786             $old = clone($t);
787             $t->setFrom($cg);
788             $t->beforeUpdate($old, $cg, $roo);
789             $t->update($old);
790             $t->onUpdate($old,$req, $roo);
791             
792         }
793         $t = DB_DAtaObject::Factory('mtrack_ticket');
794         $t->autoJoin();
795         $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
796         $roo->jok($t->fetchAll(false,false,'toArray'));
797         
798         
799          
800         
801         
802     }
803     
804     
805     
806     
807     function onUpdate($old,$req, $roo)
808     {
809         //print_r($old); print_r($this);
810         
811          //die("on update");
812         $ch = $this->_mtrack_change; 
813         if (!$ch) {
814             $ch = DB_DataObject::factory('mtrack_change');
815             $ch->beginChange($this, "Changed");
816             $this->_mtrack_change = $ch;
817         }
818         
819         $n = $ch->add($this, $old);
820         if (!$n && !$this->_mtrack_change) {
821             //die("deleting change");
822             $ch->delete(); // do not record the change..
823         } else { 
824         
825             $oo = clone($this);
826             $this->updated = $ch->id;
827             $this->update($oo);
828         }
829         // trigger emails..
830         
831     }
832     
833     
834     
835     function updated()
836     {
837         $ch = DB_DataObject::factory('mtrack_change');
838         $ch->get($this->updated ? $this->updated : $this->created);
839         return $ch;
840         
841     }
842     
843     
844     function toRooSingleArray($au, $req)
845     {
846         if ($this->id) {
847             return $this->toArray();
848         }
849         // empty!
850         $p = $this->factory('core_enum');
851         $p->etype='priority';
852         $p->get('name', 'normal');
853         
854         $s = $this->factory('core_enum');
855         $s->etype='severity';
856         $s->get('name', 'normal');
857         
858         $c = $this->factory('core_enum');
859         $c->etype='classification';
860         $c->get('name', 'enhancement');
861         
862         $this->severity_id = $s->id;
863         $this->classification_id = $c->id;
864         $this->priority_id = $p->id;
865         $ret = $this->toArray();
866         $ret = array_merge($ret, $p->toArray('priority_id_%s'));
867         $ret = array_merge($ret, $c->toArray('classification_id_%s'));
868         $ret = array_merge($ret, $s->toArray('severity_id_%s'));
869         
870          return $ret;
871         
872     }
873     
874     //  http://roojs.com/admin.php/Roo/Mtrack_ticket?_post=1&id=733&_reorder=0
875     function updateSequence($tbefore, $tafter = 0)
876     {
877         // fix empty sequences?
878         
879         $tn = $this->tableName();
880         $t = DB_DataObject::factory('mtrack_ticket');
881         $tt = clone($t);
882         $t->seqid = 0;
883         //$t->project_id = $this->project_id;
884         $t->autoJoin();
885         $t->selectAdd();
886         // we still apply to closed tickets????
887          $t->whereAdd("join_status_id.name != 'closed'");
888         $t->applySort(false,  'old_style', false);
889         
890         $old_seqid = $this->seqid;
891         
892         if ($t->count()) {
893             // add a seq id to everything.
894             $t->selectAdd("{$tn}.id as id");
895             $ids = $t->fetchAll('id');
896             
897             $t_all = DB_DataObject::factory('mtrack_ticket');
898             $t_all->selectAdd('max(seqid) as seqid');
899             $t_all->find(true);
900             
901             $start = empty($t_all->seqid) ? 1 : $t_all->seqid;
902             $start++;
903             foreach($ids as $i => $id) {
904                 $tt->query("UPDATE {$tn} SET seqid = " . ($start + $i) ." WHERE id = {$id}");
905             }
906             $t = DB_DataObject::Factory('mtrack_ticket');
907             $t->get($this->id);
908             $old_seqid = $t->seqid;
909             
910         }
911         
912         
913         
914         
915         $before_seq_id = 0;
916         
917         if ($tbefore) { 
918             $before = DB_DataObject::factory('mtrack_ticket');
919             $before->get($tbefore);
920             $before_seq_id = $before->seqid;
921             
922         }
923         if ($tafter) { 
924             $after = DB_DataObject::factory('mtrack_ticket');
925             $after->get($tafter);
926             $before_seq_id = $after->seqid+1;
927         }
928          
929         $t = DB_DataObject::factory('mtrack_ticket');
930         
931         
932         /// move everything down.
933         $t->query("UPDATE {$tn} SET
934                         seqid = seqid + 1
935                     WHERE
936                        seqid > {$before_seq_id}
937                 ");
938         
939         // ...1234 GAP 6789
940         
941         // puts this one in the gap..
942         $t->query("UPDATE {$tn} SET seqid = ". ( $before_seq_id  + 1) . " WHERE id = {$this->id}");
943         
944         // now there is a gap where this used to be..
945         
946         
947          $t->query("UPDATE {$tn} SET
948                         seqid = seqid - 1
949                    WHERE 
950                         seqid > {$old_seqid}
951         ");             
952          
953         
954     }
955     function toRooArray($q) {
956         if (empty($q['_future_schedule'])) {
957             return $this->toArray();
958         }
959         if (!$this->developer_id) {
960             return false;
961         }
962         $add = $this->toCalEvent();
963         if (isset($q['filter'])) {
964             foreach($q['filter'] as $k=>$v) {
965                 if (empty($v)) {
966                     continue;
967                 }
968                 if (!isset($add[$k])) {
969                     $roo->jerr('invalid filter ' .$k);
970                 }
971                 if ($v != $add[$k]) {
972                     $add = false;
973                 }
974             }
975         }
976         return $add;
977      
978     }
979      
980     
981     
982     // only run this once..
983     function toCalEvent()
984     {
985         //secho '<PRE>';print_r($this);exit;
986         static $cdata = array();
987         
988         
989         if (empty($cdata[$this->developer_id])) {
990             $start = $this->availStart(date('Y-m-d H:i:s'));
991         } else {
992             $start = $this->availStart($cdata[$this->developer_id]);
993         }
994         
995         $end = $this->availEnd($start, $this->estimated ? $this->estimated : 1);
996         $cdata[$this->developer_id]= $end;
997         
998         $ret = array_merge($this->toArray(), array(
999             'start_dt' => $start,
1000             'end_dt' =>  $end,
1001             //'start_time' => 
1002             'title' => '#' . $this->id . '  ' .$this->developer_id_name . ' : ' . $this->project_id_name,
1003             'description' => $this->summary,
1004           
1005         ));
1006         return $ret;
1007         
1008         
1009         
1010     }
1011     function availStart($dt)
1012     {
1013         //give a date time.. see if we should schedule it today?
1014         $st = strtotime($dt);
1015         
1016         if (date('H', $st) < 11) {
1017             $st = strtotime(date('Y-m-d 10:00:00',$st));
1018         }
1019         if (date('H', $st) > 17) {
1020             if(date('N',$st) > 4) {
1021                 // it's friday... (5) +3 , sat (6) +2 sun(7) +1
1022                 $days = 8 - date('N',$st);
1023                 $st = strtotime(date('Y-m-d',$st).' + ' . $days  . ' DAY');
1024                 
1025             } else {
1026                 $st = strtotime(date('Y-m-d',$st).' + 1 DAY');
1027             }
1028             $st = strtotime(date('Y-m-d 10:00:00',$st));
1029             return date('Y-m-d H:i:s', $st);
1030         }
1031         // otherwise this time is OK...
1032         return $dt;
1033     }
1034     function availEnd($start, $hours) {
1035         
1036         $et = $start;
1037         for ($i = 0; $i < ceil($hours ); $i++) {
1038             $et = $this->availStart(date('Y-m-d H:i:s', strtotime($et . ' + 1 HOUR')));
1039         }
1040         return $et;
1041          
1042     }
1043     
1044     function optionsForProject($roo,$pid)
1045     {
1046         $ret = array();
1047         $m = DB_DataObject::factory('mtrack_milestone');
1048         $m->project_id = $pid;
1049         $m->selectAdd();
1050         $m->selectAdd('id,name as name, name as display_name');
1051         $m->orderBy('startdate ASC');
1052         $ret['milestone'] =  $m->fetchAll(false,false,'toArray');
1053         
1054         $mm = DB_DataObject::factory('core_enum');
1055         $mm->orderBy('seqid ASC');
1056         $mm->active = 1;
1057         
1058         $m = clone($mm);
1059         $m->etype = 'priority';
1060         $ret['priority'] =  $m->fetchAll(false,false,'toArray');
1061         
1062         $m = clone($mm);
1063         $m->etype = 'severity';
1064         $ret['severity'] =  $m->fetchAll(false,false,'toArray');
1065         
1066         $m = clone($mm);
1067         $m->etype = 'classification';
1068         $ret['classification'] =  $m->fetchAll(false,false,'toArray');
1069
1070         
1071         
1072         $m = DB_DataObject::factory('core_person');
1073         $m->company_id = $roo->company->id;
1074         $m->selectAdd();
1075         $m->selectAdd("id,name as name, concat(name ,' <', email , '>') as display_name");
1076         $m->active = 1;
1077         $m->applyFilters(array(
1078             'query' => array(
1079                 'project_id' => $pid,
1080                 'role' => 'DEVELOPER'
1081             )
1082         ),$roo->authUser, $roo);
1083                                                 
1084         $m->orderBy('name ASC');
1085         $ret['developer'] =  $m->fetchAll(false,false,'toArray');
1086         $ret['authuser_id'] =  "".$roo->authUser->id; //string...
1087         
1088         $roo->jok($ret);
1089     }
1090  
1091 }