]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Feature: add weekly and monthly view/RSS feed for daily page
authorArthurHoaro <arthur@hoa.ro>
Fri, 16 Oct 2020 09:50:53 +0000 (11:50 +0200)
committerArthurHoaro <arthur@hoa.ro>
Tue, 27 Oct 2020 18:45:02 +0000 (19:45 +0100)
  - Heavy refactoring of DailyController
  - Add a banner like in tag cloud to display monthly and weekly links
  - Translations: t() now supports variables with optional first letter
uppercase

Fixes #160

application/Utils.php
application/bookmark/BookmarkFileService.php
application/bookmark/BookmarkServiceInterface.php
application/front/controller/visitor/DailyController.php
application/helper/DailyPageHelper.php [new file with mode: 0644]
inc/languages/fr/LC_MESSAGES/shaarli.po
tests/bookmark/BookmarkFileServiceTest.php
tests/front/controller/visitor/DailyControllerTest.php
tests/helper/DailyPageHelperTest.php [new file with mode: 0644]
tpl/default/daily.html
tpl/default/dailyrss.html

index bc1c9f5d6133b67eacc321b3d41da57f60b0f38d..db046893166aaa5a773a815822007541d5d92824 100644 (file)
@@ -326,6 +326,23 @@ function format_date($date, $time = true, $intl = true)
     return $formatter->format($date);
 }
 
+/**
+ * Format the date month according to the locale.
+ *
+ * @param DateTimeInterface $date to format.
+ *
+ * @return bool|string Formatted date, or false if the input is invalid.
+ */
+function format_month(DateTimeInterface $date)
+{
+    if (! $date instanceof DateTimeInterface) {
+        return false;
+    }
+
+    return strftime('%B', $date->getTimestamp());
+}
+
+
 /**
  * Check if the input is an integer, no matter its real type.
  *
@@ -454,16 +471,20 @@ function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
  * Wrapper function for translation which match the API
  * of gettext()/_() and ngettext().
  *
- * @param string $text   Text to translate.
- * @param string $nText  The plural message ID.
- * @param int    $nb     The number of items for plural forms.
- * @param string $domain The domain where the translation is stored (default: shaarli).
+ * @param string $text      Text to translate.
+ * @param string $nText     The plural message ID.
+ * @param int    $nb        The number of items for plural forms.
+ * @param string $domain    The domain where the translation is stored (default: shaarli).
+ * @param array  $variables Associative array of variables to replace in translated text.
+ * @param bool   $fixCase   Apply `ucfirst` on the translated string, might be useful for strings with variables.
  *
  * @return string Text translated.
  */
-function t($text, $nText = '', $nb = 1, $domain = 'shaarli')
+function t($text, $nText = '', $nb = 1, $domain = 'shaarli', $variables = [], $fixCase = false)
 {
-    return dn__($domain, $text, $nText, $nb);
+    $postFunction = $fixCase ? 'ucfirst' : function ($input) { return $input; };
+
+    return $postFunction(dn__($domain, $text, $nText, $nb, $variables));
 }
 
 /**
index 14b3d620cebfa3de717089e3cf518d15c390aca1..0df2f47f7e6d3fb4aad5ebbfa8d16253a59089a3 100644 (file)
@@ -343,26 +343,42 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function days(): array
-    {
-        $bookmarkDays = [];
-        foreach ($this->search() as $bookmark) {
-            $bookmarkDays[$bookmark->getCreated()->format('Ymd')] = 0;
+    public function findByDate(
+        \DateTimeInterface $from,
+        \DateTimeInterface $to,
+        ?\DateTimeInterface &$previous,
+        ?\DateTimeInterface &$next
+    ): array {
+        $out = [];
+        $previous = null;
+        $next = null;
+
+        foreach ($this->search([], null, false, false, true) as $bookmark) {
+            if ($to < $bookmark->getCreated()) {
+                $next = $bookmark->getCreated();
+            } else if ($from < $bookmark->getCreated() && $to > $bookmark->getCreated()) {
+                $out[] = $bookmark;
+            } else {
+                if ($previous !== null) {
+                    break;
+                }
+                $previous = $bookmark->getCreated();
+            }
         }
-        $bookmarkDays = array_keys($bookmarkDays);
-        sort($bookmarkDays);
 
-        return array_map('strval', $bookmarkDays);
+        return $out;
     }
 
     /**
      * @inheritDoc
      */
-    public function filterDay(string $request)
+    public function getLatest(): ?Bookmark
     {
-        $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC;
+        foreach ($this->search([], null, false, false, true) as $bookmark) {
+            return $bookmark;
+        }
 
-        return $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_DAY, $request, false, $visibility);
+        return null;
     }
 
     /**
index 9fa615333567416d87c228a3aae1bc041f31167d..08cdbb4ed4055cc3f6ef2672b991f9c3b1cfeda7 100644 (file)
@@ -156,22 +156,29 @@ interface BookmarkServiceInterface
     public function bookmarksCountPerTag(array $filteringTags = [], ?string $visibility = null): array;
 
     /**
-     * Returns the list of days containing articles (oldest first)
+     * Return a list of bookmark matching provided period of time.
+     * It also update directly previous and next date outside of given period found in the datastore.
      *
-     * @return array containing days (in format YYYYMMDD).
+     * @param \DateTimeInterface      $from     Starting date.
+     * @param \DateTimeInterface      $to       Ending date.
+     * @param \DateTimeInterface|null $previous (by reference) updated with first created date found before $from.
+     * @param \DateTimeInterface|null $next     (by reference) updated with first created date found after $to.
+     *
+     * @return array List of bookmarks matching provided period of time.
      */
-    public function days(): array;
+    public function findByDate(
+        \DateTimeInterface $from,
+        \DateTimeInterface $to,
+        ?\DateTimeInterface &$previous,
+        ?\DateTimeInterface &$next
+    ): array;
 
     /**
-     * Returns the list of articles for a given day.
-     *
-     * @param string $request day to filter. Format: YYYYMMDD.
+     * Returns the latest bookmark by creation date.
      *
-     * @return Bookmark[] list of shaare found.
-     *
-     * @throws BookmarkNotFoundException
+     * @return Bookmark|null Found Bookmark or null if the datastore is empty.
      */
-    public function filterDay(string $request);
+    public function getLatest(): ?Bookmark;
 
     /**
      * Creates the default database after a fresh install.
index 07617cf11fdfe49b047c24d0641477ad6b66e8d4..728bc2d8e81e4107d32efed827351255dea06b58 100644 (file)
@@ -5,8 +5,8 @@ declare(strict_types=1);
 namespace Shaarli\Front\Controller\Visitor;
 
 use DateTime;
-use DateTimeImmutable;
 use Shaarli\Bookmark\Bookmark;
+use Shaarli\Helper\DailyPageHelper;
 use Shaarli\Render\TemplatePage;
 use Slim\Http\Request;
 use Slim\Http\Response;
@@ -26,32 +26,20 @@ class DailyController extends ShaarliVisitorController
      */
     public function index(Request $request, Response $response): Response
     {
-        $day = $request->getQueryParam('day') ?? date('Ymd');
-
-        $availableDates = $this->container->bookmarkService->days();
-        $nbAvailableDates = count($availableDates);
-        $index = array_search($day, $availableDates);
-
-        if ($index === false) {
-            // no bookmarks for day, but at least one day with bookmarks
-            $day = $availableDates[$nbAvailableDates - 1] ?? $day;
-            $previousDay = $availableDates[$nbAvailableDates - 2] ?? '';
-        } else {
-            $previousDay = $availableDates[$index - 1] ?? '';
-            $nextDay = $availableDates[$index + 1] ?? '';
-        }
-
-        if ($day === date('Ymd')) {
-            $this->assignView('dayDesc', t('Today'));
-        } elseif ($day === date('Ymd', strtotime('-1 days'))) {
-            $this->assignView('dayDesc', t('Yesterday'));
-        }
-
-        try {
-            $linksToDisplay = $this->container->bookmarkService->filterDay($day);
-        } catch (\Exception $exc) {
-            $linksToDisplay = [];
-        }
+        $type = DailyPageHelper::extractRequestedType($request);
+        $format = DailyPageHelper::getFormatByType($type);
+        $latestBookmark = $this->container->bookmarkService->getLatest();
+        $dateTime = DailyPageHelper::extractRequestedDateTime($type, $request->getQueryParam($type), $latestBookmark);
+        $start = DailyPageHelper::getStartDateTimeByType($type, $dateTime);
+        $end = DailyPageHelper::getEndDateTimeByType($type, $dateTime);
+        $dailyDesc = DailyPageHelper::getDescriptionByType($type, $dateTime);
+
+        $linksToDisplay = $this->container->bookmarkService->findByDate(
+            $start,
+            $end,
+            $previousDay,
+            $nextDay
+        );
 
         $formatter = $this->container->formatterFactory->getFormatter();
         $formatter->addContextData('base_path', $this->container->basePath);
@@ -63,13 +51,15 @@ class DailyController extends ShaarliVisitorController
             $linksToDisplay[$key]['description'] = $bookmark->getDescription();
         }
 
