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}
{if="$is_logged_in"}
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