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;
);
};
+ $container['feedBuilder'] = function (ShaarliContainer $container): FeedBuilder {
+ return new FeedBuilder(
+ $container->bookmarkService,
+ $container->formatterFactory->getFormatter(),
+ $container->environment,
+ $container->loginManager->isLoggedIn()
+ );
+ };
+
return $container;
}
}
use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager;
+use Shaarli\Feed\FeedBuilder;
use Shaarli\Formatter\FormatterFactory;
use Shaarli\History;
use Shaarli\Plugin\PluginManager;
* @property PluginManager $pluginManager
* @property FormatterFactory $formatterFactory
* @property PageCacheManager $pageCacheManager
+ * @property FeedBuilder $feedBuilder
*/
class ShaarliContainer extends Container
{
* @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;
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller;
+
+use Shaarli\Feed\FeedBuilder;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+/**
+ * Class FeedController
+ *
+ * Slim controller handling ATOM and RSS feed.
+ *
+ * @package Front\Controller
+ */
+class FeedController extends ShaarliController
+{
+ public function atom(Request $request, Response $response): Response
+ {
+ return $this->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;
+ }
+}
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));
### 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
// 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;
}
$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');
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';
{
// 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;
/**
{
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);
}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller;
+
+use PHPUnit\Framework\TestCase;
+use Shaarli\Bookmark\BookmarkServiceInterface;
+use Shaarli\Config\ConfigManager;
+use Shaarli\Container\ShaarliContainer;
+use Shaarli\Feed\FeedBuilder;
+use Shaarli\Formatter\FormatterFactory;
+use Shaarli\Plugin\PluginManager;
+use Shaarli\Render\PageBuilder;
+use Shaarli\Render\PageCacheManager;
+use Shaarli\Security\LoginManager;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class FeedControllerTest extends TestCase
+{
+ /** @var ShaarliContainer */
+ protected $container;
+
+ /** @var FeedController */
+ protected $controller;
+
+ public function setUp(): void
+ {
+ $this->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;
+ })
+ ;
+ }
+}
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="same-origin">
-<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
-<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
+<link rel="alternate" type="application/atom+xml" href="{$feedurl}feed-atom?{$searchcrits}#" title="ATOM Feed" />
+<link rel="alternate" type="application/rss+xml" href="{$feedurl}feed-rss?{$searchcrits}#" title="RSS Feed" />
<link href="img/favicon.png" rel="shortcut icon" type="image/png" />
<link href="img/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180" />
<link type="text/css" rel="stylesheet" href="css/shaarli.min.css?v={$version_hash}" />
<ShortName>Shaarli search - {$pagetitle}</ShortName>
<Description>Shaarli search - {$pagetitle}</Description>
<Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" />
- <Url type="application/atom+xml" template="{$serverurl}?do=atom&searchterm={searchTerms}"/>
- <Url type="application/rss+xml" template="{$serverurl}?do=rss&searchterm={searchTerms}"/>
+ <Url type="application/atom+xml" template="{$serverurl}feed-atom?searchterm={searchTerms}"/>
+ <Url type="application/rss+xml" template="{$serverurl}feed-rss?searchterm={searchTerms}"/>
<InputEncoding>UTF-8</InputEncoding>
<Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer>
<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE
</li>
{/loop}
<li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss">
- <a href="./?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a>
+ <a href="./feed-{$feed_type}?{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a>
</li>
{if="$is_logged_in"}
<li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout">
</a>
</li>
<li class="pure-menu-item" id="shaarli-menu-desktop-rss">
- <a href="./?do={$feed_type}{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}" aria-label="{'RSS Feed'|t}">
+ <a href="./feed-{$feed_type}?{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}" aria-label="{'RSS Feed'|t}">
<i class="fa fa-rss" aria-hidden="true"></i>
</a>
</li>
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="referrer" content="same-origin">
-<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
-<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
+<link rel="alternate" type="application/rss+xml" href="{$feedurl}feed-rss?{$searchcrits}#" title="RSS Feed" />
+<link rel="alternate" type="application/atom+xml" href="{$feedurl}feed-atom?{$searchcrits}#" title="ATOM Feed" />
<link href="img/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link type="text/css" rel="stylesheet" href="css/shaarli.min.css" />
{if="$formatter==='markdown'"}
<ShortName>Shaarli search - {$pagetitle}</ShortName>
<Description>Shaarli search - {$pagetitle}</Description>
<Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" />
- <Url type="application/atom+xml" template="{$serverurl}?do=atom&searchterm={searchTerms}"/>
- <Url type="application/rss+xml" template="{$serverurl}?do=rss&searchterm={searchTerms}"/>
+ <Url type="application/atom+xml" template="{$serverurl}feed-atom?searchterm={searchTerms}"/>
+ <Url type="application/rss+xml" template="{$serverurl}feed-rss?searchterm={searchTerms}"/>
<InputEncoding>UTF-8</InputEncoding>
<Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer>
<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE
{else}
<li><a href="./login">Login</a></li>
{/if}
- <li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li>
+ <li><a href="{$feedurl}/feed-rss?{$searchcrits}" class="nomobile">RSS Feed</a></li>
{if="$showatom"}
- <li><a href="{$feedurl}?do=atom{$searchcrits}" class="nomobile">ATOM Feed</a></li>
+ <li><a href="{$feedurl}/feed-atom?{$searchcrits}" class="nomobile">ATOM Feed</a></li>
{/if}
<li><a href="./tag-cloud">Tag cloud</a></li>
<li><a href="./picture-wall{function="ltrim($searchcrits, '&')"}">Picture wall</a></li>