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