-        $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000');
         $data = [
             'linksToDisplay' => $linksToDisplay,
-            'day' => $dayDate->getTimestamp(),
-            'dayDate' => $dayDate,
-            'previousday' => $previousDay ?? '',
-            'nextday' => $nextDay ?? '',
+            'dayDate' => $start,
+            'day' => $start->getTimestamp(),
+            'previousday' => $previousDay ? $previousDay->format($format) : '',
+            'nextday' => $nextDay ? $nextDay->format($format) : '',
+            'dayDesc' => $dailyDesc,
+            'type' => $type,
+            'localizedType' => $this->translateType($type),
         ];
 
         // Hooks are called before column construction so that plugins don't have to deal with columns.
@@ -82,7 +72,7 @@ class DailyController extends ShaarliVisitorController
         $mainTitle = $this->container->conf->get('general.title', 'Shaarli');
         $this->assignView(
             'pagetitle',
-            t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle
+            $data['localizedType'] . ' - ' . $data['dayDesc'] . ' - ' . $mainTitle
         );
 
         return $response->write($this->render(TemplatePage::DAILY));
@@ -106,11 +96,14 @@ class DailyController extends ShaarliVisitorController
         }
 
         $days = [];
+        $type = DailyPageHelper::extractRequestedType($request);
+        $format = DailyPageHelper::getFormatByType($type);
+        $length = DailyPageHelper::getRssLengthByType($type);
         foreach ($this->container->bookmarkService->search() as $bookmark) {
-            $day = $bookmark->getCreated()->format('Ymd');
+            $day = $bookmark->getCreated()->format($format);
 
             // Stop iterating after DAILY_RSS_NB_DAYS entries
-            if (count($days) === static::$DAILY_RSS_NB_DAYS && !isset($days[$day])) {
+            if (count($days) === $length && !isset($days[$day])) {
                 break;
             }
 
@@ -127,12 +120,19 @@ class DailyController extends ShaarliVisitorController
 
         /** @var Bookmark[] $bookmarks */
         foreach ($days as $day => $bookmarks) {
-            $dayDatetime = DateTimeImmutable::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000');
+            $dayDateTime = DailyPageHelper::extractRequestedDateTime($type, (string) $day);
+            $endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dayDateTime);
+
+            // We only want the RSS entry to be published when the period is over.
+            if (new DateTime() < $endDateTime) {
+                continue;
+            }
+
             $dataPerDay[$day] = [
-                'date' => $dayDatetime,
-                'date_rss' => $dayDatetime->format(DateTime::RSS),
-                'date_human' => format_date($dayDatetime, false, true),
-                'absolute_url' => $indexUrl . 'daily?day=' . $day,
+                'date' => $endDateTime,
+                'date_rss' => $endDateTime->format(DateTime::RSS),
+                'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime),
+                'absolute_url' => $indexUrl . 'daily?'. $type .'=' . $day,
                 'links' => [],
             ];
 
@@ -141,16 +141,20 @@ class DailyController extends ShaarliVisitorController
 
                 // Make permalink URL absolute
                 if ($bookmark->isNote()) {
-                    $dataPerDay[$day]['links'][$key]['url'] = $indexUrl . $bookmark->getUrl();
+                    $dataPerDay[$day]['links'][$key]['url'] = rtrim($indexUrl, '/') . $bookmark->getUrl();
                 }
             }
         }
 
-        $this->assignView('title', $this->container->conf->get('general.title', 'Shaarli'));
-        $this->assignView('index_url', $indexUrl);
-        $this->assignView('page_url', $pageUrl);
-        $this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false));
-        $this->assignView('days', $dataPerDay);
+        $this->assignAllView([
+            'title' => $this->container->conf->get('general.title', 'Shaarli'),
+            'index_url' => $indexUrl,
+            'page_url' => $pageUrl,
+            'hide_timestamps' => $this->container->conf->get('privacy.hide_timestamps', false),
+            'days' => $dataPerDay,
+            'type' => $type,
+            'localizedType' => $this->translateType($type),
+        ]);
 
         $rssContent = $this->render(TemplatePage::DAILY_RSS);
 
@@ -189,4 +193,13 @@ class DailyController extends ShaarliVisitorController
 
         return $columns;
     }
+
+    protected function translateType($type): string
+    {
+        return [
+            t('day') => t('Daily'),
+            t('week') => t('Weekly'),
+            t('month') => t('Monthly'),
+        ][t($type)] ?? t('Daily');
+    }
 }
diff --git a/application/helper/DailyPageHelper.php b/application/helper/DailyPageHelper.php
new file mode 100644 (file)
index 0000000..5fabc90
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Helper;
+
+use Shaarli\Bookmark\Bookmark;
+use Slim\Http\Request;
+
+class DailyPageHelper
+{
+    public const MONTH = 'month';
+    public const WEEK = 'week';
+    public const DAY = 'day';
+
+    /**
+     * Extracts the type of the daily to display from the HTTP request parameters
+     *
+     * @param Request $request HTTP request
+     *
+     * @return string month/week/day
+     */
+    public static function extractRequestedType(Request $request): string
+    {
+        if ($request->getQueryParam(static::MONTH) !== null) {
+            return static::MONTH;
+        } elseif ($request->getQueryParam(static::WEEK) !== null) {
+            return static::WEEK;
+        }
+
+        return static::DAY;
+    }
+
+    /**
+     * Extracts a DateTimeImmutable from provided HTTP request.
+     * If no parameter is provided, we rely on the creation date of the latest provided created bookmark.
+     * If the datastore is empty or no bookmark is provided, we use the current date.
+     *
+     * @param string        $type           month/week/day
+     * @param string|null   $requestedDate  Input string extracted from the request
+     * @param Bookmark|null $latestBookmark Latest bookmark found in the datastore (by date)
+     *
+     * @return \DateTimeImmutable from input or latest bookmark.
+     *
+     * @throws \Exception Type not supported.
+     */
+    public static function extractRequestedDateTime(
+        string $type,
+        ?string $requestedDate,
+        Bookmark $latestBookmark = null
+    ): \DateTimeImmutable {
+        $format = static::getFormatByType($type);
+        if (empty($requestedDate)) {
+            return $latestBookmark instanceof Bookmark
+                ? new \DateTimeImmutable($latestBookmark->getCreated()->format(\DateTime::ATOM))
+                : new \DateTimeImmutable()
+            ;
+        }
+
+        // W is not supported by createFromFormat...
+        if ($type === static::WEEK) {
+            return (new \DateTimeImmutable())
+                ->setISODate((int) substr($requestedDate, 0, 4), (int) substr($requestedDate, 4, 2))
+            ;
+        }
+
+        return \DateTimeImmutable::createFromFormat($format, $requestedDate);
+    }
+
+    /**
+     * Get the DateTime format used by provided type
+     * Examples:
+     *   - day: 20201016 (<year><month><day>)
+     *   - week: 202041 (<year><week number>)
+     *   - month: 202010 (<year><month>)
+     *
+     * @param string $type month/week/day
+     *
+     * @return string DateTime compatible format
+     *
+     * @see https://www.php.net/manual/en/datetime.format.php
+     *
+     * @throws \Exception Type not supported.
+     */
+    public static function getFormatByType(string $type): string
+    {
+        switch ($type) {
+            case static::MONTH:
+                return 'Ym';
+            case static::WEEK:
+                return 'YW';
+            case static::DAY:
+                return 'Ymd';
+            default:
+                throw new \Exception('Unsupported daily format type');
+        }
+    }
+
+    /**
+     * Get the first DateTime of the time period depending on given datetime and type.
+     * Note: DateTimeImmutable is required because we rely heavily on DateTime->modify() syntax
+     *       and we don't want to alter original datetime.
+     *
+     * @param string             $type      month/week/day
+     * @param \DateTimeImmutable $requested DateTime extracted from request input
+     *                                      (should come from extractRequestedDateTime)
+     *
+     * @return \DateTimeInterface First DateTime of the time period
+     *
+     * @throws \Exception Type not supported.
+     */
+    public static function getStartDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface
+    {
+        switch ($type) {
+            case static::MONTH:
+                return $requested->modify('first day of this month midnight');
+            case static::WEEK:
+                return $requested->modify('Monday this week midnight');
+            case static::DAY:
+                return $requested->modify('Today midnight');
+            default:
+                throw new \Exception('Unsupported daily format type');
+        }
+    }
+
+    /**
+     * Get the last DateTime of the time period depending on given datetime and type.
+     * Note: DateTimeImmutable is required because we rely heavily on DateTime->modify() syntax
+     *       and we don't want to alter original datetime.
+     *
+     * @param string             $type      month/week/day
+     * @param \DateTimeImmutable $requested DateTime extracted from request input
+     *                                      (should come from extractRequestedDateTime)
+     *
+     * @return \DateTimeInterface Last DateTime of the time period
+     *
+     * @throws \Exception Type not supported.
+     */
+    public static function getEndDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface
+    {
+        switch ($type) {
+            case static::MONTH:
+                return $requested->modify('last day of this month 23:59:59');
+            case static::WEEK:
+                return $requested->modify('Sunday this week 23:59:59');
+            case static::DAY:
+                return $requested->modify('Today 23:59:59');
+            default:
+                throw new \Exception('Unsupported daily format type');
+        }
+    }
+
+    /**
+     * Get localized description of the time period depending on given datetime and type.
+     * Example: for a month period, it returns `October, 2020`.
+     *
+     * @param string             $type      month/week/day
+     * @param \DateTimeImmutable $requested DateTime extracted from request input
+     *                                      (should come from extractRequestedDateTime)
+     *
+     * @return string Localized time period description
+     *
+     * @throws \Exception Type not supported.
+     */
+    public static function getDescriptionByType(string $type, \DateTimeImmutable $requested): string
+    {
+        switch ($type) {
+            case static::MONTH:
+                return $requested->format('F') . ', ' . $requested->format('Y');
+            case static::WEEK:
+                $requested = $requested->modify('Monday this week');
+                return t('Week') . ' ' . $requested->format('W') . ' (' . format_date($requested, false) . ')';
+            case static::DAY:
+                $out = '';
+                if ($requested->format('Ymd') === date('Ymd')) {
+                    $out = t('Today') . ' - ';
+                } elseif ($requested->format('Ymd') === date('Ymd', strtotime('-1 days'))) {
+                    $out = t('Yesterday') . ' - ';
+                }
+                return $out . format_date($requested, false);
+            default:
+                throw new \Exception('Unsupported daily format type');
+        }
+    }
+
+    /**
+     * Get the number of items to display in the RSS feed depending on the given type.
+     *
+     * @param string $type month/week/day
+     *
+     * @return int number of elements
+     *
+     * @throws \Exception Type not supported.
+     */
+    public static function getRssLengthByType(string $type): int
+    {
+        switch ($type) {
+            case static::MONTH:
+                return 12; // 1 year
+            case static::WEEK:
+                return 26; // ~6 months
+            case static::DAY:
+                return 30; // ~1 month
+            default:
+                throw new \Exception('Unsupported daily format type');
+        }
+    }
+}
index 3f14d22c899d9caba92467ed239bcd7cf7d8c1e9..6d4ff0bdb3e5d232f349792efa87b27bb187a3e3 100644 (file)
@@ -1,8 +1,8 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: Shaarli\n"
-"POT-Creation-Date: 2020-10-27 19:32+0100\n"
-"PO-Revision-Date: 2020-10-27 19:32+0100\n"
+"POT-Creation-Date: 2020-10-27 19:44+0100\n"
+"PO-Revision-Date: 2020-10-27 19:44+0100\n"
 "Last-Translator: \n"
 "Language-Team: Shaarli\n"
 "Language: fr_FR\n"
