final move of files
[web.mtrack] / MTrack / DataObjects / Event.php
1 <?php
2
3 require_once 'MTrack/DB.php';
4 require_once 'MTrack/Config.php';
5 require_once 'MTrack/Component.php';
6 require_once 'MTrack/Wiki.php';
7
8 class MTrack_DataObject_Event {
9     
10     var $effort = array();
11     var $audit = array();
12     var $issue = false;
13     
14     static function fetchRange($issue, $start =0, $limit=10) 
15     {
16         
17         
18         $changes = array();
19         /*
20         $q = MTrackDB::q(
21                 'select * from changes where object = ?
22                 order by changedate asc LIMIT ?, ?',
23                 "ticket:{$issue->tid}",$start, $limit);
24         */
25         // limit will not really work due to the later merging..
26         $q = MTrackDB::q(
27                 'select * from changes where object = ?
28                 order by changedate asc',
29                 "ticket:{$issue->tid}");
30         
31         foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $CS) {
32             $changes[$CS['cid']] = self::init($CS);
33             $changes[$CS['cid']]->issue = $issue;
34              $cids[] = $CS['cid'];
35         }
36         
37         $cidlist = join(',', $cids);
38
39         $q= MTrackDB::q("select * from change_audit where cid in ($cidlist)");
40         foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $citem) {
41             
42             $changes[$citem['cid']]->audit[]  = $citem;
43             //$change_audit[$citem['cid']][] = $citem;
44         }
45
46           /* also need to include cases where the ticket was modified as a side-effect
47            * of other manipulations (such as milestones being closed and tickets being
48            * re-targeted.  Such manipulations do not directly reference this ticket,
49            * and so do not need to be included in the effort_audit array that is
50            * populated below. */
51
52        
53         $q = MTrackDB::q(
54             "select 
55                 c.cid as cid, c.who as who, 
56                 c.object as object, c.changedate as changedate, 
57                 c.reason as reason, ca.fieldname as fieldname, 
58                 ca.action as action, ca.oldvalue as oldvalue, 
59                 ca.value as value 
60             from change_audit ca 
61                 left join changes c on (ca.cid = c.cid) 
62             where 
63                 ca.cid not in ($cidlist) and 
64                 ca.fieldname like 'ticket:{$issue->tid}:%'
65             ");
66         
67         foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $CS) {
68             
69             if (!isset($changes[$CS['cid']])) {
70                 $changes[$CS['cid']] = self::init($CS);
71                  $changes[$CS['cid']]->issue = $issue;
72             }
73             
74             $changes[$CS['cid']]->audit[] =  $CS;
75         }
76
77         foreach (MTrackDB::q(
78               "select * from effort where cid in ($cidlist) and tid=?", $issue->tid)
79               ->fetchAll(PDO::FETCH_ASSOC) as $eff) {
80             $changes[$eff['cid']]->effort[]= $eff;
81         }   
82         return $changes;
83     }
84     
85     static function init($ar)
86     {
87         $r = new MTrack_DataObject_Event;
88         foreach($ar as $k=>$v) {
89             $r->$k = $v;
90         }
91         return $r;
92     }
93     
94     function toHtml()
95     {
96         
97         /*
98         
99 <div class="ticketevent">
100     <a class="pmark" href="#comment:3">#3 - COMMIT</a> 
101     <a name="comment:3"><abbr title="Fri, Jan 28 2011 04:44" class="timeinterval">5 days ago</abbr>  
102     - 
103     <a title="www-data" href="http://www.apdealflow.com/mtrack/user.php?user=www-data" class="userlink">www-data</a> 
104     - 
105     Fix <a class="ticketlink" href="http://www.apdealflow.com/mtrack/ticket.php/21">#21</a>- deal table page control button not stable (new defect)
106  </div>
107  
108  
109  ... 
110         */
111         
112         $link = HTML_FlexyFramework::get()->page->link;
113         $preamble = 0;
114         $cid = "comment:{$this->cid}";
115         
116         // tidied up by jquery..
117         $timestamp = $link->date($this->changedate, false);
118          
119   
120         $comments = array();
121
122         // default is that something changed..
123         $type = 'Changed';
124         
125         $comment_body = '';
126         
127         $comment_title = '';
128         $comment_fields = array();
129         
130         foreach ($this->audit as $citem) {
131                 //print_r($citem);
132             $main = false;
133             
134             $ar = explode(':', $citem['fieldname'], 3);
135             if (count($ar) != 3) {
136                 continue;
137                }
138             list($tbl,,$field) = $ar;
139
140             if ($tbl != 'ticket') {
141                 // can get here if we created a new keyword, for example
142                 //var_dump($citem);
143                 continue;
144             }
145                 
146             switch($field) {
147                 case '@comment':
148                     $comments[] = $citem['value'];
149                     $type = 'Comment added';
150                     $comment_title = array_shift(explode("\n", $citem['value']));
151                     continue;
152                
153                case 'spent':
154                     continue;
155                     
156                     
157              
158                 case '@components': 
159                     $comment_fields[] = $field;
160                     $ar = MTrackComponent::loadByIds($citem['value']);
161                     $ar = array_map( function($e) { return  $e->toHtml(); }, $ar); 
162                     $citem['value'] = join(', ', $ar);
163                     
164                     
165                     break;
166                 
167                 case '@milestones': 
168                     $comment_fields[] = $field;
169                     $citem['value'] = $this->get_milestones_list($citem['value']);
170                     break;
171                 
172                 case '@keywords':
173                 
174                 
175                     $comment_fields[] = $field;
176                     $ar = MTrackKeyword::loadByIds($citem['value']);
177                     $ar = array_map( function($e) { return  $e->toHtml(); }, $ar); 
178                     $citem['value'] = join(', ', $ar);
179                     break;
180                     
181                 case  'estimated': 
182                     $comment_fields[] = $field;
183                     if ($citem['value'] !== null) {
184                         $citem['value'] += 0;
185                     }
186                     if ($citem['oldvalue'] !== null) {
187                         $citem['oldvalue'] += 0;
188                     }
189                     break;
190                 
191                 default:
192                     $comment_fields[] = $field;
193                     if ($field[0] == '@') {
194                         $main = isset($pseudo_fields[$field]) ? $pseudo_fields[$field] : '';
195                         $field = substr($field, 1, -1);
196                     } else {
197                         $main = $this->issue->$field;
198                     }
199
200             }
201
202                  
203             require_once 'MTrack/Ticket_CustomFields.php';
204             $f = MTrackTicket_CustomFields::getInstance()->fieldByName($field);
205                 
206                 
207                 
208             if ($f) {
209                 $label = htmlentities($f->label, ENT_QUOTES, 'utf-8');
210             } else {
211                 if ($field == 'attachment' && strlen($citem['oldvalue'])) {
212                     $label = "Attachment: $citem[oldvalue]";
213                 } else {
214                     $label = ucfirst($field);
215                 }
216             }
217
218             if ($citem['oldvalue'] == null) {
219                   /* don't bother printing out a set if this is the initial thing
220                    * and if the field values are currently the same */
221
222                 if ($main != $citem['value'] || $this->cid != 'top') {
223
224                         /* Special case for description; since it is multi-line and often
225                          * very large, render it as a diff against the current ticket
226                          * description field */
227                     if ($field == 'description') {
228                         if ($this->issue->description == $citem['value']) {
229                             $comment_body .= "<b>Description</b>: no longer empty; see above<br>";
230                             continue;
231                         }
232
233                         $initial_lines = count(explode("\n", $this->issue->description));
234                         $diff = $this->diff_strings($this->issue->description, $citem['value']);
235                         $diff_add = 0;
236                         $diff_rem = 0;
237                         foreach (explode("\n", $diff) as $line) {
238                                 if (!strlen($line)) continue;
239                                 if ($line[0] == '-') {
240                                   $diff_rem++;
241                                 } else if ($line[0] == '+') {
242                                   $diff_add++;
243                                 }
244                         }
245                         if (abs($diff_add - $diff_rem) > $initial_lines / 2) {
246                             $comment_body .= "<b>initial $label</b><br>" .
247                                     MTrack_Wiki::format_to_html($citem['value']);
248                         } else {
249                                 $diff = $this->collapse_diff($diff);
250                                 $comment_body .= "<b>initial $label</b> (diff to above):<br>$diff\n";
251                         }
252                     } else {
253                           $comment_body .= "<b>$label</b> $citem[value]<br>\n";
254                     }
255                 }
256                 continue;
257             } 
258             
259             if ($citem['action'] == 'changed') {
260                 $lines = explode("\n", $citem['value'], 3);
261                 if (count($lines) >= 2) {
262                     $diff = $this->diff_strings($citem['oldvalue'], $citem['value']);
263                     $diff = $this->collapse_diff($diff);
264                     $comment_body .= "<b>$label</b> $citem[action]\n$diff\n";
265                 } else {
266                     $comment_body .= "<b>$label</b> $citem[action] to $citem[value]<br>\n";
267                 }
268                 continue;
269             } 
270             
271             $comment_body .= "<b>$label</b> $citem[action]<br>\n";
272             
273         }
274         
275         $commit_info = array();
276         if ($comment_title && 
277             preg_match('/\(In \[changeset:([^,]+),([a-z0-9]+)\]\)(.*)$/i', $comment_title, $commit_info)) {
278             $type = 'Commit';
279             
280             
281             $comment_title = '<a class="changesetlink" href="'. $GLOBALS['ABSWEB'] . 'changeset.php/'. 
282                 $commit_info[1].'/'.$commit_info[2]. '">[' . $commit_info[2] . ']</a>'.$commit_info[3];
283             
284             
285         }
286         if ($this->cid == $this->issue->created) {
287             $type = 'Created';
288             $comment_title = 'Issue Created';
289         }
290         
291         
292         
293         
294       
295         $html = '
296             <div class="ticketevent">
297                 <span class="ticketevent-expand" id="ticketevent-expand-' . $this->cid . '">+ ' . $type.'</span> 
298                 <a class="pmark" href="#'.$cid.'">#'.$this->cid.'</a> 
299                 
300                 <span class="ticketevent-expand" id="ticketevent-expand-title-' . $this->cid . '">' .
301                 (strlen($comment_title) ? $comment_title : implode(', ', $comment_fields)) . '
302                 </span>
303                 
304                 <span style="float:right">' . 
305                     $link->username($this->who, array('no_image' => true, 'fullname' => true))  . '
306                     -  <a name="'.$cid.'">'.$timestamp.'</a> 
307                 </span>
308                 
309             </div>
310             <div class="ticketchangeinfo" id="ticketchangeinfo-' . $this->cid . '">' . 
311                $link->username($this->who, array('no_name' => true, 'size' => 48));
312                    
313               
314               
315          
316         foreach ($this->effort as $eff) {
317             $exp = (float)$eff['expended'];
318             if ($eff['expended'] != 0) {
319                 $comment_body .= "<b>spent</b> $exp hours<br>\n";
320                 $preamble++;
321             }
322         }
323      
324  
325         if ($preamble) {
326             $html .= "<br>\n";
327             $preamble = 0;
328         }
329         
330         foreach ($comments as $cid => $text) {
331             // look for changesets in the comments..
332             // and display them as expandable linsk..
333             $html .= MTrack_Wiki::format_to_html($text);
334         }
335         
336         
337         $html .= '</div>';
338         
339         
340         
341         if ($commit_info) {
342             // list the files that where changed...
343             //$html.= print_r($M, true);
344             $rp = '/' . $commit_info[1] . '/' . $commit_info[2];
345             $fullrp = $rp;
346             $repo = MTrackSCM::factory($rp);
347             $cd = $repo->historyWithChangelog(null, 1, 'rev', $rp);
348             //print_r($cd);exit; 
349             
350             
351             if (!is_array($cd->ent->files) || !count($cd->ent->files)) {
352                 return $html;
353             }
354             
355             $num = count(array_keys($cd->ent->files));
356             $map = array(
357                 'D' => 'DELETED',
358                 'M' => 'MODIFIED',
359                 'A' => 'ADDED'
360             );
361             
362             $file_summary = array();
363             foreach($map as $k=>$v) {
364                 $summary[$v] = 0;
365             }
366             foreach ($cd->ent->files as $id => $file) {
367                
368                 
369                 $type = isset($map[$file->status])  ? $map[$file->status] : '??? '. $file->status;
370                 
371                 if ($num > 10) 
372                 {
373                     // will bork on unknown...
374                     $summary[$type]++;
375                     continue;
376                 }
377                 
378                 
379             $html .= '
380                 <div class="ticketevent ticketevent-fileitem">
381                     <span class="ticketevent-expand" id="ticketevent-expand-' . $this->cid . '.' . $id .'">+ ' . $type.'</span> 
382                     <span title="' . $fullrp . '?part=' . $id. '"  class="ticketevent-expand" id="ticketevent-expand-title-' . $this->cid . '.' . $id . '">' .
383                     $file->nameToHtml() . '
384                     </span>
385                     <span title="' . $fullrp . '?part=' . $id. '"  class="ticketevent-expand-diff changesetlink">[View Diff]</span> 
386                     <span title="' . $fullrp . '" class="ticketevent-expand-view changesetlink">[View File]</span>
387                     <span title="' . $fullrp . '" class="ticketevent-expand-view changesetlink">[View History]</span>
388                 </div>
389                 <div class="ticketchangeinfo" id="ticketchangeinfo-' . $this->cid . '.' . $id  . '"></div>
390         ';            
391                 
392                 
393                
394             }
395             
396             if ($num>  10) {
397                 $ar = array();
398                 foreach($summary as $k=>$v) {
399                     if (empty($v)) {
400                         continue;
401                     }
402                     $ar[] = $v . ' ' . $k;
403                 }
404                 $html .= '
405                 <div class="ticketevent ticketevent-fileitem">
406                     <span class="ticketevent-expand" id="ticketevent-expand-' . $this->cid . '.0">+ MULTIPLE FILES: </span>
407                     <span title="' . $fullrp . '?part=' . 0 . '"  class="ticketevent-expand" id="ticketevent-expand-title-' . $this->cid . '.' . $id . '">' .
408                     implode(', ', $ar) . '
409                     </span>
410                     <span title="' . $fullrp . '"  class="ticketevent-expand-diff changesetlink">[View Diff]</span> 
411                     <span title="' . $fullrp . '" class="ticketevent-expand-view changesetlink">[View File]</span>
412                     <span title="' . $fullrp . '" class="ticketevent-expand-view changesetlink">[View History]</span>
413                 </div>
414                 <div class="ticketchangeinfo" id="ticketchangeinfo-' . $this->cid . '"></div>
415         ';            
416                 
417             }
418             
419             //$html .= print_r($cd, true);
420              
421         }
422         
423          
424         return $html ;
425                 
426                 
427     }
428     
429     function collapse_diff($diff)
430     {
431       static $idnum = 1;
432       require_once 'MTrackWeb/Changeset.php';
433       $cs = new MTrackWeb_Changeset();
434       $id = 'diff_' . $idnum++;
435       return "<br>" .
436         "<button onclick='\$(&quot;#$id&quot;).toggle(); return false;'>Toggle diff</button>".
437         "<div id='$id' style='display:none'>" .
438             $cs->diff($diff) . "</div>";
439     }
440     
441     function diff_strings($before, $now)
442     {
443          
444         $tempdir = sys_get_temp_dir();
445         $afile = tempnam($tempdir, "mtrack");
446         $bfile = tempnam($tempdir, "mtrack");
447         file_put_contents($afile, $before);
448         file_put_contents($bfile, $now);
449         static $diff = false;
450         if (!$diff) {
451             require_once 'System.php';
452             $diff= System::which('diff');
453         }
454         if (PHP_OS == 'SunOS') {
455             // TODO: make an option to allow use of gnu diff on solaris
456            $diff = shell_exec("$diff -u $afile $bfile");
457            $diff = str_replace($afile, 'before', $diff);
458            $diff = str_replace($bfile, 'now', $diff);
459          } else {
460             $diff = shell_exec("$diff --label before --label now -u $afile $bfile");
461          }
462         unlink($afile);
463         unlink($bfile);
464         $diff = htmlentities($diff, ENT_COMPAT, 'utf-8');
465         return $diff;
466     }
467      
468     
469 }