import
[web.mtrack] / inc / timeline.php
1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
3
4 function mtrack_timeline_order_events_newest_first($a, $b)
5 {
6   return strcmp($b['changedate'], $a['changedate']);
7 }
8
9 function mtrack_get_timeline($start_time = '-2 weeks',
10   $only_users = null, $limit = 50)
11 {
12   if (is_string($start_time)) {
13     $date_limit = strtotime($start_time);
14   } else {
15     $date_limit = $start_time; // assume that it's a timestamp
16   }
17   /* round back to earlier minute (aids caching) */
18   $date_limit -= $date_limit % 60;
19   $db_date_limit = MTrackDB::unixtime($date_limit);
20   $last_date = null;
21
22   $filter_users = null;
23   if (is_string($only_users)) {
24     $filter_users = array(mtrack_canon_username($only_users));
25   } else if (is_array($only_users)) {
26     $filter_users = array();
27     foreach ($only_users as $user) {
28       $filter_users[] = mtrack_canon_username($user);
29     }
30   }
31
32   $proj_by_id = array();
33   foreach (MTrackDB::q('select projid from projects')->fetchAll() as $r) {
34     $proj_by_id[$r[0]] = MTrackProject::loadById($r[0]);
35   }
36   $events = array();
37
38   foreach (MTrackDB::q('select repoid from repos')->fetchAll() as $row) {
39     list($repoid) = $row;
40     $repo = MTrackRepo::loadById($repoid);
41     $reponame = $repo->getBrowseRootName();
42     if ($reponame == 'default/wiki') continue;
43     $checker = new MTrackCommitChecker($repo);
44
45     $hist = $repo->history(null, $db_date_limit);
46     if (is_array($hist)) foreach ($hist as $e) {
47       if (is_array($filter_users)) {
48         $wanted_user = false;
49         foreach ($filter_users as $fuser) {
50           if (mtrack_canon_username($e->changeby) === $fuser) {
51             $wanted_user = true;
52             break;
53           }
54         }
55         if (!$wanted_user) {
56           continue;
57         }
58       }
59       /* we want to include changesets that do not reference tickets */
60       $pid = $repo->projectFromPath($e->files);
61       if ($pid > 1) {
62         $proj = $proj_by_id[$pid];
63         $e->changelog = $proj->adjust_links($e->changelog, true);
64       }
65       $actions = $checker->parseCommitMessage($e->changelog);
66       $tickets = array();
67       foreach ($actions as $act) {
68         $tkt = $act[1];
69         $tickets[$tkt] = $tkt;
70         $repo_changes_by_ticket[$tkt][$reponame][$e->rev] = $e->rev;
71       }
72       if (count($tickets) == 0) {
73         $events[] = array(
74             'changedate' => $e->ctime,
75             'who' => $e->changeby,
76             'object' => "changeset:$reponame:$e->rev",
77             'reason' => $e->changelog,
78             );
79       }
80     }
81   }
82   foreach (MTrackDB::q("select 
83       changedate, who, object, reason from changes
84       where changedate > ?
85       order by changedate desc
86       ", $db_date_limit)->fetchAll(PDO::FETCH_ASSOC) as $row) {
87     if (is_array($filter_users)) {
88       $wanted_user = false;
89       foreach ($filter_users as $fuser) {
90         if (mtrack_canon_username($row['who']) === $fuser) {
91           $wanted_user = true;
92           break;
93         }
94       }
95       if (!$wanted_user) {
96         continue;
97       }
98     }
99     $events[] = $row;
100   }
101
102   usort($events, 'mtrack_timeline_order_events_newest_first');
103   return $events;
104 }
105
106 function _mtrack_timeline_is_repo_visible($reponame)
107 {
108   static $cache = array();
109   $me = MTrackAuth::whoami();
110   if (isset($cache[$me][$reponame])) {
111     return $cache[$me][$reponame];
112   }
113
114   if (ctype_digit($reponame)) {
115     $oid = "repo:$reponame";
116   } else {
117     $repo = MTrackRepo::loadByName($reponame);
118     if ($repo) {
119       $oid = "repo:$repo->repoid";
120     } else {
121       $oid = null;
122     }
123   }
124   if ($oid) {
125     $ok = MTrackACL::hasAnyRights($oid, array(
126     'read', 'checkout'));
127   } else {
128     $ok = false;
129   }
130   $cache[$me][$reponame] = $ok;
131   return $ok;
132 }
133
134 function mtrack_render_timeline($user = null)
135 {
136   global $ABSWEB;
137
138   $limit = 50;
139   $events = mtrack_cache('mtrack_get_timeline',
140     array('-2 weeks', $user, $limit), 300, array('Timeline', $user));
141
142   echo "<div class='timeline'>";
143   $last_date = null;
144   foreach ($events as $row) {
145     if (--$limit == 0) {
146       break;
147     }
148
149     $d = date_create($row['changedate'], new DateTimeZone('UTC'));
150     date_timezone_set($d, new DateTimeZone(date_default_timezone_get()));
151     $time = $d->format('H:i');
152     $day = $d->format('D, M d Y');
153
154     if ($last_date != $day) {
155       echo "<h1 class='timelineday'>$day</h1>\n";
156       $last_date = $day;
157     }
158
159     // figure out an event type based on the object and the reason
160     if (strpos($row['object'], ':') !== false) {
161       list($object, $id) = explode(':', $row['object'], 3);
162     } else {
163       $id = 0;
164       $object = $row['object'];
165     }
166     $eventclass = ''; 
167     $item = $row['object'];
168     switch ($object) {
169       case 'ticket':
170         if (!strncmp($row['reason'], 'created ', 8)) {
171           $eventclass = ' newticket';
172         } elseif (!strncmp($row['reason'], 'closed ', 7)) {
173           $eventclass = ' closedticket';
174         } else {
175           $eventclass = ' editticket';
176         }
177         $item = "Ticket " . mtrack_ticket($id);
178         break;
179       case 'wiki':
180         $eventclass = ' editwiki';
181         $item = "Wiki " . mtrack_wiki($id);
182         break;
183       case 'milestone':
184         $eventclass = ' editmilestone';
185         $item = "Milestone <span class='milestone'><a href='{$ABSWEB}milestone.php/$id'>$id</a></span>";
186         break;
187       case 'changeset':
188         $eventclass = ' newchangeset';
189         preg_match("/^changeset:(.*):([^:]+)$/", $row['object'], $M);
190         $repo = $M[1];
191         if (!_mtrack_timeline_is_repo_visible($repo)) {
192           continue 2;
193         }
194         $id = $M[2];
195         $item = "<a href='{$ABSWEB}browse.php/$repo'>$repo</a> change " . mtrack_changeset($id, $repo);
196         break;
197       case 'snippet':
198         $item = "<a href='{$ABSWEB}snippet.php/$id'>View Snippet</a>";
199         break;
200       case 'repo':
201         static $repos = null;
202         if ($repos === null) {
203           $repos = array();
204           foreach (MTrackDB::q(
205               'select repoid, shortname, parent from repos')->fetchAll()
206               as $r) {
207             $repos[$r[0]] = $r;
208           }
209         }
210         if (!_mtrack_timeline_is_repo_visible($id)) {
211           continue 2;
212         }
213         if (isset($repos[$id])) {
214           $name = MTrackRepo::makeDisplayName($repos[$id]);
215           $item = "<a href='{$ABSWEB}browse.php/$name'>$name</a>";
216         } else {
217           $item = "&lt;item has been deleted&gt;";
218         }
219         break;
220     }
221
222     $reason = MTrackWiki::format_to_oneliner($row['reason']);
223
224     echo "<div class='timelineevent'>",
225       mtrack_username($row['who'], array(
226         'no_name' => true,
227         'size' => 48,
228         'class' => 'timelineface'
229         )),
230       "<div class='timelinetext'>",
231       "<div class='timelinereason'>",
232       "$reason</div>\n",
233       "<span class='time'>$time</span> $item by ",
234       mtrack_username($row['who'], array('no_image' => true)),
235       "</div>\n";
236     echo "</div>\n";
237   }
238   echo "</div>\n";
239 }
240