@@ -20,78 +20,11 @@ msgstr ""
 "X-Poedit-SearchPath-3: init.php\n"
 "X-Poedit-SearchPath-4: plugins\n"
 
-#: application/ApplicationUtils.php:162
-#, php-format
-msgid ""
-"Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus "
-"cannot run. Your PHP version has known security vulnerabilities and should "
-"be updated as soon as possible."
-msgstr ""
-"Votre version de PHP est obsolète ! Shaarli nécessite au moins PHP %s, et ne "
-"peut donc pas fonctionner. Votre version de PHP a des failles de sécurités "
-"connues et devrait Ãªtre mise Ã  jour au plus tôt."
-
-#: application/ApplicationUtils.php:195 application/ApplicationUtils.php:215
-msgid "directory is not readable"
-msgstr "le répertoire n'est pas accessible en lecture"
-
-#: application/ApplicationUtils.php:218
-msgid "directory is not writable"
-msgstr "le répertoire n'est pas accessible en Ã©criture"
-
-#: application/ApplicationUtils.php:240
-msgid "file is not readable"
-msgstr "le fichier n'est pas accessible en lecture"
-
-#: application/ApplicationUtils.php:243
-msgid "file is not writable"
-msgstr "le fichier n'est pas accessible en Ã©criture"
-
-#: application/ApplicationUtils.php:277
-msgid "Configuration parsing"
-msgstr "Chargement de la configuration"
-
-#: application/ApplicationUtils.php:278
-msgid "Slim Framework (routing, etc.)"
-msgstr "Slim Framwork (routage, etc.)"
-
-#: application/ApplicationUtils.php:279
-msgid "Multibyte (Unicode) string support"
-msgstr "Support des chaînes de caractère multibytes (Unicode)"
-
-#: application/ApplicationUtils.php:280
-msgid "Required to use thumbnails"
-msgstr "Obligatoire pour utiliser les miniatures"
-
-#: application/ApplicationUtils.php:281
-msgid "Localized text sorting (e.g. e->è->f)"
-msgstr "Tri des textes traduits (ex : e->è->f)"
-
-#: application/ApplicationUtils.php:282
-msgid "Better retrieval of bookmark metadata and thumbnail"
-msgstr "Meilleure récupération des meta-données des marque-pages et minatures"
-
-#: application/ApplicationUtils.php:283
-msgid "Use the translation system in gettext mode"
-msgstr "Utiliser le système de traduction en mode gettext"
-
-#: application/ApplicationUtils.php:284
-msgid "Login using LDAP server"
-msgstr "Authentification via un serveur LDAP"
-
-#: application/FileUtils.php:100
-msgid "Provided path is not a directory."
-msgstr "Le chemin fourni n'est pas un dossier."
-
-#: application/FileUtils.php:104
-msgid "Trying to delete a folder outside of Shaarli path."
-msgstr "Tentative de supprimer un dossier en dehors du chemin de Shaarli."
-
-#: application/History.php:179
+#: application/History.php:180
 msgid "History file isn't readable or writable"
 msgstr "Le fichier d'historique n'est pas accessible en lecture ou en Ã©criture"
 
-#: application/History.php:190
+#: application/History.php:191
 msgid "Could not parse history file"
 msgstr "Format incorrect pour le fichier d'historique"
 
@@ -123,27 +56,27 @@ msgstr ""
 "l'extension php-gd doit Ãªtre chargée pour utiliser les miniatures. Les "
 "miniatures sont désormais désactivées. Rechargez la page."
 
-#: application/Utils.php:385
+#: application/Utils.php:402
 msgid "Setting not set"
 msgstr "Paramètre non défini"
 
-#: application/Utils.php:392
+#: application/Utils.php:409
 msgid "Unlimited"
 msgstr "Illimité"
 
-#: application/Utils.php:395
+#: application/Utils.php:412
 msgid "B"
 msgstr "o"
 
-#: application/Utils.php:395
+#: application/Utils.php:412
 msgid "kiB"
 msgstr "ko"
 
-#: application/Utils.php:395
+#: application/Utils.php:412
 msgid "MiB"
 msgstr "Mo"
 
-#: application/Utils.php:395
+#: application/Utils.php:412
 msgid "GiB"
 msgstr "Go"
 
@@ -156,7 +89,7 @@ msgstr "Vous n'êtes pas autorisé Ã  modifier les données"
 
 #: application/bookmark/BookmarkFileService.php:208
 msgid "This bookmarks already exists"
-msgstr "Ce marque-page existe déjà."
+msgstr "Ce marque-page existe déjà"
 
 #: application/bookmark/BookmarkInitializer.php:39
 msgid "(private bookmark with thumbnail demo)"
@@ -354,7 +287,8 @@ msgid "Direct link"
 msgstr "Liens directs"
 
 #: application/feed/FeedBuilder.php:181
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179
 msgid "Permalink"
 msgstr "Permalien"
@@ -537,20 +471,36 @@ msgstr "Outils"
 msgid "Search: "
 msgstr "Recherche : "
 
-#: application/front/controller/visitor/DailyController.php:45
-msgid "Today"
-msgstr "Aujourd'hui"
-
-#: application/front/controller/visitor/DailyController.php:47
-msgid "Yesterday"
-msgstr "Hier"
+#: application/front/controller/visitor/DailyController.php:200
+msgid "day"
+msgstr "jour"
 
-#: application/front/controller/visitor/DailyController.php:85
+#: application/front/controller/visitor/DailyController.php:200
+#: application/front/controller/visitor/DailyController.php:203
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:48
 msgid "Daily"
 msgstr "Quotidien"
 
+#: application/front/controller/visitor/DailyController.php:201
+msgid "week"
+msgstr "semaine"
+
+#: application/front/controller/visitor/DailyController.php:201
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+msgid "Weekly"
+msgstr "Hebdomadaire"
+
+#: application/front/controller/visitor/DailyController.php:202
+msgid "month"
+msgstr "mois"
+
+#: application/front/controller/visitor/DailyController.php:202
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+msgid "Monthly"
+msgstr "Mensuel"
+
 #: application/front/controller/visitor/ErrorController.php:33
 msgid "An unexpected error occurred."
 msgstr "Une erreur inattendue s'est produite."
@@ -616,7 +566,7 @@ msgstr "Mur d'images"
 
 #: application/front/controller/visitor/TagCloudController.php:88
 msgid "Tag "
