From 36e6d88dbfd753665224664d5214f39ccfbbf6a5 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Fri, 16 Oct 2020 11:50:53 +0200 Subject: [PATCH] Feature: add weekly and monthly view/RSS feed for daily page - 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 | 33 +- application/bookmark/BookmarkFileService.php | 38 +- .../bookmark/BookmarkServiceInterface.php | 27 +- .../controller/visitor/DailyController.php | 105 +++-- application/helper/DailyPageHelper.php | 208 +++++++++ inc/languages/fr/LC_MESSAGES/shaarli.po | 256 ++++++----- tests/bookmark/BookmarkFileServiceTest.php | 124 ++++-- .../visitor/DailyControllerTest.php | 412 ++++++++++++++---- tests/helper/DailyPageHelperTest.php | 262 +++++++++++ tpl/default/daily.html | 32 +- tpl/default/dailyrss.html | 11 +- 11 files changed, 1190 insertions(+), 318 deletions(-) create mode 100644 application/helper/DailyPageHelper.php create mode 100644 tests/helper/DailyPageHelperTest.php diff --git a/application/Utils.php b/application/Utils.php index bc1c9f5d..db046893 100644 --- a/application/Utils.php +++ b/application/Utils.php @@ -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)); } /** diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php index 14b3d620..0df2f47f 100644 --- a/application/bookmark/BookmarkFileService.php +++ b/application/bookmark/BookmarkFileService.php @@ -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; } /** diff --git a/application/bookmark/BookmarkServiceInterface.php b/application/bookmark/BookmarkServiceInterface.php index 9fa61533..08cdbb4e 100644 --- a/application/bookmark/BookmarkServiceInterface.php +++ b/application/bookmark/BookmarkServiceInterface.php @@ -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. diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php index 07617cf1..728bc2d8 100644 --- a/application/front/controller/visitor/DailyController.php +++ b/application/front/controller/visitor/DailyController.php @@ -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 index 00000000..5fabc907 --- /dev/null +++ b/application/helper/DailyPageHelper.php @@ -0,0 +1,208 @@ +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 () + * - week: 202041 () + * - month: 202010 () + * + * @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'); + } + } +} diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po index 3f14d22c..6d4ff0bd 100644 --- a/inc/languages/fr/LC_MESSAGES/shaarli.po +++ b/inc/languages/fr/LC_MESSAGES/shaarli.po @@ -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" diff --git a/tests/bookmark/BookmarkFileServiceTest.php b/tests/bookmark/BookmarkFileServiceTest.php index 47970117..8e0ff8dd 100644 --- a/tests/bookmark/BookmarkFileServiceTest.php +++ b/tests/bookmark/BookmarkFileServiceTest.php @@ -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); } /** diff --git a/tests/front/controller/visitor/DailyControllerTest.php b/tests/front/controller/visitor/DailyControllerTest.php index fc78bc13..758e7219 100644 --- a/tests/front/controller/visitor/DailyControllerTest.php +++ b/tests/front/controller/visitor/DailyControllerTest.php @@ -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 index 00000000..e0378491 --- /dev/null +++ b/tests/helper/DailyPageHelperTest.php @@ -0,0 +1,262 @@ +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], + ]; + } +} diff --git a/tpl/default/daily.html b/tpl/default/daily.html index 3749bffb..5e038c39 100644 --- a/tpl/default/daily.html +++ b/tpl/default/daily.html @@ -6,12 +6,25 @@ {include="page.header"} + + +

- {'The Daily Shaarli'|t} - + {$localizedType} Shaarli + t($type)])"}" + > + +

@@ -25,19 +38,19 @@
- {'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)])"}

- {if="!empty($dayDesc)"} - {$dayDesc} - - {/if} - {function="format_date($dayDate, false)"} + {$dayDesc}

diff --git a/tpl/default/dailyrss.html b/tpl/default/dailyrss.html index d40d9496..871a3ba7 100644 --- a/tpl/default/dailyrss.html +++ b/tpl/default/dailyrss.html @@ -1,9 +1,9 @@ - Daily - {$title} + {$localizedType} - {$title} {$index_url} - Daily shaared bookmarks + {function="t('All links of one :type in a single page.', '', 1, 'shaarli', [':type' => t($type)])"} {$language} {$index_url} Shaarli @@ -18,12 +18,15 @@ {loop="$value.links"}

{$value.title}

- {if="!$hide_timestamps"}{$value.created|format_date} - {/if}{if="$value.tags"}{$value.tags}{/if}
+ {if="!$hide_timestamps"}{$value.created|format_date} — {/if} + {'Permalink'|t} + {if="$value.tags"} — {$value.tags}{/if} +
{$value.url}

{if="$value.thumbnail"}thumbnail{/if}
{if="$value.description"}{$value.description}{/if} -


+

{/loop} ]]> -- 2.41.0