2007-07-19 12:40:28 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* A class with HTTP-related helpers.
|
|
|
|
* Like Debug, this is more a bundle of methods than a class ;-)
|
2008-02-25 03:10:37 +01:00
|
|
|
*
|
|
|
|
* @package sapphire
|
|
|
|
* @subpackage misc
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
|
|
|
class HTTP {
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
static $userName;
|
|
|
|
static $password;
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Turns a local system filename into a URL by comparing it to the script filename
|
|
|
|
*/
|
|
|
|
static function filename2url($filename) {
|
|
|
|
$slashPos = -1;
|
|
|
|
while(($slashPos = strpos($filename, "/", $slashPos+1)) !== false) {
|
|
|
|
if(substr($filename, 0, $slashPos) == substr($_SERVER['SCRIPT_FILENAME'],0,$slashPos)) {
|
|
|
|
$commonLength = $slashPos;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$urlBase = substr($_SERVER['PHP_SELF'], 0, -(strlen($_SERVER['SCRIPT_FILENAME']) - $commonLength));
|
|
|
|
$url = $urlBase . substr($filename, $commonLength);
|
2008-10-09 00:53:20 +02:00
|
|
|
$protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') ? "https" : "http";
|
2007-07-19 12:40:28 +02:00
|
|
|
return "$protocol://". $_SERVER['HTTP_HOST'] . $url;
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
// Count the number of extra folders the script is in.
|
|
|
|
// $prefix = str_repeat("../", substr_count(substr($_SERVER[SCRIPT_FILENAME],$commonBaseLength)));
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Turn all relative URLs in the content to absolute URLs
|
|
|
|
*/
|
|
|
|
static function absoluteURLs($html) {
|
2008-02-25 03:10:37 +01:00
|
|
|
$html = str_replace('$CurrentPageURL', $_SERVER['REQUEST_URI'], $html);
|
2007-07-19 12:40:28 +02:00
|
|
|
return HTTP::urlRewriter($html, '(substr($URL,0,1) == "/") ? ( Director::protocolAndHost() . $URL ) : ( (ereg("^[A-Za-z]+:", $URL)) ? $URL : Director::absoluteBaseURL() . $URL )' );
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/*
|
|
|
|
* Rewrite all the URLs in the given content, evaluating the given string as PHP code
|
|
|
|
*
|
|
|
|
* Put $URL where you want the URL to appear, however, you can't embed $URL in strings
|
|
|
|
* Some example code:
|
2007-09-16 17:53:35 +02:00
|
|
|
* '"../../" . $URL'
|
2007-07-19 12:40:28 +02:00
|
|
|
* 'myRewriter($URL)'
|
|
|
|
* '(substr($URL,0,1)=="/") ? "../" . substr($URL,1) : $URL'
|
|
|
|
*/
|
|
|
|
static function urlRewriter($content, $code) {
|
|
|
|
$attribs = array("src","background","a" => "href","link" => "href", "base" => "href");
|
|
|
|
foreach($attribs as $tag => $attrib) {
|
|
|
|
if(!is_numeric($tag)) $tagPrefix = "$tag ";
|
|
|
|
else $tagPrefix = "";
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *\")([^\"]*)(\")/ie";
|
|
|
|
$regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *')([^']*)(')/ie";
|
|
|
|
$regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *)([^\"' ]*)( )/ie";
|
|
|
|
}
|
|
|
|
$regExps[] = '/(background-image:[^;]*url *\()([^)]+)(\))/ie';
|
|
|
|
$regExps[] = '/(background:[^;]*url *\()([^)]+)(\))/ie';
|
2007-09-16 17:53:35 +02:00
|
|
|
|
|
|
|
// Make
|
2007-07-19 12:40:28 +02:00
|
|
|
$code = 'stripslashes("$1") . (' . str_replace('$URL', 'stripslashes("$2")', $code) . ') . stripslashes("$3")';
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
foreach($regExps as $regExp) {
|
|
|
|
$content = preg_replace($regExp, $code, $content);
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
|
|
|
return $content;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2009-01-08 00:00:54 +01:00
|
|
|
public static function setGetVar($varname, $varvalue, $currentURL = null) {
|
|
|
|
$scriptbase = $currentURL ? $currentURL : $_SERVER['REQUEST_URI'];
|
|
|
|
|
|
|
|
$scriptbase = str_replace('&', '&', $scriptbase);
|
|
|
|
$scriptbase = preg_replace('/\?' . quotemeta($varname) . '=([^&]*)&/', '?', $scriptbase);
|
|
|
|
$scriptbase = preg_replace('/([\?&]+)' . quotemeta($varname) . '=([^&]*)/', null, $scriptbase);
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$suffix = '';
|
|
|
|
if(($hashPos = strpos($scriptbase,'#')) !== false) {
|
|
|
|
$suffix .= substr($scriptbase, $hashPos);
|
|
|
|
$scriptbase = substr($scriptbase, 0, $hashPos);
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if($varvalue !== null) {
|
|
|
|
$scriptbase .= (strrpos($scriptbase,'?') !== false) ? '&' : '?';
|
|
|
|
$scriptbase .= "$varname=" . (isset($urlEncodeVarValue) ? urlencode($varvalue) : $varvalue);
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$scriptbase = str_replace('&','&',$scriptbase);
|
|
|
|
return $scriptbase . $suffix;
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
static function RAW_setGetVar($varname, $varvalue, $currentURL = null) {
|
|
|
|
$url = self::setGetVar($varname, $varvalue, $currentURL);
|
|
|
|
return Convert::xml2raw($url);
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
static function findByTagAndAttribute($content, $attribs) {
|
2008-11-18 02:48:37 +01:00
|
|
|
$regExps = array();
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
foreach($attribs as $tag => $attrib) {
|
2009-02-02 00:49:53 +01:00
|
|
|
$tagPrefix = (is_numeric($tag)) ? '' : "$tag ";
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *\")([^\"]*)(\")/ie";
|
|
|
|
$regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *')([^']*)(')/ie";
|
|
|
|
$regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *)([^\"' ]*)( )/ie";
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-11-18 02:48:37 +01:00
|
|
|
if($regExps) {
|
|
|
|
foreach($regExps as $regExp) {
|
|
|
|
$content = preg_replace($regExp, '$items[] = "$2"', $content);
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
return isset($items) ? $items : null;
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
static function getLinksIn($content) {
|
|
|
|
return self::findByTagAndAttribute($content, array("a" => "href"));
|
|
|
|
}
|
2009-02-02 00:49:53 +01:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
static function getImagesIn($content) {
|
|
|
|
return self::findByTagAndAttribute($content, array("img" => "src"));
|
|
|
|
}
|
2009-05-20 07:16:00 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/*
|
|
|
|
* Get mime type based on extension
|
|
|
|
*/
|
|
|
|
static function getMimeType($filename) {
|
|
|
|
global $global_mimetypes;
|
|
|
|
if(!$global_mimetypes) self::loadMimeTypes();
|
|
|
|
$ext = strtolower(substr($filename,strrpos($filename,'.')+1));
|
2007-09-25 05:44:07 +02:00
|
|
|
if(isset($global_mimetypes[$ext])) return $global_mimetypes[$ext];
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/*
|
|
|
|
* Load the mime-type data from the system file
|
|
|
|
*/
|
|
|
|
static function loadMimeTypes() {
|
2007-11-12 23:26:34 +01:00
|
|
|
if(@file_exists('/etc/mime.types')) {
|
2007-07-19 12:40:28 +02:00
|
|
|
$mimeTypes = file('/etc/mime.types');
|
|
|
|
foreach($mimeTypes as $typeSpec) {
|
|
|
|
if(($typeSpec = trim($typeSpec)) && substr($typeSpec,0,1) != "#") {
|
2009-06-18 11:34:17 +02:00
|
|
|
$parts = preg_split("/[ \t\r\n]+/", $typeSpec);
|
2007-07-19 12:40:28 +02:00
|
|
|
if(sizeof($parts) > 1) {
|
|
|
|
$mimeType = array_shift($parts);
|
|
|
|
foreach($parts as $ext) {
|
|
|
|
$ext = strtolower($ext);
|
|
|
|
$mimeData[$ext] = $mimeType;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
// Fail-over for if people don't have /etc/mime.types on their server. it's unclear how important this actually is
|
|
|
|
} else {
|
|
|
|
$mimeData = array(
|
|
|
|
"doc" => "application/msword",
|
|
|
|
"xls" => "application/vnd.ms-excel",
|
|
|
|
"rtf" => "application/rtf",
|
|
|
|
);
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
global $global_mimetypes;
|
|
|
|
$global_mimetypes = $mimeData;
|
|
|
|
return $mimeData;
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
2008-09-16 22:36:30 +02:00
|
|
|
* Send an HTTP request to the host.
|
|
|
|
*
|
|
|
|
* @return String Response text
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
|
|
|
static function sendRequest( $host, $path, $query, $port = 80 ) {
|
|
|
|
$socket = fsockopen( $host, $port, $errno, $error );
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if( !$socket )
|
|
|
|
return $error;
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if( $query )
|
|
|
|
$query = '?' . $query;
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-09-15 23:07:51 +02:00
|
|
|
if( self::$userName && self::$password ) {
|
2007-07-19 12:40:28 +02:00
|
|
|
$auth = "Authorization: Basic " . base64_encode( self::$userName . ':' . self::$password ) . "\r\n";
|
2007-09-15 23:07:51 +02:00
|
|
|
} else {
|
|
|
|
$auth = '';
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$request = "GET {$path}{$query} HTTP/1.1\r\nHost: $host\r\nConnection: Close\r\n{$auth}\r\n";
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
fwrite( $socket, $request );
|
|
|
|
$response = stream_get_contents( $socket );
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
return $response;
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-04-09 13:20:34 +02:00
|
|
|
/**
|
|
|
|
* Send a HTTP POST request through fsockopen().
|
|
|
|
*
|
|
|
|
* @param string $host Absolute URI without path, e.g. http://silverstripe.com
|
|
|
|
* @param string $path Path with leading slash
|
|
|
|
* @param array|string $data Payload for the request
|
|
|
|
* @param string $name Parametername for the payload (only if passed as a string)
|
|
|
|
* @param string $query
|
|
|
|
* @param string $port
|
|
|
|
* @return string Raw HTTP-result including headers
|
|
|
|
*/
|
2008-08-20 08:31:12 +02:00
|
|
|
static function sendPostRequest($host, $path, $data, $name = null, $query = '', $port = 80, $getResponse = true) {
|
2008-04-09 13:20:34 +02:00
|
|
|
$socket = fsockopen($host, $port, $errno, $error);
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-04-09 13:20:34 +02:00
|
|
|
if(!$socket)
|
2007-07-19 12:40:28 +02:00
|
|
|
return $error;
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-04-09 13:20:34 +02:00
|
|
|
if(self::$userName && self::$password)
|
|
|
|
$auth = "Authorization: Basic " . base64_encode(self::$userName . ':' . self::$password) . "\r\n";
|
2008-08-20 08:31:12 +02:00
|
|
|
else
|
|
|
|
$auth = '';
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-04-09 13:20:34 +02:00
|
|
|
if($query)
|
2007-07-19 12:40:28 +02:00
|
|
|
$query = '?' . $query;
|
|
|
|
|
2008-04-09 13:20:34 +02:00
|
|
|
$dataStr = (is_array($data)) ? http_build_query($data) : $name . '=' . urlencode($data);
|
|
|
|
$request = "POST {$path}{$query} HTTP/1.1\r\nHost: $host\r\n{$auth}Content-Type: application/x-www-form-urlencoded\r\nContent-Length: " . strlen($dataStr) . "\r\n\r\n";
|
|
|
|
$request .= $dataStr . "\r\n\r\n";
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-04-09 13:20:34 +02:00
|
|
|
fwrite($socket, $request);
|
|
|
|
|
2008-08-20 08:31:12 +02:00
|
|
|
if($getResponse){
|
|
|
|
$response = stream_get_contents($socket);
|
|
|
|
return $response;
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-08-19 12:05:52 +02:00
|
|
|
protected static $cache_age = 0, $modification_date = null;
|
2007-09-16 17:54:16 +02:00
|
|
|
protected static $etag = null;
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Set the maximum age of this page in web caches, in seconds
|
|
|
|
*/
|
|
|
|
static function set_cache_age($age) {
|
|
|
|
self::$cache_age = $age;
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
static function register_modification_date($dateString) {
|
|
|
|
$timestamp = strtotime($dateString);
|
2007-09-16 17:54:16 +02:00
|
|
|
if($timestamp > self::$modification_date)
|
|
|
|
self::$modification_date = $timestamp;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
|
|
|
static function register_modification_timestamp($timestamp) {
|
|
|
|
if($timestamp > self::$modification_date)
|
|
|
|
self::$modification_date = $timestamp;
|
|
|
|
}
|
|
|
|
|
2007-09-16 17:54:16 +02:00
|
|
|
static function register_etag($etag) {
|
|
|
|
self::$etag = $etag;
|
|
|
|
}
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
2008-10-16 05:02:29 +02:00
|
|
|
* Add the appropriate caching headers to the response, including If-Modified-Since / 304 handling.
|
2007-09-16 17:53:35 +02:00
|
|
|
*
|
2008-10-16 05:02:29 +02:00
|
|
|
* @param HTTPResponse The HTTPResponse object to augment. Omitted the argument or passing a string is deprecated; in these
|
|
|
|
* cases, the headers are output directly.
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
2007-09-16 17:53:35 +02:00
|
|
|
static function add_cache_headers($body = null) {
|
2008-10-16 05:02:29 +02:00
|
|
|
// Validate argument
|
2008-10-16 07:29:32 +02:00
|
|
|
if($body && !($body instanceof HTTPResponse)) {
|
2008-10-16 05:02:29 +02:00
|
|
|
user_error("HTTP::add_cache_headers() must be passed an HTTPResponse object", E_USER_WARNING);
|
|
|
|
$body = null;
|
|
|
|
}
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
// Development sites have frequently changing templates; this can get stuffed up by the code
|
|
|
|
// below.
|
|
|
|
if(Director::isDev()) return;
|
2008-10-16 05:02:29 +02:00
|
|
|
|
|
|
|
// The headers have been sent and we don't have an HTTPResponse object to attach things to; no point in us trying.
|
|
|
|
if(headers_sent() && !$body) return;
|
|
|
|
|
|
|
|
// Popuplate $responseHeaders with all the headers that we want to build
|
|
|
|
$responseHeaders = array();
|
|
|
|
if(function_exists('apache_request_headers')) {
|
|
|
|
$requestHeaders = apache_request_headers();
|
|
|
|
if(isset($requestHeaders['X-Requested-With']) && $requestHeaders['X-Requested-With'] == 'XMLHttpRequest') self::$cache_age = 0;
|
|
|
|
// bdc: now we must check for DUMB IE6:
|
|
|
|
if(isset($requestHeaders['x-requested-with']) && $requestHeaders['x-requested-with'] == 'XMLHttpRequest') self::$cache_age = 0;
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-10-16 05:02:29 +02:00
|
|
|
if(self::$cache_age > 0) {
|
|
|
|
$responseHeaders["Cache-Control"] = "max-age=" . self::$cache_age . ", must-revalidate";
|
|
|
|
$responseHeaders["Pragma"] = "";
|
|
|
|
} else {
|
|
|
|
$responseHeaders["Cache-Control"] = "no-cache, max-age=0, must-revalidate";
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-10-16 05:02:29 +02:00
|
|
|
if(self::$modification_date && self::$cache_age > 0) {
|
|
|
|
$responseHeaders["Last-Modified"] =self::gmt_date(self::$modification_date);
|
|
|
|
|
|
|
|
// 304 response detection
|
|
|
|
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
|
|
|
$ifModifiedSince = strtotime(stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']));
|
|
|
|
if($ifModifiedSince >= self::$modification_date) {
|
|
|
|
if($body) {
|
|
|
|
$body->setStatusCode(304);
|
|
|
|
$body->setBody('');
|
|
|
|
} else {
|
|
|
|
header('HTTP/1.0 304 Not Modified');
|
|
|
|
die();
|
|
|
|
}
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2008-10-16 05:02:29 +02:00
|
|
|
$expires = time() + self::$cache_age;
|
|
|
|
$responseHeaders["Expires"] = self::gmt_date($expires);
|
|
|
|
}
|
2007-09-16 17:54:16 +02:00
|
|
|
|
2008-10-16 05:02:29 +02:00
|
|
|
if(self::$etag) {
|
|
|
|
$responseHeaders['ETag'] = self::$etag;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we've generated them, either output them or attach them to the HTTPResponse as appropriate
|
|
|
|
foreach($responseHeaders as $k => $v) {
|
|
|
|
if($body) $body->addHeader($k, $v);
|
2008-12-15 02:30:41 +01:00
|
|
|
else if(!headers_sent()) header("$k: $v");
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
2007-09-16 17:53:35 +02:00
|
|
|
* Return an {@link http://www.faqs.org/rfcs/rfc2822 RFC 2822} date in the
|
|
|
|
* GMT timezone (a timestamp is always in GMT: the number of seconds
|
|
|
|
* since January 1 1970 00:00:00 GMT)
|
|
|
|
*/
|
2007-07-19 12:40:28 +02:00
|
|
|
static function gmt_date($timestamp) {
|
2007-09-16 17:53:35 +02:00
|
|
|
return gmdate('D, d M Y H:i:s', $timestamp) . ' GMT';
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2008-12-15 02:30:41 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Return static variable cache_age in second
|
|
|
|
*/
|
|
|
|
static function get_cache_age() {
|
|
|
|
return self::$cache_age;
|
|
|
|
}
|
2007-09-16 17:53:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
2009-02-02 00:49:53 +01:00
|
|
|
?>
|