2013-08-09 18:57:25 +02:00
|
|
|
<?php
|
|
|
|
/*
|
|
|
|
TODO :
|
2013-08-11 13:30:41 +02:00
|
|
|
- factorize the annotation system
|
2016-09-10 21:01:02 +02:00
|
|
|
- factorize to adapter : Format, Bridge, Cache(actually code is almost the same)
|
2013-08-11 13:30:41 +02:00
|
|
|
- implement annotation cache for entrance page
|
|
|
|
- Cache : I think logic must be change as least to avoid to reconvert object from json in FileCache case.
|
|
|
|
- add namespace to avoid futur problem ?
|
|
|
|
- see FIXME mentions in the code
|
|
|
|
- implement header('X-Cached-Version: '.date(DATE_ATOM, filemtime($cachefile)));
|
2013-08-09 18:57:25 +02:00
|
|
|
*/
|
|
|
|
|
2018-05-29 11:52:17 +02:00
|
|
|
if(!file_exists('config.default.ini.php'))
|
|
|
|
die('The default configuration file "config.default.ini.php" is missing!');
|
|
|
|
|
|
|
|
$config = parse_ini_file('config.default.ini.php', true, INI_SCANNER_TYPED);
|
2018-06-15 21:02:06 +02:00
|
|
|
if(!$config)
|
|
|
|
die('Error parsing config.default.ini.php');
|
2018-05-29 11:52:17 +02:00
|
|
|
|
|
|
|
if(file_exists('config.ini.php')) {
|
|
|
|
// Replace default configuration with custom settings
|
|
|
|
foreach(parse_ini_file('config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
|
|
|
|
foreach($section as $key => $value) {
|
|
|
|
// Skip unknown sections and keys
|
|
|
|
if(array_key_exists($header, $config) && array_key_exists($key, $config[$header])) {
|
|
|
|
$config[$header][$key] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!is_string($config['proxy']['url']))
|
|
|
|
die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
|
|
|
|
|
|
|
|
if(!empty($config['proxy']['url']))
|
|
|
|
define('PROXY_URL', $config['proxy']['url']);
|
|
|
|
|
|
|
|
if(!is_bool($config['proxy']['by_bridge']))
|
|
|
|
die('Parameter [proxy] => "by_bridge" is not a valid Boolean! Please check "config.ini.php"!');
|
2016-11-05 13:02:48 +01:00
|
|
|
|
2018-05-29 11:52:17 +02:00
|
|
|
define('PROXY_BYBRIDGE', $config['proxy']['by_bridge']);
|
2015-11-27 15:20:33 +01:00
|
|
|
|
2018-05-29 11:52:17 +02:00
|
|
|
if(!is_string($config['proxy']['name']))
|
|
|
|
die('Parameter [proxy] => "name" is not a valid string! Please check "config.ini.php"!');
|
|
|
|
|
|
|
|
define('PROXY_NAME', $config['proxy']['name']);
|
|
|
|
|
|
|
|
if(!is_bool($config['cache']['custom_timeout']))
|
|
|
|
die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
|
|
|
|
|
|
|
|
define('CUSTOM_CACHE_TIMEOUT', $config['cache']['custom_timeout']);
|
|
|
|
|
|
|
|
// Defines the minimum required PHP version for RSS-Bridge
|
|
|
|
define('PHP_VERSION_REQUIRED', '5.6.0');
|
2018-03-14 18:06:36 +01:00
|
|
|
|
2013-08-11 13:30:41 +02:00
|
|
|
date_default_timezone_set('UTC');
|
|
|
|
error_reporting(0);
|
2016-03-03 16:26:07 +01:00
|
|
|
|
2016-10-08 16:21:00 +02:00
|
|
|
// Specify directory for cached files (using FileCache)
|
|
|
|
define('CACHE_DIR', __DIR__ . '/cache');
|
|
|
|
|
2017-08-05 15:17:52 +02:00
|
|
|
// Specify path for whitelist file
|
|
|
|
define('WHITELIST_FILE', __DIR__ . '/whitelist.txt');
|
|
|
|
|
2017-09-25 19:14:02 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
Move the CLI arguments to the $_GET array, in order to be able to use
|
|
|
|
rss-bridge from the command line
|
|
|
|
*/
|
|
|
|
parse_str(implode('&', array_slice($argv, 1)), $cliArgs);
|
|
|
|
$params = array_merge($_GET, $cliArgs);
|
|
|
|
|
2016-06-12 22:12:49 +02:00
|
|
|
/*
|
|
|
|
Create a file named 'DEBUG' for enabling debug mode.
|
2017-08-05 15:35:47 +02:00
|
|
|
For further security, you may put whitelisted IP addresses in the file,
|
|
|
|
one IP per line. Empty file allows anyone(!).
|
|
|
|
Debugging allows displaying PHP error messages and bypasses the cache: this
|
|
|
|
can allow a malicious client to retrieve data about your server and hammer
|
|
|
|
a provider throught your rss-bridge instance.
|
2016-06-12 22:12:49 +02:00
|
|
|
*/
|
2017-07-29 19:28:00 +02:00
|
|
|
if(file_exists('DEBUG')) {
|
2016-09-10 21:01:02 +02:00
|
|
|
$debug_whitelist = trim(file_get_contents('DEBUG'));
|
2017-08-05 15:35:47 +02:00
|
|
|
|
|
|
|
$debug_enabled = empty($debug_whitelist)
|
|
|
|
|| in_array($_SERVER['REMOTE_ADDR'], explode("\n", $debug_whitelist));
|
|
|
|
|
2017-07-29 19:28:00 +02:00
|
|
|
if($debug_enabled) {
|
2016-09-10 21:01:02 +02:00
|
|
|
ini_set('display_errors', '1');
|
|
|
|
error_reporting(E_ALL);
|
2016-10-02 17:16:21 +02:00
|
|
|
define('DEBUG', true);
|
2016-09-10 21:01:02 +02:00
|
|
|
}
|
2016-03-03 16:26:07 +01:00
|
|
|
}
|
2013-08-09 18:57:25 +02:00
|
|
|
|
2015-12-04 10:19:05 +01:00
|
|
|
require_once __DIR__ . '/lib/RssBridge.php';
|
|
|
|
|
2016-11-05 13:02:48 +01:00
|
|
|
// Check PHP version
|
|
|
|
if(version_compare(PHP_VERSION, PHP_VERSION_REQUIRED) === -1)
|
|
|
|
die('RSS-Bridge requires at least PHP version ' . PHP_VERSION_REQUIRED . '!');
|
|
|
|
|
2014-05-28 00:30:12 +02:00
|
|
|
// extensions check
|
2016-09-10 21:01:02 +02:00
|
|
|
if(!extension_loaded('openssl'))
|
2014-05-28 00:30:12 +02:00
|
|
|
die('"openssl" extension not loaded. Please check "php.ini"');
|
|
|
|
|
2016-11-05 13:05:28 +01:00
|
|
|
if(!extension_loaded('libxml'))
|
|
|
|
die('"libxml" extension not loaded. Please check "php.ini"');
|
|
|
|
|
2018-03-07 19:06:42 +01:00
|
|
|
if(!extension_loaded('mbstring'))
|
|
|
|
die('"mbstring" extension not loaded. Please check "php.ini"');
|
|
|
|
|
2018-04-04 20:02:40 +02:00
|
|
|
if(!extension_loaded('simplexml'))
|
|
|
|
die('"simplexml" extension not loaded. Please check "php.ini"');
|
|
|
|
|
2018-03-25 13:11:15 +02:00
|
|
|
if(!extension_loaded('curl'))
|
|
|
|
die('"curl" extension not loaded. Please check "php.ini"');
|
|
|
|
|
2016-11-05 13:09:20 +01:00
|
|
|
// configuration checks
|
|
|
|
if(ini_get('allow_url_fopen') !== "1")
|
|
|
|
die('"allow_url_fopen" is not set to "1". Please check "php.ini');
|
|
|
|
|
2017-08-05 15:19:15 +02:00
|
|
|
// Check cache folder permissions (write permissions required)
|
|
|
|
if(!is_writable(CACHE_DIR))
|
|
|
|
die('RSS-Bridge does not have write permissions for ' . CACHE_DIR . '!');
|
|
|
|
|
|
|
|
// Check whitelist file permissions (only in DEBUG mode)
|
2017-08-12 19:15:16 +02:00
|
|
|
if(!file_exists(WHITELIST_FILE) && !is_writable(dirname(WHITELIST_FILE)))
|
2017-08-05 15:19:15 +02:00
|
|
|
die('RSS-Bridge does not have write permissions for ' . WHITELIST_FILE . '!');
|
|
|
|
|
2014-05-14 21:34:07 +02:00
|
|
|
// FIXME : beta test UA spoofing, please report any blacklisting by PHP-fopen-unfriendly websites
|
2017-03-09 23:27:14 +01:00
|
|
|
|
|
|
|
$userAgent = 'Mozilla/5.0(X11; Linux x86_64; rv:30.0)';
|
|
|
|
$userAgent .= ' Gecko/20121202 Firefox/30.0(rss-bridge/0.1;';
|
|
|
|
$userAgent .= '+https://github.com/RSS-Bridge/rss-bridge)';
|
|
|
|
|
|
|
|
ini_set('user_agent', $userAgent);
|
2015-11-30 10:47:16 +01:00
|
|
|
|
2014-05-25 19:56:59 +02:00
|
|
|
// default whitelist
|
|
|
|
$whitelist_default = array(
|
2017-08-05 15:46:16 +02:00
|
|
|
'BandcampBridge',
|
|
|
|
'CryptomeBridge',
|
|
|
|
'DansTonChatBridge',
|
|
|
|
'DuckDuckGoBridge',
|
|
|
|
'FacebookBridge',
|
|
|
|
'FlickrExploreBridge',
|
|
|
|
'GooglePlusPostBridge',
|
|
|
|
'GoogleSearchBridge',
|
|
|
|
'IdenticaBridge',
|
|
|
|
'InstagramBridge',
|
|
|
|
'OpenClassroomsBridge',
|
|
|
|
'PinterestBridge',
|
|
|
|
'ScmbBridge',
|
|
|
|
'TwitterBridge',
|
|
|
|
'WikipediaBridge',
|
|
|
|
'YoutubeBridge');
|
2014-05-25 19:56:59 +02:00
|
|
|
|
2016-09-10 21:01:02 +02:00
|
|
|
try {
|
|
|
|
|
|
|
|
Bridge::setDir(__DIR__ . '/bridges/');
|
|
|
|
Format::setDir(__DIR__ . '/formats/');
|
|
|
|
Cache::setDir(__DIR__ . '/caches/');
|
|
|
|
|
2017-08-05 15:17:52 +02:00
|
|
|
if(!file_exists(WHITELIST_FILE)) {
|
2017-03-08 11:56:39 +01:00
|
|
|
$whitelist_selection = $whitelist_default;
|
|
|
|
$whitelist_write = implode("\n", $whitelist_default);
|
2017-08-05 15:17:52 +02:00
|
|
|
file_put_contents(WHITELIST_FILE, $whitelist_write);
|
2017-03-08 11:56:39 +01:00
|
|
|
} else {
|
|
|
|
|
2017-08-05 15:17:52 +02:00
|
|
|
$whitelist_file_content = file_get_contents(WHITELIST_FILE);
|
2017-03-08 11:56:39 +01:00
|
|
|
if($whitelist_file_content != "*\n") {
|
|
|
|
$whitelist_selection = explode("\n", $whitelist_file_content);
|
|
|
|
} else {
|
|
|
|
$whitelist_selection = Bridge::listBridges();
|
|
|
|
}
|
2017-08-05 23:30:24 +02:00
|
|
|
|
|
|
|
// Prepare for case-insensitive match
|
|
|
|
$whitelist_selection = array_map('strtolower', $whitelist_selection);
|
2017-03-08 11:56:39 +01:00
|
|
|
}
|
|
|
|
|
2017-10-12 18:14:34 +02:00
|
|
|
$action = array_key_exists('action', $params) ? $params['action'] : null;
|
|
|
|
$bridge = array_key_exists('bridge', $params) ? $params['bridge'] : null;
|
2016-09-10 21:01:02 +02:00
|
|
|
|
2017-07-29 19:28:00 +02:00
|
|
|
if($action === 'display' && !empty($bridge)) {
|
2016-09-10 21:01:02 +02:00
|
|
|
// DEPRECATED: 'nameBridge' scheme is replaced by 'name' in bridge parameter values
|
|
|
|
// this is to keep compatibility until futher complete removal
|
2017-07-29 19:28:00 +02:00
|
|
|
if(($pos = strpos($bridge, 'Bridge')) === (strlen($bridge) - strlen('Bridge'))) {
|
2016-09-10 21:01:02 +02:00
|
|
|
$bridge = substr($bridge, 0, $pos);
|
|
|
|
}
|
|
|
|
|
2017-09-25 19:14:02 +02:00
|
|
|
$format = $params['format']
|
2017-08-28 20:45:04 +02:00
|
|
|
or returnClientError('You must specify a format!');
|
2013-08-09 18:57:25 +02:00
|
|
|
|
2016-09-10 21:01:02 +02:00
|
|
|
// DEPRECATED: 'nameFormat' scheme is replaced by 'name' in format parameter values
|
|
|
|
// this is to keep compatibility until futher complete removal
|
2017-07-29 19:28:00 +02:00
|
|
|
if(($pos = strpos($format, 'Format')) === (strlen($format) - strlen('Format'))) {
|
2016-09-10 21:01:02 +02:00
|
|
|
$format = substr($format, 0, $pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
// whitelist control
|
2017-08-05 23:30:24 +02:00
|
|
|
if(!Bridge::isWhitelisted($whitelist_selection, strtolower($bridge))) {
|
2016-09-10 21:01:02 +02:00
|
|
|
throw new \HttpException('This bridge is not whitelisted', 401);
|
|
|
|
die;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Data retrieval
|
|
|
|
$bridge = Bridge::create($bridge);
|
2016-10-02 16:43:08 +02:00
|
|
|
|
2017-10-12 18:14:34 +02:00
|
|
|
$noproxy = array_key_exists('_noproxy', $params) && filter_var($params['_noproxy'], FILTER_VALIDATE_BOOLEAN);
|
2017-07-29 19:28:00 +02:00
|
|
|
if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) {
|
2017-02-14 17:28:07 +01:00
|
|
|
define('NOPROXY', true);
|
2016-09-25 23:22:33 +02:00
|
|
|
}
|
2016-09-10 21:01:02 +02:00
|
|
|
|
2018-03-14 18:06:36 +01:00
|
|
|
// Custom cache timeout
|
|
|
|
$cache_timeout = -1;
|
|
|
|
if(array_key_exists('_cache_timeout', $params)) {
|
|
|
|
if(!CUSTOM_CACHE_TIMEOUT) {
|
|
|
|
throw new \HttpException('This server doesn\'t support "_cache_timeout"!');
|
|
|
|
}
|
|
|
|
|
|
|
|
$cache_timeout = filter_var($params['_cache_timeout'], FILTER_VALIDATE_INT);
|
|
|
|
}
|
|
|
|
|
2016-10-08 15:28:36 +02:00
|
|
|
// Initialize cache
|
|
|
|
$cache = Cache::create('FileCache');
|
2016-10-08 16:21:00 +02:00
|
|
|
$cache->setPath(CACHE_DIR);
|
2016-10-08 16:18:10 +02:00
|
|
|
$cache->purgeCache(86400); // 24 hours
|
2016-10-08 15:28:36 +02:00
|
|
|
$cache->setParameters($params);
|
|
|
|
|
2016-10-20 22:03:23 +02:00
|
|
|
unset($params['action']);
|
|
|
|
unset($params['bridge']);
|
|
|
|
unset($params['format']);
|
|
|
|
unset($params['_noproxy']);
|
2018-03-14 18:06:36 +01:00
|
|
|
unset($params['_cache_timeout']);
|
2016-10-20 22:03:23 +02:00
|
|
|
|
2016-10-08 15:28:36 +02:00
|
|
|
// Load cache & data
|
2017-07-29 19:16:16 +02:00
|
|
|
try {
|
|
|
|
$bridge->setCache($cache);
|
2018-03-14 18:06:36 +01:00
|
|
|
$bridge->setCacheTimeout($cache_timeout);
|
2017-07-29 19:16:16 +02:00
|
|
|
$bridge->setDatas($params);
|
2018-04-04 20:02:40 +02:00
|
|
|
} catch(Error $e) {
|
2017-08-06 12:47:48 +02:00
|
|
|
http_response_code($e->getCode());
|
2017-07-29 19:16:16 +02:00
|
|
|
header('Content-Type: text/html');
|
|
|
|
die(buildBridgeException($e, $bridge));
|
2018-04-04 20:02:40 +02:00
|
|
|
} catch(Exception $e) {
|
2018-04-06 20:34:44 +02:00
|
|
|
http_response_code($e->getCode());
|
|
|
|
header('Content-Type: text/html');
|
|
|
|
die(buildBridgeException($e, $bridge));
|
2017-07-29 19:16:16 +02:00
|
|
|
}
|
2016-09-10 21:05:29 +02:00
|
|
|
|
2016-09-10 21:01:02 +02:00
|
|
|
// Data transformation
|
|
|
|
try {
|
|
|
|
$format = Format::create($format);
|
2016-11-29 01:48:59 +01:00
|
|
|
$format->setItems($bridge->getItems());
|
|
|
|
$format->setExtraInfos($bridge->getExtraInfos());
|
|
|
|
$format->display();
|
2018-04-04 20:02:40 +02:00
|
|
|
} catch(Error $e) {
|
2017-08-06 12:47:48 +02:00
|
|
|
http_response_code($e->getCode());
|
2017-07-29 19:16:16 +02:00
|
|
|
header('Content-Type: text/html');
|
|
|
|
die(buildTransformException($e, $bridge));
|
2018-04-06 20:34:44 +02:00
|
|
|
} catch(Exception $e) {
|
|
|
|
http_response_code($e->getCode());
|
|
|
|
header('Content-Type: text/html');
|
|
|
|
die(buildBridgeException($e, $bridge));
|
|
|
|
}
|
2017-07-29 19:16:16 +02:00
|
|
|
|
2016-09-10 21:01:02 +02:00
|
|
|
die;
|
|
|
|
}
|
2017-07-29 19:28:00 +02:00
|
|
|
} catch(HttpException $e) {
|
2017-08-06 12:47:48 +02:00
|
|
|
http_response_code($e->getCode());
|
2016-09-10 21:01:02 +02:00
|
|
|
header('Content-Type: text/plain');
|
|
|
|
die($e->getMessage());
|
2017-07-29 19:28:00 +02:00
|
|
|
} catch(\Exception $e) {
|
2016-09-10 21:01:02 +02:00
|
|
|
die($e->getMessage());
|
2013-08-09 18:57:25 +02:00
|
|
|
}
|
|
|
|
|
2013-08-11 13:30:41 +02:00
|
|
|
$formats = Format::searchInformation();
|
2015-11-01 12:17:36 +01:00
|
|
|
|
2013-08-09 18:57:25 +02:00
|
|
|
?>
|
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
2013-08-11 22:32:25 +02:00
|
|
|
<head>
|
2016-09-10 21:01:02 +02:00
|
|
|
<meta charset="utf-8">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
|
<meta name="description" content="Rss-bridge" />
|
|
|
|
<title>RSS-Bridge</title>
|
2017-03-21 21:31:10 +01:00
|
|
|
<link href="static/style.css" rel="stylesheet">
|
|
|
|
<script src="static/search.js"></script>
|
2017-08-11 19:39:13 +02:00
|
|
|
<script src="static/select.js"></script>
|
2017-03-21 21:31:10 +01:00
|
|
|
<noscript>
|
|
|
|
<style>
|
|
|
|
.searchbar {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</noscript>
|
2013-08-11 22:32:25 +02:00
|
|
|
</head>
|
2013-08-11 13:30:41 +02:00
|
|
|
|
2017-03-21 21:31:10 +01:00
|
|
|
<body onload="search()">
|
2016-10-02 18:43:20 +02:00
|
|
|
<?php
|
|
|
|
$status = '';
|
2017-07-29 19:28:00 +02:00
|
|
|
if(defined('DEBUG') && DEBUG === true) {
|
2016-10-02 18:43:20 +02:00
|
|
|
$status .= 'debug mode active';
|
|
|
|
}
|
2013-08-11 13:30:41 +02:00
|
|
|
|
2017-08-11 17:43:15 +02:00
|
|
|
$query = filter_input(INPUT_GET, 'q');
|
|
|
|
|
2016-10-02 18:43:20 +02:00
|
|
|
echo <<<EOD
|
2016-09-10 21:01:02 +02:00
|
|
|
<header>
|
|
|
|
<h1>RSS-Bridge</h1>
|
|
|
|
<h2>·Reconnecting the Web·</h2>
|
2016-10-02 18:43:20 +02:00
|
|
|
<p class="status">{$status}</p>
|
2016-09-10 21:01:02 +02:00
|
|
|
</header>
|
2017-03-21 21:31:10 +01:00
|
|
|
<section class="searchbar">
|
|
|
|
<h3>Search</h3>
|
|
|
|
<input type="text" name="searchfield"
|
|
|
|
id="searchfield" placeholder="Enter the bridge you want to search for"
|
2017-08-11 17:43:15 +02:00
|
|
|
onchange="search()" onkeyup="search()" value="{$query}">
|
2017-03-21 21:31:10 +01:00
|
|
|
</section>
|
|
|
|
|
2016-10-02 18:43:20 +02:00
|
|
|
EOD;
|
|
|
|
|
2016-09-10 21:01:02 +02:00
|
|
|
$activeFoundBridgeCount = 0;
|
|
|
|
$showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
|
2014-07-20 17:11:11 +02:00
|
|
|
$inactiveBridges = '';
|
2015-11-05 21:26:48 +01:00
|
|
|
$bridgeList = Bridge::listBridges();
|
2017-07-29 19:28:00 +02:00
|
|
|
foreach($bridgeList as $bridgeName) {
|
2017-08-05 23:30:24 +02:00
|
|
|
if(Bridge::isWhitelisted($whitelist_selection, strtolower($bridgeName))) {
|
2016-09-25 23:58:52 +02:00
|
|
|
echo displayBridgeCard($bridgeName, $formats);
|
2016-09-10 21:01:02 +02:00
|
|
|
$activeFoundBridgeCount++;
|
|
|
|
} elseif($showInactive) {
|
2014-07-20 17:11:11 +02:00
|
|
|
// inactive bridges
|
2016-09-25 23:58:52 +02:00
|
|
|
$inactiveBridges .= displayBridgeCard($bridgeName, $formats, false) . PHP_EOL;
|
2014-07-20 17:11:11 +02:00
|
|
|
}
|
|
|
|
}
|
2016-08-02 22:16:43 +02:00
|
|
|
echo $inactiveBridges;
|
2014-07-20 16:49:53 +02:00
|
|
|
?>
|
2017-03-21 21:31:10 +01:00
|
|
|
<section class="footer">
|
2018-06-10 22:14:49 +02:00
|
|
|
<a href="https://github.com/RSS-Bridge/rss-bridge">RSS-Bridge 2018-06-10 ~ Public Domain</a><br />
|
2016-08-02 22:16:43 +02:00
|
|
|
<?= $activeFoundBridgeCount; ?>/<?= count($bridgeList) ?> active bridges. <br />
|
2016-09-10 21:01:02 +02:00
|
|
|
<?php
|
2017-07-29 19:28:00 +02:00
|
|
|
if($activeFoundBridgeCount !== count($bridgeList)) {
|
2016-09-10 21:01:02 +02:00
|
|
|
// FIXME: This should be done in pure CSS
|
|
|
|
if(!$showInactive)
|
|
|
|
echo '<a href="?show_inactive=1"><button class="small">Show inactive bridges</button></a><br />';
|
|
|
|
else
|
|
|
|
echo '<a href="?show_inactive=0"><button class="small">Hide inactive bridges</button></a><br />';
|
|
|
|
}
|
|
|
|
?>
|
|
|
|
</section>
|
|
|
|
</body>
|
2014-05-14 21:34:07 +02:00
|
|
|
</html>
|