diff options
author | ArthurHoaro <arthur@hoa.ro> | 2020-05-17 14:16:32 +0200 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2020-07-23 21:19:21 +0200 |
commit | c4d5be53c2ae503c00da3cfe6b28d0ce9d2ca7f5 (patch) | |
tree | 2aa6b156d45da7a1bb3cfe0b6e8622030fddb990 | |
parent | e3d28be9673a9f8404ff907b8191209729ad690c (diff) | |
download | Shaarli-c4d5be53c2ae503c00da3cfe6b28d0ce9d2ca7f5.tar.gz Shaarli-c4d5be53c2ae503c00da3cfe6b28d0ce9d2ca7f5.tar.zst Shaarli-c4d5be53c2ae503c00da3cfe6b28d0ce9d2ca7f5.zip |
Process Daily RSS feed through Slim controller
The daily RSS template has been entirely rewritten to handle the whole feed through the template engine.
-rw-r--r-- | application/bookmark/Bookmark.php | 15 | ||||
-rw-r--r-- | application/bookmark/BookmarkFileService.php | 2 | ||||
-rw-r--r-- | application/container/ContainerBuilder.php | 5 | ||||
-rw-r--r-- | application/front/controllers/DailyController.php | 74 | ||||
-rw-r--r-- | application/http/HttpUtils.php | 15 | ||||
-rw-r--r-- | application/legacy/LegacyLinkDB.php | 2 | ||||
-rw-r--r-- | application/render/PageCacheManager.php | 17 | ||||
-rw-r--r-- | index.php | 103 | ||||
-rw-r--r-- | tests/front/controller/DailyControllerTest.php | 152 | ||||
-rw-r--r-- | tests/http/HttpUtils/IndexUrlTest.php | 32 | ||||
-rw-r--r-- | tests/render/PageCacheManagerTest.php | 4 | ||||
-rw-r--r-- | tpl/default/daily.html | 2 | ||||
-rw-r--r-- | tpl/default/dailyrss.html | 48 | ||||
-rw-r--r-- | tpl/vintage/daily.html | 2 | ||||
-rw-r--r-- | tpl/vintage/dailyrss.html | 46 |
15 files changed, 365 insertions, 154 deletions
diff --git a/application/bookmark/Bookmark.php b/application/bookmark/Bookmark.php index 83ddab82..90ff5b16 100644 --- a/application/bookmark/Bookmark.php +++ b/application/bookmark/Bookmark.php | |||
@@ -3,6 +3,7 @@ | |||
3 | namespace Shaarli\Bookmark; | 3 | namespace Shaarli\Bookmark; |
4 | 4 | ||
5 | use DateTime; | 5 | use DateTime; |
6 | use DateTimeInterface; | ||
6 | use Shaarli\Bookmark\Exception\InvalidBookmarkException; | 7 | use Shaarli\Bookmark\Exception\InvalidBookmarkException; |
7 | 8 | ||
8 | /** | 9 | /** |
@@ -42,10 +43,10 @@ class Bookmark | |||
42 | /** @var bool Set to true if the bookmark is set as sticky */ | 43 | /** @var bool Set to true if the bookmark is set as sticky */ |
43 | protected $sticky; | 44 | protected $sticky; |
44 | 45 | ||
45 | /** @var DateTime Creation datetime */ | 46 | /** @var DateTimeInterface Creation datetime */ |
46 | protected $created; | 47 | protected $created; |
47 | 48 | ||
48 | /** @var DateTime Update datetime */ | 49 | /** @var DateTimeInterface datetime */ |
49 | protected $updated; | 50 | protected $updated; |
50 | 51 | ||
51 | /** @var bool True if the bookmark can only be seen while logged in */ | 52 | /** @var bool True if the bookmark can only be seen while logged in */ |
@@ -100,7 +101,7 @@ class Bookmark | |||
100 | || ! is_int($this->id) | 101 | || ! is_int($this->id) |
101 | || empty($this->shortUrl) | 102 | || empty($this->shortUrl) |
102 | || empty($this->created) | 103 | || empty($this->created) |
103 | || ! $this->created instanceof DateTime | 104 | || ! $this->created instanceof DateTimeInterface |
104 | ) { | 105 | ) { |
105 | throw new InvalidBookmarkException($this); | 106 | throw new InvalidBookmarkException($this); |
106 | } | 107 | } |
@@ -188,7 +189,7 @@ class Bookmark | |||
188 | /** | 189 | /** |
189 | * Get the Created. | 190 | * Get the Created. |
190 | * | 191 | * |
191 | * @return DateTime | 192 | * @return DateTimeInterface |
192 | */ | 193 | */ |
193 | public function getCreated() | 194 | public function getCreated() |
194 | { | 195 | { |
@@ -198,7 +199,7 @@ class Bookmark | |||
198 | /** | 199 | /** |
199 | * Get the Updated. | 200 | * Get the Updated. |
200 | * | 201 | * |
201 | * @return DateTime | 202 | * @return DateTimeInterface |
202 | */ | 203 | */ |
203 | public function getUpdated() | 204 | public function getUpdated() |
204 | { | 205 | { |
@@ -270,7 +271,7 @@ class Bookmark | |||
270 | * Set the Created. | 271 | * Set the Created. |
271 | * Note: you shouldn't set this manually except for special cases (like bookmark import) | 272 | * Note: you shouldn't set this manually except for special cases (like bookmark import) |
272 | * | 273 | * |
273 | * @param DateTime $created | 274 | * @param DateTimeInterface $created |
274 | * | 275 | * |
275 | * @return Bookmark | 276 | * @return Bookmark |
276 | */ | 277 | */ |
@@ -284,7 +285,7 @@ class Bookmark | |||
284 | /** | 285 | /** |
285 | * Set the Updated. | 286 | * Set the Updated. |
286 | * | 287 | * |
287 | * @param DateTime $updated | 288 | * @param DateTimeInterface $updated |
288 | * | 289 | * |
289 | * @return Bookmark | 290 | * @return Bookmark |
290 | */ | 291 | */ |
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php index 3b3812af..7439d8d8 100644 --- a/application/bookmark/BookmarkFileService.php +++ b/application/bookmark/BookmarkFileService.php | |||
@@ -53,7 +53,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
53 | { | 53 | { |
54 | $this->conf = $conf; | 54 | $this->conf = $conf; |
55 | $this->history = $history; | 55 | $this->history = $history; |
56 | $this->pageCacheManager = new PageCacheManager($this->conf->get('resource.page_cache')); | 56 | $this->pageCacheManager = new PageCacheManager($this->conf->get('resource.page_cache'), $isLoggedIn); |
57 | $this->bookmarksIO = new BookmarkIO($this->conf); | 57 | $this->bookmarksIO = new BookmarkIO($this->conf); |
58 | $this->isLoggedIn = $isLoggedIn; | 58 | $this->isLoggedIn = $isLoggedIn; |
59 | 59 | ||
diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php index c5c4a2c3..199f3f67 100644 --- a/application/container/ContainerBuilder.php +++ b/application/container/ContainerBuilder.php | |||
@@ -94,7 +94,10 @@ class ContainerBuilder | |||
94 | }; | 94 | }; |
95 | 95 | ||
96 | $container['pageCacheManager'] = function (ShaarliContainer $container): PageCacheManager { | 96 | $container['pageCacheManager'] = function (ShaarliContainer $container): PageCacheManager { |
97 | return new PageCacheManager($container->conf->get('resource.page_cache')); | 97 | return new PageCacheManager( |
98 | $container->conf->get('resource.page_cache'), | ||
99 | $container->loginManager->isLoggedIn() | ||
100 | ); | ||
98 | }; | 101 | }; |
99 | 102 | ||
100 | return $container; | 103 | return $container; |
diff --git a/application/front/controllers/DailyController.php b/application/front/controllers/DailyController.php index 271c0ee2..4a0735aa 100644 --- a/application/front/controllers/DailyController.php +++ b/application/front/controllers/DailyController.php | |||
@@ -5,6 +5,7 @@ declare(strict_types=1); | |||
5 | namespace Shaarli\Front\Controller; | 5 | namespace Shaarli\Front\Controller; |
6 | 6 | ||
7 | use DateTime; | 7 | use DateTime; |
8 | use DateTimeImmutable; | ||
8 | use Shaarli\Bookmark\Bookmark; | 9 | use Shaarli\Bookmark\Bookmark; |
9 | use Slim\Http\Request; | 10 | use Slim\Http\Request; |
10 | use Slim\Http\Response; | 11 | use Slim\Http\Response; |
@@ -18,6 +19,8 @@ use Slim\Http\Response; | |||
18 | */ | 19 | */ |
19 | class DailyController extends ShaarliController | 20 | class DailyController extends ShaarliController |
20 | { | 21 | { |
22 | public static $DAILY_RSS_NB_DAYS = 8; | ||
23 | |||
21 | /** | 24 | /** |
22 | * Controller displaying all bookmarks published in a single day. | 25 | * Controller displaying all bookmarks published in a single day. |
23 | * It take a `day` date query parameter (format YYYYMMDD). | 26 | * It take a `day` date query parameter (format YYYYMMDD). |
@@ -88,6 +91,77 @@ class DailyController extends ShaarliController | |||
88 | } | 91 | } |
89 | 92 | ||
90 | /** | 93 | /** |
94 | * Daily RSS feed: 1 RSS entry per day giving all the bookmarks on that day. | ||
95 | * Gives the last 7 days (which have bookmarks). | ||
96 | * This RSS feed cannot be filtered and does not trigger plugins yet. | ||
97 | */ | ||
98 | public function rss(Request $request, Response $response): Response | ||
99 | { | ||
100 | $response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8'); | ||
101 | |||
102 | $pageUrl = page_url($this->container->environment); | ||
103 | $cache = $this->container->pageCacheManager->getCachePage($pageUrl); | ||
104 | |||
105 | $cached = $cache->cachedVersion(); | ||
106 | if (!empty($cached)) { | ||
107 | return $response->write($cached); | ||
108 | } | ||
109 | |||
110 | $days = []; | ||
111 | foreach ($this->container->bookmarkService->search() as $bookmark) { | ||
112 | $day = $bookmark->getCreated()->format('Ymd'); | ||
113 | |||
114 | // Stop iterating after DAILY_RSS_NB_DAYS entries | ||
115 | if (count($days) === static::$DAILY_RSS_NB_DAYS && !isset($days[$day])) { | ||
116 | break; | ||
117 | } | ||
118 | |||
119 | $days[$day][] = $bookmark; | ||
120 | } | ||
121 | |||
122 | // Build the RSS feed. | ||
123 | $indexUrl = escape(index_url($this->container->environment)); | ||
124 | |||
125 | $formatter = $this->container->formatterFactory->getFormatter(); | ||
126 | $formatter->addContextData('index_url', $indexUrl); | ||
127 | |||
128 | $dataPerDay = []; | ||
129 | |||
130 | /** @var Bookmark[] $bookmarks */ | ||
131 | foreach ($days as $day => $bookmarks) { | ||
132 | $dayDatetime = DateTimeImmutable::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); | ||
133 | $dataPerDay[$day] = [ | ||
134 | 'date' => $dayDatetime, | ||
135 | 'date_rss' => $dayDatetime->format(DateTime::RSS), | ||
136 | 'date_human' => format_date($dayDatetime, false, true), | ||
137 | 'absolute_url' => $indexUrl . '/daily?day=' . $day, | ||
138 | 'links' => [], | ||
139 | ]; | ||
140 | |||
141 | foreach ($bookmarks as $key => $bookmark) { | ||
142 | $dataPerDay[$day]['links'][$key] = $formatter->format($bookmark); | ||
143 | |||
144 | // Make permalink URL absolute | ||
145 | if ($bookmark->isNote()) { | ||
146 | $dataPerDay[$day]['links'][$key]['url'] = $indexUrl . $bookmark->getUrl(); | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | |||
151 | $this->assignView('title', $this->container->conf->get('general.title', 'Shaarli')); | ||
152 | $this->assignView('index_url', $indexUrl); | ||
153 | $this->assignView('page_url', $pageUrl); | ||
154 | $this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false)); | ||
155 | $this->assignView('days', $dataPerDay); | ||
156 | |||
157 | $rssContent = $this->render('dailyrss'); | ||
158 | |||
159 | $cache->cache($rssContent); | ||
160 | |||
161 | return $response->write($rssContent); | ||
162 | } | ||
163 | |||
164 | /** | ||
91 | * We need to spread the articles on 3 columns. | 165 | * We need to spread the articles on 3 columns. |
92 | * did not want to use a JavaScript lib like http://masonry.desandro.com/ | 166 | * did not want to use a JavaScript lib like http://masonry.desandro.com/ |
93 | * so I manually spread entries with a simple method: I roughly evaluate the | 167 | * so I manually spread entries with a simple method: I roughly evaluate the |
diff --git a/application/http/HttpUtils.php b/application/http/HttpUtils.php index 2ea9195d..f00c4336 100644 --- a/application/http/HttpUtils.php +++ b/application/http/HttpUtils.php | |||
@@ -369,7 +369,7 @@ function server_url($server) | |||
369 | */ | 369 | */ |
370 | function index_url($server) | 370 | function index_url($server) |
371 | { | 371 | { |
372 | $scriptname = $server['SCRIPT_NAME']; | 372 | $scriptname = $server['SCRIPT_NAME'] ?? ''; |
373 | if (endsWith($scriptname, 'index.php')) { | 373 | if (endsWith($scriptname, 'index.php')) { |
374 | $scriptname = substr($scriptname, 0, -9); | 374 | $scriptname = substr($scriptname, 0, -9); |
375 | } | 375 | } |
@@ -377,7 +377,7 @@ function index_url($server) | |||
377 | } | 377 | } |
378 | 378 | ||
379 | /** | 379 | /** |
380 | * Returns the absolute URL of the current script, with the query | 380 | * Returns the absolute URL of the current script, with current route and query |
381 | * | 381 | * |
382 | * If the resource is "index.php", then it is removed (for better-looking URLs) | 382 | * If the resource is "index.php", then it is removed (for better-looking URLs) |
383 | * | 383 | * |
@@ -387,10 +387,17 @@ function index_url($server) | |||
387 | */ | 387 | */ |
388 | function page_url($server) | 388 | function page_url($server) |
389 | { | 389 | { |
390 | $scriptname = $server['SCRIPT_NAME'] ?? ''; | ||
391 | if (endsWith($scriptname, 'index.php')) { | ||
392 | $scriptname = substr($scriptname, 0, -9); | ||
393 | } | ||
394 | |||
395 | $route = ltrim($server['REQUEST_URI'] ?? '', $scriptname); | ||
390 | if (! empty($server['QUERY_STRING'])) { | 396 | if (! empty($server['QUERY_STRING'])) { |
391 | return index_url($server).'?'.$server['QUERY_STRING']; | 397 | return index_url($server) . $route . '?' . $server['QUERY_STRING']; |
392 | } | 398 | } |
393 | return index_url($server); | 399 | |
400 | return index_url($server) . $route; | ||
394 | } | 401 | } |
395 | 402 | ||
396 | /** | 403 | /** |
diff --git a/application/legacy/LegacyLinkDB.php b/application/legacy/LegacyLinkDB.php index 947005ad..7bf76fd4 100644 --- a/application/legacy/LegacyLinkDB.php +++ b/application/legacy/LegacyLinkDB.php | |||
@@ -353,7 +353,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
353 | 353 | ||
354 | $this->write(); | 354 | $this->write(); |
355 | 355 | ||
356 | $pageCacheManager = new PageCacheManager($pageCacheDir); | 356 | $pageCacheManager = new PageCacheManager($pageCacheDir, $this->loggedIn); |
357 | $pageCacheManager->invalidateCaches(); | 357 | $pageCacheManager->invalidateCaches(); |
358 | } | 358 | } |
359 | 359 | ||
diff --git a/application/render/PageCacheManager.php b/application/render/PageCacheManager.php index bd91fe0d..97805c35 100644 --- a/application/render/PageCacheManager.php +++ b/application/render/PageCacheManager.php | |||
@@ -2,6 +2,8 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Render; | 3 | namespace Shaarli\Render; |
4 | 4 | ||
5 | use Shaarli\Feed\CachedPage; | ||
6 | |||
5 | /** | 7 | /** |
6 | * Cache utilities | 8 | * Cache utilities |
7 | */ | 9 | */ |
@@ -10,9 +12,13 @@ class PageCacheManager | |||
10 | /** @var string Cache directory */ | 12 | /** @var string Cache directory */ |
11 | protected $pageCacheDir; | 13 | protected $pageCacheDir; |
12 | 14 | ||
13 | public function __construct(string $pageCacheDir) | 15 | /** @var bool */ |
16 | protected $isLoggedIn; | ||
17 | |||
18 | public function __construct(string $pageCacheDir, bool $isLoggedIn) | ||
14 | { | 19 | { |
15 | $this->pageCacheDir = $pageCacheDir; | 20 | $this->pageCacheDir = $pageCacheDir; |
21 | $this->isLoggedIn = $isLoggedIn; | ||
16 | } | 22 | } |
17 | 23 | ||
18 | /** | 24 | /** |
@@ -42,4 +48,13 @@ class PageCacheManager | |||
42 | // Purge page cache shared by sessions. | 48 | // Purge page cache shared by sessions. |
43 | $this->purgeCachedPages(); | 49 | $this->purgeCachedPages(); |
44 | } | 50 | } |
51 | |||
52 | public function getCachePage(string $pageUrl): CachedPage | ||
53 | { | ||
54 | return new CachedPage( | ||
55 | $this->pageCacheDir, | ||
56 | $pageUrl, | ||
57 | false === $this->isLoggedIn | ||
58 | ); | ||
59 | } | ||
45 | } | 60 | } |
@@ -301,104 +301,6 @@ if (!isset($_SESSION['tokens'])) { | |||
301 | } | 301 | } |
302 | 302 | ||
303 | /** | 303 | /** |
304 | * Daily RSS feed: 1 RSS entry per day giving all the bookmarks on that day. | ||
305 | * Gives the last 7 days (which have bookmarks). | ||
306 | * This RSS feed cannot be filtered. | ||
307 | * | ||
308 | * @param BookmarkServiceInterface $bookmarkService | ||
309 | * @param ConfigManager $conf Configuration Manager instance | ||
310 | * @param LoginManager $loginManager LoginManager instance | ||
311 | */ | ||
312 | function showDailyRSS($bookmarkService, $conf, $loginManager) | ||
313 | { | ||
314 | // Cache system | ||
315 | $query = $_SERVER['QUERY_STRING']; | ||
316 | $cache = new CachedPage( | ||
317 | $conf->get('config.PAGE_CACHE'), | ||
318 | page_url($_SERVER), | ||
319 | startsWith($query, 'do=dailyrss') && !$loginManager->isLoggedIn() | ||
320 | ); | ||
321 | $cached = $cache->cachedVersion(); | ||
322 | if (!empty($cached)) { | ||
323 | echo $cached; | ||
324 | exit; | ||
325 | } | ||
326 | |||
327 | /* Some Shaarlies may have very few bookmarks, so we need to look | ||
328 | back in time until we have enough days ($nb_of_days). | ||
329 | */ | ||
330 | $nb_of_days = 7; // We take 7 days. | ||
331 | $today = date('Ymd'); | ||
332 | $days = array(); | ||
333 | |||
334 | foreach ($bookmarkService->search() as $bookmark) { | ||
335 | $day = $bookmark->getCreated()->format('Ymd'); // Extract day (without time) | ||
336 | if (strcmp($day, $today) < 0) { | ||
337 | if (empty($days[$day])) { | ||
338 | $days[$day] = array(); | ||
339 | } | ||
340 | $days[$day][] = $bookmark; | ||
341 | } | ||
342 | |||
343 | if (count($days) > $nb_of_days) { | ||
344 | break; // Have we collected enough days? | ||
345 | } | ||
346 | } | ||
347 | |||
348 | // Build the RSS feed. | ||
349 | header('Content-Type: application/rss+xml; charset=utf-8'); | ||
350 | $pageaddr = escape(index_url($_SERVER)); | ||
351 | echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">'; | ||
352 | echo '<channel>'; | ||
353 | echo '<title>Daily - '. $conf->get('general.title') . '</title>'; | ||
354 | echo '<link>'. $pageaddr .'</link>'; | ||
355 | echo '<description>Daily shared bookmarks</description>'; | ||
356 | echo '<language>en-en</language>'; | ||
357 | echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL; | ||
358 | |||
359 | $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); | ||
360 | $formatter = $factory->getFormatter(); | ||
361 | $formatter->addContextData('index_url', index_url($_SERVER)); | ||
362 | // For each day. | ||
363 | /** @var Bookmark[] $bookmarks */ | ||
364 | foreach ($days as $day => $bookmarks) { | ||
365 | $formattedBookmarks = []; | ||
366 | $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); | ||
367 | $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page. | ||
368 | |||
369 | // We pre-format some fields for proper output. | ||
370 | foreach ($bookmarks as $key => $bookmark) { | ||
371 | $formattedBookmarks[$key] = $formatter->format($bookmark); | ||
372 | // This page is a bit specific, we need raw description to calculate the length | ||
373 | $formattedBookmarks[$key]['formatedDescription'] = $formattedBookmarks[$key]['description']; | ||
374 | $formattedBookmarks[$key]['description'] = $bookmark->getDescription(); | ||
375 | |||
376 | if ($bookmark->isNote()) { | ||
377 | $link['url'] = index_url($_SERVER) . $bookmark->getUrl(); // make permalink URL absolute | ||
378 | } | ||
379 | } | ||
380 | |||
381 | // Then build the HTML for this day: | ||
382 | $tpl = new RainTPL(); | ||
383 | $tpl->assign('title', $conf->get('general.title')); | ||
384 | $tpl->assign('daydate', $dayDate->getTimestamp()); | ||
385 | $tpl->assign('absurl', $absurl); | ||
386 | $tpl->assign('links', $formattedBookmarks); | ||
387 | $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS))); | ||
388 | $tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false)); | ||
389 | $tpl->assign('index_url', $pageaddr); | ||
390 | $html = $tpl->draw('dailyrss', true); | ||
391 | |||
392 | echo $html . PHP_EOL; | ||
393 | } | ||
394 | echo '</channel></rss><!-- Cached version of '. escape(page_url($_SERVER)) .' -->'; | ||
395 | |||
396 | $cache->cache(ob_get_contents()); | ||
397 | ob_end_flush(); | ||
398 | exit; | ||
399 | } | ||
400 | |||
401 | /** | ||
402 | * Renders the linklist | 304 | * Renders the linklist |
403 | * | 305 | * |
404 | * @param pageBuilder $PAGE pageBuilder instance. | 306 | * @param pageBuilder $PAGE pageBuilder instance. |
@@ -424,7 +326,7 @@ function showLinkList($PAGE, $linkDb, $conf, $pluginManager, $loginManager) | |||
424 | */ | 326 | */ |
425 | function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionManager, $loginManager) | 327 | function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionManager, $loginManager) |
426 | { | 328 | { |
427 | $pageCacheManager = new PageCacheManager($conf->get('resource.page_cache')); | 329 | $pageCacheManager = new PageCacheManager($conf->get('resource.page_cache'), $loginManager->isLoggedIn()); |
428 | $updater = new Updater( | 330 | $updater = new Updater( |
429 | UpdaterUtils::read_updates_file($conf->get('resource.updates')), | 331 | UpdaterUtils::read_updates_file($conf->get('resource.updates')), |
430 | $bookmarkService, | 332 | $bookmarkService, |
@@ -1715,7 +1617,7 @@ try { | |||
1715 | $linkDb = new BookmarkFileService($conf, $history, $loginManager->isLoggedIn()); | 1617 | $linkDb = new BookmarkFileService($conf, $history, $loginManager->isLoggedIn()); |
1716 | 1618 | ||
1717 | if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { | 1619 | if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { |
1718 | showDailyRSS($linkDb, $conf, $loginManager); | 1620 | header('Location: ./daily-rss'); |
1719 | exit; | 1621 | exit; |
1720 | } | 1622 | } |
1721 | 1623 | ||
@@ -1747,6 +1649,7 @@ $app->group('', function () { | |||
1747 | $this->get('/tag-cloud', '\Shaarli\Front\Controller\TagCloudController:cloud')->setName('tagcloud'); | 1649 | $this->get('/tag-cloud', '\Shaarli\Front\Controller\TagCloudController:cloud')->setName('tagcloud'); |
1748 | $this->get('/tag-list', '\Shaarli\Front\Controller\TagCloudController:list')->setName('taglist'); | 1650 | $this->get('/tag-list', '\Shaarli\Front\Controller\TagCloudController:list')->setName('taglist'); |
1749 | $this->get('/daily', '\Shaarli\Front\Controller\DailyController:index')->setName('daily'); | 1651 | $this->get('/daily', '\Shaarli\Front\Controller\DailyController:index')->setName('daily'); |
1652 | $this->get('/daily-rss', '\Shaarli\Front\Controller\DailyController:rss')->setName('dailyrss'); | ||
1750 | 1653 | ||
1751 | $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\TagController:addTag')->setName('add-tag'); | 1654 | $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\TagController:addTag')->setName('add-tag'); |
1752 | })->add('\Shaarli\Front\ShaarliMiddleware'); | 1655 | })->add('\Shaarli\Front\ShaarliMiddleware'); |
diff --git a/tests/front/controller/DailyControllerTest.php b/tests/front/controller/DailyControllerTest.php index 2714bfd9..72a0339f 100644 --- a/tests/front/controller/DailyControllerTest.php +++ b/tests/front/controller/DailyControllerTest.php | |||
@@ -9,11 +9,13 @@ use Shaarli\Bookmark\Bookmark; | |||
9 | use Shaarli\Bookmark\BookmarkServiceInterface; | 9 | use Shaarli\Bookmark\BookmarkServiceInterface; |
10 | use Shaarli\Config\ConfigManager; | 10 | use Shaarli\Config\ConfigManager; |
11 | use Shaarli\Container\ShaarliContainer; | 11 | use Shaarli\Container\ShaarliContainer; |
12 | use Shaarli\Feed\CachedPage; | ||
12 | use Shaarli\Formatter\BookmarkFormatter; | 13 | use Shaarli\Formatter\BookmarkFormatter; |
13 | use Shaarli\Formatter\BookmarkRawFormatter; | 14 | use Shaarli\Formatter\BookmarkRawFormatter; |
14 | use Shaarli\Formatter\FormatterFactory; | 15 | use Shaarli\Formatter\FormatterFactory; |
15 | use Shaarli\Plugin\PluginManager; | 16 | use Shaarli\Plugin\PluginManager; |
16 | use Shaarli\Render\PageBuilder; | 17 | use Shaarli\Render\PageBuilder; |
18 | use Shaarli\Render\PageCacheManager; | ||
17 | use Shaarli\Security\LoginManager; | 19 | use Shaarli\Security\LoginManager; |
18 | use Slim\Http\Request; | 20 | use Slim\Http\Request; |
19 | use Slim\Http\Response; | 21 | use Slim\Http\Response; |
@@ -30,9 +32,10 @@ class DailyControllerTest extends TestCase | |||
30 | { | 32 | { |
31 | $this->container = $this->createMock(ShaarliContainer::class); | 33 | $this->container = $this->createMock(ShaarliContainer::class); |
32 | $this->controller = new DailyController($this->container); | 34 | $this->controller = new DailyController($this->container); |
35 | DailyController::$DAILY_RSS_NB_DAYS = 2; | ||
33 | } | 36 | } |
34 | 37 | ||
35 | public function testValidControllerInvokeDefault(): void | 38 | public function testValidIndexControllerInvokeDefault(): void |
36 | { | 39 | { |
37 | $this->createValidContainerMockSet(); | 40 | $this->createValidContainerMockSet(); |
38 | 41 | ||
@@ -173,7 +176,7 @@ class DailyControllerTest extends TestCase | |||
173 | /** | 176 | /** |
174 | * Daily page - test that everything goes fine with no future or past bookmarks | 177 | * Daily page - test that everything goes fine with no future or past bookmarks |
175 | */ | 178 | */ |
176 | public function testValidControllerInvokeNoFutureOrPast(): void | 179 | public function testValidIndexControllerInvokeNoFutureOrPast(): void |
177 | { | 180 | { |
178 | $this->createValidContainerMockSet(); | 181 | $this->createValidContainerMockSet(); |
179 | 182 | ||
@@ -247,7 +250,7 @@ class DailyControllerTest extends TestCase | |||
247 | /** | 250 | /** |
248 | * Daily page - test that height adjustment in columns is working | 251 | * Daily page - test that height adjustment in columns is working |
249 | */ | 252 | */ |
250 | public function testValidControllerInvokeHeightAdjustment(): void | 253 | public function testValidIndexControllerInvokeHeightAdjustment(): void |
251 | { | 254 | { |
252 | $this->createValidContainerMockSet(); | 255 | $this->createValidContainerMockSet(); |
253 | 256 | ||
@@ -318,7 +321,7 @@ class DailyControllerTest extends TestCase | |||
318 | /** | 321 | /** |
319 | * Daily page - no bookmark | 322 | * Daily page - no bookmark |
320 | */ | 323 | */ |
321 | public function testValidControllerInvokeNoBookmark(): void | 324 | public function testValidIndexControllerInvokeNoBookmark(): void |
322 | { | 325 | { |
323 | $this->createValidContainerMockSet(); | 326 | $this->createValidContainerMockSet(); |
324 | 327 | ||
@@ -364,6 +367,136 @@ class DailyControllerTest extends TestCase | |||
364 | static::assertEquals((new \DateTime())->setTime(0, 0), $assignedVariables['dayDate']); | 367 | static::assertEquals((new \DateTime())->setTime(0, 0), $assignedVariables['dayDate']); |
365 | } | 368 | } |
366 | 369 | ||
370 | /** | ||
371 | * Daily RSS - default behaviour | ||
372 | */ | ||
373 | public function testValidRssControllerInvokeDefault(): void | ||
374 | { | ||
375 | $this->createValidContainerMockSet(); | ||
376 | |||
377 | $dates = [ | ||
378 | new \DateTimeImmutable('2020-05-17'), | ||
379 | new \DateTimeImmutable('2020-05-15'), | ||
380 | new \DateTimeImmutable('2020-05-13'), | ||
381 | ]; | ||
382 | |||
383 | $request = $this->createMock(Request::class); | ||
384 | $response = new Response(); | ||
385 | |||
386 | $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([ | ||
387 | (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'), | ||
388 | (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'), | ||
389 | (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'), | ||
390 | (new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'), | ||
391 | ]); | ||
392 | |||
393 | $this->container->pageCacheManager | ||
394 | ->expects(static::once()) | ||
395 | ->method('getCachePage') | ||
396 | ->willReturnCallback(function (): CachedPage { | ||
397 | $cachedPage = $this->createMock(CachedPage::class); | ||
398 | $cachedPage->expects(static::once())->method('cache')->with('dailyrss'); | ||
399 | |||
400 | return $cachedPage; | ||
401 | } | ||
402 | ); | ||
403 | |||
404 | // Save RainTPL assigned variables | ||
405 | $assignedVariables = []; | ||
406 | $this->assignTemplateVars($assignedVariables); | ||
407 | |||
408 | $result = $this->controller->rss($request, $response); | ||
409 | |||
410 | static::assertSame(200, $result->getStatusCode()); | ||
411 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
412 | static::assertSame('dailyrss', (string) $result->getBody()); | ||
413 | static::assertSame('Shaarli', $assignedVariables['title']); | ||
414 | static::assertSame('http://shaarli', $assignedVariables['index_url']); | ||
415 | static::assertSame('http://shaarli/daily-rss', $assignedVariables['page_url']); | ||
416 | static::assertFalse($assignedVariables['hide_timestamps']); | ||
417 | static::assertCount(2, $assignedVariables['days']); | ||
418 | |||
419 | $day = $assignedVariables['days'][$dates[0]->format('Ymd')]; | ||
420 | |||
421 | static::assertEquals($dates[0], $day['date']); | ||
422 | static::assertSame($dates[0]->format(\DateTimeInterface::RSS), $day['date_rss']); | ||
423 | static::assertSame(format_date($dates[0], false), $day['date_human']); | ||
424 | static::assertSame('http://shaarli/daily?day='. $dates[0]->format('Ymd'), $day['absolute_url']); | ||
425 | static::assertCount(1, $day['links']); | ||
426 | static::assertSame(1, $day['links'][0]['id']); | ||
427 | static::assertSame('http://domain.tld/1', $day['links'][0]['url']); | ||
428 | static::assertEquals($dates[0], $day['links'][0]['created']); | ||
429 | |||
430 | $day = $assignedVariables['days'][$dates[1]->format('Ymd')]; | ||
431 | |||
432 | static::assertEquals($dates[1], $day['date']); | ||
433 | static::assertSame($dates[1]->format(\DateTimeInterface::RSS), $day['date_rss']); | ||
434 | static::assertSame(format_date($dates[1], false), $day['date_human']); | ||
435 | static::assertSame('http://shaarli/daily?day='. $dates[1]->format('Ymd'), $day['absolute_url']); | ||
436 | static::assertCount(2, $day['links']); | ||
437 | |||
438 | static::assertSame(2, $day['links'][0]['id']); | ||
439 | static::assertSame('http://domain.tld/2', $day['links'][0]['url']); | ||
440 | static::assertEquals($dates[1], $day['links'][0]['created']); | ||
441 | static::assertSame(3, $day['links'][1]['id']); | ||
442 | static::assertSame('http://domain.tld/3', $day['links'][1]['url']); | ||
443 | static::assertEquals($dates[1], $day['links'][1]['created']); | ||
444 | } | ||
445 | |||
446 | /** | ||
447 | * Daily RSS - trigger cache rendering | ||
448 | */ | ||
449 | public function testValidRssControllerInvokeTriggerCache(): void | ||
450 | { | ||
451 | $this->createValidContainerMockSet(); | ||
452 | |||
453 | $request = $this->createMock(Request::class); | ||
454 | $response = new Response(); | ||
455 | |||
456 | $this->container->pageCacheManager->method('getCachePage')->willReturnCallback(function (): CachedPage { | ||
457 | $cachedPage = $this->createMock(CachedPage::class); | ||
458 | $cachedPage->method('cachedVersion')->willReturn('this is cache!'); | ||
459 | |||
460 | return $cachedPage; | ||
461 | }); | ||
462 | |||
463 | $this->container->bookmarkService->expects(static::never())->method('search'); | ||
464 | |||
465 | $result = $this->controller->rss($request, $response); | ||
466 | |||
467 | static::assertSame(200, $result->getStatusCode()); | ||
468 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
469 | static::assertSame('this is cache!', (string) $result->getBody()); | ||
470 | } | ||
471 | |||
472 | /** | ||
473 | * Daily RSS - No bookmark | ||
474 | */ | ||
475 | public function testValidRssControllerInvokeNoBookmark(): void | ||
476 | { | ||
477 | $this->createValidContainerMockSet(); | ||
478 | |||
479 | $request = $this->createMock(Request::class); | ||
480 | $response = new Response(); | ||
481 | |||
482 | $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([]); | ||
483 | |||
484 | // Save RainTPL assigned variables | ||
485 | $assignedVariables = []; | ||
486 | $this->assignTemplateVars($assignedVariables); | ||
487 | |||
488 | $result = $this->controller->rss($request, $response); | ||
489 | |||
490 | static::assertSame(200, $result->getStatusCode()); | ||
491 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
492 | static::assertSame('dailyrss', (string) $result->getBody()); | ||
493 | static::assertSame('Shaarli', $assignedVariables['title']); | ||
494 | static::assertSame('http://shaarli', $assignedVariables['index_url']); | ||
495 | static::assertSame('http://shaarli/daily-rss', $assignedVariables['page_url']); | ||
496 | static::assertFalse($assignedVariables['hide_timestamps']); | ||
497 | static::assertCount(0, $assignedVariables['days']); | ||
498 | } | ||
499 | |||
367 | protected function createValidContainerMockSet(): void | 500 | protected function createValidContainerMockSet(): void |
368 | { | 501 | { |
369 | $loginManager = $this->createMock(LoginManager::class); | 502 | $loginManager = $this->createMock(LoginManager::class); |
@@ -403,6 +536,17 @@ class DailyControllerTest extends TestCase | |||
403 | }) | 536 | }) |
404 | ; | 537 | ; |
405 | $this->container->formatterFactory = $formatterFactory; | 538 | $this->container->formatterFactory = $formatterFactory; |
539 | |||
540 | // CacheManager | ||
541 | $pageCacheManager = $this->createMock(PageCacheManager::class); | ||
542 | $this->container->pageCacheManager = $pageCacheManager; | ||
543 | |||
544 | // $_SERVER | ||
545 | $this->container->environment = [ | ||
546 | 'SERVER_NAME' => 'shaarli', | ||
547 | 'SERVER_PORT' => '80', | ||
548 | 'REQUEST_URI' => '/daily-rss', | ||
549 | ]; | ||
406 | } | 550 | } |
407 | 551 | ||
408 | protected function assignTemplateVars(array &$variables): void | 552 | protected function assignTemplateVars(array &$variables): void |
diff --git a/tests/http/HttpUtils/IndexUrlTest.php b/tests/http/HttpUtils/IndexUrlTest.php index bcbe59cb..73d33cd4 100644 --- a/tests/http/HttpUtils/IndexUrlTest.php +++ b/tests/http/HttpUtils/IndexUrlTest.php | |||
@@ -71,4 +71,36 @@ class IndexUrlTest extends \PHPUnit\Framework\TestCase | |||
71 | ) | 71 | ) |
72 | ); | 72 | ); |
73 | } | 73 | } |
74 | |||
75 | /** | ||
76 | * The route is stored in REQUEST_URI | ||
77 | */ | ||
78 | public function testPageUrlWithRoute() | ||
79 | { | ||
80 | $this->assertEquals( | ||
81 | 'http://host.tld/picture-wall', | ||
82 | page_url( | ||
83 | array( | ||
84 | 'HTTPS' => 'Off', | ||
85 | 'SERVER_NAME' => 'host.tld', | ||
86 | 'SERVER_PORT' => '80', | ||
87 | 'SCRIPT_NAME' => '/index.php', | ||
88 | 'REQUEST_URI' => '/picture-wall', | ||
89 | ) | ||
90 | ) | ||
91 | ); | ||
92 | |||
93 | $this->assertEquals( | ||
94 | 'http://host.tld/admin/picture-wall', | ||
95 | page_url( | ||
96 | array( | ||
97 | 'HTTPS' => 'Off', | ||
98 | 'SERVER_NAME' => 'host.tld', | ||
99 | 'SERVER_PORT' => '80', | ||
100 | 'SCRIPT_NAME' => '/admin/index.php', | ||
101 | 'REQUEST_URI' => '/admin/picture-wall', | ||
102 | ) | ||
103 | ) | ||
104 | ); | ||
105 | } | ||
74 | } | 106 | } |
diff --git a/tests/render/PageCacheManagerTest.php b/tests/render/PageCacheManagerTest.php index 991515d0..b870e6eb 100644 --- a/tests/render/PageCacheManagerTest.php +++ b/tests/render/PageCacheManagerTest.php | |||
@@ -32,7 +32,7 @@ class PageCacheManagerTest extends TestCase | |||
32 | */ | 32 | */ |
33 | public function setUp() | 33 | public function setUp() |
34 | { | 34 | { |
35 | $this->cacheManager = new PageCacheManager(static::$testCacheDir); | 35 | $this->cacheManager = new PageCacheManager(static::$testCacheDir, true); |
36 | 36 | ||
37 | if (!is_dir(self::$testCacheDir)) { | 37 | if (!is_dir(self::$testCacheDir)) { |
38 | mkdir(self::$testCacheDir); | 38 | mkdir(self::$testCacheDir); |
@@ -73,7 +73,7 @@ class PageCacheManagerTest extends TestCase | |||
73 | */ | 73 | */ |
74 | public function testPurgeCachedPagesMissingDir() | 74 | public function testPurgeCachedPagesMissingDir() |
75 | { | 75 | { |
76 | $this->cacheManager = new PageCacheManager(self::$testCacheDir . '_missing'); | 76 | $this->cacheManager = new PageCacheManager(self::$testCacheDir . '_missing', true); |
77 | 77 | ||
78 | $oldlog = ini_get('error_log'); | 78 | $oldlog = ini_get('error_log'); |
79 | ini_set('error_log', '/dev/null'); | 79 | ini_set('error_log', '/dev/null'); |
diff --git a/tpl/default/daily.html b/tpl/default/daily.html index f07c0a8b..9ccd1e61 100644 --- a/tpl/default/daily.html +++ b/tpl/default/daily.html | |||
@@ -11,7 +11,7 @@ | |||
11 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor" id="daily"> | 11 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor" id="daily"> |
12 | <h2 class="window-title"> | 12 | <h2 class="window-title"> |
13 | {'The Daily Shaarli'|t} | 13 | {'The Daily Shaarli'|t} |
14 | <a href="./?do=dailyrss" title="{'1 RSS entry per day'|t}"><i class="fa fa-rss"></i></a> | 14 | <a href="./daily-rss" title="{'1 RSS entry per day'|t}"><i class="fa fa-rss"></i></a> |
15 | </h2> | 15 | </h2> |
16 | 16 | ||
17 | <div id="plugin_zone_start_daily" class="plugin_zone"> | 17 | <div id="plugin_zone_start_daily" class="plugin_zone"> |
diff --git a/tpl/default/dailyrss.html b/tpl/default/dailyrss.html index f589b06e..d40d9496 100644 --- a/tpl/default/dailyrss.html +++ b/tpl/default/dailyrss.html | |||
@@ -1,16 +1,32 @@ | |||
1 | <item> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <title>{$title} - {function="strftime('%A %e %B %Y', $daydate)"}</title> | 2 | <rss version="2.0"> |
3 | <guid>{$absurl}</guid> | 3 | <channel> |
4 | <link>{$absurl}</link> | 4 | <title>Daily - {$title}</title> |
5 | <pubDate>{$rssdate}</pubDate> | 5 | <link>{$index_url}</link> |
6 | <description><![CDATA[ | 6 | <description>Daily shaared bookmarks</description> |
7 | {loop="links"} | 7 | <language>{$language}</language> |
8 | <h3><a href="{$value.url}">{$value.title}</a></h3> | 8 | <copyright>{$index_url}</copyright> |
9 | <small>{if="!$hide_timestamps"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> | 9 | <generator>Shaarli</generator> |
10 | {$value.url}</small><br> | 10 | |
11 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> | 11 | {loop="$days"} |
12 | {if="$value.description"}{$value.formatedDescription}{/if} | 12 | <item> |
13 | <br><br><hr> | 13 | <title>{$value.date_human} - {$title}</title> |
14 | {/loop} | 14 | <guid>{$value.absolute_url}</guid> |
15 | ]]></description> | 15 | <link>{$value.absolute_url}</link> |
16 | </item> | 16 | <pubDate>{$value.date_rss}</pubDate> |
17 | <description><![CDATA[ | ||
18 | {loop="$value.links"} | ||
19 | <h3><a href="{$value.url}">{$value.title}</a></h3> | ||
20 | <small> | ||
21 | {if="!$hide_timestamps"}{$value.created|format_date} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> | ||
22 | {$value.url} | ||
23 | </small><br> | ||
24 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> | ||
25 | {if="$value.description"}{$value.description}{/if} | ||
26 | <br><br><hr> | ||
27 | {/loop} | ||
28 | ]]></description> | ||
29 | </item> | ||
30 | {/loop} | ||
31 | </channel> | ||
32 | </rss><!-- Cached version of {$page_url} --> | ||
diff --git a/tpl/vintage/daily.html b/tpl/vintage/daily.html index 4892e0e1..adcdf6ab 100644 --- a/tpl/vintage/daily.html +++ b/tpl/vintage/daily.html | |||
@@ -24,7 +24,7 @@ | |||
24 | {/loop} | 24 | {/loop} |
25 | 25 | ||
26 | <br> | 26 | <br> |
27 | <a href="./?do=dailyrss" title="1 RSS entry per day"><img src="img/feed-icon-14x14.png" alt="rss_feed">Daily RSS Feed</a> | 27 | <a href="./daily-rss" title="1 RSS entry per day"><img src="img/feed-icon-14x14.png" alt="rss_feed">Daily RSS Feed</a> |
28 | </div> | 28 | </div> |
29 | 29 | ||
30 | <div class="dailyTitle"> | 30 | <div class="dailyTitle"> |
diff --git a/tpl/vintage/dailyrss.html b/tpl/vintage/dailyrss.html index f589b06e..ff19bbfb 100644 --- a/tpl/vintage/dailyrss.html +++ b/tpl/vintage/dailyrss.html | |||
@@ -1,16 +1,32 @@ | |||
1 | <item> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <title>{$title} - {function="strftime('%A %e %B %Y', $daydate)"}</title> | 2 | <rss version="2.0"> |
3 | <guid>{$absurl}</guid> | 3 | <channel> |
4 | <link>{$absurl}</link> | 4 | <title>Daily - {$title}</title> |
5 | <pubDate>{$rssdate}</pubDate> | 5 | <link>{$index_url}</link> |
6 | <description><![CDATA[ | 6 | <description>Daily shaared bookmarks</description> |
7 | {loop="links"} | 7 | <language>{$language}</language> |
8 | <h3><a href="{$value.url}">{$value.title}</a></h3> | 8 | <copyright>{$index_url}</copyright> |
9 | <small>{if="!$hide_timestamps"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> | 9 | <generator>Shaarli</generator> |
10 | {$value.url}</small><br> | 10 | |
11 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> | 11 | {loop="$days"} |
12 | {if="$value.description"}{$value.formatedDescription}{/if} | 12 | <item> |
13 | <br><br><hr> | 13 | <title>{$value.date_human} - {$title}</title> |
14 | <guid>{$value.absolute_url}</guid> | ||
15 | <link>{$value.absolute_url}</link> | ||
16 | <pubDate>{$value.date_rss}</pubDate> | ||
17 | <description><![CDATA[ | ||
18 | {loop="$value.links"} | ||
19 | <h3><a href="{$value.url}">{$value.title}</a></h3> | ||
20 | <small> | ||
21 | {if="!$hide_timestamps"}{$value.created|format_date} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> | ||
22 | {$value.url} | ||
23 | </small><br> | ||
24 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> | ||
25 | {if="$value.description"}{$value.description}{/if} | ||
26 | <br><br><hr> | ||
14 | {/loop} | 27 | {/loop} |
15 | ]]></description> | 28 | ]]></description> |
16 | </item> | 29 | </item> |
30 | {/loop} | ||
31 | </channel> | ||
32 | </rss><!-- Cached version of {$page_url} --> | ||