aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2020-05-18 17:17:36 +0200
committerArthurHoaro <arthur@hoa.ro>2020-07-23 21:19:21 +0200
commit7b2ba6ef820335df682fbe3dcfaceef3a62cf4a5 (patch)
treeb9d4b744f514fb3d1b45a40f81323983ca31f7ad
parentf4929b1188b4bc5e92b925ebc44f5ad40bb1a4ed (diff)
downloadShaarli-7b2ba6ef820335df682fbe3dcfaceef3a62cf4a5.tar.gz
Shaarli-7b2ba6ef820335df682fbe3dcfaceef3a62cf4a5.tar.zst
Shaarli-7b2ba6ef820335df682fbe3dcfaceef3a62cf4a5.zip
RSS/ATOM feeds: process through Slim controller
-rw-r--r--application/container/ContainerBuilder.php10
-rw-r--r--application/container/ShaarliContainer.php2
-rw-r--r--application/feed/FeedBuilder.php2
-rw-r--r--application/front/controllers/FeedController.php79
-rw-r--r--application/front/controllers/ShaarliController.php14
-rw-r--r--doc/md/RSS-feeds.md8
-rw-r--r--index.php41
-rw-r--r--plugins/pubsubhubbub/pubsubhubbub.php4
-rw-r--r--tests/feed/CachedPageTest.php6
-rw-r--r--tests/front/controller/FeedControllerTest.php219
-rw-r--r--tpl/default/includes.html4
-rw-r--r--tpl/default/opensearch.html4
-rw-r--r--tpl/default/page.header.html4
-rw-r--r--tpl/vintage/includes.html4
-rw-r--r--tpl/vintage/opensearch.html4
-rw-r--r--tpl/vintage/page.header.html4
16 files changed, 349 insertions, 60 deletions
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;
7use Shaarli\Bookmark\BookmarkFileService; 7use Shaarli\Bookmark\BookmarkFileService;
8use Shaarli\Bookmark\BookmarkServiceInterface; 8use Shaarli\Bookmark\BookmarkServiceInterface;
9use Shaarli\Config\ConfigManager; 9use Shaarli\Config\ConfigManager;
10use Shaarli\Feed\FeedBuilder;
10use Shaarli\Formatter\FormatterFactory; 11use Shaarli\Formatter\FormatterFactory;
11use Shaarli\History; 12use Shaarli\History;
12use Shaarli\Plugin\PluginManager; 13use Shaarli\Plugin\PluginManager;
@@ -100,6 +101,15 @@ class ContainerBuilder
100 ); 101 );
101 }; 102 };
102 103
104 $container['feedBuilder'] = function (ShaarliContainer $container): FeedBuilder {
105 return new FeedBuilder(
106 $container->bookmarkService,
107 $container->formatterFactory->getFormatter(),
108 $container->environment,
109 $container->loginManager->isLoggedIn()
110 );
111 };
112
103 return $container; 113 return $container;
104 } 114 }
105} 115}
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;
6 6
7use Shaarli\Bookmark\BookmarkServiceInterface; 7use Shaarli\Bookmark\BookmarkServiceInterface;
8use Shaarli\Config\ConfigManager; 8use Shaarli\Config\ConfigManager;
9use Shaarli\Feed\FeedBuilder;
9use Shaarli\Formatter\FormatterFactory; 10use Shaarli\Formatter\FormatterFactory;
10use Shaarli\History; 11use Shaarli\History;
11use Shaarli\Plugin\PluginManager; 12use Shaarli\Plugin\PluginManager;
@@ -29,6 +30,7 @@ use Slim\Container;
29 * @property PluginManager $pluginManager 30 * @property PluginManager $pluginManager
30 * @property FormatterFactory $formatterFactory 31 * @property FormatterFactory $formatterFactory
31 * @property PageCacheManager $pageCacheManager 32 * @property PageCacheManager $pageCacheManager
33 * @property FeedBuilder $feedBuilder
32 */ 34 */
33class ShaarliContainer extends Container 35class ShaarliContainer extends Container
34{ 36{
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
78 * @param array $serverInfo $_SERVER. 78 * @param array $serverInfo $_SERVER.
79 * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise. 79 * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise.
80 */ 80 */
81 public function __construct($linkDB, $formatter, array $serverInfo, $isLoggedIn) 81 public function __construct($linkDB, $formatter, $serverInfo, $isLoggedIn)
82 { 82 {
83 $this->linkDB = $linkDB; 83 $this->linkDB = $linkDB;
84 $this->formatter = $formatter; 84 $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 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller;
6
7use Shaarli\Feed\FeedBuilder;
8use Slim\Http\Request;
9use Slim\Http\Response;
10
11/**
12 * Class FeedController
13 *
14 * Slim controller handling ATOM and RSS feed.
15 *
16 * @package Front\Controller
17 */
18class FeedController extends ShaarliController
19{
20 public function atom(Request $request, Response $response): Response
21 {
22 return $this->processRequest(FeedBuilder::$FEED_ATOM, $request, $response);
23 }
24
25 public function rss(Request $request, Response $response): Response
26 {
27 return $this->processRequest(FeedBuilder::$FEED_RSS, $request, $response);
28 }
29
30 protected function processRequest(string $feedType, Request $request, Response $response): Response
31 {
32 $response = $response->withHeader('Content-Type', 'application/'. $feedType .'+xml; charset=utf-8');
33
34 $pageUrl = page_url($this->container->environment);
35 $cache = $this->container->pageCacheManager->getCachePage($pageUrl);
36
37 $cached = $cache->cachedVersion();
38 if (!empty($cached)) {
39 return $response->write($cached);
40 }
41
42 // Generate data.
43 $this->container->feedBuilder->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
44 $this->container->feedBuilder->setHideDates($this->container->conf->get('privacy.hide_timestamps', false));
45 $this->container->feedBuilder->setUsePermalinks(
46 null !== $request->getParam('permalinks') || !$this->container->conf->get('feed.rss_permalinks')
47 );
48
49 $data = $this->container->feedBuilder->buildData($feedType, $request->getParams());
50
51 $this->executeHooks($data, $feedType);
52 $this->assignAllView($data);
53
54 $content = $this->render('feed.'. $feedType);
55
56 $cache->cache($content);
57
58 return $response->write($content);
59 }
60
61 /**
62 * @param mixed[] $data Template data
63 *
64 * @return mixed[] Template data after active plugins hook execution.
65 */
66 protected function executeHooks(array $data, string $feedType): array
67 {
68 $this->container->pluginManager->executeHooks(
69 'render_feed',
70 $data,
71 [
72 'loggedin' => $this->container->loginManager->isLoggedIn(),
73 'target' => $feedType,
74 ]
75 );
76
77 return $data;
78 }
79}
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
30 return $this; 30 return $this;
31 } 31 }
32 32
33 /**
34 * Assign variables to RainTPL template through the PageBuilder.
35 *
36 * @param mixed $data Values to assign to the template and their keys
37 */
38 protected function assignAllView(array $data): self
39 {
40 foreach ($data as $key => $value) {
41 $this->assignView($key, $value);
42 }
43
44 return $this;
45 }
46
33 protected function render(string $template): string 47 protected function render(string $template): string
34 { 48 {
35 $this->assignView('linkcount', $this->container->bookmarkService->count(BookmarkFilter::$ALL)); 49 $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 @@
1### Feeds options 1### Feeds options
2 2
3Feeds are available in ATOM with `?do=atom` and RSS with `do=RSS`. 3Feeds are available in ATOM with `/feed-atom` and RSS with `/feed-rss`.
4 4
5Options: 5Options:
6 6
7- You can use `permalinks` in the feed URL to get permalink to Shaares instead of direct link to shaared URL. 7- You can use `permalinks` in the feed URL to get permalink to Shaares instead of direct link to shaared URL.
8 - E.G. `https://my.shaarli.domain/?do=atom&permalinks`. 8 - E.G. `https://my.shaarli.domain/feed-atom?permalinks`.
9- 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. 9- 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.
10 - `https://my.shaarli.domain/?do=atom&permalinks&nb=42` 10 - `https://my.shaarli.domain/feed-atom?permalinks&nb=42`
11 - `https://my.shaarli.domain/?do=atom&permalinks&nb=all` 11 - `https://my.shaarli.domain/feed-atom?permalinks&nb=all`
12 12
13### RSS Feeds or Picture Wall for a specific search/tag 13### RSS Feeds or Picture Wall for a specific search/tag
14 14
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
432 // ATOM and RSS feed. 432 // ATOM and RSS feed.
433 if ($targetPage == Router::$PAGE_FEED_ATOM || $targetPage == Router::$PAGE_FEED_RSS) { 433 if ($targetPage == Router::$PAGE_FEED_ATOM || $targetPage == Router::$PAGE_FEED_RSS) {
434 $feedType = $targetPage == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM; 434 $feedType = $targetPage == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM;
435 header('Content-Type: application/'. $feedType .'+xml; charset=utf-8');
436
437 // Cache system
438 $query = $_SERVER['QUERY_STRING'];
439 $cache = new CachedPage(
440 $conf->get('resource.page_cache'),
441 page_url($_SERVER),
442 startsWith($query, 'do='. $targetPage) && !$loginManager->isLoggedIn()
443 );
444 $cached = $cache->cachedVersion();
445 if (!empty($cached)) {
446 echo $cached;
447 exit;
448 }
449 435
450 $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); 436 header('Location: ./feed-'. $feedType .'?'. http_build_query($_GET));
451 // Generate data.
452 $feedGenerator = new FeedBuilder(
453 $bookmarkService,
454 $factory->getFormatter(),
455 $_SERVER,
456 $loginManager->isLoggedIn()
457 );
458 $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
459 $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !$loginManager->isLoggedIn());
460 $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks'));
461 $data = $feedGenerator->buildData($feedType, $_GET);
462
463 // Process plugin hook.
464 $pluginManager->executeHooks('render_feed', $data, array(
465 'loggedin' => $loginManager->isLoggedIn(),
466 'target' => $targetPage,
467 ));
468
469 // Render the template.
470 $PAGE->assignAll($data);
471 $PAGE->renderPage('feed.'. $feedType);
472 $cache->cache(ob_get_contents());
473 ob_end_flush();
474 exit; 437 exit;
475 } 438 }
476 439
@@ -1610,6 +1573,8 @@ $app->group('', function () {
1610 $this->get('/tag-list', '\Shaarli\Front\Controller\TagCloudController:list')->setName('taglist'); 1573 $this->get('/tag-list', '\Shaarli\Front\Controller\TagCloudController:list')->setName('taglist');
1611 $this->get('/daily', '\Shaarli\Front\Controller\DailyController:index')->setName('daily'); 1574 $this->get('/daily', '\Shaarli\Front\Controller\DailyController:index')->setName('daily');
1612 $this->get('/daily-rss', '\Shaarli\Front\Controller\DailyController:rss')->setName('dailyrss'); 1575 $this->get('/daily-rss', '\Shaarli\Front\Controller\DailyController:rss')->setName('dailyrss');
1576 $this->get('/feed-atom', '\Shaarli\Front\Controller\FeedController:atom')->setName('feedatom');
1577 $this->get('/feed-rss', '\Shaarli\Front\Controller\FeedController:rss')->setName('feedrss');
1613 1578
1614 $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\TagController:addTag')->setName('add-tag'); 1579 $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\TagController:addTag')->setName('add-tag');
1615})->add('\Shaarli\Front\ShaarliMiddleware'); 1580})->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)
60function hook_pubsubhubbub_save_link($data, $conf) 60function hook_pubsubhubbub_save_link($data, $conf)
61{ 61{
62 $feeds = array( 62 $feeds = array(
63 index_url($_SERVER) .'?do=atom', 63 index_url($_SERVER) .'feed-atom',
64 index_url($_SERVER) .'?do=rss', 64 index_url($_SERVER) .'feed-rss',
65 ); 65 );
66 66
67 $httpPost = function_exists('curl_version') ? false : 'nocurl_http_post'; 67 $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
11{ 11{
12 // test cache directory 12 // test cache directory
13 protected static $testCacheDir = 'sandbox/pagecache'; 13 protected static $testCacheDir = 'sandbox/pagecache';
14 protected static $url = 'http://shaar.li/?do=atom'; 14 protected static $url = 'http://shaar.li/feed-atom';
15 protected static $filename; 15 protected static $filename;
16 16
17 /** 17 /**
@@ -42,8 +42,8 @@ class CachedPageTest extends \PHPUnit\Framework\TestCase
42 { 42 {
43 new CachedPage(self::$testCacheDir, '', true); 43 new CachedPage(self::$testCacheDir, '', true);
44 new CachedPage(self::$testCacheDir, '', false); 44 new CachedPage(self::$testCacheDir, '', false);
45 new CachedPage(self::$testCacheDir, 'http://shaar.li/?do=rss', true); 45 new CachedPage(self::$testCacheDir, 'http://shaar.li/feed-rss', true);
46 new CachedPage(self::$testCacheDir, 'http://shaar.li/?do=atom', false); 46 new CachedPage(self::$testCacheDir, 'http://shaar.li/feed-atom', false);
47 $this->addToAssertionCount(1); 47 $this->addToAssertionCount(1);
48 } 48 }
49 49
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 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller;
6
7use PHPUnit\Framework\TestCase;
8use Shaarli\Bookmark\BookmarkServiceInterface;
9use Shaarli\Config\ConfigManager;
10use Shaarli\Container\ShaarliContainer;
11use Shaarli\Feed\FeedBuilder;
12use Shaarli\Formatter\FormatterFactory;
13use Shaarli\Plugin\PluginManager;
14use Shaarli\Render\PageBuilder;
15use Shaarli\Render\PageCacheManager;
16use Shaarli\Security\LoginManager;
17use Slim\Http\Request;
18use Slim\Http\Response;
19
20class FeedControllerTest extends TestCase
21{
22 /** @var ShaarliContainer */
23 protected $container;
24
25 /** @var FeedController */
26 protected $controller;
27
28 public function setUp(): void
29 {
30 $this->container = $this->createMock(ShaarliContainer::class);
31 $this->controller = new FeedController($this->container);
32 }
33
34 /**
35 * Feed Controller - RSS default behaviour
36 */
37 public function testDefaultRssController(): void
38 {
39 $this->createValidContainerMockSet();
40
41 $request = $this->createMock(Request::class);
42 $response = new Response();
43
44 $this->container->feedBuilder->expects(static::once())->method('setLocale');
45 $this->container->feedBuilder->expects(static::once())->method('setHideDates')->with(false);
46 $this->container->feedBuilder->expects(static::once())->method('setUsePermalinks')->with(true);
47
48 // Save RainTPL assigned variables
49 $assignedVariables = [];
50 $this->assignTemplateVars($assignedVariables);
51
52 $this->container->feedBuilder->method('buildData')->willReturn(['content' => 'data']);
53
54 // Make sure that PluginManager hook is triggered
55 $this->container->pluginManager
56 ->expects(static::at(0))
57 ->method('executeHooks')
58 ->willReturnCallback(function (string $hook, array $data, array $param): void {
59 static::assertSame('render_feed', $hook);
60 static::assertSame('data', $data['content']);
61
62 static::assertArrayHasKey('loggedin', $param);
63 static::assertSame('rss', $param['target']);
64 })
65 ;
66
67 $result = $this->controller->rss($request, $response);
68
69 static::assertSame(200, $result->getStatusCode());
70 static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]);
71 static::assertSame('feed.rss', (string) $result->getBody());
72 static::assertSame('data', $assignedVariables['content']);
73 }
74
75 /**
76 * Feed Controller - ATOM default behaviour
77 */
78 public function testDefaultAtomController(): void
79 {
80 $this->createValidContainerMockSet();
81
82 $request = $this->createMock(Request::class);
83 $response = new Response();
84
85 $this->container->feedBuilder->expects(static::once())->method('setLocale');
86 $this->container->feedBuilder->expects(static::once())->method('setHideDates')->with(false);
87 $this->container->feedBuilder->expects(static::once())->method('setUsePermalinks')->with(true);
88
89 // Save RainTPL assigned variables
90 $assignedVariables = [];
91 $this->assignTemplateVars($assignedVariables);
92
93 $this->container->feedBuilder->method('buildData')->willReturn(['content' => 'data']);
94
95 // Make sure that PluginManager hook is triggered
96 $this->container->pluginManager
97 ->expects(static::at(0))
98 ->method('executeHooks')
99 ->willReturnCallback(function (string $hook, array $data, array $param): void {
100 static::assertSame('render_feed', $hook);
101 static::assertSame('data', $data['content']);
102
103 static::assertArrayHasKey('loggedin', $param);
104 static::assertSame('atom', $param['target']);
105 })
106 ;
107
108 $result = $this->controller->atom($request, $response);
109
110 static::assertSame(200, $result->getStatusCode());
111 static::assertStringContainsString('application/atom', $result->getHeader('Content-Type')[0]);
112 static::assertSame('feed.atom', (string) $result->getBody());
113 static::assertSame('data', $assignedVariables['content']);
114 }
115
116 /**
117 * Feed Controller - ATOM with parameters
118 */
119 public function testAtomControllerWithParameters(): void
120 {
121 $this->createValidContainerMockSet();
122
123 $request = $this->createMock(Request::class);
124 $request->method('getParams')->willReturn(['parameter' => 'value']);
125 $response = new Response();
126
127 // Save RainTPL assigned variables
128 $assignedVariables = [];
129 $this->assignTemplateVars($assignedVariables);
130
131 $this->container->feedBuilder
132 ->method('buildData')
133 ->with('atom', ['parameter' => 'value'])
134 ->willReturn(['content' => 'data'])
135 ;
136
137 // Make sure that PluginManager hook is triggered
138 $this->container->pluginManager
139 ->expects(static::at(0))
140 ->method('executeHooks')
141 ->willReturnCallback(function (string $hook, array $data, array $param): void {
142 static::assertSame('render_feed', $hook);
143 static::assertSame('data', $data['content']);
144
145 static::assertArrayHasKey('loggedin', $param);
146 static::assertSame('atom', $param['target']);
147 })
148 ;
149
150 $result = $this->controller->atom($request, $response);
151
152 static::assertSame(200, $result->getStatusCode());
153 static::assertStringContainsString('application/atom', $result->getHeader('Content-Type')[0]);
154 static::assertSame('feed.atom', (string) $result->getBody());
155 static::assertSame('data', $assignedVariables['content']);
156 }
157
158 protected function createValidContainerMockSet(): void
159 {
160 $loginManager = $this->createMock(LoginManager::class);
161 $this->container->loginManager = $loginManager;
162
163 // Config
164 $conf = $this->createMock(ConfigManager::class);
165 $this->container->conf = $conf;
166 $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) {
167 return $default;
168 });
169
170 // PageBuilder
171 $pageBuilder = $this->createMock(PageBuilder::class);
172 $pageBuilder
173 ->method('render')
174 ->willReturnCallback(function (string $template): string {
175 return $template;
176 })
177 ;
178 $this->container->pageBuilder = $pageBuilder;
179
180 $bookmarkService = $this->createMock(BookmarkServiceInterface::class);
181 $this->container->bookmarkService = $bookmarkService;
182
183 // Plugin Manager
184 $pluginManager = $this->createMock(PluginManager::class);
185 $this->container->pluginManager = $pluginManager;
186
187 // Formatter
188 $formatterFactory = $this->createMock(FormatterFactory::class);
189 $this->container->formatterFactory = $formatterFactory;
190
191 // CacheManager
192 $pageCacheManager = $this->createMock(PageCacheManager::class);
193 $this->container->pageCacheManager = $pageCacheManager;
194
195 // FeedBuilder
196 $feedBuilder = $this->createMock(FeedBuilder::class);
197 $this->container->feedBuilder = $feedBuilder;
198
199 // $_SERVER
200 $this->container->environment = [
201 'SERVER_NAME' => 'shaarli',
202 'SERVER_PORT' => '80',
203 'REQUEST_URI' => '/daily-rss',
204 ];
205 }
206
207 protected function assignTemplateVars(array &$variables): void
208 {
209 $this->container->pageBuilder
210 ->expects(static::atLeastOnce())
211 ->method('assign')
212 ->willReturnCallback(function ($key, $value) use (&$variables) {
213 $variables[$key] = $value;
214
215 return $this;
216 })
217 ;
218 }
219}
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 @@
3<meta name="format-detection" content="telephone=no" /> 3<meta name="format-detection" content="telephone=no" />
4<meta name="viewport" content="width=device-width, initial-scale=1"> 4<meta name="viewport" content="width=device-width, initial-scale=1">
5<meta name="referrer" content="same-origin"> 5<meta name="referrer" content="same-origin">
6<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> 6<link rel="alternate" type="application/atom+xml" href="{$feedurl}feed-atom?{$searchcrits}#" title="ATOM Feed" />
7<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" /> 7<link rel="alternate" type="application/rss+xml" href="{$feedurl}feed-rss?{$searchcrits}#" title="RSS Feed" />
8<link href="img/favicon.png" rel="shortcut icon" type="image/png" /> 8<link href="img/favicon.png" rel="shortcut icon" type="image/png" />
9<link href="img/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180" /> 9<link href="img/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180" />
10<link type="text/css" rel="stylesheet" href="css/shaarli.min.css?v={$version_hash}" /> 10<link type="text/css" rel="stylesheet" href="css/shaarli.min.css?v={$version_hash}" />
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 @@
3 <ShortName>Shaarli search - {$pagetitle}</ShortName> 3 <ShortName>Shaarli search - {$pagetitle}</ShortName>
4 <Description>Shaarli search - {$pagetitle}</Description> 4 <Description>Shaarli search - {$pagetitle}</Description>
5 <Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" /> 5 <Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" />
6 <Url type="application/atom+xml" template="{$serverurl}?do=atom&amp;searchterm={searchTerms}"/> 6 <Url type="application/atom+xml" template="{$serverurl}feed-atom?searchterm={searchTerms}"/>
7 <Url type="application/rss+xml" template="{$serverurl}?do=rss&amp;searchterm={searchTerms}"/> 7 <Url type="application/rss+xml" template="{$serverurl}feed-rss?searchterm={searchTerms}"/>
8 <InputEncoding>UTF-8</InputEncoding> 8 <InputEncoding>UTF-8</InputEncoding>
9 <Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer> 9 <Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer>
10 <Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE 10 <Image width="16" height="16">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 @@
52 </li> 52 </li>
53 {/loop} 53 {/loop}
54 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss"> 54 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss">
55 <a href="./?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a> 55 <a href="./feed-{$feed_type}?{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a>
56 </li> 56 </li>
57 {if="$is_logged_in"} 57 {if="$is_logged_in"}
58 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout"> 58 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout">
@@ -74,7 +74,7 @@
74 </a> 74 </a>
75 </li> 75 </li>
76 <li class="pure-menu-item" id="shaarli-menu-desktop-rss"> 76 <li class="pure-menu-item" id="shaarli-menu-desktop-rss">
77 <a href="./?do={$feed_type}{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}" aria-label="{'RSS Feed'|t}"> 77 <a href="./feed-{$feed_type}?{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}" aria-label="{'RSS Feed'|t}">
78 <i class="fa fa-rss" aria-hidden="true"></i> 78 <i class="fa fa-rss" aria-hidden="true"></i>
79 </a> 79 </a>
80 </li> 80 </li>
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 @@
3<meta name="format-detection" content="telephone=no" /> 3<meta name="format-detection" content="telephone=no" />
4<meta name="viewport" content="width=device-width,initial-scale=1.0" /> 4<meta name="viewport" content="width=device-width,initial-scale=1.0" />
5<meta name="referrer" content="same-origin"> 5<meta name="referrer" content="same-origin">
6<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" /> 6<link rel="alternate" type="application/rss+xml" href="{$feedurl}feed-rss?{$searchcrits}#" title="RSS Feed" />
7<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> 7<link rel="alternate" type="application/atom+xml" href="{$feedurl}feed-atom?{$searchcrits}#" title="ATOM Feed" />
8<link href="img/favicon.ico" rel="shortcut icon" type="image/x-icon" /> 8<link href="img/favicon.ico" rel="shortcut icon" type="image/x-icon" />
9<link type="text/css" rel="stylesheet" href="css/shaarli.min.css" /> 9<link type="text/css" rel="stylesheet" href="css/shaarli.min.css" />
10{if="$formatter==='markdown'"} 10{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 @@
3 <ShortName>Shaarli search - {$pagetitle}</ShortName> 3 <ShortName>Shaarli search - {$pagetitle}</ShortName>
4 <Description>Shaarli search - {$pagetitle}</Description> 4 <Description>Shaarli search - {$pagetitle}</Description>
5 <Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" /> 5 <Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" />
6 <Url type="application/atom+xml" template="{$serverurl}?do=atom&amp;searchterm={searchTerms}"/> 6 <Url type="application/atom+xml" template="{$serverurl}feed-atom?searchterm={searchTerms}"/>
7 <Url type="application/rss+xml" template="{$serverurl}?do=rss&amp;searchterm={searchTerms}"/> 7 <Url type="application/rss+xml" template="{$serverurl}feed-rss?searchterm={searchTerms}"/>
8 <InputEncoding>UTF-8</InputEncoding> 8 <InputEncoding>UTF-8</InputEncoding>
9 <Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer> 9 <Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer>
10 <Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE 10 <Image width="16" height="16">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 @@
27 {else} 27 {else}
28 <li><a href="./login">Login</a></li> 28 <li><a href="./login">Login</a></li>
29 {/if} 29 {/if}
30 <li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li> 30 <li><a href="{$feedurl}/feed-rss?{$searchcrits}" class="nomobile">RSS Feed</a></li>
31 {if="$showatom"} 31 {if="$showatom"}
32 <li><a href="{$feedurl}?do=atom{$searchcrits}" class="nomobile">ATOM Feed</a></li> 32 <li><a href="{$feedurl}/feed-atom?{$searchcrits}" class="nomobile">ATOM Feed</a></li>
33 {/if} 33 {/if}
34 <li><a href="./tag-cloud">Tag cloud</a></li> 34 <li><a href="./tag-cloud">Tag cloud</a></li>
35 <li><a href="./picture-wall{function="ltrim($searchcrits, '&')"}">Picture wall</a></li> 35 <li><a href="./picture-wall{function="ltrim($searchcrits, '&')"}">Picture wall</a></li>