Rev 172 Rev 185
Line 1... Line 1...
1 <?php 1 <?php
2 # vim:et:ts=3:sts=3:sw=3:fdm=marker: 2 # vim:et:ts=3:sts=3:sw=3:fdm=marker:
3   3  
4 // WebSVN - Subversion repository viewing via the web using PHP 4 // WebSVN - Subversion repository viewing via the web using PHP
5 // Copyright © 2004-2006 Tim Armes, Matt Sicker 5 // Copyright © 2004-2006 Tim Armes, Matt Sicker
6 // 6 //
7 // This program is free software; you can redistribute it and/or modify 7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by 8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or 9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version. 10 // (at your option) any later version.
11 // 11 //
12 // This program is distributed in the hope that it will be useful, 12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details. 15 // GNU General Public License for more details.
16 // 16 //
17 // You should have received a copy of the GNU General Public License 17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software 18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 // 20 //
21 // -- 21 // --
22 // 22 //
23 // svn-look.inc 23 // svn-look.inc
24 // 24 //
25 // Svn bindings 25 // Svn bindings
26 // 26 //
27 // These binding currently use the svn command line to achieve their goal. Once a proper 27 // These binding currently use the svn command line to achieve their goal. Once a proper
28 // SWIG binding has been produced for PHP, there'll be an option to use that instead. 28 // SWIG binding has been produced for PHP, there'll be an option to use that instead.
29   29  
30 require_once("include/utils.inc"); 30 require_once("include/utils.inc");
31   31  
32 // {{{ Classes for retaining log information --- 32 // {{{ Classes for retaining log information ---
33   33  
34 $debugxml = false; 34 $debugxml = false;
35   35  
36 Class SVNMod 36 Class SVNMod
37 { 37 {
38 var $action = ''; 38 var $action = '';
39 var $copyfrom = ''; 39 var $copyfrom = '';
40 var $copyrev = ''; 40 var $copyrev = '';
41 var $path = ''; 41 var $path = '';
42 } 42 }
43   43  
44 Class SVNLogEntry 44 Class SVNLogEntry
45 { 45 {
46 var $rev = 1; 46 var $rev = 1;
47 var $author = ''; 47 var $author = '';
48 var $date = ''; 48 var $date = '';
49 var $committime; 49 var $committime;
50 var $age = ''; 50 var $age = '';
51 var $msg = ''; 51 var $msg = '';
52 var $path = ''; 52 var $path = '';
53 53
54 var $mods; 54 var $mods;
55 var $curMod; 55 var $curMod;
56 } 56 }
57   57  
58 Class SVNLog 58 Class SVNLog
59 { 59 {
60 var $entries; // Array of entries 60 var $entries; // Array of entries
61 var $curEntry; // Current entry 61 var $curEntry; // Current entry
62 62
63 var $path = ''; // Temporary variable used to trace path history 63 var $path = ''; // Temporary variable used to trace path history
64 64
65 // findEntry 65 // findEntry
66 // 66 //
67 // Return the entry for a given revision 67 // Return the entry for a given revision
68 68
69 function findEntry($rev) 69 function findEntry($rev)
70 { 70 {
71 foreach ($this->entries as $index => $entry) 71 foreach ($this->entries as $index => $entry)
72 { 72 {
73 if ($entry->rev == $rev) 73 if ($entry->rev == $rev)
74 return $index; 74 return $index;
75   75  
76 } 76 }
77 } 77 }
78 } 78 }
79   79  
80 // }}} 80 // }}}
81   81  
82 // {{{ XML parsing functions--- 82 // {{{ XML parsing functions---
83   83  
84 $curLog = 0; 84 $curLog = 0;
85 $curTag = ''; 85 $curTag = '';
86   86  
87 // {{{ startElement 87 // {{{ startElement
88   88  
89 function startElement($parser, $name, $attrs) 89 function startElement($parser, $name, $attrs)
90 { 90 {
91 global $curLog, $curTag, $debugxml; 91 global $curLog, $curTag, $debugxml;
92   92  
93 switch ($name) 93 switch ($name)
94 { 94 {
95 case "LOGENTRY": 95 case "LOGENTRY":
96 if ($debugxml) print "Creating new log entry\n"; 96 if ($debugxml) print "Creating new log entry\n";
97 $curLog->curEntry = new SVNLogEntry; 97 $curLog->curEntry = new SVNLogEntry;
98 $curLog->curEntry->mods = array(); 98 $curLog->curEntry->mods = array();
99 99
100 $curLog->curEntry->path = $curLog->path; 100 $curLog->curEntry->path = $curLog->path;
101 101
102 if (sizeof($attrs)) 102 if (sizeof($attrs))
103 { 103 {
104 while (list($k, $v) = each($attrs)) 104 while (list($k, $v) = each($attrs))
105 { 105 {
106 switch ($k) 106 switch ($k)
107 { 107 {
108 case "REVISION": 108 case "REVISION":
109 if ($debugxml) print "Revision $v\n"; 109 if ($debugxml) print "Revision $v\n";
110 $curLog->curEntry->rev = $v; 110 $curLog->curEntry->rev = $v;
111 break; 111 break;
112 } 112 }
113 } 113 }
114 } 114 }
115 break; 115 break;
116 116
117 case "PATH": 117 case "PATH":
118 if ($debugxml) print "Creating new path\n"; 118 if ($debugxml) print "Creating new path\n";
119 $curLog->curEntry->curMod = new SVNMod; 119 $curLog->curEntry->curMod = new SVNMod;
120 120
121 if (sizeof($attrs)) 121 if (sizeof($attrs))
122 { 122 {
123 while (list($k, $v) = each($attrs)) 123 while (list($k, $v) = each($attrs))
124 { 124 {
125 switch ($k) 125 switch ($k)
126 { 126 {
127 case "ACTION": 127 case "ACTION":
128 if ($debugxml) print "Action $v\n"; 128 if ($debugxml) print "Action $v\n";
129 $curLog->curEntry->curMod->action = $v; 129 $curLog->curEntry->curMod->action = $v;
130 break; 130 break;
131   131  
132 case "COPYFROM-PATH": 132 case "COPYFROM-PATH":
133 if ($debugxml) print "Copy from: $v\n"; 133 if ($debugxml) print "Copy from: $v\n";
134 $curLog->curEntry->curMod->copyfrom = $v; 134 $curLog->curEntry->curMod->copyfrom = $v;
135 break; 135 break;
136   136  
137 case "COPYFROM-REV": 137 case "COPYFROM-REV":
138 $curLog->curEntry->curMod->copyrev = $v; 138 $curLog->curEntry->curMod->copyrev = $v;
139 break; 139 break;
140 } 140 }
141 } 141 }
142 } 142 }
143 143
144 $curTag = $name; 144 $curTag = $name;
145 break; 145 break;
146 146
147 default: 147 default:
148 $curTag = $name; 148 $curTag = $name;
149 break; 149 break;
150 } 150 }
151 } 151 }
152   152  
153 // }}} 153 // }}}
154   154  
155 // {{{ endElement 155 // {{{ endElement
156   156  
157 function endElement($parser, $name) 157 function endElement($parser, $name)
158 { 158 {
159 global $curLog, $debugxml, $curTag; 159 global $curLog, $debugxml, $curTag;
160   160  
161 switch ($name) 161 switch ($name)
162 { 162 {
163 case "LOGENTRY": 163 case "LOGENTRY":
164 if ($debugxml) print "Ending new log entry\n"; 164 if ($debugxml) print "Ending new log entry\n";
165 $curLog->entries[] = $curLog->curEntry; 165 $curLog->entries[] = $curLog->curEntry;
166 break; 166 break;
167   167  
168 case "PATH": 168 case "PATH":
169 if ($debugxml) print "Ending path\n"; 169 if ($debugxml) print "Ending path\n";
170 $curLog->curEntry->mods[] = $curLog->curEntry->curMod; 170 $curLog->curEntry->mods[] = $curLog->curEntry->curMod;
171 break; 171 break;
172   172  
173 case "MSG": 173 case "MSG":
174 $curLog->curEntry->msg = trim($curLog->curEntry->msg); 174 $curLog->curEntry->msg = trim($curLog->curEntry->msg);
175 if ($debugxml) print "Completed msg = '".$curLog->curEntry->msg."'\n"; 175 if ($debugxml) print "Completed msg = '".$curLog->curEntry->msg."'\n";
176 break; 176 break;
177 } 177 }
178 178
179 $curTag = ""; 179 $curTag = "";
180 } 180 }
181   181  
182 // }}} 182 // }}}
183   183  
184 // {{{ characterData 184 // {{{ characterData
185   185  
186 function characterData($parser, $data) 186 function characterData($parser, $data)
187 { 187 {
188 global $curLog, $curTag, $lang, $debugxml; 188 global $curLog, $curTag, $lang, $debugxml;
189   189  
190 switch ($curTag) 190 switch ($curTag)
191 { 191 {
192 case "AUTHOR": 192 case "AUTHOR":
193 if ($debugxml) print "Author: $data\n"; 193 if ($debugxml) print "Author: $data\n";
194 if (empty($data)) return; 194 if (empty($data)) return;
195 $curLog->curEntry->author .= htmlentities($data, ENT_COMPAT, "UTF-8"); 195 $curLog->curEntry->author .= htmlentities($data, ENT_COMPAT, "UTF-8");
196 break; 196 break;
197   197  
198 case "DATE": 198 case "DATE":
199 if ($debugxml) print "Date: $data\n"; 199 if ($debugxml) print "Date: $data\n";
200 $data = trim($data); 200 $data = trim($data);
201 if (empty($data)) return; 201 if (empty($data)) return;
202 202
203 sscanf($data, "%d-%d-%dT%d:%d:%d.", $y, $mo, $d, $h, $m, $s); 203 sscanf($data, "%d-%d-%dT%d:%d:%d.", $y, $mo, $d, $h, $m, $s);
204 204
205 $mo = substr("00".$mo, -2); 205 $mo = substr("00".$mo, -2);
206 $d = substr("00".$d, -2); 206 $d = substr("00".$d, -2);
207 $h = substr("00".$h, -2); 207 $h = substr("00".$h, -2);
208 $m = substr("00".$m, -2); 208 $m = substr("00".$m, -2);
209 $s = substr("00".$s, -2); 209 $s = substr("00".$s, -2);
210 210
211 $curLog->curEntry->date = "$y-$mo-$d $h:$m:$s GMT"; 211 $curLog->curEntry->date = "$y-$mo-$d $h:$m:$s GMT";
212 212
213 $committime = strtotime($curLog->curEntry->date); 213 $committime = strtotime($curLog->curEntry->date);
214 $curLog->curEntry->committime = $committime; 214 $curLog->curEntry->committime = $committime;
215 $curtime = time(); 215 $curtime = time();
216 216
217 // Get the number of seconds since the commit 217 // Get the number of seconds since the commit
218 $agesecs = $curtime - $committime; 218 $agesecs = $curtime - $committime;
219 if ($agesecs < 0) $agesecs = 0; 219 if ($agesecs < 0) $agesecs = 0;
220 220
221 $curLog->curEntry->age = datetimeFormatDuration($agesecs, true, true); 221 $curLog->curEntry->age = datetimeFormatDuration($agesecs, true, true);
222 222
223 break; 223 break;
224   224  
225 case "MSG": 225 case "MSG":
226 if ($debugxml) print "Msg: '$data'\n"; 226 if ($debugxml) print "Msg: '$data'\n";
227 $curLog->curEntry->msg .= htmlentities($data, ENT_COMPAT, "UTF-8"); 227 $curLog->curEntry->msg .= htmlentities($data, ENT_COMPAT, "UTF-8");
228 break; 228 break;
229 229
230 case "PATH": 230 case "PATH":
231 if ($debugxml) print "Path name: '$data'\n"; 231 if ($debugxml) print "Path name: '$data'\n";
232 $data = trim($data); 232 $data = trim($data);
233 if (empty($data)) return; 233 if (empty($data)) return;
234   234  
235 $curLog->curEntry->curMod->path .= $data; 235 $curLog->curEntry->curMod->path .= $data;
236 236
237 // The XML returned when a file is renamed/branched in inconsistant. In the case 237 // The XML returned when a file is renamed/branched in inconsistant. In the case
238 // of a branch, the path information doesn't include the leafname. In the case of 238 // of a branch, the path information doesn't include the leafname. In the case of
239 // a rename, it does. Ludicrous. 239 // a rename, it does. Ludicrous.
240 240
241 if (!empty($curLog->path)) 241 if (!empty($curLog->path))
242 { 242 {
243 $pos = strrpos($curLog->path, "/"); 243 $pos = strrpos($curLog->path, "/");
244 $curpath = substr($curLog->path, 0, $pos); 244 $curpath = substr($curLog->path, 0, $pos);
245 $leafname = substr($curLog->path, $pos + 1); 245 $leafname = substr($curLog->path, $pos + 1);
246 } 246 }
247 else 247 else
248 { 248 {
249 $curpath = ""; 249 $curpath = "";
250 $leafname = ""; 250 $leafname = "";
251 } 251 }
252 252
253 if ($curLog->curEntry->curMod->action == "A") 253 if ($curLog->curEntry->curMod->action == "A")
254 { 254 {
255 if ($debugxml) print "Examining added path '".$curLog->curEntry->curMod->copyfrom."' - Current path = '$curpath', leafname = '$leafname'\n"; 255 if ($debugxml) print "Examining added path '".$curLog->curEntry->curMod->copyfrom."' - Current path = '$curpath', leafname = '$leafname'\n";
256 if ($data == $curLog->path) // For directories and renames 256 if ($data == $curLog->path) // For directories and renames
257 { 257 {
258 if ($debugxml) print "New path for comparison: '".$curLog->curEntry->curMod->copyfrom."'\n"; 258 if ($debugxml) print "New path for comparison: '".$curLog->curEntry->curMod->copyfrom."'\n";
259 $curLog->path = $curLog->curEntry->curMod->copyfrom; 259 $curLog->path = $curLog->curEntry->curMod->copyfrom;
260 } 260 }
261 else if ($data == $curpath || $data == $curpath."/") // Logs of files that have moved due to branching 261 else if ($data == $curpath || $data == $curpath."/") // Logs of files that have moved due to branching
262 { 262 {
263 if ($debugxml) print "New path for comparison: '".$curLog->curEntry->curMod->copyfrom."/$leafname'\n"; 263 if ($debugxml) print "New path for comparison: '".$curLog->curEntry->curMod->copyfrom."/$leafname'\n";
264 $curLog->path = $curLog->curEntry->curMod->copyfrom."/$leafname"; 264 $curLog->path = $curLog->curEntry->curMod->copyfrom."/$leafname";
265 } 265 }
266 } 266 }
267 break; 267 break;
268 } 268 }
269 } 269 }
270   270  
271 // }}} 271 // }}}
272   272  
273 // }}} 273 // }}}
274   274  
275 // Function returns true if the give entry in a directory tree is at the top level 275 // Function returns true if the give entry in a directory tree is at the top level
276   276  
277 function _topLevel($entry) 277 function _topLevel($entry)
278 { 278 {
279 // To be at top level, there must be one space before the entry 279 // To be at top level, there must be one space before the entry
280 return (strlen($entry) > 1 && $entry{0} == " " && $entry{1} != " "); 280 return (strlen($entry) > 1 && $entry{0} == " " && $entry{1} != " ");
281 } 281 }
282   282  
283 // Function to sort two given directory entries. Directories go at the top 283 // Function to sort two given directory entries. Directories go at the top
284   284  
285 function _dirSort($e1, $e2) 285 function _dirSort($e1, $e2)
286 { 286 {
287 $isDir1 = $e1{strlen($e1) - 1} == "/"; 287 $isDir1 = $e1{strlen($e1) - 1} == "/";
288 $isDir2 = $e2{strlen($e2) - 1} == "/"; 288 $isDir2 = $e2{strlen($e2) - 1} == "/";
289 289
290 if ($isDir1 && !$isDir2) return -1; 290 if ($isDir1 && !$isDir2) return -1;
291 if ($isDir2 && !$isDir1) return 1; 291 if ($isDir2 && !$isDir1) return 1;
292 292
293 return strnatcasecmp($e1, $e2); 293 return strnatcasecmp($e1, $e2);
294 } 294 }
295   295  
296 // Return the revision string to pass to a command 296 // Return the revision string to pass to a command
297   297  
298 function _revStr($rev) 298 function _revStr($rev)
299 { 299 {
300 if ($rev > 0) 300 if ($rev > 0)
301 return "-r $rev"; 301 return "-r $rev";
302 else 302 else
303 return ""; 303 return "";
304 } 304 }
305   305  
306 // {{{ encodePath 306 // {{{ encodePath
307   307  
308 // Function to encode a URL without encoding the /'s 308 // Function to encode a URL without encoding the /'s
309   309  
310 function encodePath($uri) 310 function encodePath($uri)
311 { 311 {
312 global $config; 312 global $config;
313   313  
314 $uri = str_replace(DIRECTORY_SEPARATOR, "/", $uri); 314 $uri = str_replace(DIRECTORY_SEPARATOR, "/", $uri);
315   315  
316 $parts = explode('/', $uri); 316 $parts = explode('/', $uri);
317 for ($i = 0; $i < count($parts); $i++) 317 for ($i = 0; $i < count($parts); $i++)
318 { 318 {
319 if ( function_exists("mb_detect_encoding") && function_exists("mb_convert_encoding")) 319 if ( function_exists("mb_detect_encoding") && function_exists("mb_convert_encoding"))
320 { 320 {
321 $parts[$i] = mb_convert_encoding($parts[$i], "UTF-8", mb_detect_encoding($parts[$i])); 321 $parts[$i] = mb_convert_encoding($parts[$i], "UTF-8", mb_detect_encoding($parts[$i]));
322 } 322 }
323 323
324 $parts[$i] = rawurlencode($parts[$i]); 324 $parts[$i] = rawurlencode($parts[$i]);
325 } 325 }
326 326
327 $uri = implode('/', $parts); 327 $uri = implode('/', $parts);
328 328
329 // Quick hack. Subversion seems to have a bug surrounding the use of %3A instead of : 329 // Quick hack. Subversion seems to have a bug surrounding the use of %3A instead of :
330 330
331 $uri = str_replace("%3A" ,":", $uri); 331 $uri = str_replace("%3A" ,":", $uri);
332 332
333 // Correct for Window share names 333 // Correct for Window share names
334 if ( $config->serverIsWindows==true ) 334 if ( $config->serverIsWindows==true )
335 { 335 {
336 if ( substr($uri, 0,2)=="//" ) 336 if ( substr($uri, 0,2)=="//" )
337 $uri="\\".substr($uri, 2, strlen($uri)); 337 $uri="\\".substr($uri, 2, strlen($uri));
338 } 338 }
339   339  
340 return $uri; 340 return $uri;
341 } 341 }
342   342  
343 // }}} 343 // }}}
344   344  
345 // The SVNRepository Class 345 // The SVNRepository Class
346   346  
347 Class SVNRepository 347 Class SVNRepository
348 { 348 {
349 var $repConfig; 349 var $repConfig;
350 350
351 function SVNRepository($repConfig) 351 function SVNRepository($repConfig)
352 { 352 {
353 $this->repConfig = $repConfig; 353 $this->repConfig = $repConfig;
354 } 354 }
355 355
356 // {{{ dirContents 356 // {{{ dirContents
357   357  
358 function dirContents($path, $rev = 0) 358 function dirContents($path, $rev = 0)
359 { 359 {
360 global $config, $locwebsvnreal; 360 global $config, $locwebsvnreal;
361 361
362 $revstr = _revStr($rev); 362 $revstr = _revStr($rev);
363 363
364 $tree = array(); 364 $tree = array();
365 365
366 if ($rev == 0) 366 if ($rev == 0)
367 { 367 {
368 $headlog = $this->getLog("/", "", "", true, 1); 368 $headlog = $this->getLog("/", "", "", true, 1);
369 $rev = $headlog->entries[0]->rev; 369 $rev = $headlog->entries[0]->rev;
370 } 370 }
371 371
372 $path = encodepath($this->repConfig->path.$path); 372 $path = encodepath($this->repConfig->path.$path);
373 $output = runCommand($config->svn." list $revstr ".$this->repConfig->svnParams().quote($path), true); 373 $output = runCommand($config->svn." list $revstr ".$this->repConfig->svnParams().quote($path), true);
374 374
375 foreach ($output as $entry) 375 foreach ($output as $entry)
376 { 376 {
377 if ($entry != "") 377 if ($entry != "")
378 $tree[] = $entry; 378 $tree[] = $entry;
379 } 379 }
380 380
381 // Sort the entries into alphabetical order with the directories at the top of the list 381 // Sort the entries into alphabetical order with the directories at the top of the list
382 usort($tree, "_dirSort"); 382 usort($tree, "_dirSort");
383 383
384 return $tree; 384 return $tree;
385 } 385 }
386   386  
387 // }}} 387 // }}}
388 388
389 // {{{ highlightLine 389 // {{{ highlightLine
390 // 390 //
391 // Distill line-spanning syntax highlighting so that each line can stand alone 391 // Distill line-spanning syntax highlighting so that each line can stand alone
392 // (when invoking on the first line, $attributes should be an empty array) 392 // (when invoking on the first line, $attributes should be an empty array)
393 // Invoked to make sure all open syntax highlighting tags (<font>, <i>, <b>, etc.) 393 // Invoked to make sure all open syntax highlighting tags (<font>, <i>, <b>, etc.)
394 // are closed at the end of each line and re-opened on the next line 394 // are closed at the end of each line and re-opened on the next line
395 395
396 function highlightLine($line, &$attributes) 396 function highlightLine($line, &$attributes)
397 { 397 {
398 $hline = ""; 398 $hline = "";
399 399
400 // Apply any highlighting in effect from the previous line 400 // Apply any highlighting in effect from the previous line
401 foreach($attributes as $attr) 401 foreach($attributes as $attr)
402 { 402 {
403 $hline.=$attr['text']; 403 $hline.=$attr['text'];
404 } 404 }
405   405  
406 // append the new line 406 // append the new line
407 $hline.=$line; 407 $hline.=$line;
408   408  
409 // update attributes 409 // update attributes
410 for ($line = strstr($line, "<"); $line; $line = strstr(substr($line,1), "<")) 410 for ($line = strstr($line, "<"); $line; $line = strstr(substr($line,1), "<"))
411 { 411 {
412 // if this closes a tag, remove most recent corresponding opener 412 // if this closes a tag, remove most recent corresponding opener
413 if (substr($line,1,1) == "/") 413 if (substr($line,1,1) == "/")
414 { 414 {
415 $tagNamLen = strcspn($line, "> \t", 2); 415 $tagNamLen = strcspn($line, "> \t", 2);
416 $tagNam = substr($line,2,$tagNamLen); 416 $tagNam = substr($line,2,$tagNamLen);
417 foreach(array_reverse(array_keys($attributes)) as $k) 417 foreach(array_reverse(array_keys($attributes)) as $k)
418 { 418 {
419 if ($attributes[$k]['tag'] == $tagNam) 419 if ($attributes[$k]['tag'] == $tagNam)
420 { 420 {
421 unset($attributes[$k]); 421 unset($attributes[$k]);
422 break; 422 break;
423 } 423 }
424 } 424 }
425 } 425 }
426 else 426 else
427 // if this opens a tag, add it to the list 427 // if this opens a tag, add it to the list
428 { 428 {
429 $tagNamLen = strcspn($line, "> \t", 1); 429 $tagNamLen = strcspn($line, "> \t", 1);
430 $tagNam = substr($line,1,$tagNamLen); 430 $tagNam = substr($line,1,$tagNamLen);
431 $tagLen = strcspn($line, ">") + 1; 431 $tagLen = strcspn($line, ">") + 1;
432 $attributes[] = array('tag' => $tagNam, 'text' => substr($line,0,$tagLen)); 432 $attributes[] = array('tag' => $tagNam, 'text' => substr($line,0,$tagLen));
433 } 433 }
434 } 434 }
435   435  
436 // close any still-open tags 436 // close any still-open tags
437 foreach(array_reverse($attributes) as $attr) 437 foreach(array_reverse($attributes) as $attr)
438 { 438 {
439 $hline.="</".$attr['tag'].">"; 439 $hline.="</".$attr['tag'].">";
440 } 440 }
441   441  
442 return($hline); 442 return($hline);
443 } 443 }
444   444  
445 // }}} 445 // }}}
446   446  
447 // {{{ getFileContents 447 // {{{ getFileContents
448 // 448 //
449 // Dump the content of a file to the given filename 449 // Dump the content of a file to the given filename
450 450
451 function getFileContents($path, $filename, $rev = 0, $pipe = "", $perLineHighlighting = false) 451 function getFileContents($path, $filename, $rev = 0, $pipe = "", $perLineHighlighting = false)
452 { 452 {
453 global $config, $extEnscript; 453 global $config, $extEnscript;
454 454
455 $revstr = _revStr($rev); 455 $revstr = _revStr($rev);
456 456
457 // If there's no filename, we'll just deliver the contents as it is to the user 457 // If there's no filename, we'll just deliver the contents as it is to the user
458 if ($filename == "") 458 if ($filename == "")
459 { 459 {
460 $path = encodepath($this->repConfig->path.$path); 460 $path = encodepath($this->repConfig->path.$path);
461 passthru(quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." $pipe", false)); 461 passthru(quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." $pipe", false));
462 return; 462 return;
463 } 463 }
464 464
465 // Get the file contents info 465 // Get the file contents info
466 466
467 $ext = strrchr($path, "."); 467 $ext = strrchr($path, ".");
468 $l = @$extEnscript[$ext]; 468 $l = @$extEnscript[$ext];
469 469
470 if ($l == "php") 470 if ($l == "php")
471 { 471 {
472 // Output the file to the filename 472 // Output the file to the filename
473 $path = encodepath($this->repConfig->path.$path); 473 $path = encodepath($this->repConfig->path.$path);
474 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." > $filename", false); 474 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." > $filename", false);
475 @exec($cmd); 475 @exec($cmd);
476 476
477 // Get the file as a string (memory hogging, but we have no other options) 477 // Get the file as a string (memory hogging, but we have no other options)
478 $content = highlight_file($filename, true); 478 $content = highlight_file($filename, true);
479 479
480 // Destroy the previous version, and replace it with the highlighted version 480 // Destroy the previous version, and replace it with the highlighted version
481 $f = fopen($filename, "w"); 481 $f = fopen($filename, "w");
482 if ($f) 482 if ($f)
483 { 483 {
484 // The highlight file function doesn't deal with line endings very nicely at all. We'll have to do it 484 // The highlight file function doesn't deal with line endings very nicely at all. We'll have to do it
485 // by hand. 485 // by hand.
486 486
487 // Remove the first line generated by highlight() 487 // Remove the first line generated by highlight()
488 $pos = strpos($content, "\n"); 488 $pos = strpos($content, "\n");
489 $content = substr($content, $pos+1); 489 $content = substr($content, $pos+1);
490 490
491 $content = explode("<br />", $content); 491 $content = explode("<br />", $content);
492 492
493 if ($perLineHighlighting) 493 if ($perLineHighlighting)
494 { 494 {
495 // If we need each line independently highlighted (e.g. for diff or blame) 495 // If we need each line independently highlighted (e.g. for diff or blame)
496 // hen we'll need to filter the output of the highlighter 496 // hen we'll need to filter the output of the highlighter
497 // to make sure tags like <font>, <i> or <b> don't span lines 497 // to make sure tags like <font>, <i> or <b> don't span lines
498 498
499 // $attributes is used to remember what highlighting attributes 499 // $attributes is used to remember what highlighting attributes
500 // are in effect from one line to the next 500 // are in effect from one line to the next
501 $attributes = array(); // start with no attributes in effect 501 $attributes = array(); // start with no attributes in effect
502 502
503 foreach ($content as $line) 503 foreach ($content as $line)
504 { 504 {
505 fputs($f, $this->highlightLine(rtrim($line),$attributes)."\n"); 505 fputs($f, $this->highlightLine(rtrim($line),$attributes)."\n");
506 } 506 }
507 } 507 }
508 else 508 else
509 { 509 {
510 foreach ($content as $line) 510 foreach ($content as $line)
511 { 511 {
512 fputs($f, rtrim($line)."\n"); 512 fputs($f, rtrim($line)."\n");
513 } 513 }
514 } 514 }
515   515  
516 fclose($f); 516 fclose($f);
517 } 517 }
518 } 518 }
519 else 519 else
520 { 520 {
521 if ($config->useEnscript) 521 if ($config->useEnscript)
522 { 522 {
523 // Get the files, feed it through enscript, then remove the enscript headers using sed 523 // Get the files, feed it through enscript, then remove the enscript headers using sed
524 // 524 //
525 // Note that the sec command returns only the part of the file between <PRE> and </PRE>. 525 // Note that the sec command returns only the part of the file between <PRE> and </PRE>.
526 // It's complicated because it's designed not to return those lines themselves. 526 // It's complicated because it's designed not to return those lines themselves.
527 527
528 $path = encodepath($this->repConfig->path.$path); 528 $path = encodepath($this->repConfig->path.$path);
529 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." | ". 529 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." | ".
530 $config->enscript." --language=html ". 530 $config->enscript." --language=html ".
531 ($l ? "--color --pretty-print=$l" : "")." -o - | ". 531 ($l ? "--color --pretty-print=$l" : "")." -o - | ".
532 $config->sed." -n ".$config->quote."1,/^<PRE.$/!{/^<\\/PRE.$/,/^<PRE.$/!p;}".$config->quote." > $filename", false); 532 $config->sed." -n ".$config->quote."1,/^<PRE.$/!{/^<\\/PRE.$/,/^<PRE.$/!p;}".$config->quote." > $filename", false);
533 @exec($cmd); 533 @exec($cmd);
534 } 534 }
535 else 535 else
536 { 536 {
537 $path = encodepath(str_replace(DIRECTORY_SEPARATOR, "/", $this->repConfig->path.$path)); 537 $path = encodepath(str_replace(DIRECTORY_SEPARATOR, "/", $this->repConfig->path.$path));
538 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." > $filename", false); 538 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." > $filename", false);
539 @exec($cmd); 539 @exec($cmd);
540 } 540 }
541 } 541 }
542 } 542 }
543   543  
544 // }}} 544 // }}}
545   545  
546 // {{{ listFileContents 546 // {{{ listFileContents
547 // 547 //
548 // Print the contents of a file without filling up Apache's memory 548 // Print the contents of a file without filling up Apache's memory
549 549
550 function listFileContents($path, $rev = 0) 550 function listFileContents($path, $rev = 0)
551 { 551 {
552 global $config, $extEnscript; 552 global $config, $extEnscript;
553 553
554 $revstr = _revStr($rev); 554 $revstr = _revStr($rev);
555 $pre = false; 555 $pre = false;
556 556
557 // Get the file contents info 557 // Get the file contents info
558 558
559 $ext = strrchr($path, "."); 559 $ext = strrchr($path, ".");
560 $l = @$extEnscript[$ext]; 560 $l = @$extEnscript[$ext];
561 561
562 // Deal with php highlighting internally 562 // Deal with php highlighting internally
563 if ($l == "php") 563 if ($l == "php")
564 { 564 {
565 $tmp = tempnam("temp", "wsvn"); 565 $tmp = tempnam("temp", "wsvn");
566 566
567 // Output the file to a temporary file 567 // Output the file to a temporary file
568 $path = encodepath($this->repConfig->path.$path); 568 $path = encodepath($this->repConfig->path.$path);
569 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." > $tmp", false); 569 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." > $tmp", false);
570 @exec($cmd); 570 @exec($cmd);
571 highlight_file($tmp); 571 highlight_file($tmp);
572 unlink($tmp); 572 unlink($tmp);
573 } 573 }
574 else 574 else
575 { 575 {
576 if ($config->useEnscript) 576 if ($config->useEnscript)
577 { 577 {
578 $path = encodepath($this->repConfig->path.$path); 578 $path = encodepath($this->repConfig->path.$path);
579 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." | ". 579 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." | ".
580 $config->enscript." --language=html ". 580 $config->enscript." --language=html ".
581 ($l ? "--color --pretty-print=$l" : "")." -o - | ". 581 ($l ? "--color --pretty-print=$l" : "")." -o - | ".
582 $config->sed." -n ".$config->quote."/^<PRE.$/,/^<\\/PRE.$/p".$config->quote." 2>&1", false); 582 $config->sed." -n ".$config->quote."/^<PRE.$/,/^<\\/PRE.$/p".$config->quote." 2>&1", false);
583 583
584 if (!($result = popen($cmd, "r"))) 584 if (!($result = popen($cmd, "r")))
585 return; 585 return;
586 } 586 }
587 else 587 else
588 { 588 {
589 $path = encodepath($this->repConfig->path.$path); 589 $path = encodepath($this->repConfig->path.$path);
590 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." 2>&1", false); 590 $cmd = quoteCommand($config->svn." cat $revstr ".$this->repConfig->svnParams().quote($path)." 2>&1", false);
591 591
592 if (!($result = popen($cmd, "r"))) 592 if (!($result = popen($cmd, "r")))
593 return; 593 return;
594 594
595 $pre = true; 595 $pre = true;
596 } 596 }
597 597
598 if ($pre) 598 if ($pre)
599 echo "<PRE>"; 599 echo "<PRE>";
600 600
601 while (!feof($result)) 601 while (!feof($result))
602 { 602 {
603 $line = fgets($result, 1024); 603 $line = fgets($result, 1024);
604 if ($pre) $line = replaceEntities($line, $this->repConfig); 604 if ($pre) $line = replaceEntities($line, $this->repConfig);
605 605
606 print hardspace($line); 606 print hardspace($line);
607 } 607 }
608 608
609 if ($pre) 609 if ($pre)
610 echo "</PRE>"; 610 echo "</PRE>";
611 611
612 pclose($result); 612 pclose($result);
613 } 613 }
614 } 614 }
615   615  
616 // }}} 616 // }}}
617   617  
618 // {{{ getBlameDetails 618 // {{{ getBlameDetails
619 // 619 //
620 // Dump the blame content of a file to the given filename 620 // Dump the blame content of a file to the given filename
621 621
622 function getBlameDetails($path, $filename, $rev = 0) 622 function getBlameDetails($path, $filename, $rev = 0)
623 { 623 {
624 global $config; 624 global $config;
625 625
626 $revstr = _revStr($rev); 626 $revstr = _revStr($rev);
627 627
628 $path = encodepath($this->repConfig->path.$path); 628 $path = encodepath($this->repConfig->path.$path);
629 $cmd = quoteCommand($config->svn." blame $revstr ".$this->repConfig->svnParams().quote($path)." > $filename", false); 629 $cmd = quoteCommand($config->svn." blame $revstr ".$this->repConfig->svnParams().quote($path)." > $filename", false);
630 630
631 @exec($cmd); 631 @exec($cmd);
632 } 632 }
633   633  
634 // }}} 634 // }}}
635   635  
636 // {{{ getProperty 636 // {{{ getProperty
637   637  
638 function getProperty($path, $property, $rev = 0) 638 function getProperty($path, $property, $rev = 0)
639 { 639 {
640 global $config; 640 global $config;
641 641
642 $revstr = _revStr($rev); 642 $revstr = _revStr($rev);
643 643
644 $path = encodepath($this->repConfig->path.$path); 644 $path = encodepath($this->repConfig->path.$path);
645 $ret = runCommand($config->svn." propget $property $revstr ".$this->repConfig->svnParams().quote($path), true); 645 $ret = runCommand($config->svn." propget $property $revstr ".$this->repConfig->svnParams().quote($path), true);
646 646
647 // Remove the surplus newline 647 // Remove the surplus newline
648 if (count($ret)) 648 if (count($ret))
649 unset($ret[count($ret) - 1]); 649 unset($ret[count($ret) - 1]);
650 650
651 return implode("\n", $ret); 651 return implode("\n", $ret);
652 } 652 }
653   653  
654 // }}} 654 // }}}
655   655  
656 // {{{ exportDirectory 656 // {{{ exportDirectory
657 // 657 //
658 // Exports the directory to the given location 658 // Exports the directory to the given location
659 659
660 function exportDirectory($path, $filename, $rev = 0) 660 function exportDirectory($path, $filename, $rev = 0)
661 { 661 {
662 global $config; 662 global $config;
663 663
664 $revstr = _revStr($rev); 664 $revstr = _revStr($rev);
665 665
666 $path = encodepath($this->repConfig->path.$path); 666 $path = encodepath($this->repConfig->path.$path);
667 $cmd = quoteCommand($config->svn." export $revstr ".$this->repConfig->svnParams().quote($path)." ".quote($filename), false); 667 $cmd = quoteCommand($config->svn." export $revstr ".$this->repConfig->svnParams().quote($path)." ".quote($filename), false);
668   668  
669 @exec($cmd); 669 @exec($cmd);
670 } 670 }
671   671  
672 // }}} 672 // }}}
673   673  
674 // {{{ getLog 674 // {{{ getLog
675 675
676 function getLog($path, $brev = "", $erev = 1, $quiet = false, $limit = 2) 676 function getLog($path, $brev = "", $erev = 1, $quiet = false, $limit = 2)
677 { 677 {
678 global $config, $curLog, $vars, $lang; 678 global $config, $curLog, $vars, $lang;
679 679
680 $xml_parser = xml_parser_create("UTF-8"); 680 $xml_parser = xml_parser_create("UTF-8");
681 xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true); 681 xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);
682 xml_set_element_handler($xml_parser, "startElement", "endElement"); 682 xml_set_element_handler($xml_parser, "startElement", "endElement");
683 xml_set_character_data_handler($xml_parser, "characterData"); 683 xml_set_character_data_handler($xml_parser, "characterData");
684   684  
685 // Since directories returned by svn log don't have trailing slashes (:-(), we need to remove 685 // Since directories returned by svn log don't have trailing slashes (:-(), we need to remove
686 // the trailing slash from the path for comparison purposes 686 // the trailing slash from the path for comparison purposes
687 687
688 if ($path{strlen($path) - 1} == "/" && $path != "/") 688 if ($path{strlen($path) - 1} == "/" && $path != "/")
689 $path = substr($path, 0, -1); 689 $path = substr($path, 0, -1);
690 690
691 $curLog = new SVNLog; 691 $curLog = new SVNLog;
692 $curLog->entries = array(); 692 $curLog->entries = array();
693 $curLog->path = $path; 693 $curLog->path = $path;
694 694
695 $revStr = ""; 695 $revStr = "";
696 696
697 if ($brev && $erev) 697 if ($brev && $erev)
698 $revStr = "-r$brev:$erev"; 698 $revStr = "-r$brev:$erev";
699 else if ($brev) 699 else if ($brev)
700 $revStr = "-r$brev:1"; 700 $revStr = "-r$brev:1";
701   701  
702 if (($config->subversionMajorVersion > 1 || $config->subversionMinorVersion >=2) && $limit != 0) 702 if (($config->subversionMajorVersion > 1 || $config->subversionMinorVersion >=2) && $limit != 0)
703 $revStr .= " --limit $limit"; 703 $revStr .= " --limit $limit";
704 704
705 // Get the log info 705 // Get the log info
706 $path = encodepath($this->repConfig->path.$path); 706 $path = encodepath($this->repConfig->path.$path);
707 $info = "--verbose"; 707 $info = "--verbose";
708 if ($quiet) 708 if ($quiet)
709 $info = "--quiet"; 709 $info = "--quiet";
710 710
711 $cmd = quoteCommand($config->svn." log --xml $info $revStr ".$this->repConfig->svnParams().quote($path), false); 711 $cmd = quoteCommand($config->svn." log --xml $info $revStr ".$this->repConfig->svnParams().quote($path), false);
712   712  
713 if ($handle = popen($cmd, "r")) 713 if ($handle = popen($cmd, "r"))
714 { 714 {
715 $firstline = true; 715 $firstline = true;
716 while (!feof($handle)) 716 while (!feof($handle))
717 { 717 {
718 $line = fgets($handle); 718 $line = fgets($handle);
719 if (!xml_parse($xml_parser, $line, feof($handle))) 719 if (!xml_parse($xml_parser, $line, feof($handle)))
720 { 720 {
721 if (xml_get_error_code($xml_parser) != 5) 721 if (xml_get_error_code($xml_parser) != 5)
722 { 722 {
723 die(sprintf("XML error: %s (%d) at line %d column %d byte %d<br>cmd: %s<nr>", 723 die(sprintf("XML error: %s (%d) at line %d column %d byte %d<br>cmd: %s<nr>",
724 xml_error_string(xml_get_error_code($xml_parser)), 724 xml_error_string(xml_get_error_code($xml_parser)),
725 xml_get_error_code($xml_parser), 725 xml_get_error_code($xml_parser),
726 xml_get_current_line_number($xml_parser), 726 xml_get_current_line_number($xml_parser),
727 xml_get_current_column_number($xml_parser), 727 xml_get_current_column_number($xml_parser),
728 xml_get_current_byte_index($xml_parser), 728 xml_get_current_byte_index($xml_parser),
729 $cmd)); 729 $cmd));
730 } 730 }
731 else 731 else
732 { 732 {
733 $vars["error"] = $lang["UNKNOWNREVISION"]; 733 $vars["error"] = $lang["UNKNOWNREVISION"];
734 return 0; 734 return 0;
735 } 735 }
736 } 736 }
737 } 737 }
738 pclose($handle); 738 pclose($handle);
739 } 739 }
740   740  
741 xml_parser_free($xml_parser); 741 xml_parser_free($xml_parser);
742 return $curLog; 742 return $curLog;
743 } 743 }
744   744  
745 // }}} 745 // }}}
746   746  
747 } 747 }
748   748  
749 // {{{ initSvnVersion 749 // {{{ initSvnVersion
750   750  
751 function initSvnVersion(&$major, &$minor) 751 function initSvnVersion(&$major, &$minor)
752 { 752 {
753 global $config; 753 global $config;
754 754
755 $ret = runCommand($config->svn_noparams." --version", false); 755 $ret = runCommand($config->svn_noparams." --version", false);
756   756  
757 if (preg_match("~([0-9]?)\.([0-9]?)\.([0-9]?)~",$ret[0],$matches)) 757 if (preg_match("~([0-9]?)\.([0-9]?)\.([0-9]?)~",$ret[0],$matches))
758 { 758 {
759 $major = $matches[1]; 759 $major = $matches[1];
760 $minor = $matches[2]; 760 $minor = $matches[2];
761 } 761 }
762 762
763 $config->setSubversionMajorVersion($major); 763 $config->setSubversionMajorVersion($major);
764 $config->setSubversionMinorVersion($minor); 764 $config->setSubversionMinorVersion($minor);
765 } 765 }
766   766  
767 // }}} 767 // }}}
768   768  
769 ?> 769 ?>