-msgstr "Tag"
+msgstr "Tag "
 
 #: application/front/exceptions/AlreadyInstalledException.php:11
 msgid "Shaarli has already been installed. Login to edit the configuration."
@@ -644,6 +594,86 @@ msgstr ""
 msgid "Wrong token."
 msgstr "Jeton invalide."
 
+#: application/helper/ApplicationUtils.php:162
+#, php-format
+msgid ""
+"Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus "
+"cannot run. Your PHP version has known security vulnerabilities and should "
+"be updated as soon as possible."
+msgstr ""
+"Votre version de PHP est obsolète ! Shaarli nécessite au moins PHP %s, et ne "
+"peut donc pas fonctionner. Votre version de PHP a des failles de sécurités "
+"connues et devrait Ãªtre mise Ã  jour au plus tôt."
+
+#: application/helper/ApplicationUtils.php:195
+#: application/helper/ApplicationUtils.php:215
+msgid "directory is not readable"
+msgstr "le répertoire n'est pas accessible en lecture"
+
+#: application/helper/ApplicationUtils.php:218
+msgid "directory is not writable"
+msgstr "le répertoire n'est pas accessible en Ã©criture"
+
+#: application/helper/ApplicationUtils.php:240
+msgid "file is not readable"
+msgstr "le fichier n'est pas accessible en lecture"
+
+#: application/helper/ApplicationUtils.php:243
+msgid "file is not writable"
+msgstr "le fichier n'est pas accessible en Ã©criture"
+
+#: application/helper/ApplicationUtils.php:277
+msgid "Configuration parsing"
+msgstr "Chargement de la configuration"
+
+#: application/helper/ApplicationUtils.php:278
+msgid "Slim Framework (routing, etc.)"
+msgstr "Slim Framwork (routage, etc.)"
+
+#: application/helper/ApplicationUtils.php:279
+msgid "Multibyte (Unicode) string support"
+msgstr "Support des chaînes de caractère multibytes (Unicode)"
+
+#: application/helper/ApplicationUtils.php:280
+msgid "Required to use thumbnails"
+msgstr "Obligatoire pour utiliser les miniatures"
+
+#: application/helper/ApplicationUtils.php:281
+msgid "Localized text sorting (e.g. e->è->f)"
+msgstr "Tri des textes traduits (ex : e->è->f)"
+
+#: application/helper/ApplicationUtils.php:282
+msgid "Better retrieval of bookmark metadata and thumbnail"
+msgstr "Meilleure récupération des meta-données des marque-pages et minatures"
+
+#: application/helper/ApplicationUtils.php:283
+msgid "Use the translation system in gettext mode"
+msgstr "Utiliser le système de traduction en mode gettext"
+
+#: application/helper/ApplicationUtils.php:284
+msgid "Login using LDAP server"
+msgstr "Authentification via un serveur LDAP"
+
+#: application/helper/DailyPageHelper.php:172
+msgid "Week"
+msgstr "Semaine"
+
+#: application/helper/DailyPageHelper.php:176
+msgid "Today"
+msgstr "Aujourd'hui"
+
+#: application/helper/DailyPageHelper.php:178
+msgid "Yesterday"
+msgstr "Hier"
+
+#: application/helper/FileUtils.php:100
+msgid "Provided path is not a directory."
+msgstr "Le chemin fourni n'est pas un dossier."
+
+#: application/helper/FileUtils.php:104
+msgid "Trying to delete a folder outside of Shaarli path."
+msgstr "Tentative de supprimer un dossier en dehors du chemin de Shaarli."
+
 #: application/legacy/LegacyLinkDB.php:131
 msgid "You are not authorized to add a link."
 msgstr "Vous n'êtes pas autorisé Ã  ajouter un lien."
@@ -1103,25 +1133,30 @@ msgstr "Aucune"
 msgid "Save"
 msgstr "Enregistrer"
 
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
-msgid "The Daily Shaarli"
-msgstr "Le Quotidien Shaarli"
-
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
-msgid "1 RSS entry per day"
-msgstr "1 entrée RSS par jour"
-
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37
-msgid "Previous day"
-msgstr "Jour précédent"
-
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
-msgid "All links of one day in a single page."
-msgstr "Tous les liens d'un jour sur une page."
-
-#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
-msgid "Next day"
-msgstr "Jour suivant"
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+msgid "1 RSS entry per :type"
+msgid_plural ""
+msgstr[0] "1 entrée RSS par :type"
+msgstr[1] ""
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49
+msgid "Previous :type"
+msgid_plural ""
+msgstr[0] ":type précédent"
+msgstr[1] "Jour précédent"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
+#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
+msgid "All links of one :type in a single page."
+msgid_plural ""
+msgstr[0] "Tous les liens d'un :type sur une page."
+msgstr[1] "Tous les liens d'un jour sur une page."
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
+msgid "Next :type"
+msgid_plural ""
+msgstr[0] ":type suivant"
+msgstr[1] ""
 
 #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21
 msgid "Edit Shaare"
@@ -1821,8 +1856,11 @@ msgstr ""
 "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et Â« "
 "Ajouter aux favoris Â»"
 
-#~ msgid "Rename"
-#~ msgstr "Renommer"
+#~ msgid "Display:"
+#~ msgstr "Afficher :"
+
+#~ msgid "The Daily Shaarli"
+#~ msgstr "Le Quotidien Shaarli"
 
 #, fuzzy
 #~| msgid "Selection"
index 479701171dfa277cfe027f56bf42e81f531079c4..8e0ff8dd0281f0021e728133d12a039c34478f0e 100644 (file)
@@ -685,22 +685,6 @@ class BookmarkFileServiceTest extends TestCase
         $this->assertEquals(0, $linkDB->count());
     }
 
-    /**
-     * List the days for which bookmarks have been posted
-     */
-    public function testDays()
-    {
-        $this->assertSame(
-            ['20100309', '20100310', '20121206', '20121207', '20130614', '20150310'],
-            $this->publicLinkDB->days()
-        );
-
-        $this->assertSame(
-            ['20100309', '20100310', '20121206', '20121207', '20130614', '20141125', '20150310'],
-            $this->privateLinkDB->days()
-        );
-    }
-
     /**
      * The URL corresponds to an existing entry in the DB
      */
@@ -1074,33 +1058,105 @@ class BookmarkFileServiceTest extends TestCase
     }
 
     /**
-     * Test filterDay while logged in
+     * Test find by dates in the middle of the datastore (sorted by dates) with a single bookmark as a result.
      */
-    public function testFilterDayLoggedIn(): void
+    public function testFilterByDateMidTimePeriodSingleBookmark(): void
     {
-        $bookmarks = $this->privateLinkDB->filterDay('20121206');
-        $expectedIds = [4, 9, 1, 0];
+        $bookmarks = $this->privateLinkDB->findByDate(
+            DateTime::createFromFormat('Ymd_His', '20121206_150000'),
+            DateTime::createFromFormat('Ymd_His', '20121206_160000'),
+            $before,
+            $after
+        );
 
-        static::assertCount(4, $bookmarks);
-        foreach ($bookmarks as $bookmark) {
-            $i = ($i ?? -1) + 1;
-            static::assertSame($expectedIds[$i], $bookmark->getId());
-        }
+        static::assertCount(1, $bookmarks);
+
+        static::assertSame(9, $bookmarks[0]->getId());
+        static::assertEquals(DateTime::createFromFormat('Ymd_His', '20121206_142300'), $before);
+        static::assertEquals(DateTime::createFromFormat('Ymd_His', '20121206_172539'), $after);
     }
 
     /**
-     * Test filterDay while logged out
+     * Test find by dates in the middle of the datastore (sorted by dates) with a multiple bookmarks as a result.
      */
