diff options
-rw-r--r-- | actions/ConnectivityAction.php | 8 | ||||
-rw-r--r-- | actions/DetectAction.php | 4 | ||||
-rw-r--r-- | actions/DisplayAction.php | 14 | ||||
-rw-r--r-- | actions/FrontpageAction.php | 142 | ||||
-rw-r--r-- | actions/ListAction.php | 5 | ||||
-rw-r--r-- | lib/ActionFactory.php | 38 | ||||
-rw-r--r-- | lib/ActionInterface.php | 2 | ||||
-rw-r--r-- | lib/RssBridge.php | 22 | ||||
-rw-r--r-- | lib/contents.php | 32 | ||||
-rw-r--r-- | static/style.css | 4 | ||||
-rw-r--r-- | templates/frontpage.html.php | 62 | ||||
-rw-r--r-- | tests/Actions/ListActionTest.php | 39 |
12 files changed, 147 insertions, 225 deletions
diff --git a/actions/ConnectivityAction.php b/actions/ConnectivityAction.php index 9ebd640c..19e6b9a6 100644 --- a/actions/ConnectivityAction.php +++ b/actions/ConnectivityAction.php @@ -38,8 +38,7 @@ class ConnectivityAction implements ActionInterface } if (!isset($request['bridge'])) { - print render_template('connectivity.html.php'); - return; + return render_template('connectivity.html.php'); } $bridgeClassName = $this->bridgeFactory->sanitizeBridgeName($request['bridge']); @@ -48,7 +47,7 @@ class ConnectivityAction implements ActionInterface throw new \InvalidArgumentException('Bridge name invalid!'); } - $this->reportBridgeConnectivity($bridgeClassName); + return $this->reportBridgeConnectivity($bridgeClassName); } private function reportBridgeConnectivity($bridgeClassName) @@ -80,7 +79,6 @@ class ConnectivityAction implements ActionInterface $retVal['successful'] = false; } - header('Content-Type: text/json'); - print Json::encode($retVal); + return new Response(Json::encode($retVal), 200, ['Content-Type' => 'text/json']); } } diff --git a/actions/DetectAction.php b/actions/DetectAction.php index 71060bb8..6524bdfe 100644 --- a/actions/DetectAction.php +++ b/actions/DetectAction.php @@ -44,8 +44,8 @@ class DetectAction implements ActionInterface $bridgeParams['bridge'] = $bridgeClassName; $bridgeParams['format'] = $format; - header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301); - return; + $url = '?action=display&' . http_build_query($bridgeParams); + return new Response('', 301, ['Location' => $url]); } throw new \Exception('No bridge found for given URL: ' . $targetURL); diff --git a/actions/DisplayAction.php b/actions/DisplayAction.php index 67c7c741..a0b23fd0 100644 --- a/actions/DisplayAction.php +++ b/actions/DisplayAction.php @@ -52,8 +52,7 @@ class DisplayAction implements ActionInterface if (! Configuration::getConfig('cache', 'custom_timeout')) { unset($request['_cache_timeout']); $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($request); - header('Location: ' . $uri, true, 301); - return; + return new Response('', 301, ['Location' => $uri]); } $cache_timeout = filter_var($request['_cache_timeout'], FILTER_VALIDATE_INT); @@ -116,8 +115,8 @@ class DisplayAction implements ActionInterface if ($mtime <= $stime) { // Cached data is older or same - header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304); - return; + $lastModified2 = gmdate('D, d M Y H:i:s ', $mtime) . 'GMT'; + return new Response('', 304, ['Last-Modified' => $lastModified2]); } } @@ -197,11 +196,12 @@ class DisplayAction implements ActionInterface $format->setExtraInfos($infos); $lastModified = $cache->getTime(); $format->setLastModified($lastModified); + $headers = []; if ($lastModified) { - header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $lastModified) . 'GMT'); + $headers['Last-Modified'] = gmdate('D, d M Y H:i:s ', $lastModified) . 'GMT'; } - header('Content-Type: ' . $format->getMimeType() . '; charset=' . $format->getCharset()); - print $format->stringify(); + $headers['Content-Type'] = $format->getMimeType() . '; charset=' . $format->getCharset(); + return new Response($format->stringify(), 200, $headers); } private static function createGithubIssueUrl($bridge, $e, string $message): string diff --git a/actions/FrontpageAction.php b/actions/FrontpageAction.php index 9980facf..4b9d7cd3 100644 --- a/actions/FrontpageAction.php +++ b/actions/FrontpageAction.php @@ -5,91 +5,7 @@ final class FrontpageAction implements ActionInterface public function execute(array $request) { $showInactive = (bool) ($request['show_inactive'] ?? null); - - $totalBridges = 0; - $totalActiveBridges = 0; - - $html = self::getHead() - . self::getHeader() - . self::getSearchbar() - . self::getBridges($showInactive, $totalBridges, $totalActiveBridges) - . self::getFooter($totalBridges, $totalActiveBridges, $showInactive); - - print $html; - } - - private static function getHead() - { - return <<<EOD -<!DOCTYPE html><html lang="en"> -<head> - <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> - <link href="static/style.css" rel="stylesheet"> - <link rel="icon" type="image/png" href="static/favicon.png"> - <script src="static/rss-bridge.js"></script> - <script> - document.addEventListener('DOMContentLoaded', rssbridge_toggle_bridge); - </script> -</head> -<body onload="rssbridge_list_search()"> -<div class="container"> -EOD; - } - - private static function getHeader() - { - $warning = ''; - - if (Debug::isEnabled()) { - if (!Debug::isSecure()) { - $warning .= <<<EOD -<section class="critical-warning">Warning : Debug mode is active from any location, - make sure only you can access RSS-Bridge.</section> -EOD; - } else { - $warning .= <<<EOD -<section class="warning">Warning : Debug mode is active from your IP address, - your requests will bypass the cache.</section> -EOD; - } - } - - return <<<EOD -<header> - <div class="logo"></div> - {$warning} -</header> -EOD; - } - - private static function getSearchbar() - { - $query = filter_input(INPUT_GET, 'q', \FILTER_SANITIZE_SPECIAL_CHARS); - - return <<<EOD -<section class="searchbar"> - <h3>Search</h3> - <input - type="text" - name="searchfield" - id="searchfield" - placeholder="Insert URL or bridge name" - onchange="rssbridge_list_search()" - onkeyup="rssbridge_list_search()" - value="{$query}" - > -</section> -EOD; - } - - private static function getBridges($showInactive, &$totalBridges, &$totalActiveBridges) - { - $body = ''; - $totalActiveBridges = 0; - $inactiveBridges = ''; + $activeBridges = 0; $bridgeFactory = new BridgeFactory(); $bridgeClassNames = $bridgeFactory->getBridgeClassNames(); @@ -97,58 +13,22 @@ EOD; $formatFactory = new FormatFactory(); $formats = $formatFactory->getFormatNames(); - $totalBridges = count($bridgeClassNames); - + $body = ''; foreach ($bridgeClassNames as $bridgeClassName) { if ($bridgeFactory->isWhitelisted($bridgeClassName)) { $body .= BridgeCard::displayBridgeCard($bridgeClassName, $formats); - $totalActiveBridges++; + $activeBridges++; } elseif ($showInactive) { - $inactiveBridges .= BridgeCard::displayBridgeCard($bridgeClassName, $formats, false) . PHP_EOL; - } - } - - $body .= $inactiveBridges; - - return $body; - } - - private static function getFooter($totalBridges, $totalActiveBridges, $showInactive) - { - $version = Configuration::getVersion(); - - $email = Configuration::getConfig('admin', 'email'); - $admininfo = ''; - if ($email) { - $admininfo = <<<EOD -<br /> -<span> - You may email the administrator of this RSS-Bridge instance - at <a href="mailto:{$email}">{$email}</a> -</span> -EOD; - } - - $inactive = ''; - - if ($totalActiveBridges !== $totalBridges) { - if ($showInactive) { - $inactive = '<a href="?show_inactive=0"><button class="small">Hide inactive bridges</button></a><br>'; - } else { - $inactive = '<a href="?show_inactive=1"><button class="small">Show inactive bridges</button></a><br>'; + $body .= BridgeCard::displayBridgeCard($bridgeClassName, $formats, false) . PHP_EOL; } } - return <<<EOD -<section class="footer"> - <a href="https://github.com/rss-bridge/rss-bridge">RSS-Bridge ~ Public Domain</a><br> - <p class="version">{$version}</p> - {$totalActiveBridges}/{$totalBridges} active bridges.<br> - {$inactive} - {$admininfo} -</section> -</div> -</body></html> -EOD; + return render(__DIR__ . '/../templates/frontpage.html.php', [ + 'admin_email' => Configuration::getConfig('admin', 'email'), + 'bridges' => $body, + 'active_bridges' => $activeBridges, + 'total_bridges' => count($bridgeClassNames), + 'show_inactive' => $showInactive, + ]); } } diff --git a/actions/ListAction.php b/actions/ListAction.php index e2b0ccb9..3e151690 100644 --- a/actions/ListAction.php +++ b/actions/ListAction.php @@ -36,10 +36,7 @@ class ListAction implements ActionInterface 'description' => $bridge->getDescription() ]; } - $list->total = count($list->bridges); - - header('Content-Type: application/json'); - print Json::encode($list); + return new Response(Json::encode($list), 200, ['Content-Type' => 'application/json']); } } diff --git a/lib/ActionFactory.php b/lib/ActionFactory.php deleted file mode 100644 index c97891b7..00000000 --- a/lib/ActionFactory.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php - -/** - * This file is part of RSS-Bridge, a PHP project capable of generating RSS and - * Atom feeds for websites that don't have one. - * - * For the full license information, please view the UNLICENSE file distributed - * with this source code. - * - * @package Core - * @license http://unlicense.org/ UNLICENSE - * @link https://github.com/rss-bridge/rss-bridge - */ - -class ActionFactory -{ - private $folder; - - public function __construct(string $folder = PATH_LIB_ACTIONS) - { - $this->folder = $folder; - } - - /** - * @param string $name The name of the action e.g. "Display", "List", or "Connectivity" - */ - public function create(string $name): ActionInterface - { - $name = strtolower($name) . 'Action'; - $name = implode(array_map('ucfirst', explode('-', $name))); - $filePath = $this->folder . $name . '.php'; - if (!file_exists($filePath)) { - throw new \Exception('Invalid action'); - } - $className = '\\' . $name; - return new $className(); - } -} diff --git a/lib/ActionInterface.php b/lib/ActionInterface.php index ea5020a3..4eb9cc65 100644 --- a/lib/ActionInterface.php +++ b/lib/ActionInterface.php @@ -22,7 +22,7 @@ interface ActionInterface * * Note: This function directly outputs data to the user. * - * @return void + * @return ?string */ public function execute(array $request); } diff --git a/lib/RssBridge.php b/lib/RssBridge.php index 3ff118f7..904a1cd4 100644 --- a/lib/RssBridge.php +++ b/lib/RssBridge.php @@ -60,6 +60,7 @@ final class RssBridge } }); + // Consider: ini_set('error_reporting', E_ALL & ~E_DEPRECATED); date_default_timezone_set(Configuration::getConfig('system', 'timezone')); $authenticationMiddleware = new AuthenticationMiddleware(); @@ -73,9 +74,22 @@ final class RssBridge } } - $actionFactory = new ActionFactory(); - $action = $request['action'] ?? 'Frontpage'; - $action = $actionFactory->create($action); - $action->execute($request); + $actionName = $request['action'] ?? 'Frontpage'; + $actionName = strtolower($actionName) . 'Action'; + $actionName = implode(array_map('ucfirst', explode('-', $actionName))); + + $filePath = __DIR__ . '/../actions/' . $actionName . '.php'; + if (!file_exists($filePath)) { + throw new \Exception(sprintf('Invalid action: %s', $actionName)); + } + $className = '\\' . $actionName; + $action = new $className(); + + $response = $action->execute($request); + if (is_string($response)) { + print $response; + } elseif ($response instanceof Response) { + $response->send(); + } } } diff --git a/lib/contents.php b/lib/contents.php index 33f20cc2..c339d3ca 100644 --- a/lib/contents.php +++ b/lib/contents.php @@ -44,6 +44,38 @@ final class Response '504' => 'Gateway Timeout', '505' => 'HTTP Version Not Supported' ]; + private string $body; + private int $code; + private array $headers; + + public function __construct( + string $body = '', + int $code = 200, + array $headers = [] + ) { + $this->body = $body; + $this->code = $code; + $this->headers = $headers; + } + + public function getBody() + { + return $this->body; + } + + public function getHeaders() + { + return $this->headers; + } + + public function send(): void + { + http_response_code($this->code); + foreach ($this->headers as $name => $value) { + header(sprintf('%s: %s', $name, $value)); + } + print $this->body; + } } /** diff --git a/static/style.css b/static/style.css index b5f3c00b..786e1526 100644 --- a/static/style.css +++ b/static/style.css @@ -78,12 +78,12 @@ header > div.logo { margin: auto; } -header > section.warning { +section.warning { background-color: #ffc600; color: #5f5f5f; } -header > section.critical-warning { +section.critical-warning { background-color: #cf3e3e; font-weight: bold; color: white; diff --git a/templates/frontpage.html.php b/templates/frontpage.html.php new file mode 100644 index 00000000..4560dc28 --- /dev/null +++ b/templates/frontpage.html.php @@ -0,0 +1,62 @@ +<script src="static/rss-bridge.js"></script> +<script> + document.addEventListener('DOMContentLoaded', rssbridge_toggle_bridge); + document.addEventListener('DOMContentLoaded', rssbridge_list_search); +</script> + +<?php if (Debug::isEnabled()): ?> + <?php if (!Debug::isSecure()): ?> + <section class="critical-warning"> + Warning : Debug mode is active from any location, + make sure only you can access RSS-Bridge. + </section> + <?php else: ?> + <section class="warning"> + Warning : Debug mode is active from your IP address, + your requests will bypass the cache. + </section> + <?php endif; ?> +<?php endif; ?> + +<section class="searchbar"> + <h3>Search</h3> + <input + type="text" + name="searchfield" + id="searchfield" + placeholder="Insert URL or bridge name" + onchange="rssbridge_list_search()" + onkeyup="rssbridge_list_search()" + value="" + > +</section> + +<?= raw($bridges) ?> + +<section class="footer"> + <a href="https://github.com/rss-bridge/rss-bridge">RSS-Bridge ~ Public Domain</a><br> + <p class="version"><?= e(Configuration::getVersion()) ?></p> + + <?= $active_bridges ?>/<?= $total_bridges ?> active bridges.<br> + + <?php if ($active_bridges !== $total_bridges): ?> + <?php if ($show_inactive): ?> + <a href="?show_inactive=0"> + <button class="small">Hide inactive bridges</button> + </a> + <br> + <?php else: ?> + <a href="?show_inactive=1"> + <button class="small">Show inactive bridges</button> + </a> + <br> + <?php endif; ?> + <?php endif; ?> + + <?php if ($admin_email): ?> + <span> + You may email the administrator of this RSS-Bridge instance at + <a href="mailto:<?= e($admin_email) ?>"><?= e($admin_email) ?></a> + </span> + <?php endif; ?> +</section> diff --git a/tests/Actions/ListActionTest.php b/tests/Actions/ListActionTest.php index f3d06db6..4373be76 100644 --- a/tests/Actions/ListActionTest.php +++ b/tests/Actions/ListActionTest.php @@ -2,36 +2,26 @@ namespace RssBridge\Tests\Actions; -use ActionFactory; use BridgeFactory; use PHPUnit\Framework\TestCase; class ListActionTest extends TestCase { - private $data; - - /** - * @runInSeparateProcess - * @requires function xdebug_get_headers - */ public function testHeaders() { - $this->initAction(); - - $this->assertContains( - 'Content-Type: application/json', - xdebug_get_headers() - ); + $action = new \ListAction(); + $response = $action->execute([]); + $headers = $response->getHeaders(); + $this->assertSame($headers['Content-Type'], 'application/json'); } - /** - * @runInSeparateProcess - */ public function testOutput() { - $this->initAction(); + $action = new \ListAction(); + $response = $action->execute([]); + $data = $response->getBody(); - $items = json_decode($this->data, true); + $items = json_decode($data, true); $this->assertNotNull($items, 'invalid JSON output: ' . json_last_error_msg()); @@ -77,17 +67,4 @@ class ListActionTest extends TestCase $this->assertContains($bridge['status'], $allowedStatus, 'Invalid status value'); } } - - private function initAction() - { - $actionFactory = new ActionFactory(); - - $action = $actionFactory->create('list'); - - ob_start(); - $action->execute([]); - $this->data = ob_get_contents(); - ob_clean(); - ob_end_flush(); - } } |