55ac5112ff3e313b53c8ae961637a15e75e3c547
[Pman.MTrack] / DataObjects / Mtrack_change.php
1 <?php
2 /**
3  * Table Definition for mtrack_change
4  */
5 class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php';
6
7 class Pman_MTrack_DataObjects_Mtrack_change 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_change';                   // table name
13     public $id;                              // int(11)  not_null primary_key auto_increment
14     public $person_id;                       // int(11)  
15     public $ontable;                         // string(128)  
16     public $onid;                            // int(11)  not_null
17     public $changedate;                      // datetime(19)  not_null binary
18     public $reason;                          // blob(65535)  blob
19
20     
21     /* the code above is auto generated do not remove the tag below */
22     ###END_AUTOCODE
23      ###END_AUTOCODE
24      
25     function objectCached()
26     {
27         static $cache;
28         if (empty($this->ontable) || empty($this->onid)) {
29             return false;
30         }
31         
32         if (isset($cache[$this->ontable.':'. $this->onid])) {
33             $cache[$this->ontable.':'. $this->onid];
34         }
35         $o = DB_DataObject::factory($this->ontable);
36         $o->autoJoin();
37         $o->get($this->onid);
38         $cache[$this->ontable.':'. $this->onid] = $o;
39         return $o;
40         
41      
42     }
43     
44      //checkPerm('S'/'E'/'A', $authuser) - can
45     function checkPerm($perm, $au)
46     {
47         
48         if ($au && $au->company()->comptype == 'OWNER') {
49             // owner can do anything..????
50             return true;
51         }
52         
53         if ($perm == 'E' || $perm == 'D' ) { // Edit and delete ..not allowed..
54             return false;
55         }
56         
57         if (!$au && $perm != 'S') {
58             // non-authenticated users can only list stuff..
59             return false;
60             
61         }
62         
63         //DB_DataObject::debugLevel(1);
64         $obj = $this->objectCached();
65         if (!$obj) {
66             return true;
67         }
68         if (!method_exists($obj, 'checkPerm')) {
69             return false;
70         }
71         
72         
73         return $obj->checkPerm($perm, $au);
74         
75          
76     }
77     
78     
79      
80     function applyFilters($q, $au, $roo)
81     {
82         
83         
84         if (!empty($q['timeline'])) {
85             return $this->applyFilterTimeline($q,$au, $roo);
86         }
87         
88         if (!empty($q['_is_update_request'])) {
89             return;
90         }
91         
92         $this->setFrom($q);
93         
94         
95         $obj = $this->objectCached();
96         
97         
98         // global searching on non-object...
99         // needed for timelime, but not ready yet...
100         
101         if (!$obj) {
102             $this->whereAdd('1=0');
103             return;
104            
105         }
106         if (!method_exists($obj, 'checkPerm')) {
107             $this->whereAdd('1=0');
108             return;
109         }
110         
111         if (!$au || (!$obj->checkPerm('S', $au) || !$obj->checkPerm('E', $au)) ) {
112             $this->whereAdd('1=0');
113             return;
114         }    
115         
116         
117         
118            
119     }
120     // this is the query for timelines..
121     function applyFilterTimeline($q, $au, $roo)
122     { 
123         
124         
125         $start = empty($q['on_date']) ? date('Y-m-d') : 
126             date('Y-m-d', strtotime($q['on_date']));
127
128         
129         //DB_DataObject::debugLevel(1);
130         
131         
132         $pd = DB_DataObject::factory('ProjectDirectory');
133         $pd->whereAdd("role != ''");
134         $pids = $pd->projects($au);
135         if (empty($pids)) {
136             $roo->jerr("User is not a member of any projects");
137         }
138         
139         if (!empty($q['project_id'])) {
140             if (!in_array($q['project_id'], $pids)) {
141                 $roo->jerr("Selected project not in your permitted project list.");
142             }
143             $pids = array($q['project_id']);
144         }
145         
146         $pids = implode(',', $pids);
147         
148         // add emails???
149         $this->whereAdd("
150             ( ontable='mtrack_ticket' AND
151                 onid IN (SELECT id FROM mtrack_ticket where project_id IN ( $pids ))
152             )
153             OR
154             ( ontable='mtrack_repos' AND
155                 onid IN (SELECT id FROM mtrack_repos where project_id IN ( $pids ))
156             )
157         ");
158         $this->orderBy('changedate DESC');
159  
160         if (!empty($q['viewtype']) && $q['viewtype'] == 'summary') {
161             //DB_DataObject::debugLevel(1);
162             $isSummary = true;
163             $this->whereAdd("
164                         changedate >= '$start 00:00:00'
165                         AND
166                         changedate < '$start 00:00:00' + INTERVAL 1 DAY
167             ");
168             
169             
170             $this->selectAdd();
171             //$this->joinAdd(DB_DataObject::factory('Person'), 'LEFT');
172             $this->selectAdd("
173                      DATE_FORMAT(changedate, '%Y-%m-%d')  as changeday,
174                      join_person_id_id.id   as person_id,
175                      join_person_id_id.name as person_name,
176                      Projects.id as  project_id,
177                      mtrack_repos.shortname as repo_name,
178                      mtrack_repos.id as repo_id,
179
180                      Projects.code  as project_code,
181                         
182                      COUNT(mtrack_change.id) as nchanges
183                     ");
184             $this->_join .= "
185                 LEFT JOIN Projects ON
186                    Projects.id = IF (ontable='mtrack_repos',
187                        (SELECT project_id FROM mtrack_repos where onid = mtrack_repos.id ),
188                        (SELECT project_id FROM mtrack_ticket where onid = mtrack_ticket.id )
189                      )
190                 LEFT JOIN mtrack_repos ON
191                    mtrack_repos.id = IF (ontable='mtrack_repos', onid,  0)
192                 
193                 
194                 ";
195                 
196             $this->groupBy("changeday,
197                             join_person_id_id.id,
198                             join_person_id_id.name,
199                             repo_id, repo_name,
200                             mtrack_repos.id,
201                              mtrack_repos.shortname,
202                             Projects.code");
203             $this->orderBy('project_code asc, repo_name ASC, person_name asc');
204             $ret = array();
205             $roo->jdata($this->fetchAll(false,false,'toArray'));   
206         }
207         
208          
209         $this->whereAdd("changedate >= '$start_day 00:00:00' AND
210                       changedate < '$start_day 00:00:00' + INTERVAL 1 DAY");
211          
212         //DB_DataObject::debugLevel(1);
213         
214        
215     
216     
217     }
218     // do not accept use input for this..
219     function autoJoinObject($tbl)
220     {
221          
222         
223         $d = DB_DataObject::Factory($tbl);
224         $ji = $d->autoJoin();
225         //echo '<PRE>';print_R($ji);
226         // get cols
227         foreach($ji['join_names'] as $cname=>$fname) {
228              $this->selectAdd($fname . ' as ontable_id_' . $cname );
229         }
230         
231         //$this->selectAdd($d->_query['data_select']); -- this will cause the same dataIndex...
232         $this->_join .= "
233             LEFT JOIN {$d->tableName()} ON {$this->tableName()}.onid = {$d->tableName()}.id
234             {$d->_join}
235         "; 
236         $this->selectAs($d, 'ontable_%s');
237         
238         
239         
240     }
241     
242     
243     
244     
245     function person()
246     {
247         static $cache = array();
248         if (isset($cache[$this->person_id])) {
249             return $cache[$this->person_id];
250         }
251         $p = DB_DataObject::factory('core_person');
252         $p->get($this->person_id);
253         $cache[$this->person_id] = $p;
254         return $p;
255         
256     }
257     
258     function getCommit($repo, $hash, $runhistory=true) 
259     {
260         $this->object = $repo->toIdString(); // should be repo:1
261         $this->rev = $id;
262         if ($this->find(true)) {
263             $this->fetchAudit();
264             return;
265         }
266         if (!$runhistory) {
267             throw new Exception("Can not find revision");
268         }
269         $this->refreshHistory($repo);
270         if ( $this->find(true)) {
271             $this->fetchAudit();
272             return true;
273         }; /// yes I know we say you should not run this twice...
274         return false;
275     }
276     
277     /**
278      *createFromCommit:
279      *@param {TrackCommitHookChangeEvent} ce the change event..
280      */
281     function createFromCommit($ce, $checker, $ticket = false)
282     {
283         $rev_ar = explode(',', $ce->rev);
284         $rev = substr(array_pop($rev_ar), 0, -1);
285         
286         $tc = clone($this);
287         $tc->ontable = 'mtrack_repos';
288         $tc->onid = $checker->repo->id;
289         $tc->rev= $rev;
290         $tc->cgtype = 'COMMIT';
291         $tc->branch = $ce->branch;
292         if ($tc->count()) { // if we have already tracked this..
293             return false;
294         }
295         
296         
297         
298         
299         
300         $tcid = 0;
301         
302         if ($ticket) {
303             $tc = clone($this);    
304             $tc->ontable = $ticket->tableName();
305             $tc->onid = $ticket->id;
306             $tc->changedate = date('Y-m-d H:i:s', $ce->ctime);
307             $tc->reason = $ce->changelog;
308             $tc->person_id = $ce->changeby_id;
309             $tc->rev = $rev;
310             $tcid  = $tc->insert();
311             
312         }
313         
314         
315         $this->ontable = 'mtrack_repos';
316         $this->onid = $checker->repo->id;
317         $this->changedate = date('Y-m-d H:i:s', $ce->ctime);
318         $this->reason = $ce->changelog;
319         $this->person_id = $ce->changeby_id;
320         $this->branch = $ce->branch;
321         $this->rev = $rev;
322         $this->insert();
323         
324         // add all the chanedge files..
325         foreach($ce->fileActions as $f => $cg) { 
326             
327             $au = DB_DataObject::factory('mtrack_change_audit');  
328             $au->change_id  = $this->id;
329             $au->ticket_change_id  = $tcid;
330             $au->fieldname  = $rev;
331             $au->action     = 'commit-'.$cg;
332             $au->value      = $f; 
333             $au->insert();
334         }
335         
336         $this->onInsert(array(), HTML_FlexyFramework::get()->page);
337         
338         // finnaly trigger watchers
339         $matches = array();
340         if (!$ticket && preg_match('/_T([0-9]+)_/', $ce->branch, $matches)) {
341             $ticket = DB_DataObject::factory('mtrack_ticket');
342             if (!$ticket->get($matches[1])) {
343                 $ticket = false;
344             }
345         }
346         if ($ticket) {
347             $ts = DB_DataObject::Factory('cash_invoice_entry');
348             $ts->updateFromCommit($ce, $ticket);
349         }
350         
351         
352         return true;
353         
354         //print_r($this);
355         
356         // audit should contain files changed..
357     }
358     
359     function onInsert($request,$roo) 
360     { 
361         // this will send
362         // for a commit ? - ontable = repos, onid = the repo id...
363         // for a change... ontable - will be the ticked, and onid will be the ticket id..
364         
365         $obj = $this->objectCached();
366         $target = $obj;
367         if ($obj->tableName() == 'mtrack_ticket') {
368             $obj = $obj->project();
369             $target = $obj;
370         } else {
371             $target = empty($this->branch) ? $obj : $obj->branchObject($this->branch);
372         }
373         if ($obj) {
374            
375             $core_watch = DB_DataObject::Factory('core_watch');
376             $core_watch->notify(
377                     $obj->tableName(),
378                     $obj->pid(),
379                     "medium='ENDOFDAYMAIL'",
380                         // end of day..
381                     date('Y-m-d 23:59:59'),
382                     $target->tableName(),
383                     $target->pid()
384             );
385         }    
386     }
387     
388     
389     /*
390     * scans repo history and imports it... ??? does not know about branches yet...
391     */
392     function refreshHistory($repo) 
393     {
394         $c = DB_DataObject::factory('mtrack_changes');
395         $c->object =  $repo->toIdString(); 
396         $c->orderBy('changedate DESC');
397         $c->whereAdd("rev != ''");
398         $last  = '';
399         if ($c->find(true)) {
400             $last = $c->scmid;
401         }
402         // history returns an array of scmEvents... - should actually return an array of this object...
403         $recs = $repo->history('', null, $last ? 'rev' : null, $last ? $last  :null);
404         
405         foreach($recs as $r) { 
406             // see if we have a copy of it already.
407             $c = DB_DataObject::factory('mtrack_change');
408             $c->object =  $repo->toIdString(); 
409             $c->changedate != $r->ctime;
410             $cr  = clone($c);
411             $c->whereAdd("rev != ''");
412             if (!$c->find(true)) {
413                 // not found, create a new one..
414                 $c->insert();
415                 
416             }
417             $cr->rev = $r->rev;
418             $cr->reason = $r->changelog;
419             $cr->who =  $r->changeby;
420             $cr->update();
421             foreach($cr->files as $f) {
422                 // we could do with adding 'X' lines changed on this
423                 $ca = DB_DataObject::factory('change_audit');
424                 $ca->cid = $cr->id;
425                 $ca->fieldname = $cr->object. ':file';
426                 $ca->action = $f->status . ($f->added ? ' +' . $f->added : '') . ($f->removed ? ' -' . $f->removed : '') ;
427                 $ca->value = $f->name;
428                 $ca->insert();
429                 
430             }
431             
432             
433         }
434     
435     }
436      
437
438     function beginChange($object, $reason = '', $when = null)
439     {
440          // $db->beginTransaction();
441          //$this->query('BEGIN');
442         $pg = HTML_FlexyFramework::get()->page;
443         $au  = $pg->authUser;
444
445         $this->person_id = $au->id;
446         $this->ontable      = $object->tableName();                         // string(128)  
447         $this->onid = $object->id; // should use keys( really..)
448         $this->reason = $reason;
449         //$d = date_create("@" . (empty($when)  ? time() : $when), new DateTimeZone('UTC'));
450         $this->changedate =  date('Y-m-d H:i:s'); // everythign at server time..
451                                   //$d->format('Y-m-d\TH:i:s.u\Z');
452         $this->cgtype = $reason == 'Changed' ? 'CHANGE' : 'COMMENT';
453         $this->insert();
454         $this->onInsert(array(), $pg);
455          
456     }
457
458     function commit()
459     {
460         if ($this->count == 0) {
461             //      throw new Exception("no changes were made as part of this changeset");
462         }
463         if (self::$use_txn) {
464             //$this->query('COMMIT');
465           //$db->commit();
466         }
467     }
468     
469     function addentry($fieldname, $action, $old, $value = null)
470     {
471         
472         // final sanity check..
473         $o = DB_DataObject::factory($this->ontable);
474         $info = $o->tableColumns();
475         $far = explode(':',$fieldname);
476         $col = array_pop($far);
477         if (!isset($info[$col])) {
478             return 0;
479         }
480         
481         
482         if ($info[$col] & (defined('DB_DATAOBJECT_INT') ? DB_DATAOBJECT_INT : PDO_DataObject::INT)) {
483             if (((int) $old)  == ((int) $value)) {
484                 return 0;
485             }
486         }
487         
488         
489         
490         $ca = DB_DataObject::factory('mtrack_change_audit');
491         $ca->setFrom(array(
492             'change_id'     => $this->id,
493             'fieldname'     => $fieldname,
494             'action'        => $action,
495             'oldvalue'  => $old,
496             'value'     => $value
497         ));
498         $ca->insert();
499         //print_r($ca);
500         
501         return 1;
502     }
503    
504     /**
505      * usage
506      *      add('xxx:yyy:zzz' , new , old)
507      *      add('xxx:yyy', new_do, old_do) // two dataobjects.. (will diff the two..
508      *      add($new_do, $old_do) 
509      */
510     
511     
512     function add($fieldname, $new, $old = false)
513     {
514         //print_r(array("ADD", $fieldname, $new, $old));
515         //echo "check: $fieldname ($old) => ($new)\n"; 
516         $ret = 0;
517         if (is_object($fieldname)) {
518             return $this->add($this->ontable.':'. $this->onid , $fieldname, $new );  
519         }
520         if (is_object($new)) {
521             // should check keys() - so it does not log primary key addition.
522             
523             $cols = array_keys($new->tableColumns());
524             $keys  = $new->keys();
525             $links = $new->links();
526             
527             foreach($cols as $k) {
528                 if (in_array($k, $keys)) {
529                     continue;
530                 } 
531                 $alinks = isset($links[$k])  ? explode(':', $links[$k]) : false;
532                 if ($alinks && $this->tablename() == array_shift($alinks)) {
533                     continue;
534                 }
535                 if (!$old && !isset($new->{$k})) {
536                     continue;
537                 }
538                 
539                 $ret += $this->add($fieldname .':'. $k, $new->{$k}, !$old ? '' :  $old->{$k});
540             }
541             
542             return $ret;
543         }
544      
545         if ($old == $new) {
546             return $ret;
547         }
548         if ($old !== false) {
549             return  $this->addentry($fieldname, 'set', $old, $new);
550              
551         }
552         if ($new === false) {
553             return    $this->addentry($fieldname, 'deleted', $old, $new);
554             
555         }
556         return   $this->addentry($fieldname, 'changed', $old, $new);
557          
558     }
559     
560     
561     
562     
563     
564     var $_audit = false;
565     function cachedAudit()
566     {
567         static $cache = array();
568         
569         if (!isset($cache[$this->id])) {
570             $cache[$this->id]= $this->audit();
571         }
572         return $cache[$this->id];
573     }
574     function cachedAuditToJSONArray()
575     {
576          
577         $ar = $this->cachedAudit();
578         
579        
580         $ret = array();
581         foreach($ar as $a) {
582              
583             $add = $a->toJSONArray($this);
584             if (!$add) {
585                 continue;
586             }
587             
588             $ret[] = $add;
589         }
590         return $ret;
591         
592     }
593      
594     function relatedCommits()
595     {
596         if (empty($this->rev))  {
597             return '';
598         }
599         // occurs on a ticket... when listing...
600         $mc = DB_DataObject::Factory('mtrack_change');
601         $mc->rev = $this->rev;
602         $mc->ontable = 'mtrack_repos';
603         if (!$mc->find(true)) {
604             return '';
605         }
606         // got the repo.. and the commit..
607         
608         $repo = $mc->objectCached();
609          
610         return  $repo->historyToSummary(
611                     $repo->impl()->history('.', 1, 'rev', $this->rev),
612                 $this->changedate
613         );
614         
615         
616         
617         
618     }
619     
620     
621     function audit()
622     {
623         $a = DB_DataObject::Factory('mtrack_change_audit');
624         $a->whereAdd(" 
625            {$a->tableName()}.change_id = {$this->id} OR
626            {$a->tableName()}.ticket_change_id = {$this->id}   
627         ");
628         //       var_dump($a->find());exit;
629         
630         return $a->fetchAll();
631     }
632     
633     function changesSince($object,$since)
634     {
635         $q = DB_DataObject::factory('mtrack_change');
636         $q->onid = $object->id;
637         $q->ontable = $object->tableName();
638         $q->whereAdd("changedate > '$since'");
639         $q->orderBy('changedate ASC');
640         $q->autoJoin();
641         // we are going to end up with a list of objects that have changed.
642         // eg. a ticket or a repo..
643         
644         return  $q->fetchAll();
645         
646         
647     }
648     
649     
650     function cachedAuditToString()
651     {
652         // move these to 
653         $body = array();
654         $ar = $this->cachedAudit();
655         foreach($ar as $audit) {
656             $add = $audit->toAuditString($this);
657             if ($add === false) {
658                 continue;
659             }
660             $body[] = $add ;
661                   
662         }
663         return implode("\n",$body);
664     }
665     
666     function toRooArray()
667     {
668         $ret = $this->toArray();
669         $ret['audit'] = $this->cachedAuditToJSONArray();
670         $ret['commit']  = $this->relatedCommits();
671          
672         return $ret;
673         
674     }
675     
676     function beforeInsert($req, $roo)
677     {
678         // originally this was blocked - we are going to try and use it now???
679         if (empty($roo->authUser)) {
680             $roo->jerr("Invalid user inserting");
681         }
682         $this->person_id = $roo->authUser->id;
683         
684         
685         $obj = $this->objectCached();
686          
687         if (!$obj || !$obj->checkPerm('E', $roo->authUser)) {
688             $roo->jerr("Invalid object / permission denied");
689         }
690         
691         $this->changedate = $this->sqlValue('NOW()');
692         
693     }
694     function beforeUpdate($old,$req,$roo)
695     {
696         $roo->jerr("update not allowed by user interface.");
697         
698         
699     }
700     
701     function changedate($format)
702     {
703         return date($format, strtotime($this->changedate));
704     }
705     function postListFilter($data, $authUser, $q)
706     {
707         if (empty($q['ontable']) || empty($q['onid']) || $q['ontable'] != 'mtrack_ticket') {
708             return $data;
709         }
710         //DB_DataObject::debugLevel(1);
711         // look up in the accounting system what time was spent on the ticket..
712         $ie = DB_DataObject::factory('cash_invoice_entry');
713         $ie->ticket_id = $q['onid'];
714         $ie->orderBy('entered_dt ASC'); // doesnt matter really - we will sort it..
715         $ie->autoJoin();
716         $ie->selectAdd();
717         $ie->selectAdd("
718             cash_invoice_entry.id * -1 as id,
719             staff_id as person_id,
720             join_staff_id_id.name as person_id_name,
721             entered_dt as changedate,
722             'TIMESHEET' as cgtype ,
723             CONCAT(qtyvalue , ' Hours worked: ', cash_invoice_entry.description) as reason,
724             '' as rev,
725             '' as audit
726         ");
727         $hours = $ie->fetchAll(false,false,'toArray');
728         
729         $ret =  array_merge($data , $hours);
730         
731         usort($ret, function ($a,$b) use($q)  {
732             $aa = $a['changedate'];
733             $bb =  $b['changedate'];
734             if ($aa == $bb) {
735                 return 0;
736             }
737             if ($q['dir'] == 'ASC') {
738                 return strtotime($aa) < strtotime($bb) ? -1 : 1;
739             } else {
740                 return strtotime($aa) > strtotime($bb) ? -1 : 1;
741             }
742         });
743         
744         return $ret;
745         
746
747         
748         
749     }
750     function sendTelegram()
751     {
752         if ($this->ontable != 'mtrack_ticket') {
753             return;
754         }
755         
756         static $tg = false;
757         if ($tg === false) {
758             require_once 'Net/Telegram.php';
759             $tg = new Net_Telegram(HTML_FlexyFramework::get()->Pman_Telegram['token']);
760         }
761         $t = $this->objectCached();
762         
763         foreach ($this->audit() as $a) {
764             $str[] = $a->toAuditString($this);
765         }
766  
767  
768         // who to send to..
769         $d = DB_DataObject::factory('core_person');
770         $d->whereAddIn('id', array($t->created_person_id, $t->developer_id), 'int');
771         $d->whereAdd('telegram_id > 0');
772         $sendto = $d->fetchAll('id','telegram_id');
773         
774         // creator  //created_person_id
775         // assigned to developer_id
776         // exclude whoever modfiied it? (disable for testing.)
777         foreach($sendto as $pid => $tid) { 
778             if ($this->person_id == $pid) {
779                 // continue;
780             }
781             $res = $tg->factory('SendMessage',array(
782                 'chat_id' => $tid, 
783                 'parse_mode' => 'MarkdownV2',
784                 'text' => "/ticket@{$this->onid}  *". $tg->escape("[{$t->project_id_name}]] {$t->summary}") . "*\n\n".
785                         "{$this->cgtype} by ". $tg->escape($this->person()->name) . "\n\n" . $tg->escape(implode("\n", $str))
786             ))->send();
787         }
788         
789          //print_R($res); 
790     }
791
792     
793 }