-    public function testFilterDayLoggedOut(): void
+    public function testFilterByDateMidTimePeriodMultipleBookmarks(): void
     {
-        $bookmarks = $this->publicLinkDB->filterDay('20121206');
-        $expectedIds = [4, 9, 1];
+        $bookmarks = $this->privateLinkDB->findByDate(
+            DateTime::createFromFormat('Ymd_His', '20121206_150000'),
+            DateTime::createFromFormat('Ymd_His', '20121206_180000'),
+            $before,
+            $after
+        );
 
-        static::assertCount(3, $bookmarks);
-        foreach ($bookmarks as $bookmark) {
-            $i = ($i ?? -1) + 1;
-            static::assertSame($expectedIds[$i], $bookmark->getId());
-        }
+        static::assertCount(2, $bookmarks);
+
+        static::assertSame(1, $bookmarks[0]->getId());
+        static::assertSame(9, $bookmarks[1]->getId());
+        static::assertEquals(DateTime::createFromFormat('Ymd_His', '20121206_142300'), $before);
+        static::assertEquals(DateTime::createFromFormat('Ymd_His', '20121206_182539'), $after);
+    }
+
+    /**
+     * Test find by dates at the end of the datastore (sorted by dates).
+     */
+    public function testFilterByDateLastTimePeriod(): void
+    {
+        $after = new DateTime();
+        $bookmarks = $this->privateLinkDB->findByDate(
+            DateTime::createFromFormat('Ymd_His', '20150310_114640'),
+            DateTime::createFromFormat('Ymd_His', '20450101_010101'),
+            $before,
+            $after
+        );
+
+        static::assertCount(1, $bookmarks);
+
+        static::assertSame(41, $bookmarks[0]->getId());
+        static::assertEquals(DateTime::createFromFormat('Ymd_His', '20150310_114633'), $before);
+        static::assertNull($after);
+    }
+
+    /**
+     * Test find by dates at the beginning of the datastore (sorted by dates).
+     */
+    public function testFilterByDateFirstTimePeriod(): void
+    {
+        $before = new DateTime();
+        $bookmarks = $this->privateLinkDB->findByDate(
+            DateTime::createFromFormat('Ymd_His', '20000101_101010'),
+            DateTime::createFromFormat('Ymd_His', '20100309_110000'),
+            $before,
+            $after
+        );
+
+        static::assertCount(1, $bookmarks);
+
+        static::assertSame(11, $bookmarks[0]->getId());
+        static::assertNull($before);
+        static::assertEquals(DateTime::createFromFormat('Ymd_His', '20100310_101010'), $after);
+    }
+
+    /**
+     * Test getLatest with a sticky bookmark: it should be ignored and return the latest by creation date instead.
+     */
+    public function testGetLatestWithSticky(): void
+    {
+        $bookmark = $this->publicLinkDB->getLatest();
+
+        static::assertSame(41, $bookmark->getId());
+    }
+
+    /**
+     * Test getLatest with a sticky bookmark: it should be ignored and return the latest by creation date instead.
+     */
+    public function testGetLatestEmptyDatastore(): void
+    {
+        unlink($this->conf->get('resource.datastore'));
+        $this->publicLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, false);
+
+        $bookmark = $this->publicLinkDB->getLatest();
+
+        static::assertNull($bookmark);
     }
 
     /**
index fc78bc13dc5020411198d2f710ccda1dc79fa016..758e7219a4b5bdc7211140d4339ed28713c38c94 100644 (file)
@@ -28,52 +28,49 @@ class DailyControllerTest extends TestCase
     public function testValidIndexControllerInvokeDefault(): void
     {
         $currentDay = new \DateTimeImmutable('2020-05-13');
+        $previousDate = new \DateTime('2 days ago 00:00:00');
+        $nextDate = new \DateTime('today 00:00:00');
 
         $request = $this->createMock(Request::class);
-        $request->method('getQueryParam')->willReturn($currentDay->format('Ymd'));
+        $request->method('getQueryParam')->willReturnCallback(function (string $key) use ($currentDay): ?string {
+            return $key === 'day' ? $currentDay->format('Ymd') : null;
+        });
         $response = new Response();
 
         // Save RainTPL assigned variables
         $assignedVariables = [];
         $this->assignTemplateVars($assignedVariables);
 
-        // Links dataset: 2 links with thumbnails
-        $this->container->bookmarkService
-            ->expects(static::once())
-            ->method('days')
-            ->willReturnCallback(function () use ($currentDay): array {
-               return [
-                   '20200510',
-                   $currentDay->format('Ymd'),
-                   '20200516',
-               ];
-            })
-        ;
         $this->container->bookmarkService
             ->expects(static::once())
-            ->method('filterDay')
-            ->willReturnCallback(function (): array {
-                return [
-                    (new Bookmark())
-                        ->setId(1)
-                        ->setUrl('http://url.tld')
-                        ->setTitle(static::generateString(50))
-                        ->setDescription(static::generateString(500))
-                    ,
-                    (new Bookmark())
-                        ->setId(2)
-                        ->setUrl('http://url2.tld')
-                        ->setTitle(static::generateString(50))
-                        ->setDescription(static::generateString(500))
-                    ,
-                    (new Bookmark())
-                        ->setId(3)
-                        ->setUrl('http://url3.tld')
-                        ->setTitle(static::generateString(50))
-                        ->setDescription(static::generateString(500))
-                    ,
-                ];
-            })
+            ->method('findByDate')
+            ->willReturnCallback(
+                function ($from, $to, &$previous, &$next) use ($currentDay, $previousDate, $nextDate): array {
+                    $previous = $previousDate;
+                    $next = $nextDate;
+
+                    return [
+                        (new Bookmark())
+                            ->setId(1)
+                            ->setUrl('http://url.tld')
+                            ->setTitle(static::generateString(50))
+                            ->setDescription(static::generateString(500))
+                        ,
+                        (new Bookmark())
+                            ->setId(2)
+                            ->setUrl('http://url2.tld')
+                            ->setTitle(static::generateString(50))
+                            ->setDescription(static::generateString(500))
+                        ,
+                        (new Bookmark())
+                            ->setId(3)
+                            ->setUrl('http://url3.tld')
+                            ->setTitle(static::generateString(50))
+                            ->setDescription(static::generateString(500))
+                        ,
+                    ];
+                }
+            )
         ;
 
         // Make sure that PluginManager hook is triggered
@@ -81,20 +78,22 @@ class DailyControllerTest extends TestCase
             ->expects(static::atLeastOnce())
             ->method('executeHooks')
             ->withConsecutive(['render_daily'])
-            ->willReturnCallback(function (string $hook, array $data, array $param) use ($currentDay): array {
-                if ('render_daily' === $hook) {
-                    static::assertArrayHasKey('linksToDisplay', $data);
-                    static::assertCount(3, $data['linksToDisplay']);
-                    static::assertSame(1, $data['linksToDisplay'][0]['id']);
-                    static::assertSame($currentDay->getTimestamp(), $data['day']);
-                    static::assertSame('20200510', $data['previousday']);
-                    static::assertSame('20200516', $data['nextday']);
-
-                    static::assertArrayHasKey('loggedin', $param);
+            ->willReturnCallback(
+                function (string $hook, array $data, array $param) use ($currentDay, $previousDate, $nextDate): array {
+                    if ('render_daily' === $hook) {
+                        static::assertArrayHasKey('linksToDisplay', $data);
+                        static::assertCount(3, $data['linksToDisplay']);
+                        static::assertSame(1, $data['linksToDisplay'][0]['id']);
+                        static::assertSame($currentDay->getTimestamp(), $data['day']);
+                        static::assertSame($previousDate->format('Ymd'), $data['previousday']);
+                        static::assertSame($nextDate->format('Ymd'), $data['nextday']);
+
+                        static::assertArrayHasKey('loggedin', $param);
+                    }
+
+                    return $data;
                 }
-
-                return $data;
-            })
+            )
         ;
 
         $result = $this->controller->index($request, $response);
@@ -107,6 +106,11 @@ class DailyControllerTest extends TestCase
         );
         static::assertEquals($currentDay, $assignedVariables['dayDate']);
         static::assertEquals($currentDay->getTimestamp(), $assignedVariables['day']);
+        static::assertSame($previousDate->format('Ymd'), $assignedVariables['previousday']);
+        static::assertSame($nextDate->format('Ymd'), $assignedVariables['nextday']);
+        static::assertSame('day', $assignedVariables['type']);
+        static::assertSame('May 13, 2020', $assignedVariables['dayDesc']);
+        static::assertSame('Daily', $assignedVariables['localizedType']);
         static::assertCount(3, $assignedVariables['linksToDisplay']);
 
         $link = $assignedVariables['linksToDisplay'][0];
@@ -171,26 +175,19 @@ class DailyControllerTest extends TestCase
         $currentDay = new \DateTimeImmutable('2020-05-13');
 
         $request = $this->createMock(Request::class);
+        $request->method('getQueryParam')->willReturnCallback(function (string $key) use ($currentDay): ?string {
+            return $key === 'day' ? $currentDay->format('Ymd') : null;
+        });
         $response = new Response();
 
         // Save RainTPL assigned variables
         $assignedVariables = [];
         $this->assignTemplateVars($assignedVariables);
 
-        // Links dataset: 2 links with thumbnails
         $this->container->bookmarkService
             ->expects(static::once())
-            ->method('days')
+            ->method('findByDate')
             ->willReturnCallback(function () use ($currentDay): array {
-                return [
-                    $currentDay->format($currentDay->format('Ymd')),
-                ];
-            })
-        ;
-        $this->container->bookmarkService
-            ->expects(static::once())
-            ->method('filterDay')
-            ->willReturnCallback(function (): array {
                 return [
                     (new Bookmark())
                         ->setId(1)
@@ -250,20 +247,10 @@ class DailyControllerTest extends TestCase
         $assignedVariables = [];
         $this->assignTemplateVars($assignedVariables);
 
-        // Links dataset: 2 links with thumbnails
         $this->container->bookmarkService
             ->expects(static::once())
-            ->method('days')
+            ->method('findByDate')
             ->willReturnCallback(function () use ($currentDay): array {
-                return [
-                    $currentDay->format($currentDay->format('Ymd')),
-                ];
-            })
-        ;
-        $this->container->bookmarkService
-            ->expects(static::once())
-            ->method('filterDay')
-            ->willReturnCallback(function (): array {
                 return [
                     (new Bookmark())->setId(1)->setUrl('http://url.tld')->setTitle('title'),
                     (new Bookmark())
@@ -320,14 +307,7 @@ class DailyControllerTest extends TestCase
         // Links dataset: 2 links with thumbnails
         $this->container->bookmarkService
             ->expects(static::once())
-            ->method('days')
-            ->willReturnCallback(function (): array {
-                return [];
-            })
-        ;
-        $this->container->bookmarkService
-            ->expects(static::once())
-            ->method('filterDay')
+            ->method('findByDate')
             ->willReturnCallback(function (): array {
                 return [];
             })
@@ -347,7 +327,7 @@ class DailyControllerTest extends TestCase
         static::assertSame(200, $result->getStatusCode());
         static::assertSame('daily', (string) $result->getBody());
         static::assertCount(0, $assignedVariables['linksToDisplay']);
-        static::assertSame('Today', $assignedVariables['dayDesc']);
+        static::assertSame('Today - ' . (new \DateTime())->format('F d, Y'), $assignedVariables['dayDesc']);
         static::assertEquals((new \DateTime())->setTime(0, 0)->getTimestamp(), $assignedVariables['day']);
         static::assertEquals((new \DateTime())->setTime(0, 0), $assignedVariables['dayDate']);
     }
@@ -361,6 +341,7 @@ class DailyControllerTest extends TestCase
             new \DateTimeImmutable('2020-05-17'),
             new \DateTimeImmutable('2020-05-15'),
             new \DateTimeImmutable('2020-05-13'),
+            new \DateTimeImmutable('+1 month'),
         ];
 
         $request = $this->createMock(Request::class);
@@ -371,6 +352,7 @@ class DailyControllerTest extends TestCase
             (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
             (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
             (new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'),
+            (new Bookmark())->setId(5)->setCreated($dates[3])->setUrl('http://domain.tld/5'),
         ]);
 
         $this->container->pageCacheManager
@@ -397,13 +379,14 @@ class DailyControllerTest extends TestCase
         static::assertSame('http://shaarli/subfolder/', $assignedVariables['index_url']);
         static::assertSame('http://shaarli/subfolder/daily-rss', $assignedVariables['page_url']);
         static::assertFalse($assignedVariables['hide_timestamps']);
-        static::assertCount(2, $assignedVariables['days']);
+        static::assertCount(3, $assignedVariables['days']);
 
         $day = $assignedVariables['days'][$dates[0]->format('Ymd')];
+        $date = $dates[0]->setTime(23, 59, 59);
 
-        static::assertEquals($dates[0], $day['date']);
-        static::assertSame($dates[0]->format(\DateTime::RSS), $day['date_rss']);
-        static::assertSame(format_date($dates[0], false), $day['date_human']);
+        static::assertEquals($date, $day['date']);
+        static::assertSame($date->format(\DateTime::RSS), $day['date_rss']);
+        static::assertSame(format_date($date, false), $day['date_human']);
         static::assertSame('http://shaarli/subfolder/daily?day='. $dates[0]->format('Ymd'), $day['absolute_url']);
         static::assertCount(1, $day['links']);
         static::assertSame(1, $day['links'][0]['id']);
@@ -411,10 +394,11 @@ class DailyControllerTest extends TestCase
         static::assertEquals($dates[0], $day['links'][0]['created']);
 
         $day = $assignedVariables['days'][$dates[1]->format('Ymd')];
+        $date = $dates[1]->setTime(23, 59, 59);
 
-        static::assertEquals($dates[1], $day['date']);
-        static::assertSame($dates[1]->format(\DateTime::RSS), $day['date_rss']);
-        static::assertSame(format_date($dates[1], false), $day['date_human']);
+        static::assertEquals($date, $day['date']);
+        static::assertSame($date->format(\DateTime::RSS), $day['date_rss']);
+        static::assertSame(format_date($date, false), $day['date_human']);
         static::assertSame('http://shaarli/subfolder/daily?day='. $dates[1]->format('Ymd'), $day['absolute_url']);
         static::assertCount(2, $day['links']);
 
@@ -424,6 +408,18 @@ class DailyControllerTest extends TestCase
         static::assertSame(3, $day['links'][1]['id']);
         static::assertSame('http://domain.tld/3', $day['links'][1]['url']);
         static::assertEquals($dates[1], $day['links'][1]['created']);
+
+        $day = $assignedVariables['days'][$dates[2]->format('Ymd')];
+        $date = $dates[2]->setTime(23, 59, 59);
+
+        static::assertEquals($date, $day['date']);
+        static::assertSame($date->format(\DateTime::RSS), $day['date_rss']);
+        static::assertSame(format_date($date, false), $day['date_human']);
+        static::assertSame('http://shaarli/subfolder/daily?day='. $dates[2]->format('Ymd'), $day['absolute_url']);
+        static::assertCount(1, $day['links']);
+        static::assertSame(4, $day['links'][0]['id']);
+        static::assertSame('http://domain.tld/4', $day['links'][0]['url']);
+        static::assertEquals($dates[2], $day['links'][0]['created']);
     }
 
     /**
@@ -475,4 +471,246 @@ class DailyControllerTest extends TestCase
         static::assertFalse($assignedVariables['hide_timestamps']);
         static::assertCount(0, $assignedVariables['days']);
     }
+
+    /**
+     * Test simple display index with week parameter
+     */
+    public function testSimpleIndexWeekly(): void
+    {
+        $currentDay = new \DateTimeImmutable('2020-05-13');
+        $expectedDay = new \DateTimeImmutable('2020-05-11');
+
+        $request = $this->createMock(Request::class);
+        $request->method('getQueryParam')->willReturnCallback(function (string $key) use ($currentDay): ?string {
+            return $key === 'week' ? $currentDay->format('YW') : null;
+        });
+        $response = new Response();
+
+        // Save RainTPL assigned variables
+        $assignedVariables = [];
+        $this->assignTemplateVars($assignedVariables);
+
+        $this->container->bookmarkService
+            ->expects(static::once())
+            ->method('findByDate')
+            ->willReturnCallback(
+                function (): array {
+                    return [
+                        (new Bookmark())
+                            ->setId(1)
+                            ->setUrl('http://url.tld')
+                            ->setTitle(static::generateString(50))
+                            ->setDescription(static::generateString(500))
+                        ,
+                        (new Bookmark())
+                            ->setId(2)
+                            ->setUrl('http://url2.tld')
+                            ->setTitle(static::generateString(50))
+                            ->setDescription(static::generateString(500))
+                        ,
+                    ];
+                }
+            )
+        ;
+
+        $result = $this->controller->index($request, $response);
+
+        static::assertSame(200, $result->getStatusCode());
+        static::assertSame('daily', (string) $result->getBody());
+        static::assertSame(
+            'Weekly - Week 20 (May 11, 2020) - Shaarli',
+            $assignedVariables['pagetitle']
+        );
+
+        static::assertCount(2, $assignedVariables['linksToDisplay']);
+        static::assertEquals($expectedDay->setTime(0, 0), $assignedVariables['dayDate']);
+        static::assertSame($expectedDay->setTime(0, 0)->getTimestamp(), $assignedVariables['day']);
+        static::assertSame('', $assignedVariables['previousday']);
+        static::assertSame('', $assignedVariables['nextday']);
+        static::assertSame('Week 20 (May 11, 2020)', $assignedVariables['dayDesc']);
+        static::assertSame('week', $assignedVariables['type']);
+        static::assertSame('Weekly', $assignedVariables['localizedType']);
+    }
+
+    /**
+     * Test simple display index with month parameter
+     */
+    public function testSimpleIndexMonthly(): void
+    {
+        $currentDay = new \DateTimeImmutable('2020-05-13');
+        $expectedDay = new \DateTimeImmutable('2020-05-01');
+
+        $request = $this->createMock(Request::class);
+        $request->method('getQueryParam')->willReturnCallback(function (string $key) use ($currentDay): ?string {
+            return $key === 'month' ? $currentDay->format('Ym') : null;
+        });
+        $response = new Response();
+
+        // Save RainTPL assigned variables
+        $assignedVariables = [];
+        $this->assignTemplateVars($assignedVariables);
+
+        $this->container->bookmarkService
+            ->expects(static::once())
+            ->method('findByDate')
+            ->willReturnCallback(
+                function (): array {
+                    return [
+                        (new Bookmark())
+                            ->setId(1)
+                            ->setUrl('http://url.tld')
+                            ->setTitle(static::generateString(50))
+                            ->setDescription(static::generateString(500))
+                        ,
+                        (new Bookmark())
+                            ->setId(2)
+                            ->setUrl('http://url2.tld')
+                            ->setTitle(static::generateString(50))
+                            ->setDescription(static::generateString(500))
+                        ,
+                    ];
+                }
+            )
+        ;
+
+        $result = $this->controller->index($request, $response);
+
+        static::assertSame(200, $result->getStatusCode());
+        static::assertSame('daily', (string) $result->getBody());
+        static::assertSame(
+            'Monthly - May, 2020 - Shaarli',
+            $assignedVariables['pagetitle']
+        );
+
+        static::assertCount(2, $assignedVariables['linksToDisplay']);
+        static::assertEquals($expectedDay->setTime(0, 0), $assignedVariables['dayDate']);
+        static::assertSame($expectedDay->setTime(0, 0)->getTimestamp(), $assignedVariables['day']);
+        static::assertSame('', $assignedVariables['previousday']);
+        static::assertSame('', $assignedVariables['nextday']);
+        static::assertSame('May, 2020', $assignedVariables['dayDesc']);
+        static::assertSame('month', $assignedVariables['type']);
+        static::assertSame('Monthly', $assignedVariables['localizedType']);
+    }
+
+    /**
+     * Test simple display RSS with week parameter
+     */
+    public function testSimpleRssWeekly(): void
+    {
+        $dates = [
+            new \DateTimeImmutable('2020-05-19'),
+            new \DateTimeImmutable('2020-05-13'),
+        ];
+        $expectedDates = [
+            new \DateTimeImmutable('2020-05-24 23:59:59'),
+            new \DateTimeImmutable('2020-05-17 23:59:59'),
+        ];
+
+        $this->container->environment['QUERY_STRING'] = 'week';
+        $request = $this->createMock(Request::class);
+        $request->method('getQueryParam')->willReturnCallback(function (string $key): ?string {
+            return $key === 'week' ? '' : null;
+        });
+        $response = new Response();
+
+        $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([
+            (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
+            (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
+            (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
+        ]);
+
+        // Save RainTPL assigned variables
+        $assignedVariables = [];
+        $this->assignTemplateVars($assignedVariables);
+
+        $result = $this->controller->rss($request, $response);
+
+        static::assertSame(200, $result->getStatusCode());
+        static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]);
+        static::assertSame('dailyrss', (string) $result->getBody());
+        static::assertSame('Shaarli', $assignedVariables['title']);
+        static::assertSame('http://shaarli/subfolder/', $assignedVariables['index_url']);
+        static::assertSame('http://shaarli/subfolder/daily-rss?week', $assignedVariables['page_url']);
+        static::assertFalse($assignedVariables['hide_timestamps']);
+        static::assertCount(2, $assignedVariables['days']);
+
+        $day = $assignedVariables['days'][$dates[0]->format('YW')];
+        $date = $expectedDates[0];
+
+        static::assertEquals($date, $day['date']);
+        static::assertSame($date->format(\DateTime::RSS), $day['date_rss']);
+        static::assertSame('Week 21 (May 18, 2020)', $day['date_human']);
+        static::assertSame('http://shaarli/subfolder/daily?week='. $dates[0]->format('YW'), $day['absolute_url']);
+        static::assertCount(1, $day['links']);
+
+        $day = $assignedVariables['days'][$dates[1]->format('YW')];
+        $date = $expectedDates[1];
+
+        static::assertEquals($date, $day['date']);
+        static::assertSame($date->format(\DateTime::RSS), $day['date_rss']);
+        static::assertSame('Week 20 (May 11, 2020)', $day['date_human']);
+        static::assertSame('http://shaarli/subfolder/daily?week='. $dates[1]->format('YW'), $day['absolute_url']);
+        static::assertCount(2, $day['links']);
+    }
+
+    /**
+     * Test simple display RSS with month parameter
+     */
+    public function testSimpleRssMonthly(): void
+    {
+        $dates = [
+            new \DateTimeImmutable('2020-05-19'),
+            new \DateTimeImmutable('2020-04-13'),
+        ];
+        $expectedDates = [
+            new \DateTimeImmutable('2020-05-31 23:59:59'),
+            new \DateTimeImmutable('2020-04-30 23:59:59'),
+        ];
+
+        $this->container->environment['QUERY_STRING'] = 'month';
+        $request = $this->createMock(Request::class);
+        $request->method('getQueryParam')->willReturnCallback(function (string $key): ?string {
+            return $key === 'month' ? '' : null;
+        });
+        $response = new Response();
+
+        $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([
+            (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'),
+            (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'),
+            (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'),
+        ]);
+
+        // Save RainTPL assigned variables
+        $assignedVariables = [];
+        $this->assignTemplateVars($assignedVariables);
+
+        $result = $this->controller->rss($request, $response);
+
+        static::assertSame(200, $result->getStatusCode());
+        static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]);
+        static::assertSame('dailyrss', (string) $result->getBody());
+        static::assertSame('Shaarli', $assignedVariables['title']);
+        static::assertSame('http://shaarli/subfolder/', $assignedVariables['index_url']);
+        static::assertSame('http://shaarli/subfolder/daily-rss?month', $assignedVariables['page_url']);
+        static::assertFalse($assignedVariables['hide_timestamps']);
+        static::assertCount(2, $assignedVariables['days']);
+
+        $day = $assignedVariables['days'][$dates[0]->format('Ym')];
+        $date = $expectedDates[0];
+
+        static::assertEquals($date, $day['date']);
+        static::assertSame($date->format(\DateTime::RSS), $day['date_rss']);
+        static::assertSame('May, 2020', $day['date_human']);
+        static::assertSame('http://shaarli/subfolder/daily?month='. $dates[0]->format('Ym'), $day['absolute_url']);
+        static::assertCount(1, $day['links']);
+
+        $day = $assignedVariables['days'][$dates[1]->format('Ym')];
+        $date = $expectedDates[1];
+
+        static::assertEquals($date, $day['date']);
+        static::assertSame($date->format(\DateTime::RSS), $day['date_rss']);
+        static::assertSame('April, 2020', $day['date_human']);
+        static::assertSame('http://shaarli/subfolder/daily?month='. $dates[1]->format('Ym'), $day['absolute_url']);
+        static::assertCount(2, $day['links']);
+    }
 }
diff --git a/tests/helper/DailyPageHelperTest.php b/tests/helper/DailyPageHelperTest.php
new file mode 100644 (file)
index 0000000..e037849
--- /dev/null
@@ -0,0 +1,262 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Helper;
+
+use Shaarli\Bookmark\Bookmark;
+use Shaarli\TestCase;
+use Slim\Http\Request;
+
+class DailyPageHelperTest extends TestCase
+{
+    /**
+     * @dataProvider getRequestedTypes
+     */
+    public function testExtractRequestedType(array $queryParams, string $expectedType): void
+    {
+        $request = $this->createMock(Request::class);
+        $request->method('getQueryParam')->willReturnCallback(function ($key) use ($queryParams): ?string {
+            return $queryParams[$key] ?? null;
+        });
+
+        $type = DailyPageHelper::extractRequestedType($request);
+
+        static::assertSame($type, $expectedType);
+    }
+
+    /**
+     * @dataProvider getRequestedDateTimes
+     */
+    public function testExtractRequestedDateTime(
+        string $type,
+        string $input,
+        ?Bookmark $bookmark,
+        \DateTimeInterface $expectedDateTime,
+        string $compareFormat = 'Ymd'
+    ): void {
+        $dateTime = DailyPageHelper::extractRequestedDateTime($type, $input, $bookmark);
+
+        static::assertSame($dateTime->format($compareFormat), $expectedDateTime->format($compareFormat));
+    }
+
+    public function testExtractRequestedDateTimeExceptionUnknownType(): void
+    {
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('Unsupported daily format type');
+
+        DailyPageHelper::extractRequestedDateTime('nope', null, null);
+    }
+
+    /**
+     * @dataProvider getFormatsByType
+     */
+    public function testGetFormatByType(string $type, string $expectedFormat): void
+    {
+        $format = DailyPageHelper::getFormatByType($type);
+
+        static::assertSame($expectedFormat, $format);
+    }
+
+    public function testGetFormatByTypeExceptionUnknownType(): void
+    {
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('Unsupported daily format type');
+
+        DailyPageHelper::getFormatByType('nope');
+    }
+
+    /**
+     * @dataProvider getStartDatesByType
+     */
+    public function testGetStartDatesByType(
+        string $type,
+        \DateTimeImmutable $dateTime,
+        \DateTimeInterface $expectedDateTime
+    ): void {
+        $startDateTime = DailyPageHelper::getStartDateTimeByType($type, $dateTime);
+
+        static::assertEquals($expectedDateTime, $startDateTime);
+    }
+
+    public function testGetStartDatesByTypeExceptionUnknownType(): void
+    {
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('Unsupported daily format type');
+
+        DailyPageHelper::getStartDateTimeByType('nope', new \DateTimeImmutable());
+    }
+
+    /**
+     * @dataProvider getEndDatesByType
+     */
+    public function testGetEndDatesByType(
+        string $type,
+        \DateTimeImmutable $dateTime,
+        \DateTimeInterface $expectedDateTime
+    ): void {
+        $endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dateTime);
+
+        static::assertEquals($expectedDateTime, $endDateTime);
+    }
+
+    public function testGetEndDatesByTypeExceptionUnknownType(): void
+    {
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('Unsupported daily format type');
+
+        DailyPageHelper::getEndDateTimeByType('nope', new \DateTimeImmutable());
+    }
+
+    /**
+     * @dataProvider getDescriptionsByType
+     */
+    public function testGeDescriptionsByType(
+        string $type,
+        \DateTimeImmutable $dateTime,
+        string $expectedDescription
+    ): void {
+        $description = DailyPageHelper::getDescriptionByType($type, $dateTime);
+
+        static::assertEquals($expectedDescription, $description);
+    }
+
+    public function getDescriptionByTypeExceptionUnknownType(): void
+    {
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('Unsupported daily format type');
+
+        DailyPageHelper::getDescriptionByType('nope', new \DateTimeImmutable());
+    }
+
+    /**
+     * @dataProvider getRssLengthsByType
+     */
+    public function testGeRssLengthsByType(string $type): void {
+        $length = DailyPageHelper::getRssLengthByType($type);
+
+        static::assertIsInt($length);
+    }
+
+    public function testGeRssLengthsByTypeExceptionUnknownType(): void
+    {
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('Unsupported daily format type');
+
+        DailyPageHelper::getRssLengthByType('nope');
+    }
+
+    /**
+     * Data provider for testExtractRequestedType() test method.
+     */
+    public function getRequestedTypes(): array
+    {
+        return [
+            [['month' => null], DailyPageHelper::DAY],
+            [['month' => ''], DailyPageHelper::MONTH],
+            [['month' => 'content'], DailyPageHelper::MONTH],
+            [['week' => null], DailyPageHelper::DAY],
+            [['week' => ''], DailyPageHelper::WEEK],
+            [['week' => 'content'], DailyPageHelper::WEEK],
+            [['day' => null], DailyPageHelper::DAY],
+            [['day' => ''], DailyPageHelper::DAY],
+            [['day' => 'content'], DailyPageHelper::DAY],
+        ];
+    }
+
+    /**
+     * Data provider for testExtractRequestedDateTime() test method.
+     */
+    public function getRequestedDateTimes(): array
+    {
+        return [
+            [DailyPageHelper::DAY, '20201013', null, new \DateTime('2020-10-13')],
+            [
+                DailyPageHelper::DAY,
+                '',
+                (new Bookmark())->setCreated($date = new \DateTime('2020-10-13 12:05:31')),
+                $date,
+            ],
+            [DailyPageHelper::DAY, '', null, new \DateTime()],
+            [DailyPageHelper::WEEK, '202030', null, new \DateTime('2020-07-20')],
+            [
+                DailyPageHelper::WEEK,
+                '',
+                (new Bookmark())->setCreated($date = new \DateTime('2020-10-13 12:05:31')),
+                new \DateTime('2020-10-13'),
+            ],
+            [DailyPageHelper::WEEK, '', null, new \DateTime(), 'Ym'],
+            [DailyPageHelper::MONTH, '202008', null, new \DateTime('2020-08-01'), 'Ym'],
+            [
+                DailyPageHelper::MONTH,
+                '',
+                (new Bookmark())->setCreated($date = new \DateTime('2020-10-13 12:05:31')),
+                new \DateTime('2020-10-13'),
+                'Ym'
+            ],
+            [DailyPageHelper::MONTH, '', null, new \DateTime(), 'Ym'],
+        ];
+    }
+
+    /**
+     * Data provider for testGetFormatByType() test method.
+     */
+    public function getFormatsByType(): array
+    {
+        return [
+            [DailyPageHelper::DAY, 'Ymd'],
+            [DailyPageHelper::WEEK, 'YW'],
+            [DailyPageHelper::MONTH, 'Ym'],
+        ];
+    }
+
+    /**
+     * Data provider for testGetStartDatesByType() test method.
+     */
+    public function getStartDatesByType(): array
+    {
+        return [
+            [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 00:00:00')],
+            [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-05 00:00:00')],
+            [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-01 00:00:00')],
+        ];
+    }
+
+    /**
+     * Data provider for testGetEndDatesByType() test method.
+     */
+    public function getEndDatesByType(): array
+    {
+        return [
+            [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 23:59:59')],
+            [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-11 23:59:59')],
+            [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-31 23:59:59')],
+        ];
+    }
+
+    /**
+     * Data provider for testGetDescriptionsByType() test method.
+     */
+    public function getDescriptionsByType(): array
+    {
+        return [
+            [DailyPageHelper::DAY, $date = new \DateTimeImmutable(), 'Today - ' . $date->format('F d, Y')],
+            [DailyPageHelper::DAY, $date = new \DateTimeImmutable('-1 day'), 'Yesterday - ' . $date->format('F d, Y')],
+            [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'],
+            [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'],
+            [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'],
+        ];
+    }
+
+    /**
+     * Data provider for testGetDescriptionsByType() test method.
+     */
+    public function getRssLengthsByType(): array
+    {
+        return [
+            [DailyPageHelper::DAY],
+            [DailyPageHelper::WEEK],
+            [DailyPageHelper::MONTH],
+        ];
+    }
+}
index 3749bffb620a317c276c4b63a2e9aa42b031a67d..5e038c393822105287678394fa6e1b9ab4211d77 100644 (file)
@@ -6,12 +6,25 @@
 <body>
 {include="page.header"}
 
+<div class="pure-g">
+  <div class="pure-u-1 pure-alert pure-alert-success tag-sort">
+    <a href="{$base_path}/daily?day">{'Daily'|t}</a>
+    <a href="{$base_path}/daily?week">{'Weekly'|t}</a>
+    <a href="{$base_path}/daily?month">{'Monthly'|t}</a>
+  </div>
+</div>
+
+
 <div class="pure-g">
   <div class="pure-u-lg-1-6 pure-u-1-24"></div>
   <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor" id="daily">
     <h2 class="window-title">
-      {'The Daily Shaarli'|t}
-      <a href="{$base_path}/daily-rss" title="{'1 RSS entry per day'|t}"><i class="fa fa-rss"></i></a>
+      {$localizedType} Shaarli
+      <a href="{$base_path}/daily-rss?{$type}"
+         title="{function="t('1 RSS entry per :type', '', 1, 'shaarli', [':type' => t($type)])"}"
+      >
+        <i class="fa fa-rss"></i>
+      </a>
     </h2>
 
     <div id="plugin_zone_start_daily" class="plugin_zone">
       <div class="pure-g">
         <div class="pure-u-lg-1-3 pure-u-1 center">
           {if="$previousday"}
-            <a href="{$base_path}/daily?day={$previousday}">
+            <a href="{$base_path}/daily?{$type}={$previousday}">
               <i class="fa fa-arrow-left"></i>
-              {'Previous day'|t}
+              {function="t('Previous :type', '', 1, 'shaarli', [':type' => t($type)], true)"}
             </a>
           {/if}
         </div>
         <div class="daily-desc pure-u-lg-1-3 pure-u-1 center">
-          {'All links of one day in a single page.'|t}
+          {function="t('All links of one :type in a single page.', '', 1, 'shaarli', [':type' => t($type)])"}
         </div>
         <div class="pure-u-lg-1-3 pure-u-1 center">
           {if="$nextday"}
-            <a href="{$base_path}/daily?day={$nextday}">
-              {'Next day'|t}
+            <a href="{$base_path}/daily?{$type}={$nextday}">
+              {function="t('Next :type', '', 1, 'shaarli', [':type' => t($type)], true)"}
               <i class="fa fa-arrow-right"></i>
             </a>
           {/if}
       </div>
       <div>
         <h3 class="window-subtitle">
-          {if="!empty($dayDesc)"}
-            {$dayDesc} -
-          {/if}
-          {function="format_date($dayDate, false)"}
+          {$dayDesc}
         </h3>
 
         <div id="plugin_zone_about_daily" class="plugin_zone">
index d40d94968ad6d75b2145b4116af5f449de7788f5..871a3ba7531abb0c2268ab424ef54ffb5b2abafd 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <rss version="2.0">
   <channel>
-    <title>Daily - {$title}</title>
+    <title>{$localizedType} - {$title}</title>
     <link>{$index_url}</link>
-    <description>Daily shaared bookmarks</description>
+    <description>{function="t('All links of one :type in a single page.', '', 1, 'shaarli', [':type' => t($type)])"}</description>
     <language>{$language}</language>
     <copyright>{$index_url}</copyright>
     <generator>Shaarli</generator>
           {loop="$value.links"}
             <h3><a href="{$value.url}">{$value.title}</a></h3>
             <small>
-              {if="!$hide_timestamps"}{$value.created|format_date} - {/if}{if="$value.tags"}{$value.tags}{/if}<br>
+              {if="!$hide_timestamps"}{$value.created|format_date} &#8212; {/if}
+              <a href="{$index_url}shaare/{$value.shorturl}">{'Permalink'|t}</a>
+              {if="$value.tags"} &#8212; {$value.tags}{/if}
+              <br>
               {$value.url}
             </small><br>
             {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br>
             {if="$value.description"}{$value.description}{/if}
-            <br><br><hr>
+            <br><hr>
           {/loop}
         ]]></description>
       </item>