Rev Author Line No. Line
4988 kaklik 1 <?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 // utils.php
22 //
23 // General utility commands
24  
25 // {{{ createDirLinks
26 //
27 // Create a list of links to the current path that'll be available from the template
28  
29 function createPathLinks($rep, $path, $rev, $peg = '') {
30 global $config, $lang, $vars;
31  
32 $pathComponents = explode('/', $path);
33 $count = count($pathComponents);
34  
35 // The number of links depends on the last item. It's empty if we're looking
36 // at a directory, and non-empty if we're looking at a file.
37 if (empty($pathComponents[$count - 1])) {
38 $limit = $count - 2;
39 $dir = true;
40 } else {
41 $limit = $count - 1;
42 $dir = false;
43 }
44  
45 $passRevString = createRevAndPegString($rev, $peg);
46 $pathSoFar = '/';
47 $pathSoFarURL = $config->getURL($rep, $pathSoFar, 'dir').$passRevString;
48  
49 $repoName = $rep->getDisplayName();
50 $rootName = $lang['BREADCRUMB_REPO_ROOT'];
51  
52 $vars['path_links'] = '';
53 $vars['path_links_root_root'] = "<a href=\"{$pathSoFarURL}\" class=\"root\"><span>{$rootName}</span></a>";
54 $vars['path_links_root_repo'] = "<a href=\"{$pathSoFarURL}\" class=\"root\"><span>{$repoName}</span></a>";
55 $vars['path_links_root_config'] = $config->getBreadcrumbRepoRootAsRepo()
56 ? $vars['path_links_root_repo']
57 : $vars['path_links_root_root'];
58  
59 for ($n = 1; $n < $limit; $n++) {
60 $pathSoFar .= $pathComponents[$n].'/';
61 $pathSoFarURL = $config->getURL($rep, $pathSoFar, 'dir').$passRevString;
62 $vars['path_links'] .= '<a href="'.$pathSoFarURL.'#'.anchorForPath($pathSoFar).'">'.escape($pathComponents[$n]).'</a>/';
63 }
64  
65 if (!empty($pathComponents[$n])) {
66 $pegrev = ($peg && $peg != $rev) ? ' <a class="peg" href="'.'?'.escape(str_replace('&peg='.$peg, '', $_SERVER['QUERY_STRING'])).'">@ '.$peg.'</a>' : '';
67 if ($dir) {
68 $vars['path_links'] .= '<span class="dir">'.escape($pathComponents[$n]).'/'.$pegrev.'</span>';
69 } else {
70 $vars['path_links'] .= '<span class="file">'.escape($pathComponents[$n]).$pegrev.'</span>';
71 }
72 }
73 }
74  
75 // }}}
76  
77 function createRevAndPegString($rev, $peg) {
78 $params = array();
79 if ($rev) $params[] = 'rev='.$rev;
80 if ($peg) $params[] = 'peg='.$peg;
81 return implode('&amp;', $params);
82 }
83  
84 function createDifferentRevAndPegString($rev, $peg) {
85 $params = array();
86 if ($rev && (!$peg || $rev != $peg)) $params[] = 'rev='.$rev;
87 if ($peg) $params[] = 'peg='.$peg;
88 return implode('&amp;', $params);
89 }
90  
91 function anchorForPath($path) {
92 global $config;
93  
94 // (X)HMTL id/name attribute must be this format: [A-Za-z][A-Za-z0-9-_.:]*
95 // MD5 hashes are 32 characters, deterministic, quite collision-resistant,
96 // and work for any string, regardless of encoding or special characters.
97 if ($config->treeView)
98 return 'a'.md5($path);
99 else
100 return '';
101 }
102  
103 // {{{ create_anchors
104 //
105 // Create links out of http:// and mailto: tags
106  
107 // TODO: the target="_blank" nonsense should be optional (or specified by the template)
108 function create_anchors($text) {
109 $ret = $text;
110  
111 // Match correctly formed URLs that aren't already links
112 $ret = preg_replace('#\b(?<!href=")([a-z]+?)://(\S*)([\w/]+)#i',
113 '<a href="\\1://\\2\\3" target="_blank">\\1://\\2\\3</a>',
114 $ret);
115  
116 // Now match anything beginning with www, as long as it's not //www since they were matched above
117 $ret = preg_replace('#\b(?<!//)www\.(\S*)([\w/]+)#i',
118 '<a href="http://www.\\1\\2" target="_blank">www.\\1\\2</a>',
119 $ret);
120  
121 // Match email addresses
122 $ret = preg_replace('#\b([\w\-_.]+)@([\w\-.]+)\.(\w{2,})\b#i',
123 '<a href="mailto:\\1@\\2.\\3">\\1@\\2.\\3</a>',
124 $ret);
125  
126 return $ret;
127 }
128  
129 // }}}
130  
131 // {{{ getFullURL
132  
133 function getFullURL($loc) {
134 $protocol = 'http';
135  
136 if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
137 $protocol = $_SERVER['HTTP_X_FORWARDED_PROTO'];
138 } else if (isset($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off')) {
139 $protocol = 'https';
140 }
141  
142 $port = ':'.$_SERVER['SERVER_PORT'];
143 if ((':80' == $port && 'http' == $protocol) || (':443' == $port && 'https' == $protocol)) {
144 $port = '';
145 }
146  
147 if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
148 $host = $_SERVER['HTTP_X_FORWARDED_HOST'];
149 } else if (isset($_SERVER['HTTP_HOST'])) {
150 $host = $_SERVER['HTTP_HOST'];
151 } else if (isset($_SERVER['SERVER_NAME'])) {
152 $host = $_SERVER['SERVER_NAME'].$port;
153 } else if (isset($_SERVER['SERVER_ADDR'])) {
154 $host = $_SERVER['SERVER_ADDR'].$port;
155 } else {
156 print 'Unable to redirect';
157 exit;
158 }
159  
160 // make sure we have a directory to go to
161 if (empty($loc)) {
162 $loc = '/';
163 } else if ($loc[0] != '/') {
164 $loc = '/'.$loc;
165 }
166  
167 $url = $protocol . '://' . $host . $loc;
168  
169 return $url;
170 }
171  
172 // }}}
173  
174 function xml_entities($str) {
175 $entities = array();
176 $entities['&'] = '&amp;';
177 $entities['<'] = '&lt;';
178 $entities['>'] = '&gt;';
179 $entities['"'] = '&quot;';
180 $entities['\''] = '&apos;';
181 return str_replace(array_keys($entities), array_values($entities), $str);
182 }
183  
184 // {{{ hardspace
185 //
186 // Replace the spaces at the front of a line with hard spaces
187  
188 // XXX: this is an unnecessary function; you can prevent whitespace from being
189 // trimmed via CSS (use the "white-space: pre;" properties). ~J
190 // in the meantime, here's an improved function (does nothing)
191  
192 function hardspace($s) {
193 return '<code>' . expandTabs($s) . '</code>';
194 }
195  
196 // }}}
197  
198 function wrapInCodeTagIfNecessary($string) {
199 global $config;
200 return ($config->getUseGeshi()) ? $string : '<code>'.$string.'</code>';
201 }
202  
203 // {{{ expandTabs
204  
205 /**
206 * Expands the tabs in a line that may or may not include HTML.
207 *
208 * Enscript generates code with HTML, so we need to take that into account.
209 *
210 * @param string $s Line of possibly HTML-encoded text to expand
211 * @param int $tabwidth Tab width, -1 to use repository's default, 0 to collapse
212 * all tabs.
213 * @return string The expanded line.
214 * @since 2.1
215 */
216  
217 function expandTabs($s, $tabwidth = - 1) {
218 global $rep;
219  
220 if ($tabwidth == -1) {
221 $tabwidth = $rep->getExpandTabsBy();
222 }
223 $pos = 0;
224  
225 // Parse the string into chunks that are either 1 of: HTML tag, tab char, run of any other stuff
226 $chunks = preg_split('/((?:<.+?>)|(?:&.+?;)|(?:\t))/', $s, -1, PREG_SPLIT_DELIM_CAPTURE);
227  
228 // Count the sizes of the chunks and replace tabs as we go
229 $chunkscount = count($chunks);
230 for ($i = 0; $i < $chunkscount; $i++) {
231 // make sure we're not dealing with an empty string
232 if (empty($chunks[$i])) continue;
233 switch ($chunks[$i][0]) {
234 case '<': // HTML tag: ignore its width by doing nothing
235 break;
236  
237 case '&': // HTML entity: count its width as 1 char
238 $pos++;
239 break;
240  
241 case "\t": // Tab char: replace it with a run of spaces between length tabwidth and 1
242 $tabsize = $tabwidth - ($pos % $tabwidth);
243 $chunks[$i] = str_repeat(' ', $tabsize);
244 $pos += $tabsize;
245 break;
246  
247 default: // Anything else: just keep track of its width
248 $pos += strlen($chunks[$i]);
249 break;
250 }
251 }
252  
253 // Put the chunks back together and we've got the original line, detabbed.
254 return join('', $chunks);
255 }
256  
257 // }}}
258  
259 // {{{ datetimeFormatDuration
260 //
261 // Formats a duration of seconds for display.
262 //
263 // $seconds the number of seconds until something
264 // $nbsp true if spaces should be replaced by nbsp
265 // $skipSeconds true if seconds should be omitted
266 //
267 // return the formatted duration (e.g. @c "8h 6m 1s")
268  
269 function datetimeFormatDuration($seconds, $nbsp = false, $skipSeconds = false) {
270 global $lang;
271  
272 $neg = false;
273 if ($seconds < 0) {
274 $seconds = 0 - $seconds;
275 $neg = true;
276 }
277  
278 $qty = array();
279 $names = array($lang['DAYLETTER'], $lang['HOURLETTER'], $lang['MINUTELETTER']);
280  
281 $qty[] = (int)($seconds / (60 * 60 * 24));
282 $seconds %= 60 * 60 * 24;
283  
284 $qty[] = (int)($seconds / (60 * 60));
285 $seconds %= 60 * 60;
286  
287 $qty[] = (int)($seconds / 60);
288  
289 if (!$skipSeconds) {
290 $qty[] = (int)($seconds % 60);
291 $names[] = $lang['SECONDLETTER'];
292 }
293  
294 $text = $neg ? '-' : '';
295 $any = false;
296 $count = count($names);
297 $parts = 0;
298 for ($i = 0; $i < $count; $i++) {
299 // If a "higher valued" time slot had a value or this time slot
300 // has a value or this is the very last entry (i.e. all values
301 // are 0 and we still want to print seconds)
302 if ($any || $qty[$i] > 0 || $i == $count - 1) {
303 if ($any) $text .= $nbsp ? '&nbsp;' : ' ';
304 $text .= $qty[$i].' '.$names[$i];
305 $any = true;
306 $parts++;
307 if ($parts >= 2) break;
308 }
309 }
310 return $text;
311 }
312  
313 // }}}
314  
315 function parseSvnTimestamp($dateString) {
316 // Try the simple approach of a built-in PHP function first.
317 $date = strtotime($dateString);
318 // If the resulting timestamp isn't sane, try parsing manually.
319 if ($date <= 0) {
320 $y = 0;
321 $mo = 0;
322 $d = 0;
323 $h = 0;
324 $m = 0;
325 $s = 0;
326 sscanf($dateString, '%d-%d-%dT%d:%d:%d.', $y, $mo, $d, $h, $m, $s);
327  
328 $mo = substr('00'.$mo, -2);
329 $d = substr('00'.$d, -2);
330 $h = substr('00'.$h, -2);
331 $m = substr('00'.$m, -2);
332 $s = substr('00'.$s, -2);
333 $date = strtotime($y.'-'.$mo.'-'.$d.' '.$h.':'.$m.':'.$s.' GMT');
334 }
335 return $date;
336 }
337  
338 // {{{ buildQuery
339 //
340 // Build parameters for url query part
341  
342 function buildQuery($data, $separator = '&amp;', $key = '') {
343 if (is_object($data))
344 $data = get_object_vars($data);
345 $p = array();
346 foreach ($data as $k => $v) {
347 $k = rawurlencode($k);
348 if (!empty($key))
349 $k = $key.'['.$k.']';
350 if (is_array($v) || is_object($v)) {
351 $p[] = buildQuery($v, $separator, $k);
352 } else {
353 $p[] = $k.'='.rawurlencode($v);
354 }
355 }
356 return implode($separator, $p);
357 }
358  
359 // }}}
360  
361 // {{{ getUserLanguage
362  
363 function getUserLanguage($languages, $default, $userchoice) {
364 global $config;
365 if (!$config->useAcceptedLanguages()) return $default;
366  
367 $acceptlangs = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : false;
368 if (!$acceptlangs)
369 return $default;
370  
371 $langs = array();
372 $sublangs = array();
373  
374 foreach (explode(',', $acceptlangs) as $str) {
375 $a = explode(';', $str, 2);
376 $lang = trim($a[0]);
377 $pos = strpos($lang, '-');
378 if ($pos !== false)
379 $sublangs[] = substr($lang, 0, $pos);
380 $q = 1.0;
381 if (count($a) == 2) {
382 $v = trim($a[1]);
383 if (substr($v, 0, 2) == 'q=')
384 $q = doubleval(substr($v, 2));
385 }
386 if ($userchoice)
387 $q *= 0.9;
388 $langs[$lang] = $q;
389 }
390  
391 foreach ($sublangs as $l)
392 if (!isset($langs[$l]))
393 $langs[$l] = 0.1;
394  
395 if ($userchoice)
396 $langs[$userchoice] = 1.0;
397  
398 arsort($langs);
399 foreach ($langs as $code => $q) {
400 if (isset($languages[$code])) {
401 return $code;
402 }
403 }
404  
405 return $default;
406 }
407  
408 // }}}
409  
410 // {{{ removeDirectory
411  
412 function removeDirectory($dir)
413 {
414 if (!is_dir($dir))
415 {
416 return false;
417 }
418  
419 $dir = rtrim($dir, '/');
420 $handle = dir($dir);
421  
422 while (($file = $handle->read()) !== false)
423 {
424 if (($file == '.') || ($file == '..'))
425 {
426 continue;
427 }
428  
429 $f = $dir.DIRECTORY_SEPARATOR.$file;
430 if (!is_link($f) && is_dir($f))
431 {
432 removeDirectory($f);
433 }
434 else
435 {
436 @unlink($f);
437 }
438 }
439  
440 $handle->close();
441 @rmdir($dir);
442  
443 return true;
444 }
445  
446 // }}}
447  
448 // {{{ tempnameWithCheck
449  
450 function tempnamWithCheck($dir, $prefix, $rmOnShutdown = true) {
451 $tmp = tempnam($dir, $prefix);
452  
453 if ($tmp && !$rmOnShutdown)
454 {
455 return $tmp;
456 }
457 if ($tmp && $rmOnShutdown)
458 {
459 register_shutdown_function('removeDirectory', $tmp);
460 return $tmp;
461 }
462  
463 if (!$tmp && !headers_sent())
464 {
465 http_response_code(500);
466 error_log('Unable to create a temporary file. Either make the currently used directory ("' . $dir . '") writable for WebSVN or change the directory in the configuration.');
467 print 'Unable to create a temporary file. Either make the currently used directory writable for WebSVN or change the directory in the configuration.';
468 exit(0);
469 }
470  
471 global $vars;
472 $vars['warning'] = 'Unable to create a temporary file. Either make the currently used directory writable for WebSVN or change the directory in the configuration.';
473  
474 return $tmp;
475 }
476  
477 // }}}