From: ArthurHoaro Date: Mon, 18 May 2020 15:17:36 +0000 (+0200) Subject: RSS/ATOM feeds: process through Slim controller X-Git-Tag: v0.12.0-beta~4^2~43 X-Git-Url: https://git.immae.eu/?p=github%2Fshaarli%2FShaarli.git;a=commitdiff_plain;h=7b2ba6ef820335df682fbe3dcfaceef3a62cf4a5 RSS/ATOM feeds: process through Slim controller --- diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php index 199f3f67..84406979 100644 --- a/application/container/ContainerBuilder.php +++ b/application/container/ContainerBuilder.php @@ -7,6 +7,7 @@ namespace Shaarli\Container; use Shaarli\Bookmark\BookmarkFileService; use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Config\ConfigManager; +use Shaarli\Feed\FeedBuilder; use Shaarli\Formatter\FormatterFactory; use Shaarli\History; use Shaarli\Plugin\PluginManager; @@ -100,6 +101,15 @@ class ContainerBuilder ); }; + $container['feedBuilder'] = function (ShaarliContainer $container): FeedBuilder { + return new FeedBuilder( + $container->bookmarkService, + $container->formatterFactory->getFormatter(), + $container->environment, + $container->loginManager->isLoggedIn() + ); + }; + return $container; } } diff --git a/application/container/ShaarliContainer.php b/application/container/ShaarliContainer.php index 3995f669..deb07197 100644 --- a/application/container/ShaarliContainer.php +++ b/application/container/ShaarliContainer.php @@ -6,6 +6,7 @@ namespace Shaarli\Container; use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Config\ConfigManager; +use Shaarli\Feed\FeedBuilder; use Shaarli\Formatter\FormatterFactory; use Shaarli\History; use Shaarli\Plugin\PluginManager; @@ -29,6 +30,7 @@ use Slim\Container; * @property PluginManager $pluginManager * @property FormatterFactory $formatterFactory * @property PageCacheManager $pageCacheManager + * @property FeedBuilder $feedBuilder */ class ShaarliContainer extends Container { diff --git a/application/feed/FeedBuilder.php b/application/feed/FeedBuilder.php index bcf27c2c..c97ae1ea 100644 --- a/application/feed/FeedBuilder.php +++ b/application/feed/FeedBuilder.php @@ -78,7 +78,7 @@ class FeedBuilder * @param array $serverInfo $_SERVER. * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise. */ - public function __construct($linkDB, $formatter, array $serverInfo, $isLoggedIn) + public function __construct($linkDB, $formatter, $serverInfo, $isLoggedIn) { $this->linkDB = $linkDB; $this->formatter = $formatter; diff --git a/application/front/controllers/FeedController.php b/application/front/controllers/FeedController.php new file mode 100644 index 00000000..78d826d9 --- /dev/null +++ b/application/front/controllers/FeedController.php @@ -0,0 +1,79 @@ +processRequest(FeedBuilder::$FEED_ATOM, $request, $response); + } + + public function rss(Request $request, Response $response): Response + { + return $this->processRequest(FeedBuilder::$FEED_RSS, $request, $response); + } + + protected function processRequest(string $feedType, Request $request, Response $response): Response + { + $response = $response->withHeader('Content-Type', 'application/'. $feedType .'+xml; charset=utf-8'); + + $pageUrl = page_url($this->container->environment); + $cache = $this->container->pageCacheManager->getCachePage($pageUrl); + + $cached = $cache->cachedVersion(); + if (!empty($cached)) { + return $response->write($cached); + } + + // Generate data. + $this->container->feedBuilder->setLocale(strtolower(setlocale(LC_COLLATE, 0))); + $this->container->feedBuilder->setHideDates($this->container->conf->get('privacy.hide_timestamps', false)); + $this->container->feedBuilder->setUsePermalinks( + null !== $request->getParam('permalinks') || !$this->container->conf->get('feed.rss_permalinks') + ); + + $data = $this->container->feedBuilder->buildData($feedType, $request->getParams()); + + $this->executeHooks($data, $feedType); + $this->assignAllView($data); + + $content = $this->render('feed.'. $feedType); + + $cache->cache($content); + + return $response->write($content); + } + + /** + * @param mixed[] $data Template data + * + * @return mixed[] Template data after active plugins hook execution. + */ + protected function executeHooks(array $data, string $feedType): array + { + $this->container->pluginManager->executeHooks( + 'render_feed', + $data, + [ + 'loggedin' => $this->container->loginManager->isLoggedIn(), + 'target' => $feedType, + ] + ); + + return $data; + } +} diff --git a/application/front/controllers/ShaarliController.php b/application/front/controllers/ShaarliController.php index 2b828588..0c5d363e 100644 --- a/application/front/controllers/ShaarliController.php +++ b/application/front/controllers/ShaarliController.php @@ -30,6 +30,20 @@ abstract class ShaarliController return $this; } + /** + * Assign variables to RainTPL template through the PageBuilder. + * + * @param mixed $data Values to assign to the template and their keys + */ + protected function assignAllView(array $data): self + { + foreach ($data as $key => $value) { + $this->assignView($key, $value); + } + + return $this; + } + protected function render(string $template): string { $this->assignView('linkcount', $this->container->bookmarkService->count(BookmarkFilter::$ALL)); diff --git a/doc/md/RSS-feeds.md b/doc/md/RSS-feeds.md index 71f4d7ee..cdfb8c78 100644 --- a/doc/md/RSS-feeds.md +++ b/doc/md/RSS-feeds.md @@ -1,14 +1,14 @@ ### Feeds options -Feeds are available in ATOM with `?do=atom` and RSS with `do=RSS`. +Feeds are available in ATOM with `/feed-atom` and RSS with `/feed-rss`. Options: - You can use `permalinks` in the feed URL to get permalink to Shaares instead of direct link to shaared URL. - - E.G. `https://my.shaarli.domain/?do=atom&permalinks`. + - E.G. `https://my.shaarli.domain/feed-atom?permalinks`. - You can use `nb` parameter in the feed URL to specify the number of Shaares you want in a feed (default if not specified: `50`). The keyword `all` is available if you want everything. - - `https://my.shaarli.domain/?do=atom&permalinks&nb=42` - - `https://my.shaarli.domain/?do=atom&permalinks&nb=all` + - `https://my.shaarli.domain/feed-atom?permalinks&nb=42` + - `https://my.shaarli.domain/feed-atom?permalinks&nb=all` ### RSS Feeds or Picture Wall for a specific search/tag diff --git a/index.php b/index.php index 980e7704..c3e0a5bf 100644 --- a/index.php +++ b/index.php @@ -432,45 +432,8 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM // ATOM and RSS feed. if ($targetPage == Router::$PAGE_FEED_ATOM || $targetPage == Router::$PAGE_FEED_RSS) { $feedType = $targetPage == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM; - header('Content-Type: application/'. $feedType .'+xml; charset=utf-8'); - - // Cache system - $query = $_SERVER['QUERY_STRING']; - $cache = new CachedPage( - $conf->get('resource.page_cache'), - page_url($_SERVER), - startsWith($query, 'do='. $targetPage) && !$loginManager->isLoggedIn() - ); - $cached = $cache->cachedVersion(); - if (!empty($cached)) { - echo $cached; - exit; - } - $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); - // Generate data. - $feedGenerator = new FeedBuilder( - $bookmarkService, - $factory->getFormatter(), - $_SERVER, - $loginManager->isLoggedIn() - ); - $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); - $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !$loginManager->isLoggedIn()); - $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks')); - $data = $feedGenerator->buildData($feedType, $_GET); - - // Process plugin hook. - $pluginManager->executeHooks('render_feed', $data, array( - 'loggedin' => $loginManager->isLoggedIn(), - 'target' => $targetPage, - )); - - // Render the template. - $PAGE->assignAll($data); - $PAGE->renderPage('feed.'. $feedType); - $cache->cache(ob_get_contents()); - ob_end_flush(); + header('Location: ./feed-'. $feedType .'?'. http_build_query($_GET)); exit; } @@ -1610,6 +1573,8 @@ $app->group('', function () { $this->get('/tag-list', '\Shaarli\Front\Controller\TagCloudController:list')->setName('taglist'); $this->get('/daily', '\Shaarli\Front\Controller\DailyController:index')->setName('daily'); $this->get('/daily-rss', '\Shaarli\Front\Controller\DailyController:rss')->setName('dailyrss'); + $this->get('/feed-atom', '\Shaarli\Front\Controller\FeedController:atom')->setName('feedatom'); + $this->get('/feed-rss', '\Shaarli\Front\Controller\FeedController:rss')->setName('feedrss'); $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\TagController:addTag')->setName('add-tag'); })->add('\Shaarli\Front\ShaarliMiddleware'); diff --git a/plugins/pubsubhubbub/pubsubhubbub.php b/plugins/pubsubhubbub/pubsubhubbub.php index 2878c050..41634dda 100644 --- a/plugins/pubsubhubbub/pubsubhubbub.php +++ b/plugins/pubsubhubbub/pubsubhubbub.php @@ -60,8 +60,8 @@ function hook_pubsubhubbub_render_feed($data, $conf) function hook_pubsubhubbub_save_link($data, $conf) { $feeds = array( - index_url($_SERVER) .'?do=atom', - index_url($_SERVER) .'?do=rss', + index_url($_SERVER) .'feed-atom', + index_url($_SERVER) .'feed-rss', ); $httpPost = function_exists('curl_version') ? false : 'nocurl_http_post'; diff --git a/tests/feed/CachedPageTest.php b/tests/feed/CachedPageTest.php index 363028a2..57f3b09b 100644 --- a/tests/feed/CachedPageTest.php +++ b/tests/feed/CachedPageTest.php @@ -11,7 +11,7 @@ class CachedPageTest extends \PHPUnit\Framework\TestCase { // test cache directory protected static $testCacheDir = 'sandbox/pagecache'; - protected static $url = 'http://shaar.li/?do=atom'; + protected static $url = 'http://shaar.li/feed-atom'; protected static $filename; /** @@ -42,8 +42,8 @@ class CachedPageTest extends \PHPUnit\Framework\TestCase { new CachedPage(self::$testCacheDir, '', true); new CachedPage(self::$testCacheDir, '', false); - new CachedPage(self::$testCacheDir, 'http://shaar.li/?do=rss', true); - new CachedPage(self::$testCacheDir, 'http://shaar.li/?do=atom', false); + new CachedPage(self::$testCacheDir, 'http://shaar.li/feed-rss', true); + new CachedPage(self::$testCacheDir, 'http://shaar.li/feed-atom', false); $this->addToAssertionCount(1); } diff --git a/tests/front/controller/FeedControllerTest.php b/tests/front/controller/FeedControllerTest.php new file mode 100644 index 00000000..d4cc5536 --- /dev/null +++ b/tests/front/controller/FeedControllerTest.php @@ -0,0 +1,219 @@ +container = $this->createMock(ShaarliContainer::class); + $this->controller = new FeedController($this->container); + } + + /** + * Feed Controller - RSS default behaviour + */ + public function testDefaultRssController(): void + { + $this->createValidContainerMockSet(); + + $request = $this->createMock(Request::class); + $response = new Response(); + + $this->container->feedBuilder->expects(static::once())->method('setLocale'); + $this->container->feedBuilder->expects(static::once())->method('setHideDates')->with(false); + $this->container->feedBuilder->expects(static::once())->method('setUsePermalinks')->with(true); + + // Save RainTPL assigned variables + $assignedVariables = []; + $this->assignTemplateVars($assignedVariables); + + $this->container->feedBuilder->method('buildData')->willReturn(['content' => 'data']); + + // Make sure that PluginManager hook is triggered + $this->container->pluginManager + ->expects(static::at(0)) + ->method('executeHooks') + ->willReturnCallback(function (string $hook, array $data, array $param): void { + static::assertSame('render_feed', $hook); + static::assertSame('data', $data['content']); + + static::assertArrayHasKey('loggedin', $param); + static::assertSame('rss', $param['target']); + }) + ; + + $result = $this->controller->rss($request, $response); + + static::assertSame(200, $result->getStatusCode()); + static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); + static::assertSame('feed.rss', (string) $result->getBody()); + static::assertSame('data', $assignedVariables['content']); + } + + /** + * Feed Controller - ATOM default behaviour + */ + public function testDefaultAtomController(): void + { + $this->createValidContainerMockSet(); + + $request = $this->createMock(Request::class); + $response = new Response(); + + $this->container->feedBuilder->expects(static::once())->method('setLocale'); + $this->container->feedBuilder->expects(static::once())->method('setHideDates')->with(false); + $this->container->feedBuilder->expects(static::once())->method('setUsePermalinks')->with(true); + + // Save RainTPL assigned variables + $assignedVariables = []; + $this->assignTemplateVars($assignedVariables); + + $this->container->feedBuilder->method('buildData')->willReturn(['content' => 'data']); + + // Make sure that PluginManager hook is triggered + $this->container->pluginManager + ->expects(static::at(0)) + ->method('executeHooks') + ->willReturnCallback(function (string $hook, array $data, array $param): void { + static::assertSame('render_feed', $hook); + static::assertSame('data', $data['content']); + + static::assertArrayHasKey('loggedin', $param); + static::assertSame('atom', $param['target']); + }) + ; + + $result = $this->controller->atom($request, $response); + + static::assertSame(200, $result->getStatusCode()); + static::assertStringContainsString('application/atom', $result->getHeader('Content-Type')[0]); + static::assertSame('feed.atom', (string) $result->getBody()); + static::assertSame('data', $assignedVariables['content']); + } + + /** + * Feed Controller - ATOM with parameters + */ + public function testAtomControllerWithParameters(): void + { + $this->createValidContainerMockSet(); + + $request = $this->createMock(Request::class); + $request->method('getParams')->willReturn(['parameter' => 'value']); + $response = new Response(); + + // Save RainTPL assigned variables + $assignedVariables = []; + $this->assignTemplateVars($assignedVariables); + + $this->container->feedBuilder + ->method('buildData') + ->with('atom', ['parameter' => 'value']) + ->willReturn(['content' => 'data']) + ; + + // Make sure that PluginManager hook is triggered + $this->container->pluginManager + ->expects(static::at(0)) + ->method('executeHooks') + ->willReturnCallback(function (string $hook, array $data, array $param): void { + static::assertSame('render_feed', $hook); + static::assertSame('data', $data['content']); + + static::assertArrayHasKey('loggedin', $param); + static::assertSame('atom', $param['target']); + }) + ; + + $result = $this->controller->atom($request, $response); + + static::assertSame(200, $result->getStatusCode()); + static::assertStringContainsString('application/atom', $result->getHeader('Content-Type')[0]); + static::assertSame('feed.atom', (string) $result->getBody()); + static::assertSame('data', $assignedVariables['content']); + } + + protected function createValidContainerMockSet(): void + { + $loginManager = $this->createMock(LoginManager::class); + $this->container->loginManager = $loginManager; + + // Config + $conf = $this->createMock(ConfigManager::class); + $this->container->conf = $conf; + $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) { + return $default; + }); + + // PageBuilder + $pageBuilder = $this->createMock(PageBuilder::class); + $pageBuilder + ->method('render') + ->willReturnCallback(function (string $template): string { + return $template; + }) + ; + $this->container->pageBuilder = $pageBuilder; + + $bookmarkService = $this->createMock(BookmarkServiceInterface::class); + $this->container->bookmarkService = $bookmarkService; + + // Plugin Manager + $pluginManager = $this->createMock(PluginManager::class); + $this->container->pluginManager = $pluginManager; + + // Formatter + $formatterFactory = $this->createMock(FormatterFactory::class); + $this->container->formatterFactory = $formatterFactory; + + // CacheManager + $pageCacheManager = $this->createMock(PageCacheManager::class); + $this->container->pageCacheManager = $pageCacheManager; + + // FeedBuilder + $feedBuilder = $this->createMock(FeedBuilder::class); + $this->container->feedBuilder = $feedBuilder; + + // $_SERVER + $this->container->environment = [ + 'SERVER_NAME' => 'shaarli', + 'SERVER_PORT' => '80', + 'REQUEST_URI' => '/daily-rss', + ]; + } + + protected function assignTemplateVars(array &$variables): void + { + $this->container->pageBuilder + ->expects(static::atLeastOnce()) + ->method('assign') + ->willReturnCallback(function ($key, $value) use (&$variables) { + $variables[$key] = $value; + + return $this; + }) + ; + } +} diff --git a/tpl/default/includes.html b/tpl/default/includes.html index 3e7d6320..cdbfeea1 100644 --- a/tpl/default/includes.html +++ b/tpl/default/includes.html @@ -3,8 +3,8 @@ - - + + diff --git a/tpl/default/opensearch.html b/tpl/default/opensearch.html index 3fcc30b7..5774d38d 100644 --- a/tpl/default/opensearch.html +++ b/tpl/default/opensearch.html @@ -3,8 +3,8 @@ Shaarli search - {$pagetitle} Shaarli search - {$pagetitle} - - + + UTF-8 Shaarli Community - https://github.com/shaarli/Shaarli/ data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE diff --git a/tpl/default/page.header.html b/tpl/default/page.header.html index 6b686580..2d015b26 100644 --- a/tpl/default/page.header.html +++ b/tpl/default/page.header.html @@ -52,7 +52,7 @@ {/loop}
  • - {'RSS Feed'|t} + {'RSS Feed'|t}
  • {if="$is_logged_in"}
  • @@ -74,7 +74,7 @@
  • - +
  • diff --git a/tpl/vintage/includes.html b/tpl/vintage/includes.html index d77ce497..cf56ca61 100644 --- a/tpl/vintage/includes.html +++ b/tpl/vintage/includes.html @@ -3,8 +3,8 @@ - - + + {if="$formatter==='markdown'"} diff --git a/tpl/vintage/opensearch.html b/tpl/vintage/opensearch.html index 3fcc30b7..5774d38d 100644 --- a/tpl/vintage/opensearch.html +++ b/tpl/vintage/opensearch.html @@ -3,8 +3,8 @@ Shaarli search - {$pagetitle} Shaarli search - {$pagetitle} - - + + UTF-8 Shaarli Community - https://github.com/shaarli/Shaarli/ data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE diff --git a/tpl/vintage/page.header.html b/tpl/vintage/page.header.html index d9451555..0a8392b6 100644 --- a/tpl/vintage/page.header.html +++ b/tpl/vintage/page.header.html @@ -27,9 +27,9 @@ {else}
  • Login
  • {/if} -
  • RSS Feed
  • +
  • RSS Feed
  • {if="$showatom"} -
  • ATOM Feed
  • +
  • ATOM Feed
  • {/if}
  • Tag cloud
  • Picture wall