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('Projects');
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; //??? ignore?
623                     case '@components':
624                         continue;
625                    
626                     case '@milestones':
627                         continue;
628                      
629                     case '@keywords':
630                         continue;
631                    
632                     //case 'commit?'  
633                       
634                     default:
635                         if ($audit->oldvalue == $audit->value) {
636                             continue; // 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;
649                         }
650                         
651                         if (!strlen($value)) {
652                             $body .= "Removed {$field} - was: {$lb}{$oldvalue}\n";
653                             continue;
654                         }
655                         $body .= "Changed {$field} from : {$lb}{$oldvalue} {$lb}-> {$lb}{$value}\n";
656                         continue;  
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         if (!empty($req['reason'])) {
683             $ch = DB_DataObject::factory('mtrack_change');
684             $ch->beginChange($this, $req['reason']);
685             $this->_mtrack_change= $ch;
686         }
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");
694             }
695             $this->status = $n->id;
696             
697         }
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");
703             }
704             if ($n->etype != 'ticketstate') {
705                 if ($n->etype != 'resolution') {
706                     $roo->jerr("status set invalid");
707                 }
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;
713             }
714             
715         }
716         
717         
718         
719         
720         if (isset($req['_reorder'])) {
721             $old->updateSequence($req['_reorder']);
722             $roo->jok("UPDATED");
723         }
724         
725         
726     }
727     
728     function beforeInsert($req, $roo)
729     {
730         
731         if (isset($req['_bulk_update'])) {
732             $this->bulkUpdate($req,$roo);
733             $this->jerr("oops");
734         }
735         
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;
741         }
742     }
743     
744     
745     function bulkUpdate($req, $roo)
746     {
747         if (empty($req['_bulk_update'])) {
748             $roo->jerr("invalid tickets");
749         }
750         
751         $t = DB_DAtaObject::Factory('mtrack_ticket');
752         $t->autoJoin();
753         $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
754         $all = $t->fetchAll();
755         
756         foreach($req as $k=>$v) {
757             if (empty($v)) {
758                 continue;
759             }
760             $cg[$k] = $v;
761         }
762         foreach($all as $t) {
763             $old = clone($t);
764             $t->setFrom($cg);
765             $t->beforeUpdate($old, $cg, $roo);
766             $t->update($old);
767             $t->onUpdate($old,$req, $roo);
768             
769         }
770         $t = DB_DAtaObject::Factory('mtrack_ticket');
771         $t->autoJoin();
772         $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
773         $roo->jok($t->fetchAll(false,false,'toArray'));
774         
775         
776          
777         
778         
779     }
780     
781     
782     
783     
784     function onUpdate($old,$req, $roo)
785     {
786         //print_r($old); print_r($this);
787         
788          //die("on update");
789         $ch = $this->_mtrack_change; 
790         if (!$ch) {
791             $ch = DB_DataObject::factory('mtrack_change');
792             $ch->beginChange($this, "Changed");
793             $this->_mtrack_change = $ch;
794         }
795         
796         $n = $ch->add($this, $old);
797         if (!$n && !$this->_mtrack_change) {
798             //die("deleting change");
799             $ch->delete(); // do not record the change..
800         } else { 
801         
802             $oo = clone($this);
803             $this->updated = $ch->id;
804             $this->update($oo);
805         }
806         // trigger emails..
807         
808     }
809     
810     
811     
812     function updated()
813     {
814         $ch = DB_DataObject::factory('mtrack_change');
815         $ch->get($this->updated ? $this->updated : $this->created);
816         return $ch;
817         
818     }
819     
820     
821     function toRooSingleArray($au, $req)
822     {
823         if ($this->id) {
824             return $this->toArray();
825         }
826         // empty!
827         $p = $this->factory('core_enum');
828         $p->etype='priority';
829         $p->get('name', 'normal');
830         
831         $s = $this->factory('core_enum');
832         $s->etype='severity';
833         $s->get('name', 'normal');
834         
835         $c = $this->factory('core_enum');
836         $c->etype='classification';
837         $c->get('name', 'enhancement');
838         
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'));
846         
847          return $ret;
848         
849     }
850     
851     //  http://roojs.com/admin.php/Roo/Mtrack_ticket?_post=1&id=733&_reorder=0
852     function updateSequence($tbefore)
853     {
854         $tn = $this->tableName();
855         $t = DB_DataObject::factory('mtrack_ticket');
856         $tt = clone($t);
857         $t->seqid = 0;
858         //$t->project_id = $this->project_id;
859         $t->autoJoin();
860         $t->selectAdd();
861         // we still apply to closed tickets????
862         //$t->whereAdd("join_status_id.name != 'closed'");
863         $t->applySort(false,  'old_style', false);
864         
865         $old_seqid = $this->seqid;
866         
867         if ($t->count()) {
868             // add a seq id to everything.
869             $t->selectAdd("{$tn}.id as id");
870             $ids = $t->fetchAll('id');
871             
872             $t_all = DB_DataObject::factory('mtrack_ticket');
873             $t_all->selectAdd('max(seqid) as seqid');
874             $t_all->find(true);
875             
876             $start = empty($t_all->seqid) ? 1 : $t_all->seqid;
877             $start++;
878             foreach($ids as $i => $id) {
879                 $tt->query("UPDATE {$tn} SET seqid = " . ($start + $i) ." WHERE id = {$id}");
880             }
881             $t = DB_DataObject::Factory('mtrack_ticket');
882             $t->get($this->id);
883             $old_seqid = $t->seqid;
884             
885         }
886         $before_seq_id = 0;
887         
888         if ($tbefore) { 
889             $before = DB_DataObject::factory('mtrack_ticket');
890             $before->get($tbefore);
891             $before_seq_id = $before->seqid;
892         }
893         
894         $t = DB_DataObject::factory('mtrack_ticket');
895         
896         
897         /// move everything down.
898         $t->query("UPDATE {$tn} SET
899                         seqid = seqid + 1
900                     WHERE
901                        seqid > {$before_seq_id}
902                 ");
903         
904         // ...1234 GAP 6789
905         
906         // puts this one in the gap..
907         $t->query("UPDATE {$tn} SET seqid = ". ( $before_seq_id  + 1) . " WHERE id = {$this->id}");
908         
909         // now there is a gap where this used to be..
910         
911         
912          $t->query("UPDATE {$tn} SET
913                         seqid = seqid - 1
914                    WHERE 
915                         seqid > {$old_seqid}
916         ");             
917          
918         
919     }
920     function toRooArray($q) {
921         if (empty($q['_future_schedule'])) {
922             return $this->toArray();
923         }
924         if (!$this->developer_id) {
925             return false;
926         }
927         $add = $this->toCalEvent();
928         if (isset($q['filter'])) {
929             foreach($q['filter'] as $k=>$v) {
930                 if (empty($v)) {
931                     continue;
932                 }
933                 if (!isset($add[$k])) {
934                     $roo->jerr('invalid filter ' .$k);
935                 }
936                 if ($v != $add[$k]) {
937                     $add = false;
938                 }
939             }
940         }
941         return $add;
942      
943     }
944      
945     
946     
947     // only run this once..
948     function toCalEvent()
949     {
950         //secho '<PRE>';print_r($this);exit;
951         static $cdata = array();
952         
953         
954         if (empty($cdata[$this->developer_id])) {
955             $start = $this->availStart(date('Y-m-d H:i:s'));
956         } else {
957             $start = $this->availStart($cdata[$this->developer_id]);
958         }
959         
960         $end = $this->availEnd($start, $this->estimated ? $this->estimated : 1);
961         $cdata[$this->developer_id]= $end;
962         
963         $ret = array_merge($this->toArray(), array(
964             'start_dt' => $start,
965             'end_dt' =>  $end,
966             //'start_time' => 
967             'title' => '#' . $this->id . '  ' .$this->developer_id_name . ' : ' . $this->project_id_name,
968             'description' => $this->summary,
969           
970         ));
971         return $ret;
972         
973         
974         
975     }
976     function availStart($dt)
977     {
978         //give a date time.. see if we should schedule it today?
979         $st = strtotime($dt);
980         
981         if (date('H', $st) < 11) {
982             $st = strtotime(date('Y-m-d 10:00:00',$st));
983         }
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');
989                 
990             } else {
991                 $st = strtotime(date('Y-m-d',$st).' + 1 DAY');
992             }
993             $st = strtotime(date('Y-m-d 10:00:00',$st));
994             return date('Y-m-d H:i:s', $st);
995         }
996         // otherwise this time is OK...
997         return $dt;
998     }
999     function availEnd($start, $hours) {
1000         
1001         $et = $start;
1002         for ($i = 0; $i < ceil($hours ); $i++) {
1003             $et = $this->availStart(date('Y-m-d H:i:s', strtotime($et . ' + 1 HOUR')));
1004         }
1005         return $et;
1006          
1007     }
1008     
1009     function optionsForProject($roo,$pid)
1010     {
1011         $ret = array();
1012         $m = DB_DataObject::factory('mtrack_milestone');
1013         $m->project_id = $pid;
1014         $m->selectAdd();
1015         $m->selectAdd('id,name as name, name as display_name');
1016         $m->orderBy('startdate ASC');
1017         $ret['milestone'] =  $m->fetchAll(false,false,'toArray');
1018         
1019         $mm = DB_DataObject::factory('core_enum');
1020         $mm->orderBy('seqid ASC');
1021         $mm->active = 1;
1022         
1023         $m = clone($mm);
1024         $m->etype = 'priority';
1025         $ret['priority'] =  $m->fetchAll(false,false,'toArray');
1026         
1027         $m = clone($mm);
1028         $m->etype = 'severity';
1029         $ret['severity'] =  $m->fetchAll(false,false,'toArray');
1030         
1031         $m = clone($mm);
1032         $m->etype = 'classification';
1033         $ret['classification'] =  $m->fetchAll(false,false,'toArray');
1034
1035         
1036         
1037         $m = DB_DataObject::factory('core_person');
1038         $m->company_id = $roo->company->id;
1039         $m->selectAdd();
1040         $m->selectAdd("id,name as name, concat(name ,' <', email , '>') as display_name");
1041         $m->active = 1;
1042         $m->applyFilters(array(
1043             'query' => array(
1044                 'project_id' => $pid,
1045                 'role' => 'DEVELOPER'
1046             )
1047         ),$roo->authUser, $roo);
1048                                                 
1049         $m->orderBy('name ASC');
1050         $ret['developer'] =  $m->fetchAll(false,false,'toArray');
1051         $ret['authuser_id'] =  "".$roo->authUser->id; //string...
1052         
1053         $roo->jok($ret);
1054     }
1055  
1056 }