1 | 1 | simandl | <?php |
2 | | | // WebSVN - Subversion repository viewing via the web using PHP |
3 | | | // Copyright (C) 2004-2006 Tim Armes |
4 | | | // |
5 | | | // This program is free software; you can redistribute it and/or modify |
6 | | | // it under the terms of the GNU General Public License as published by |
7 | | | // the Free Software Foundation; either version 2 of the License, or |
8 | | | // (at your option) any later version. |
9 | | | // |
10 | | | // This program is distributed in the hope that it will be useful, |
11 | | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | | // GNU General Public License for more details. |
14 | | | // |
15 | | | // You should have received a copy of the GNU General Public License |
16 | | | // along with this program; if not, write to the Free Software |
17 | | | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
18 | | | // |
19 | | | // -- |
20 | | | // |
21 | | | // listing.php |
22 | | | // |
23 | | | // Show the listing for the given repository/path/revision |
24 | | | |
25 | | | require_once("include/setup.php"); |
26 | | | require_once("include/svnlook.php"); |
27 | | | require_once("include/utils.php"); |
28 | | | require_once("include/template.php"); |
29 | | | require_once("include/bugtraq.php"); |
30 | | | |
31 | | | function removeURLSeparator($url) { |
32 | | | return preg_replace('#(\?|&(amp;)?)$#', '', $url); |
33 | | | } |
34 | | | |
35 | | | function fileLink($path, $file, $returnjoin = false) { |
36 | | | global $rep, $passrev, $config; |
37 | | | |
38 | | | if ($path == "" || $path{0} != "/") { |
39 | | | $ppath = "/".$path; |
40 | | | } else { |
41 | | | $ppath = $path; |
42 | | | } |
43 | | | |
44 | | | if ($ppath{strlen($ppath)-1} != "/") { |
45 | | | $ppath .= "/"; |
46 | | | } |
47 | | | |
48 | | | if ($file{0} == "/") { |
49 | | | $pfile = substr($file, 1); |
50 | | | } else { |
51 | | | $pfile = $file; |
52 | | | } |
53 | | | |
54 | | | if ($returnjoin) { |
55 | | | return $ppath.$pfile; |
56 | | | } |
57 | | | |
58 | | | $isDir = $pfile{strlen($pfile) - 1} == "/"; |
59 | | | |
60 | | | if ($passrev) $passrevstr = "rev=$passrev&"; else $passrevstr = ""; |
61 | | | |
62 | | | if ($isDir) { |
63 | | | $url = $config->getURL($rep, $ppath.$pfile, "dir"); |
64 | | | |
65 | | | // XHTML doesn't allow slashes in IDs and must begin with a letter |
66 | | | $id = str_replace('/', '_', 'path'.$ppath.$pfile); |
67 | | | $url = "<a id='$id' href=\"${url}$passrevstr"; |
68 | | | |
69 | | | $url = removeURLSeparator($url); |
70 | | | if ($config->treeView) $url .= "#$id"; |
71 | | | $url .= "\">$pfile</a>"; |
72 | | | |
73 | | | } else { |
74 | | | $url = $config->getURL($rep, $ppath.$pfile, "file"); |
75 | | | $url .= $passrevstr; |
76 | | | $url = removeURLSeparator($url); |
77 | | | $url = "<a href=\"${url}\">$pfile</a>"; |
78 | | | } |
79 | | | |
80 | | | return $url; |
81 | | | } |
82 | | | |
83 | | | function showDirFiles($svnrep, $subs, $level, $limit, $rev, $listing, $index, $treeview = true) { |
84 | | | global $rep, $passrev, $config, $lang; |
85 | | | |
86 | | | $path = ""; |
87 | | | |
88 | | | if (!$treeview) { |
89 | | | $level = $limit; |
90 | | | } |
91 | | | |
92 | | | for ($n = 0; $n <= $level; $n++) { |
93 | | | $path .= $subs[$n]."/"; |
94 | | | } |
95 | | | |
96 | | | $logList = $svnrep->getList($path, $rev); |
97 | | | |
98 | | | // List each file in the current directory |
99 | | | $loop = 0; |
100 | | | $last_index = 0; |
101 | | | $openDir = false; |
102 | | | $fullaccess = $rep->hasReadAccess($path, false); |
103 | | | |
104 | | | foreach ($logList->entries as $entry) { |
105 | 3 | simandl | $file = $entry->file; |
106 | 1 | simandl | $isDir = $entry->isdir; |
107 | | | $access = false; |
108 | | | |
109 | | | if ($isDir) { |
110 | | | if ($rep->hasReadAccess($path.$file, true)) { |
111 | | | $access = true; |
112 | | | $openDir = isset($subs[$level+1]) && (!strcmp($subs[$level+1]."/", $file) || !strcmp($subs[$level+1], $file)); |
113 | | | |
114 | | | if ($openDir) { |
115 | | | $listing[$index]["filetype"] = "diropen"; |
116 | | | } else { |
117 | | | $listing[$index]["filetype"] = "dir"; |
118 | | | } |
119 | | | |
120 | | | if ($rep->isDownloadAllowed($path.$file)) { |
121 | | | $dlurl = $config->getURL($rep, $path.$file, "dl"); |
122 | | | $listing[$index]["fileviewdllink"] = "<a href=\"${dlurl}rev=$passrev&isdir=1\">${lang["TARBALL"]}</a>"; |
123 | | | $listing[$index]['downloadurl'] = $dlurl.'rev='.$passrev.'&isdir=1'; |
124 | | | } else { |
125 | | | $listing[$index]['downloadurl'] = ''; |
126 | | | } |
127 | 3 | simandl | $listing[$index]['downloadplainurl'] = ''; |
128 | 1 | simandl | } |
129 | | | |
130 | | | } else { |
131 | | | if ($fullaccess) { |
132 | | | $access = true; |
133 | | | if ($level != $limit) { |
134 | | | // List directories only, skip all files |
135 | | | continue; |
136 | | | } |
137 | | | |
138 | | | $listing[$index]['downloadurl'] = ''; |
139 | 3 | simandl | if ($rep->isDownloadAllowed($path.$file)) { |
140 | | | $dlurl = $config->getURL($rep, $path.$file, "dl"); |
141 | | | $listing[$index]['downloadplainurl'] = $dlurl.'rev='.$passrev; |
142 | | | } else { |
143 | | | $listing[$index]['downloadplainurl'] = ''; |
144 | | | } |
145 | 1 | simandl | $listing[$index]["filetype"] = strtolower(strrchr($file, ".")); |
146 | | | } |
147 | | | } |
148 | | | |
149 | | | if ($access) { |
150 | | | $listing[$index]["rowparity"] = ($index % 2)?"1":"0"; |
151 | | | |
152 | | | if ($treeview) { |
153 | | | $listing[$index]["compare_box"] = "<input type=\"checkbox\" name=\"compare[]\" value=\"".fileLink($path, $file, true)."@$passrev\" onclick=\"checkCB(this)\" />"; |
154 | | | } else { |
155 | | | $listing[$index]["compare_box"] = ""; |
156 | | | } |
157 | | | |
158 | | | if ($openDir) { |
159 | | | $listing[$index]["filelink"] = "<b>".fileLink($path, $file)."</b>"; |
160 | | | } else { |
161 | | | $listing[$index]["filelink"] = fileLink($path, $file); |
162 | | | } |
163 | | | |
164 | | | // The history command doesn't return with a trailing slash. We need to remember here if the |
165 | | | // file is a directory or not! |
166 | | | |
167 | | | $listing[$index]["isDir"] = $isDir; |
168 | | | |
169 | | | if ($treeview) { |
170 | | | $listing[$index]["level"] = $level; |
171 | | | } else { |
172 | | | $listing[$index]["level"] = 0; |
173 | | | } |
174 | | | |
175 | | | $listing[$index]["node"] = 0; // t-node |
176 | | | |
177 | | | $fileurl = $config->getURL($rep, $path.$file, "log"); |
178 | | | $listing[$index]["fileviewloglink"] = "<a href=\"${fileurl}rev=$passrev&isdir=$isDir\">${lang["VIEWLOG"]}</a>"; |
179 | | | $listing[$index]['logurl'] = $fileurl.'rev='.$passrev.'&isdir='.$isDir; |
180 | | | |
181 | | | if ($rep->getHideRss()) { |
182 | | | $rssurl = $config->getURL($rep, $path.$file, 'rss'); |
183 | | | $listing[$index]['rssurl'] = $rssurl.'rev='.$passrev.'&isdir='.$isDir; |
184 | | | } |
185 | | | |
186 | | | if ($config->showLastMod) { |
187 | | | $listing[$index]['revision'] = $entry->rev; |
188 | | | $listing[$index]['author'] = $entry->author; |
189 | | | $listing[$index]['date'] = $entry->date; |
190 | | | $listing[$index]['committime'] = $entry->committime; |
191 | | | $listing[$index]['age'] = $entry->age; |
192 | 3 | simandl | $listing[$index]['revurl'] = $config->getURL($rep, $path.$file, 'revision').'rev='.$entry->rev.'&'; |
193 | 1 | simandl | } |
194 | | | |
195 | | | $index++; |
196 | | | $loop++; |
197 | | | $last_index = $index; |
198 | | | |
199 | | | if (($level != $limit) && ($isDir)) { |
200 | | | if (isset($subs[$level + 1]) && !strcmp(htmlentities($subs[$level + 1],ENT_QUOTES).'/', htmlentities($file))) { |
201 | | | $listing = showDirFiles($svnrep, $subs, $level + 1, $limit, $rev, $listing, $index); |
202 | | | $index = count($listing); |
203 | | | } |
204 | | | } |
205 | | | } |
206 | | | } |
207 | | | |
208 | | | if ($last_index != 0 && $treeview) { |
209 | | | $listing[$last_index - 1]["node"] = 1; // l-node |
210 | | | } |
211 | | | |
212 | | | return $listing; |
213 | | | } |
214 | | | |
215 | | | function showTreeDir($svnrep, $path, $rev, $listing) { |
216 | | | global $vars, $config; |
217 | | | |
218 | | | $subs = explode("/", $path); |
219 | | | |
220 | | | // For directory, the last element in the subs is empty. |
221 | | | // For file, the last element in the subs is the file name. |
222 | | | // Therefore, it is always count($subs) - 2 |
223 | | | $limit = count($subs) - 2; |
224 | | | |
225 | | | for ($n = 0; $n < $limit; $n++) { |
226 | | | $vars["last_i_node"][$n] = FALSE; |
227 | | | } |
228 | | | |
229 | | | return showDirFiles($svnrep, $subs, 0, $limit, $rev, $listing, 0, $config->treeView); |
230 | | | } |
231 | | | |
232 | | | // Make sure that we have a repository |
233 | | | if (!isset($rep)) { |
234 | | | echo $lang["NOREP"]; |
235 | | | exit; |
236 | | | } |
237 | | | |
238 | | | $svnrep = new SVNRepository($rep); |
239 | | | |
240 | | | // Revision info to pass along chain |
241 | | | $passrev = $rev; |
242 | | | |
243 | | | // If there's no revision info, go to the lastest revision for this path |
244 | | | $history = $svnrep->getLog($path, "", "", false); |
245 | 3 | simandl | if (is_string($history)) { |
246 | | | echo $history; |
247 | | | exit; |
248 | | | } |
249 | 1 | simandl | |
250 | | | if (!empty($history->entries[0])) { |
251 | | | $youngest = $history->entries[0]->rev; |
252 | | | } else { |
253 | | | $youngest = -1; |
254 | | | } |
255 | | | |
256 | | | // Unless otherwise specified, we get the log details of the latest change |
257 | | | if (empty($rev)) { |
258 | | | $logrev = $youngest; |
259 | | | } else { |
260 | | | $logrev = $rev; |
261 | | | } |
262 | | | |
263 | | | if ($logrev != $youngest) { |
264 | | | $logEntry = $svnrep->getLog($path, $logrev, $logrev, false); |
265 | 3 | simandl | if (is_string($logEntry)) { |
266 | | | echo $logEntry; |
267 | | | exit; |
268 | | | } |
269 | 1 | simandl | $logEntry = isset($logEntry->entries[0]) ? $logEntry->entries[0] : false; |
270 | | | } else { |
271 | | | $logEntry = isset($history->entries[0]) ? $history->entries[0] : false; |
272 | | | } |
273 | | | |
274 | | | $headlog = $svnrep->getLog("/", "", "", true, 1); |
275 | 3 | simandl | if (is_string($headlog)) { |
276 | | | echo $headlog; |
277 | | | exit; |
278 | | | } |
279 | 1 | simandl | $headrev = isset($headlog->entries[0]) ? $headlog->entries[0]->rev : 0; |
280 | | | |
281 | | | // If we're not looking at a specific revision, get the HEAD revision number |
282 | | | // (the revision of the rest of the tree display) |
283 | | | |
284 | | | if (empty($rev)) { |
285 | | | $rev = $headrev; |
286 | | | } |
287 | | | |
288 | | | if ($path == "" || $path{0} != "/") { |
289 | | | $ppath = "/".$path; |
290 | | | } else { |
291 | | | $ppath = $path; |
292 | | | } |
293 | | | |
294 | | | $vars["repname"] = $rep->getDisplayName(); |
295 | | | |
296 | | | $compurl = $config->getURL($rep, "/", "comp"); |
297 | | | $revisionurl = $config->getURL($rep, $path, 'revision'); |
298 | | | |
299 | | | if ($passrev != 0 && $passrev != $headrev && $youngest != -1) { |
300 | | | $vars['goyoungesturl'] = $config->getURL($rep, $path, 'dir').'opt=dir'; |
301 | | | } else { |
302 | | | $vars['goyoungesturl'] = ''; |
303 | | | } |
304 | | | |
305 | | | $bugtraq = new Bugtraq($rep, $svnrep, $ppath); |
306 | | | |
307 | | | $vars["action"] = ""; |
308 | | | $vars["rev"] = $rev; |
309 | | | $vars["path"] = htmlentities($ppath, ENT_QUOTES, 'UTF-8'); |
310 | | | $vars["lastchangedrev"] = $logrev; |
311 | | | $vars["date"] = $logEntry ? $logEntry->date : ''; |
312 | | | $vars["author"] = $logEntry ? $logEntry->author : ''; |
313 | | | $vars["log"] = $logEntry ? nl2br($bugtraq->replaceIDs(create_anchors($logEntry->msg))) : ''; |
314 | | | $vars["changesurl"] = $revisionurl.'rev='.$passrev; |
315 | | | |
316 | | | createDirLinks($rep, $ppath, $passrev); |
317 | | | |
318 | | | $logurl = $config->getURL($rep, $path, "log"); |
319 | | | $vars['logurl'] = $logurl.'rev='.$passrev.'&isdir=1'; |
320 | | | |
321 | | | $vars['indexurl'] = $config->getURL($rep, '', 'index'); |
322 | | | $vars['repurl'] = $config->getURL($rep, '', 'dir'); |
323 | | | |
324 | | | if ($rep->getHideRss()) { |
325 | | | $rssurl = $config->getURL($rep, $path, "rss"); |
326 | | | // $vars["curdirrsslink"] = "<a href=\"${rssurl}isdir=1\">${lang["RSSFEED"]}</a>"; |
327 | | | $vars['rssurl'] = $rssurl.'isdir=1'; |
328 | | | // $vars["curdirrssanchor"] = "<a href=\"${rssurl}isdir=1\">"; |
329 | | | } |
330 | | | |
331 | | | // Set up the tarball link |
332 | | | |
333 | | | $subs = explode("/", $path); |
334 | | | $level = count($subs) - 2; |
335 | | | if ($rep->isDownloadAllowed($path)) { |
336 | | | $dlurl = $config->getURL($rep, $path, "dl"); |
337 | | | $vars["curdirdllink"] = "<a href=\"${dlurl}rev=$passrev&isdir=1\">${lang["TARBALL"]}</a>"; |
338 | | | $vars['downloadurl'] = $dlurl.'rev='.$passrev.'&isdir=1'; |
339 | | | } else { |
340 | | | $vars["curdirdllink"] = ''; |
341 | | | $vars['downloadurl'] = ''; |
342 | | | } |
343 | | | |
344 | | | $url = $config->getURL($rep, "/", "comp"); |
345 | | | |
346 | | | $vars["compare_form"] = "<form action=\"$url\" method=\"post\">"; |
347 | | | $vars["compare_submit"] = "<input name=\"comparesubmit\" type=\"submit\" value=\"${lang["COMPAREPATHS"]}\" />"; |
348 | | | $vars["compare_hidden"] = "<input type=\"hidden\" name=\"op\" value=\"comp\" />"; |
349 | | | $vars["compare_endform"] = "</form>"; |
350 | | | |
351 | | | $vars['showlastmod'] = $config->showLastMod; |
352 | 3 | simandl | $vars['showageinsteadofdate'] = $config->showAgeInsteadOfDate; |
353 | 1 | simandl | |
354 | | | $listing = array(); |
355 | | | $listing = showTreeDir($svnrep, $path, $rev, $listing); |
356 | | | |
357 | | | $vars["version"] = $version; |
358 | | | |
359 | | | if (!$rep->hasReadAccess($path, true)) { |
360 | | | $vars["noaccess"] = true; |
361 | | | } |
362 | | | if (!$rep->hasReadAccess($path, false)) { |
363 | | | $vars["restricted"] = true; |
364 | | | } |
365 | | | |
366 | | | parseTemplate($rep->getTemplatePath()."header.tmpl", $vars, $listing); |
367 | | | parseTemplate($rep->getTemplatePath()."directory.tmpl", $vars, $listing); |
368 | | | parseTemplate($rep->getTemplatePath()."footer.tmpl", $vars, $listing); |