missing variable
[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') 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 NOT IN('closed', '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  NOT IN('closed', 'CLOSED')  OR  {$tn}.status  = 0)
251                             OR
252                             (join_status_id.name    IN('closed', '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 ('REVIEW_BY_ADMIN', 'REVIEW_BY_CLIENT )");
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'
361                         AND
362                         name !='CLOSED'  AND  name != 'NEW', 100, 0) +
363                 IF( etype='ticketstate' , 0 , 1000) +
364
365                 seqid
366                 AS
367                 ts_order
368                              
369             ");
370             
371             // hide stuff...
372             switch($t->status_name) {
373                 
374                 case 'new':
375                 case 'NEW':
376                     // do not show closed..
377                     $et->whereAdd("
378                         ( CONCAT(etype , '.', name) != 'ticketstate.closed')
379                         AND
380                          ( CONCAT(etype , '.', name) != 'ticketstate.reopened')
381                          AND 
382                          ( CONCAT(etype , '.', name) != 'ticketstate.CLOSED') 
383                     ");
384                     break;
385                   
386                 case 'closed':
387                 case 'CANCELLED':
388                     $et->whereAdd("
389                                     ( CONCAT(etype , '.', name) = 'ticketstate.reopened')
390                                     OR
391                                     core_enum.id = {$t->status}
392                     ");
393                     break;
394                 
395                 case 'reopened':
396                 
397                     $et->whereAdd("
398                              ( CONCAT(etype , '.', name) != 'ticketstate.closed')
399                              AND
400                             ( CONCAT(etype , '.', name) != 'ticketstate.new')
401                             AND
402                              ( CONCAT(etype , '.', name) != 'ticketstate.NEW')
403                              AND
404                            ( CONCAT(etype , '.', name) != 'ticketstate.open')
405                             ");
406                     break;
407
408                 case 'open':
409                 case 'OPEN':
410                     
411                 default:
412                     $et->whereAdd("
413                            ( CONCAT(etype , '.', name) != 'ticketstate.closed')
414                              AND
415                             ( CONCAT(etype , '.', name) != 'ticketstate.new')
416                              AND
417                            ( CONCAT(etype , '.', name) != 'ticketstate.reopened')
418                             ");
419                     break;
420                  
421             }
422             
423             $et->selectAdd("
424                 CASE WHEN etype= 'ticketstate' THEN
425                     LOWER(REPLACE(name, '_', ' '))
426                 ELSE
427                     CONCAT('closed - ', LOWER(REPLACE(name, '_', ' ')))
428                 END AS name_mixed,
429                 CASE WHEN etype= 'ticketstate' THEN
430                     display_name
431                 ELSE
432                     CONCAT('Closed - ', display_name)
433                 END AS display_name_mixed
434                 
435             ");
436             
437             
438             $et->orderBy("ts_order ASC");
439             
440             $roo->jdata( $et->fetchAll(false,false,'toArray'));
441             exit;
442             
443     }
444     
445     
446     /**
447      *-- return false to let system do default sort code.
448      **/
449     
450     function applySort($au, $sortcol, $direction)
451     {
452         // fixme smarter?
453         $direction = $direction == 'DESC' ? 'DESC' : 'ASC';
454         
455         $tn = $this->tableName();
456         switch( $sortcol ) {
457             
458             case 'id':
459             //..... others..
460                 $this->orderBy("$sortcol $direction");
461                 return;
462             
463             case 'milestone_id':
464                    $this->orderBy("
465                         case
466                             when join_status_id.display_name = 'IN_PROGRESS' then 1
467                             when join_status_id.display_name = 'REVIEW_BY_ADMIN' then -1
468                             else 0
469                         end DESC,
470                        (case when join_milestone_id_id.duedate is null then 1 else 0 end) ASC,
471                             join_milestone_id_id.duedate $direction ,
472                               {$tn}.seqid $direction ,
473                               join_milestone_id_id.name $direction
474                        ");
475                   
476                 return;
477                 
478             case 'old_style':
479                 $this->orderBy('
480                        (case when join_milestone_id_id.duedate is null then 1 else 0 end) ASC,
481                             join_milestone_id_id.duedate ASC ,
482                             join_severity_id_id.seqid ASC,
483                             join_priority_id_id.seqid ASC,
484                             join_classification_id_id.seqid ASC,
485                             join_updated_id.changedate  DESC
486                        ');
487                 return;
488             case 'priority_id':
489                 $this->orderBy("
490                         case
491                             when join_status_id.display_name = 'IN_PROGRESS' then 1
492                             when join_status_id.display_name = 'REVIEW_BY_ADMIN' then -1
493                             else 0
494                         end DESC,
495                         join_priority_id_id.seqid $direction, {$tn}.seqid ASC
496                     ");
497                 break;
498             default:
499                 // this should favour in_progress, and disfavour resolved
500                 
501                 $this->orderBy("
502                         case
503                             when join_status_id.display_name = 'IN_PROGRESS' then 1
504                             when join_status_id.display_name = 'REVIEW_BY_ADMIN' then -1
505                             else 0
506                         end DESC,
507                         join_priority_id_id.seqid ASC,
508                         {$tn}.seqid ASC   ");
509                         
510                
511                             
512                 
513                 return;
514         }
515         
516     }
517     
518     /*
519      
520      (case when join_milestone_id_id.duedate is null then 1 else 0 end),
521                             join_milestone_id_id.duedate ASC ,
522                             join_severity_id_id.seqid ASC,
523                             join_priority_id_id.seqid ASC,
524                             join_classification_id_id.seqid ASC,
525                             join_updated_id.changedate  DESC
526     */
527     
528     
529     
530     
531     
532     
533     
534     
535     
536     
537     function isOpen()
538     {
539         switch ($this->status) {
540             case 'closed':
541             case 'CLOSED':
542                 return false;
543             
544           default:
545                 return true;
546         }
547     }
548     function descriptionToHtml() /* depreciated */
549     {
550        // return MTrack_Wiki::format_to_html($this->description);
551         return htmlspecialchars($this->description);
552     }
553     
554     
555     
556     function toEmailCalcAddress($changes, $person)
557     {
558         $ff = HTML_FlexyFramework::get();
559      
560         
561         $headers['To'] = '"'. addslashes($person->name). '" <' . $person->email .'>';
562         $headers['From'] = $ff->MTrackWeb['email_address']; // could be the user who made the change...
563         
564         //if (count($from) > 1) {
565         //    $rep = array();
566         //    array_shift($from);
567          //       foreach ($from as $email) {
568          // $rep[] = make_email($email[0], $email[1]);
569         // }
570         
571         $headers['Reply-To'] =  $ff->MTrackWeb['email_address'];
572         return $headers;
573     }
574     
575     function images()
576     {
577         $i = DB_DataObject::factory('Images');
578         return $i->gather($this);
579     }
580     
581     function toEventName()
582     {
583         return $this->summary;
584         
585     }
586     
587     
588     function toEmail($person, $lasttime)
589     {
590         $ff = HTML_FlexyFramework::get(); // used to work out url to show..
591         
592         $CS = DB_DataObject::factory('mtrack_change');
593         $changes = $CS->changesSince($this,$lasttime);
594         $headers = $this->toEmailCalcAddress($changes, $person);
595         
596         $headers += array(
597             'MIME-Version' => '1.0',
598             'Content-Type' => 'text/plain; charset="UTF-8"',
599             'Content-Transfer-Encoding' => 'quoted-printable',
600         );
601     
602         
603         $mid = $this->id . '@' . php_uname('n');
604         
605         $p = $this->project();
606          
607         $subj = "[" . $p->code . ' - ' . $p->name . "] ";
608     
609         $headers['X-mtrack-project-list'] = $p->code;
610      
611     
612         $headers['Subject'] = sprintf("%s#%s %s (%s %s)",
613           $subj, $this->id, $this->summary, $this->status_name, $this->classification_id_name);
614     
615         $owner =  $this->owner_id ? $this->owner_id_name : 'nobody';
616     
617         $body = 
618             sprintf("%s/Ticket/%s\n\n", $ff->MTrackWeb['url'], $this->id) .
619             sprintf("#%s: %s (%s %s)\n", $this->id, $this->summary, $this->status_name, $this->classification_id_name) .
620             sprintf("Responsible: %s (%s / %s)\n", $owner, $this->priority_id_name, $this->severity_id_name) .
621     
622             sprintf("\nDescription:\n%s\n", wordwrap($this->description,80, "\n")) .
623      //sprintf("Milestone: %s\n", join(', ', $T->getMilestones()));
624             //sprintf("Component: %s\n", join(', ', $T->getComponents()));
625             "\n";
626     
627       // Display changed fields grouped by the person that last changed them
628         //$who_changed = array();
629         //foreach ($field_changers as $field => $who) {
630         //  $who_changed[$who][] = $field;
631         //}
632         $body.="\n";
633         foreach ($changes as $change) {
634             
635             $body .= "\n{$change->changedate}:  Change by {$change->person_id_name}\n";
636             $body .= str_repeat('-', 80) . "\n";
637             if (!empty($change->reason)) {
638                 $body.= wordwrap($change->reason, 80, "\n") ."\n\n";
639             }
640             $ar = $change->cachedAudit();
641             
642             foreach($ar as $audit) {
643                 
644                 switch($audit->field()) {
645                     case 'id':
646                         continue 2; //??? ignore?
647                     case '@components':
648                         continue 2;
649                    
650                     case '@milestones':
651                         continue 2;
652                      
653                     case '@keywords':
654                         continue 2;
655                    
656                     //case 'commit?'  
657                       
658                     default:
659                         if ($audit->oldvalue == $audit->value) {
660                             continue 2; // no change?!?!?
661                         }
662                         $oldvalue = $audit->oldvalue($change);
663                         $value = $audit->value($change);
664                         $field = $audit->field();
665                         
666                         $field  = preg_replace('/_id$/', '', $field);
667                         $field  = str_replace('/_/', ' ', $field);
668                         $lb = strpos($oldvalue,"\n") > -1 || strpos($value,"\n") > -1 ? "\n" : '';
669                         
670                         if (!strlen($oldvalue)) {
671                             $body .= "Set {$field} to: {$lb}{$value}\n";
672                             continue 2;
673                         }
674                         
675                         if (!strlen($value)) {
676                             $body .= "Removed {$field} - was: {$lb}{$oldvalue}\n";
677                             continue 2;
678                         }
679                         $body .= "Changed {$field} from : {$lb}{$oldvalue} {$lb}-> {$lb}{$value}\n";
680                         continue 2;  
681                 }
682             }
683         }
684         
685         $body .= sprintf("\n\n%s/Ticket/%s\n\n", $ff->MTrackWeb['url'], $this->id);
686         
687         return array(
688             'body' => $body,
689             'headers' => $headers
690         );
691          
692          
693     }
694     
695     function toEventString()
696     {
697         return '#'.$this->id . ' ' . $this->summary;
698         
699     }
700     
701     var $_mtrack_change = false;
702     
703     function beforeUpdate($old, $req, $roo)
704     {
705         
706          if (!empty($req['reason'])) {
707             $ch = DB_DataObject::factory('mtrack_change');
708             $ch->beginChange($this, $req['reason']);
709             $this->_mtrack_change= $ch;
710         }
711         // used by gitlive close ticket...
712         if (!empty($req['status_name']) && empty($req['status'])) {
713             if ($req['status_name'] == 'resolved') {
714                 $req['status_name']  = 'REVIEW_BY_ADMIN';
715                 // if it's an admin doing it.. then set it to fixed?
716             }
717             $n = DB_DataObject::Factory('core_enum');
718             $n->whereAddIn('name', array( $req['status_name'], strtoupper($req['status_name'])), 'string');
719             $n->whereAddIn('etype', array('ticketstate','resolution'),'string');
720             if (!$n->find(true)) {
721                 $roo->jerr("invalid status name");
722             }
723             $this->status = $n->id;
724             
725         }
726         if ($old->status != $this->status) {
727             // status has been changed. - check to see if it's really a resolution.
728             $n = DB_DataObject::Factory('core_enum');
729             if (!$this->status || !$n->get($this->status)) {
730                 $roo->jerr("status set invalid");
731             }
732             if ($n->etype != 'ticketstate') {
733                 if ($n->etype != 'resolution') {
734                     $roo->jerr("status set invalid");
735                 }
736                 $this->resolution_id = $n->id;
737                 $n = DB_DataObject::Factory('core_enum');
738                 $n->etype = 'ticketstate';
739                 $n->get('name', 'closed'); // no error checking!?!
740                 $this->status = $n->id;
741             }
742             
743         }
744         
745         
746         
747         
748         if (isset($req['_reorder'])) {
749             $old->updateSequence($req['_reorder']);
750             $roo->jok("UPDATED");
751         }
752         
753         if (isset($req['_before']) || isset($req['_after']))  {
754             $req['_after'] = isset($req['_after']) ? $req['_after'] : 0;
755             $req['_before'] = isset($req['_before']) ? $req['_before'] : 0;
756             $old->updateSequence($req['_before'],$req['_after']);
757             
758             
759             if ($req['_before']) { 
760                 $before = DB_DataObject::factory('mtrack_ticket');
761                 $before->get($req['_before']);
762                 $this->seqid = $before->seqid;
763             } else if ($req['_after']) {
764                 $after = DB_DataObject::factory('mtrack_ticket');
765                 $after->get($req['_after']);
766                 $this->seqid =  $after->seqid+1;
767             } else {
768                 
769                 
770                 
771             }
772         }   
773          
774         
775         
776     }
777     
778     function beforeInsert($req, $roo)
779     {
780         
781         if (isset($req['_bulk_update'])) {
782             $this->bulkUpdate($req,$roo);
783             $this->jerr("oops");
784         }
785         
786         if (!$this->status) {
787             $n = DB_DataObject::Factory('core_enum');
788             $n->etype = 'ticketstate';
789             $n->get('name', 'new'); // no error checking!?!
790             $this->status = $n->id;
791         }
792     }
793     
794     
795     function bulkUpdate($req, $roo)
796     {
797         if (empty($req['_bulk_update'])) {
798             $roo->jerr("invalid tickets");
799         }
800         
801         $t = DB_DAtaObject::Factory('mtrack_ticket');
802         $t->autoJoin();
803         $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
804         $all = $t->fetchAll();
805         
806         foreach($req as $k=>$v) {
807             if (empty($v)) {
808                 continue;
809             }
810             $cg[$k] = $v;
811         }
812         foreach($all as $t) {
813             $old = clone($t);
814             $t->setFrom($cg);
815             $t->beforeUpdate($old, $cg, $roo);
816             $t->update($old);
817             $t->onUpdate($old,$req, $roo);
818             
819         }
820         $t = DB_DAtaObject::Factory('mtrack_ticket');
821         $t->autoJoin();
822         $t->whereAddIn($this->tableName().'.id', explode(',',$req['_bulk_update'] ), 'int');
823         $roo->jok($t->fetchAll(false,false,'toArray'));
824         
825         
826          
827         
828         
829     }
830     
831     
832     
833     
834     function onUpdate($old,$req, $roo)
835     {
836         //print_r($old); print_r($this);
837         
838          //die("on update");
839         $ch = $this->_mtrack_change; 
840         if (!$ch) {
841             $ch = DB_DataObject::factory('mtrack_change');
842             $ch->beginChange($this, "Changed");
843             $this->_mtrack_change = $ch;
844         }
845         
846         $n = $ch->add($this, $old);
847         if (!$n && !$this->_mtrack_change) {
848             //die("deleting change");
849             $ch->delete(); // do not record the change..
850         } else { 
851         
852             $oo = clone($this);
853             $this->updated = $ch->id;
854             $this->update($oo);
855         }
856         // trigger emails..
857         
858         if (isset($req['uploads'])) {
859             $this->updateUploads(json_decode($req['uploads']),$roo);
860         }
861         
862         
863     }
864     function onInsert($req,$roo, $even)
865     {        
866         $ch = $this->_mtrack_change; 
867         if (!$ch) {
868             $ch = DB_DataObject::factory('mtrack_change');
869             $ch->beginChange($this, "Changed");
870             $this->_mtrack_change = $ch;
871         }
872         $ch->add($this, false);
873         if (isset($req['uploads'])) {
874             $this->updateUploads(json_decode($req['uploads']). $roo);
875         }
876     }
877     
878     function updateUploads($uploads, $roo)
879     {
880         foreach($uploads as $up) {
881             
882             if (empty($up->id) || $up->id < 0) {
883                 if ($up->is_deleted) {
884                     continue;
885                 }
886                 
887                 
888                 $i = DB_DAtaObject::factory('Images');
889                 $i->object($this);
890                 $i->setFrom(array(
891                     'mimetype' => $up->mimetype,
892                     'title' => $up->title,
893                     'filename' => $up->title,
894                     'created' => date('Y-m-d H:i:s')
895                 ));
896                 $i->createFromData($up->srcdata);
897                 continue;
898             }
899             // edit..
900             $i = DB_DAtaObject::factory('Images');
901             $i->object($this);
902             if (!$i->get($up->id)) {
903                 $roo->jerr("invalid update id for image {$up->id}");
904             }
905             if ($up->is_deleted) {
906             
907                 $i->beforeDelete(array(), $roo);
908                 $i->delete();
909             }
910                
911             // update???
912             
913         }
914     }
915     
916     
917     function updated()
918     {
919         $ch = DB_DataObject::factory('mtrack_change');
920         $ch->get($this->updated ? $this->updated : $this->created);
921         return $ch;
922         
923     }
924     
925     
926     function toRooSingleArray($au, $req)
927     {
928         if ($this->id) {
929             $ret = $this->toArray();;
930             $ret['images'] = array();
931             foreach($this->images() as $i) {
932                 $ret['images'][] = $i->toRooArray(array());
933             }
934             
935             return $ret;
936         }
937         // empty!
938         $p = $this->factory('core_enum');
939         $p->etype='priority';
940         $p->get('name', 'normal');
941         
942         $s = $this->factory('core_enum');
943         $s->etype='severity';
944         $s->get('name', 'normal');
945         
946         $c = $this->factory('core_enum');
947         $c->etype='classification';
948         $c->get('name', 'enhancement');
949         
950         $this->severity_id = $s->id;
951         $this->classification_id = $c->id;
952         $this->priority_id = $p->id;
953         $ret = $this->toArray();
954         $ret = array_merge($ret, $p->toArray('priority_id_%s'));
955         $ret = array_merge($ret, $c->toArray('classification_id_%s'));
956         $ret = array_merge($ret, $s->toArray('severity_id_%s'));
957         
958        
959         return $ret;
960         
961     }
962     
963     //  http://roojs.com/admin.php/Roo/Mtrack_ticket?_post=1&id=733&_reorder=0
964     function updateSequence($tbefore, $tafter = 0)
965     {
966         // fix empty sequences?
967         
968         $tn = $this->tableName();
969         $t = DB_DataObject::factory('mtrack_ticket');
970         $tt = clone($t);
971         $t->seqid = 0;
972         //$t->project_id = $this->project_id;
973         $t->autoJoin();
974         $t->selectAdd();
975         // we still apply to closed tickets????
976          $t->whereAddIn('join_status_id.name', array(  'CLOSED'), 'string');
977         $t->applySort(false,  'old_style', false);
978         
979         $old_seqid = $this->seqid;
980         
981         if ($t->count()) {
982             // add a seq id to everything.
983             $t->selectAdd("{$tn}.id as id");
984             $ids = $t->fetchAll('id');
985             
986             $t_all = DB_DataObject::factory('mtrack_ticket');
987             $t_all->selectAdd('max(seqid) as seqid');
988             $t_all->find(true);
989             
990             $start = empty($t_all->seqid) ? 1 : $t_all->seqid;
991             $start++;
992             foreach($ids as $i => $id) {
993                 $tt->query("UPDATE {$tn} SET seqid = " . ($start + $i) ." WHERE id = {$id}");
994             }
995             $t = DB_DataObject::Factory('mtrack_ticket');
996             $t->get($this->id);
997             $old_seqid = $t->seqid;
998             
999         }
1000         
1001         
1002         
1003         
1004         $before_seq_id = 0;
1005         
1006         if ($tbefore) { 
1007             $before = DB_DataObject::factory('mtrack_ticket');
1008             $before->get($tbefore);
1009             $before_seq_id = $before->seqid;
1010             
1011         }
1012         if ($tafter) { 
1013             $after = DB_DataObject::factory('mtrack_ticket');
1014             $after->get($tafter);
1015             $before_seq_id = $after->seqid+1;
1016         }
1017          
1018         $t = DB_DataObject::factory('mtrack_ticket');
1019         
1020         
1021         /// move everything down.
1022         $t->query("UPDATE {$tn} SET
1023                         seqid = seqid + 1
1024                     WHERE
1025                        seqid > {$before_seq_id}
1026                 ");
1027         
1028         // ...1234 GAP 6789
1029         
1030         // puts this one in the gap..
1031         $t->query("UPDATE {$tn} SET seqid = ". ( $before_seq_id  + 1) . " WHERE id = {$this->id}");
1032         
1033         // now there is a gap where this used to be..
1034         
1035         
1036          $t->query("UPDATE {$tn} SET
1037                         seqid = seqid - 1
1038                    WHERE 
1039                         seqid > {$old_seqid}
1040         ");             
1041          
1042         
1043     }
1044     function toRooArray($q) {
1045         if (empty($q['_future_schedule'])) {
1046             return $this->toArray();
1047         }
1048         if (!$this->developer_id) {
1049             return false;
1050         }
1051         $add = $this->toCalEvent();
1052         if (isset($q['filter'])) {
1053             foreach($q['filter'] as $k=>$v) {
1054                 if (empty($v)) {
1055                     continue;
1056                 }
1057                 if (!isset($add[$k])) {
1058                     $roo->jerr('invalid filter ' .$k);
1059                 }
1060                 if ($v != $add[$k]) {
1061                     $add = false;
1062                 }
1063             }
1064         }
1065         return $add;
1066      
1067     }
1068      
1069     
1070     
1071     // only run this once..
1072     function toCalEvent()
1073     {
1074         //secho '<PRE>';print_r($this);exit;
1075         static $cdata = array();
1076         
1077         
1078         if (empty($cdata[$this->developer_id])) {
1079             $start = $this->availStart(date('Y-m-d H:i:s'));
1080         } else {
1081             $start = $this->availStart($cdata[$this->developer_id]);
1082         }
1083         
1084         $end = $this->availEnd($start, $this->estimated ? $this->estimated : 1);
1085         $cdata[$this->developer_id]= $end;
1086         
1087         $ret = array_merge($this->toArray(), array(
1088             'start_dt' => $start,
1089             'end_dt' =>  $end,
1090             //'start_time' => 
1091             'title' => '#' . $this->id . '  ' .$this->developer_id_name . ' : ' . $this->project_id_name,
1092             'description' => $this->summary,
1093           
1094         ));
1095         return $ret;
1096         
1097         
1098         
1099     }
1100     function availStart($dt)
1101     {
1102         //give a date time.. see if we should schedule it today?
1103         $st = strtotime($dt);
1104         
1105         if (date('H', $st) < 11) {
1106             $st = strtotime(date('Y-m-d 10:00:00',$st));
1107         }
1108         if (date('H', $st) > 17) {
1109             if(date('N',$st) > 4) {
1110                 // it's friday... (5) +3 , sat (6) +2 sun(7) +1
1111                 $days = 8 - date('N',$st);
1112                 $st = strtotime(date('Y-m-d',$st).' + ' . $days  . ' DAY');
1113                 
1114             } else {
1115                 $st = strtotime(date('Y-m-d',$st).' + 1 DAY');
1116             }
1117             $st = strtotime(date('Y-m-d 10:00:00',$st));
1118             return date('Y-m-d H:i:s', $st);
1119         }
1120         // otherwise this time is OK...
1121         return $dt;
1122     }
1123     function availEnd($start, $hours) {
1124         
1125         $et = $start;
1126         for ($i = 0; $i < ceil($hours ); $i++) {
1127             $et = $this->availStart(date('Y-m-d H:i:s', strtotime($et . ' + 1 HOUR')));
1128         }
1129         return $et;
1130          
1131     }
1132     
1133     function optionsForProject($roo,$pid)
1134     {
1135         $ret = array();
1136         $m = DB_DataObject::factory('mtrack_milestone');
1137         $m->project_id = $pid;
1138         $m->selectAdd();
1139         $m->selectAdd('id,name as name, name as display_name');
1140         $m->orderBy('startdate ASC');
1141         $ret['milestone'] =  $m->fetchAll(false,false,'toArray');
1142         
1143         $mm = DB_DataObject::factory('core_enum');
1144         $mm->orderBy('seqid ASC');
1145         $mm->active = 1;
1146         
1147         $m = clone($mm);
1148         $m->etype = 'priority';
1149         $ret['priority'] =  $m->fetchAll(false,false,'toArray');
1150         
1151         $m = clone($mm);
1152         $m->etype = 'severity';
1153         $ret['severity'] =  $m->fetchAll(false,false,'toArray');
1154         
1155         $m = clone($mm);
1156         $m->etype = 'classification';
1157         $ret['classification'] =  $m->fetchAll(false,false,'toArray');
1158
1159         
1160         
1161         $m = DB_DataObject::factory('core_person');
1162         $m->company_id = $roo->company->id;
1163         $m->selectAdd();
1164         $m->selectAdd("id,name as name, concat(name ,' <', email , '>') as display_name");
1165         $m->active = 1;
1166         $m->applyFilters(array(
1167             'query' => array(
1168                 'project_id' => $pid,
1169                 'role' => 'DEVELOPER'
1170             )
1171         ),$roo->authUser, $roo);
1172                                                 
1173         $m->orderBy('name ASC');
1174         $ret['developer'] =  $m->fetchAll(false,false,'toArray');
1175         $ret['authuser_id'] =  "".$roo->authUser->id; //string...
1176         
1177         $roo->jok($ret);
1178     }
1179  
1180 }