From 2899ebb5b5e82890c877151f5c02045266ac9973 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Fri, 22 May 2020 13:20:31 +0200 Subject: Initialize admin Slim controllers - Reorganize visitor controllers - Fix redirection with Slim's requests base path - Fix daily links --- .../front/controller/visitor/DailyController.php | 208 +++++++++++++++++++++ .../front/controller/visitor/FeedController.php | 77 ++++++++ .../front/controller/visitor/LoginController.php | 46 +++++ .../controller/visitor/OpenSearchController.php | 26 +++ .../controller/visitor/PictureWallController.php | 70 +++++++ .../visitor/ShaarliVisitorController.php | 131 +++++++++++++ .../controller/visitor/TagCloudController.php | 131 +++++++++++++ .../front/controller/visitor/TagController.php | 118 ++++++++++++ 8 files changed, 807 insertions(+) create mode 100644 application/front/controller/visitor/DailyController.php create mode 100644 application/front/controller/visitor/FeedController.php create mode 100644 application/front/controller/visitor/LoginController.php create mode 100644 application/front/controller/visitor/OpenSearchController.php create mode 100644 application/front/controller/visitor/PictureWallController.php create mode 100644 application/front/controller/visitor/ShaarliVisitorController.php create mode 100644 application/front/controller/visitor/TagCloudController.php create mode 100644 application/front/controller/visitor/TagController.php (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php new file mode 100644 index 00000000..47e2503a --- /dev/null +++ b/application/front/controller/visitor/DailyController.php @@ -0,0 +1,208 @@ +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 = []; + } + + $formatter = $this->container->formatterFactory->getFormatter(); + // We pre-format some fields for proper output. + foreach ($linksToDisplay as $key => $bookmark) { + $linksToDisplay[$key] = $formatter->format($bookmark); + // This page is a bit specific, we need raw description to calculate the length + $linksToDisplay[$key]['formatedDescription'] = $linksToDisplay[$key]['description']; + $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 ?? '', + ]; + + // Hooks are called before column construction so that plugins don't have to deal with columns. + $this->executeHooks($data); + + $data['cols'] = $this->calculateColumns($data['linksToDisplay']); + + foreach ($data as $key => $value) { + $this->assignView($key, $value); + } + + $mainTitle = $this->container->conf->get('general.title', 'Shaarli'); + $this->assignView( + 'pagetitle', + t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle + ); + + return $response->write($this->render('daily')); + } + + /** + * Daily RSS feed: 1 RSS entry per day giving all the bookmarks on that day. + * Gives the last 7 days (which have bookmarks). + * This RSS feed cannot be filtered and does not trigger plugins yet. + */ + public function rss(Request $request, Response $response): Response + { + $response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8'); + + $pageUrl = page_url($this->container->environment); + $cache = $this->container->pageCacheManager->getCachePage($pageUrl); + + $cached = $cache->cachedVersion(); + if (!empty($cached)) { + return $response->write($cached); + } + + $days = []; + foreach ($this->container->bookmarkService->search() as $bookmark) { + $day = $bookmark->getCreated()->format('Ymd'); + + // Stop iterating after DAILY_RSS_NB_DAYS entries + if (count($days) === static::$DAILY_RSS_NB_DAYS && !isset($days[$day])) { + break; + } + + $days[$day][] = $bookmark; + } + + // Build the RSS feed. + $indexUrl = escape(index_url($this->container->environment)); + + $formatter = $this->container->formatterFactory->getFormatter(); + $formatter->addContextData('index_url', $indexUrl); + + $dataPerDay = []; + + /** @var Bookmark[] $bookmarks */ + foreach ($days as $day => $bookmarks) { + $dayDatetime = DateTimeImmutable::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); + $dataPerDay[$day] = [ + 'date' => $dayDatetime, + 'date_rss' => $dayDatetime->format(DateTime::RSS), + 'date_human' => format_date($dayDatetime, false, true), + 'absolute_url' => $indexUrl . '/daily?day=' . $day, + 'links' => [], + ]; + + foreach ($bookmarks as $key => $bookmark) { + $dataPerDay[$day]['links'][$key] = $formatter->format($bookmark); + + // Make permalink URL absolute + if ($bookmark->isNote()) { + $dataPerDay[$day]['links'][$key]['url'] = $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); + + $rssContent = $this->render('dailyrss'); + + $cache->cache($rssContent); + + return $response->write($rssContent); + } + + /** + * We need to spread the articles on 3 columns. + * did not want to use a JavaScript lib like http://masonry.desandro.com/ + * so I manually spread entries with a simple method: I roughly evaluate the + * height of a div according to title and description length. + */ + protected function calculateColumns(array $links): array + { + // Entries to display, for each column. + $columns = [[], [], []]; + // Rough estimate of columns fill. + $fill = [0, 0, 0]; + foreach ($links as $link) { + // Roughly estimate length of entry (by counting characters) + // Title: 30 chars = 1 line. 1 line is 30 pixels height. + // Description: 836 characters gives roughly 342 pixel height. + // This is not perfect, but it's usually OK. + $length = strlen($link['title'] ?? '') + (342 * strlen($link['description'] ?? '')) / 836; + if (! empty($link['thumbnail'])) { + $length += 100; // 1 thumbnails roughly takes 100 pixels height. + } + // Then put in column which is the less filled: + $smallest = min($fill); // find smallest value in array. + $index = array_search($smallest, $fill); // find index of this smallest value. + array_push($columns[$index], $link); // Put entry in this column. + $fill[$index] += $length; + } + + return $columns; + } + + /** + * @param mixed[] $data Variables passed to the template engine + * + * @return mixed[] Template data after active plugins render_picwall hook execution. + */ + protected function executeHooks(array $data): array + { + $this->container->pluginManager->executeHooks( + 'render_daily', + $data, + ['loggedin' => $this->container->loginManager->isLoggedIn()] + ); + + return $data; + } +} diff --git a/application/front/controller/visitor/FeedController.php b/application/front/controller/visitor/FeedController.php new file mode 100644 index 00000000..70664635 --- /dev/null +++ b/application/front/controller/visitor/FeedController.php @@ -0,0 +1,77 @@ +processRequest(FeedBuilder::$FEED_ATOM, $request, $response); + } + + public function rss(Request $request, Response $response): Response + { + return $this->processRequest(FeedBuilder::$FEED_RSS, $request, $response); + } + + protected function processRequest(string $feedType, Request $request, Response $response): Response + { + $response = $response->withHeader('Content-Type', 'application/'. $feedType .'+xml; charset=utf-8'); + + $pageUrl = page_url($this->container->environment); + $cache = $this->container->pageCacheManager->getCachePage($pageUrl); + + $cached = $cache->cachedVersion(); + if (!empty($cached)) { + return $response->write($cached); + } + + // Generate data. + $this->container->feedBuilder->setLocale(strtolower(setlocale(LC_COLLATE, 0))); + $this->container->feedBuilder->setHideDates($this->container->conf->get('privacy.hide_timestamps', false)); + $this->container->feedBuilder->setUsePermalinks( + null !== $request->getParam('permalinks') || !$this->container->conf->get('feed.rss_permalinks') + ); + + $data = $this->container->feedBuilder->buildData($feedType, $request->getParams()); + + $this->executeHooks($data, $feedType); + $this->assignAllView($data); + + $content = $this->render('feed.'. $feedType); + + $cache->cache($content); + + return $response->write($content); + } + + /** + * @param mixed[] $data Template data + * + * @return mixed[] Template data after active plugins hook execution. + */ + protected function executeHooks(array $data, string $feedType): array + { + $this->container->pluginManager->executeHooks( + 'render_feed', + $data, + [ + 'loggedin' => $this->container->loginManager->isLoggedIn(), + 'target' => $feedType, + ] + ); + + return $data; + } +} diff --git a/application/front/controller/visitor/LoginController.php b/application/front/controller/visitor/LoginController.php new file mode 100644 index 00000000..4de2f55d --- /dev/null +++ b/application/front/controller/visitor/LoginController.php @@ -0,0 +1,46 @@ +container->loginManager->isLoggedIn() + || $this->container->conf->get('security.open_shaarli', false) + ) { + return $response->withRedirect('./'); + } + + $userCanLogin = $this->container->loginManager->canLogin($request->getServerParams()); + if ($userCanLogin !== true) { + throw new LoginBannedException(); + } + + if ($request->getParam('username') !== null) { + $this->assignView('username', escape($request->getParam('username'))); + } + + $this + ->assignView('returnurl', escape($request->getServerParam('HTTP_REFERER'))) + ->assignView('remember_user_default', $this->container->conf->get('privacy.remember_user_default', true)) + ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli')) + ; + + return $response->write($this->render('loginform')); + } +} diff --git a/application/front/controller/visitor/OpenSearchController.php b/application/front/controller/visitor/OpenSearchController.php new file mode 100644 index 00000000..0fd68db6 --- /dev/null +++ b/application/front/controller/visitor/OpenSearchController.php @@ -0,0 +1,26 @@ +withHeader('Content-Type', 'application/opensearchdescription+xml; charset=utf-8'); + + $this->assignView('serverurl', index_url($this->container->environment)); + + return $response->write($this->render('opensearch')); + } +} diff --git a/application/front/controller/visitor/PictureWallController.php b/application/front/controller/visitor/PictureWallController.php new file mode 100644 index 00000000..4e1dce8c --- /dev/null +++ b/application/front/controller/visitor/PictureWallController.php @@ -0,0 +1,70 @@ +container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) { + throw new ThumbnailsDisabledException(); + } + + $this->assignView( + 'pagetitle', + t('Picture wall') .' - '. $this->container->conf->get('general.title', 'Shaarli') + ); + + // Optionally filter the results: + $links = $this->container->bookmarkService->search($request->getQueryParams()); + $linksToDisplay = []; + + // Get only bookmarks which have a thumbnail. + // Note: we do not retrieve thumbnails here, the request is too heavy. + $formatter = $this->container->formatterFactory->getFormatter('raw'); + foreach ($links as $key => $link) { + if (!empty($link->getThumbnail())) { + $linksToDisplay[] = $formatter->format($link); + } + } + + $data = $this->executeHooks($linksToDisplay); + foreach ($data as $key => $value) { + $this->assignView($key, $value); + } + + return $response->write($this->render('picwall')); + } + + /** + * @param mixed[] $linksToDisplay List of formatted bookmarks + * + * @return mixed[] Template data after active plugins render_picwall hook execution. + */ + protected function executeHooks(array $linksToDisplay): array + { + $data = [ + 'linksToDisplay' => $linksToDisplay, + ]; + $this->container->pluginManager->executeHooks( + 'render_picwall', + $data, + ['loggedin' => $this->container->loginManager->isLoggedIn()] + ); + + return $data; + } +} diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php new file mode 100644 index 00000000..655b3baa --- /dev/null +++ b/application/front/controller/visitor/ShaarliVisitorController.php @@ -0,0 +1,131 @@ +container = $container; + } + + /** + * Assign variables to RainTPL template through the PageBuilder. + * + * @param mixed $value Value to assign to the template + */ + protected function assignView(string $name, $value): self + { + $this->container->pageBuilder->assign($name, $value); + + return $this; + } + + /** + * Assign variables to RainTPL template through the PageBuilder. + * + * @param mixed $data Values to assign to the template and their keys + */ + protected function assignAllView(array $data): self + { + foreach ($data as $key => $value) { + $this->assignView($key, $value); + } + + return $this; + } + + protected function render(string $template): string + { + $this->assignView('linkcount', $this->container->bookmarkService->count(BookmarkFilter::$ALL)); + $this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE)); + $this->assignView('plugin_errors', $this->container->pluginManager->getErrors()); + + $this->executeDefaultHooks($template); + + return $this->container->pageBuilder->render($template); + } + + /** + * Call plugin hooks for header, footer and includes, specifying which page will be rendered. + * Then assign generated data to RainTPL. + */ + protected function executeDefaultHooks(string $template): void + { + $common_hooks = [ + 'includes', + 'header', + 'footer', + ]; + + foreach ($common_hooks as $name) { + $plugin_data = []; + $this->container->pluginManager->executeHooks( + 'render_' . $name, + $plugin_data, + [ + 'target' => $template, + 'loggedin' => $this->container->loginManager->isLoggedIn() + ] + ); + $this->assignView('plugins_' . $name, $plugin_data); + } + } + + /** + * Generates a redirection to the previous page, based on the HTTP_REFERER. + * It fails back to the home page. + * + * @param array $loopTerms Terms to remove from path and query string to prevent direction loop. + * @param array $clearParams List of parameter to remove from the query string of the referrer. + */ + protected function redirectFromReferer( + Request $request, + Response $response, + array $loopTerms = [], + array $clearParams = [] + ): Response { + $defaultPath = $request->getUri()->getBasePath(); + $referer = $this->container->environment['HTTP_REFERER'] ?? null; + + if (null !== $referer) { + $currentUrl = parse_url($referer); + parse_str($currentUrl['query'] ?? '', $params); + $path = $currentUrl['path'] ?? $defaultPath; + } else { + $params = []; + $path = $defaultPath; + } + + // Prevent redirection loop + if (isset($currentUrl)) { + foreach ($clearParams as $value) { + unset($params[$value]); + } + + $checkQuery = implode('', array_keys($params)); + foreach ($loopTerms as $value) { + if (strpos($path . $checkQuery, $value) !== false) { + $params = []; + $path = $defaultPath; + break; + } + } + } + + $queryString = count($params) > 0 ? '?'. http_build_query($params) : ''; + + return $response->withRedirect($path . $queryString); + } +} diff --git a/application/front/controller/visitor/TagCloudController.php b/application/front/controller/visitor/TagCloudController.php new file mode 100644 index 00000000..15b6d7b7 --- /dev/null +++ b/application/front/controller/visitor/TagCloudController.php @@ -0,0 +1,131 @@ +processRequest(static::TYPE_CLOUD, $request, $response); + } + + /** + * Display the tag list through the template engine. + * This controller a few filters: + * - Visibility stored in the session for logged in users + * - `searchtags` query parameter: will return tags associated with filter in at least one bookmark + * - `sort` query parameters: + * + `usage` (default): most used tags first + * + `alpha`: alphabetical order + */ + public function list(Request $request, Response $response): Response + { + return $this->processRequest(static::TYPE_LIST, $request, $response); + } + + /** + * Process the request for both tag cloud and tag list endpoints. + */ + protected function processRequest(string $type, Request $request, Response $response): Response + { + if ($this->container->loginManager->isLoggedIn() === true) { + $visibility = $this->container->sessionManager->getSessionParameter('visibility'); + } + + $sort = $request->getQueryParam('sort'); + $searchTags = $request->getQueryParam('searchtags'); + $filteringTags = $searchTags !== null ? explode(' ', $searchTags) : []; + + $tags = $this->container->bookmarkService->bookmarksCountPerTag($filteringTags, $visibility ?? null); + + if (static::TYPE_CLOUD === $type || 'alpha' === $sort) { + // TODO: the sorting should be handled by bookmarkService instead of the controller + alphabetical_sort($tags, false, true); + } + + if (static::TYPE_CLOUD === $type) { + $tags = $this->formatTagsForCloud($tags); + } + + $searchTags = implode(' ', escape($filteringTags)); + $data = [ + 'search_tags' => $searchTags, + 'tags' => $tags, + ]; + $data = $this->executeHooks('tag' . $type, $data); + foreach ($data as $key => $value) { + $this->assignView($key, $value); + } + + $searchTags = !empty($searchTags) ? $searchTags .' - ' : ''; + $this->assignView( + 'pagetitle', + $searchTags . t('Tag '. $type) .' - '. $this->container->conf->get('general.title', 'Shaarli') + ); + + return $response->write($this->render('tag.'. $type)); + } + + /** + * Format the tags array for the tag cloud template. + * + * @param array $tags List of tags as key with count as value + * + * @return mixed[] List of tags as key, with count and expected font size in a subarray + */ + protected function formatTagsForCloud(array $tags): array + { + // We sort tags alphabetically, then choose a font size according to count. + // First, find max value. + $maxCount = count($tags) > 0 ? max($tags) : 0; + $logMaxCount = $maxCount > 1 ? log($maxCount, 30) : 1; + $tagList = []; + foreach ($tags as $key => $value) { + // Tag font size scaling: + // default 15 and 30 logarithm bases affect scaling, + // 2.2 and 0.8 are arbitrary font sizes in em. + $size = log($value, 15) / $logMaxCount * 2.2 + 0.8; + $tagList[$key] = [ + 'count' => $value, + 'size' => number_format($size, 2, '.', ''), + ]; + } + + return $tagList; + } + + /** + * @param mixed[] $data Template data + * + * @return mixed[] Template data after active plugins hook execution. + */ + protected function executeHooks(string $template, array $data): array + { + $this->container->pluginManager->executeHooks( + 'render_'. $template, + $data, + ['loggedin' => $this->container->loginManager->isLoggedIn()] + ); + + return $data; + } +} diff --git a/application/front/controller/visitor/TagController.php b/application/front/controller/visitor/TagController.php new file mode 100644 index 00000000..a0bc1d1b --- /dev/null +++ b/application/front/controller/visitor/TagController.php @@ -0,0 +1,118 @@ +container->environment['HTTP_REFERER'] ?? null; + + // In case browser does not send HTTP_REFERER, we search a single tag + if (null === $referer) { + if (null !== $newTag) { + return $response->withRedirect('./?searchtags='. urlencode($newTag)); + } + + return $response->withRedirect('./'); + } + + $currentUrl = parse_url($referer); + parse_str($currentUrl['query'] ?? '', $params); + + if (null === $newTag) { + return $response->withRedirect(($currentUrl['path'] ?? './') .'?'. http_build_query($params)); + } + + // Prevent redirection loop + if (isset($params['addtag'])) { + unset($params['addtag']); + } + + // Check if this tag is already in the search query and ignore it if it is. + // Each tag is always separated by a space + $currentTags = isset($params['searchtags']) ? explode(' ', $params['searchtags']) : []; + + $addtag = true; + foreach ($currentTags as $value) { + if ($value === $newTag) { + $addtag = false; + break; + } + } + + // Append the tag if necessary + if (true === $addtag) { + $currentTags[] = trim($newTag); + } + + $params['searchtags'] = trim(implode(' ', $currentTags)); + + // We also remove page (keeping the same page has no sense, since the results are different) + unset($params['page']); + + return $response->withRedirect(($currentUrl['path'] ?? './') .'?'. http_build_query($params)); + } + + /** + * Remove a tag from the current search through an HTTP redirection. + * + * @param array $args Should contain `tag` key as tag to remove from current search + */ + public function removeTag(Request $request, Response $response, array $args): Response + { + $referer = $this->container->environment['HTTP_REFERER'] ?? null; + + // If the referrer is not provided, we can update the search, so we failback on the bookmark list + if (empty($referer)) { + return $response->withRedirect('./'); + } + + $tagToRemove = $args['tag'] ?? null; + $currentUrl = parse_url($referer); + parse_str($currentUrl['query'] ?? '', $params); + + if (null === $tagToRemove) { + return $response->withRedirect(($currentUrl['path'] ?? './') .'?'. http_build_query($params)); + } + + // Prevent redirection loop + if (isset($params['removetag'])) { + unset($params['removetag']); + } + + if (isset($params['searchtags'])) { + $tags = explode(' ', $params['searchtags']); + // Remove value from array $tags. + $tags = array_diff($tags, [$tagToRemove]); + $params['searchtags'] = implode(' ', $tags); + + if (empty($params['searchtags'])) { + unset($params['searchtags']); + } + + // We also remove page (keeping the same page has no sense, since the results are different) + unset($params['page']); + } + + $queryParams = count($params) > 0 ? '?' . http_build_query($params) : ''; + + return $response->withRedirect(($currentUrl['path'] ?? './') . $queryParams); + } +} -- cgit v1.2.3 From ef00f9d2033f6de11e71bf3a909399cae6f73a9f Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Wed, 27 May 2020 13:35:48 +0200 Subject: Process password change controller through Slim --- application/front/controller/visitor/ShaarliVisitorController.php | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php index 655b3baa..f12915c1 100644 --- a/application/front/controller/visitor/ShaarliVisitorController.php +++ b/application/front/controller/visitor/ShaarliVisitorController.php @@ -9,6 +9,14 @@ use Shaarli\Container\ShaarliContainer; use Slim\Http\Request; use Slim\Http\Response; +/** + * Class ShaarliVisitorController + * + * All controllers accessible by visitors (non logged in users) should extend this abstract class. + * Contains a few helper function for template rendering, plugins, etc. + * + * @package Shaarli\Front\Controller\Visitor + */ abstract class ShaarliVisitorController { /** @var ShaarliContainer */ -- cgit v1.2.3 From c22fa57a5505fe95fd01860e3d3dfbb089f869cd Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 6 Jun 2020 14:01:03 +0200 Subject: Handle shaare creation/edition/deletion through Slim controllers --- application/front/controller/visitor/DailyController.php | 2 +- application/front/controller/visitor/FeedController.php | 2 +- .../front/controller/visitor/ShaarliVisitorController.php | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php index 47e2503a..e5c9ddac 100644 --- a/application/front/controller/visitor/DailyController.php +++ b/application/front/controller/visitor/DailyController.php @@ -71,7 +71,7 @@ class DailyController extends ShaarliVisitorController ]; // Hooks are called before column construction so that plugins don't have to deal with columns. - $this->executeHooks($data); + $data = $this->executeHooks($data); $data['cols'] = $this->calculateColumns($data['linksToDisplay']); diff --git a/application/front/controller/visitor/FeedController.php b/application/front/controller/visitor/FeedController.php index 70664635..f76f55fd 100644 --- a/application/front/controller/visitor/FeedController.php +++ b/application/front/controller/visitor/FeedController.php @@ -46,7 +46,7 @@ class FeedController extends ShaarliVisitorController $data = $this->container->feedBuilder->buildData($feedType, $request->getParams()); - $this->executeHooks($data, $feedType); + $data = $this->executeHooks($data, $feedType); $this->assignAllView($data); $content = $this->render('feed.'. $feedType); diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php index f12915c1..98423d90 100644 --- a/application/front/controller/visitor/ShaarliVisitorController.php +++ b/application/front/controller/visitor/ShaarliVisitorController.php @@ -78,16 +78,16 @@ abstract class ShaarliVisitorController ]; foreach ($common_hooks as $name) { - $plugin_data = []; + $pluginData = []; $this->container->pluginManager->executeHooks( 'render_' . $name, - $plugin_data, + $pluginData, [ 'target' => $template, 'loggedin' => $this->container->loginManager->isLoggedIn() ] ); - $this->assignView('plugins_' . $name, $plugin_data); + $this->assignView('plugins_' . $name, $pluginData); } } @@ -102,9 +102,10 @@ abstract class ShaarliVisitorController Request $request, Response $response, array $loopTerms = [], - array $clearParams = [] + array $clearParams = [], + string $anchor = null ): Response { - $defaultPath = $request->getUri()->getBasePath(); + $defaultPath = rtrim($request->getUri()->getBasePath(), '/') . '/'; $referer = $this->container->environment['HTTP_REFERER'] ?? null; if (null !== $referer) { @@ -133,7 +134,8 @@ abstract class ShaarliVisitorController } $queryString = count($params) > 0 ? '?'. http_build_query($params) : ''; + $anchor = $anchor ? '#' . $anchor : ''; - return $response->withRedirect($path . $queryString); + return $response->withRedirect($path . $queryString . $anchor); } } -- cgit v1.2.3 From 818b3193ffabec57501e3bdfa997206e3c0671ef Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 13 Jun 2020 11:22:14 +0200 Subject: Explicitly define base and asset path in templates With the new routes, all pages are not all at the same folder level anymore (e.g. /shaare and /shaare/123), so we can't just use './' everywhere. The most consistent way to handle this is to prefix all path with the proper variable, and handle the actual path in controllers. --- .../front/controller/visitor/ShaarliVisitorController.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php index 98423d90..b90b1e8f 100644 --- a/application/front/controller/visitor/ShaarliVisitorController.php +++ b/application/front/controller/visitor/ShaarliVisitorController.php @@ -60,6 +60,19 @@ abstract class ShaarliVisitorController $this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE)); $this->assignView('plugin_errors', $this->container->pluginManager->getErrors()); + /* + * Define base path (if Shaarli is installed in a domain's subfolder, e.g. `/shaarli`) + * and the asset path (subfolder/tpl/default for default theme). + * These MUST be used to create an internal link or to include an asset in templates. + */ + $this->assignView('base_path', $this->container->basePath); + $this->assignView( + 'asset_path', + $this->container->basePath . '/' . + rtrim($this->container->conf->get('resource.raintpl_tpl', 'tpl'), '/') . '/' . + $this->container->conf->get('resource.theme', 'default') + ); + $this->executeDefaultHooks($template); return $this->container->pageBuilder->render($template); @@ -105,7 +118,7 @@ abstract class ShaarliVisitorController array $clearParams = [], string $anchor = null ): Response { - $defaultPath = rtrim($request->getUri()->getBasePath(), '/') . '/'; + $defaultPath = $this->container->basePath . '/'; $referer = $this->container->environment['HTTP_REFERER'] ?? null; if (null !== $referer) { -- cgit v1.2.3 From 9c75f877935fa6adec951a4d8d32b328aaab314f Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 13 Jun 2020 13:08:01 +0200 Subject: Use multi-level routes for existing controllers instead of 1 level everywhere Also prefix most admin routes with /admin/ --- application/front/controller/visitor/LoginController.php | 2 +- .../front/controller/visitor/ShaarliVisitorController.php | 13 +++++++++++++ application/front/controller/visitor/TagController.php | 8 +++++--- 3 files changed, 19 insertions(+), 4 deletions(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/LoginController.php b/application/front/controller/visitor/LoginController.php index 4de2f55d..0db1f463 100644 --- a/application/front/controller/visitor/LoginController.php +++ b/application/front/controller/visitor/LoginController.php @@ -23,7 +23,7 @@ class LoginController extends ShaarliVisitorController if ($this->container->loginManager->isLoggedIn() || $this->container->conf->get('security.open_shaarli', false) ) { - return $response->withRedirect('./'); + return $this->redirect($response, '/'); } $userCanLogin = $this->container->loginManager->canLogin($request->getServerParams()); diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php index b90b1e8f..b494a8e6 100644 --- a/application/front/controller/visitor/ShaarliVisitorController.php +++ b/application/front/controller/visitor/ShaarliVisitorController.php @@ -104,6 +104,19 @@ abstract class ShaarliVisitorController } } + /** + * Simple helper which prepend the base path to redirect path. + * + * @param Response $response + * @param string $path Absolute path, e.g.: `/`, or `/admin/shaare/123` regardless of install directory + * + * @return Response updated + */ + protected function redirect(Response $response, string $path): Response + { + return $response->withRedirect($this->container->basePath . $path); + } + /** * Generates a redirection to the previous page, based on the HTTP_REFERER. * It fails back to the home page. diff --git a/application/front/controller/visitor/TagController.php b/application/front/controller/visitor/TagController.php index a0bc1d1b..c176f43f 100644 --- a/application/front/controller/visitor/TagController.php +++ b/application/front/controller/visitor/TagController.php @@ -11,6 +11,8 @@ use Slim\Http\Response; * Class TagController * * Slim controller handle tags. + * + * TODO: check redirections with new helper */ class TagController extends ShaarliVisitorController { @@ -27,10 +29,10 @@ class TagController extends ShaarliVisitorController // In case browser does not send HTTP_REFERER, we search a single tag if (null === $referer) { if (null !== $newTag) { - return $response->withRedirect('./?searchtags='. urlencode($newTag)); + return $this->redirect($response, '/?searchtags='. urlencode($newTag)); } - return $response->withRedirect('./'); + return $this->redirect($response, '/'); } $currentUrl = parse_url($referer); @@ -81,7 +83,7 @@ class TagController extends ShaarliVisitorController // If the referrer is not provided, we can update the search, so we failback on the bookmark list if (empty($referer)) { - return $response->withRedirect('./'); + return $this->redirect($response, '/'); } $tagToRemove = $args['tag'] ?? null; -- cgit v1.2.3 From 1a8ac737e52cb25a5c346232ee398f5908cee7d7 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Mon, 6 Jul 2020 08:04:35 +0200 Subject: Process main page (linklist) through Slim controller Including a bunch of improvements on the container, and helper used across new controllers. --- .../controller/visitor/BookmarkListController.php | 248 +++++++++++++++++++++ .../front/controller/visitor/DailyController.php | 5 +- .../front/controller/visitor/LoginController.php | 3 +- .../controller/visitor/OpenSearchController.php | 3 +- .../controller/visitor/PictureWallController.php | 3 +- 5 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 application/front/controller/visitor/BookmarkListController.php (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php new file mode 100644 index 00000000..a37a7f6b --- /dev/null +++ b/application/front/controller/visitor/BookmarkListController.php @@ -0,0 +1,248 @@ +processLegacyController($request, $response); + if (null !== $legacyResponse) { + return $legacyResponse; + } + + $formatter = $this->container->formatterFactory->getFormatter(); + + $searchTags = escape(normalize_spaces($request->getParam('searchtags') ?? '')); + $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));; + + // Filter bookmarks according search parameters. + $visibility = $this->container->sessionManager->getSessionParameter('visibility'); + $search = [ + 'searchtags' => $searchTags, + 'searchterm' => $searchTerm, + ]; + $linksToDisplay = $this->container->bookmarkService->search( + $search, + $visibility, + false, + !!$this->container->sessionManager->getSessionParameter('untaggedonly') + ) ?? []; + + // ---- Handle paging. + $keys = []; + foreach ($linksToDisplay as $key => $value) { + $keys[] = $key; + } + + $linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20; + + // Select articles according to paging. + $pageCount = (int) ceil(count($keys) / $linksPerPage) ?: 1; + $page = (int) $request->getParam('page') ?? 1; + $page = $page < 1 ? 1 : $page; + $page = $page > $pageCount ? $pageCount : $page; + + // Start index. + $i = ($page - 1) * $linksPerPage; + $end = $i + $linksPerPage; + + $linkDisp = []; + $save = false; + while ($i < $end && $i < count($keys)) { + $save = $this->updateThumbnail($linksToDisplay[$keys[$i]], false) || $save; + $link = $formatter->format($linksToDisplay[$keys[$i]]); + + $linkDisp[$keys[$i]] = $link; + $i++; + } + + if ($save) { + $this->container->bookmarkService->save(); + } + + // Compute paging navigation + $searchtagsUrl = $searchTags === '' ? '' : '&searchtags=' . urlencode($searchTags); + $searchtermUrl = $searchTerm === '' ? '' : '&searchterm=' . urlencode($searchTerm); + + $previous_page_url = ''; + if ($i !== count($keys)) { + $previous_page_url = '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl; + } + $next_page_url = ''; + if ($page > 1) { + $next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl; + } + + // Fill all template fields. + $data = array_merge( + $this->initializeTemplateVars(), + [ + 'previous_page_url' => $previous_page_url, + 'next_page_url' => $next_page_url, + 'page_current' => $page, + 'page_max' => $pageCount, + 'result_count' => count($linksToDisplay), + 'search_term' => $searchTerm, + 'search_tags' => $searchTags, + 'visibility' => $visibility, + 'links' => $linkDisp, + ] + ); + + if (!empty($searchTerm) || !empty($searchTags)) { + $data['pagetitle'] = t('Search: '); + $data['pagetitle'] .= ! empty($searchTerm) ? $searchTerm . ' ' : ''; + $bracketWrap = function ($tag) { + return '[' . $tag . ']'; + }; + $data['pagetitle'] .= ! empty($searchTags) + ? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchTags))) . ' ' + : ''; + $data['pagetitle'] .= '- '; + } + + $data['pagetitle'] = ($data['pagetitle'] ?? '') . $this->container->conf->get('general.title', 'Shaarli'); + + $this->executeHooks($data); + $this->assignAllView($data); + + return $response->write($this->render(TemplatePage::LINKLIST)); + } + + /** + * GET /shaare/{hash} - Display a single shaare + */ + public function permalink(Request $request, Response $response, array $args): Response + { + try { + $bookmark = $this->container->bookmarkService->findByHash($args['hash']); + } catch (BookmarkNotFoundException $e) { + $this->assignView('error_message', $e->getMessage()); + + return $response->write($this->render(TemplatePage::ERROR_404)); + } + + $this->updateThumbnail($bookmark); + + $data = array_merge( + $this->initializeTemplateVars(), + [ + 'pagetitle' => $bookmark->getTitle() .' - '. $this->container->conf->get('general.title', 'Shaarli'), + 'links' => [$this->container->formatterFactory->getFormatter()->format($bookmark)], + ] + ); + + $this->executeHooks($data); + $this->assignAllView($data); + + return $response->write($this->render(TemplatePage::LINKLIST)); + } + + /** + * Update the thumbnail of a single bookmark if necessary. + */ + protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool + { + // Logged in, thumbnails enabled, not a note, is HTTP + // and (never retrieved yet or no valid cache file) + if ($this->container->loginManager->isLoggedIn() + && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE + && false !== $bookmark->getThumbnail() + && !$bookmark->isNote() + && (null === $bookmark->getThumbnail() || !is_file($bookmark->getThumbnail())) + && startsWith(strtolower($bookmark->getUrl()), 'http') + ) { + $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); + $this->container->bookmarkService->set($bookmark, $writeDatastore); + + return true; + } + + return false; + } + + /** + * @param mixed[] $data Template vars to process in plugins, passed as reference. + */ + protected function executeHooks(array &$data): void + { + $this->container->pluginManager->executeHooks( + 'render_linklist', + $data, + ['loggedin' => $this->container->loginManager->isLoggedIn()] + ); + } + + /** + * @return string[] Default template variables without values. + */ + protected function initializeTemplateVars(): array + { + return [ + 'previous_page_url' => '', + 'next_page_url' => '', + 'page_max' => '', + 'search_tags' => '', + 'result_count' => '', + ]; + } + + /** + * Process legacy routes if necessary. They used query parameters. + * If no legacy routes is passed, return null. + */ + protected function processLegacyController(Request $request, Response $response): ?Response + { + // Legacy smallhash filter + $queryString = $this->container->environment['QUERY_STRING'] ?? null; + if (null !== $queryString && 1 === preg_match('/^([a-zA-Z0-9-_@]{6})($|&|#)/', $queryString, $match)) { + return $this->redirect($response, '/shaare/' . $match[1]); + } + + // Legacy controllers (mostly used for redirections) + if (null !== $request->getQueryParam('do')) { + $legacyController = new LegacyController($this->container); + + try { + return $legacyController->process($request, $response, $request->getQueryParam('do')); + } catch (UnknowLegacyRouteException $e) { + // We ignore legacy 404 + return null; + } + } + + // Legacy GET admin routes + $legacyGetRoutes = array_intersect( + LegacyController::LEGACY_GET_ROUTES, + array_keys($request->getQueryParams() ?? []) + ); + if (1 === count($legacyGetRoutes)) { + $legacyController = new LegacyController($this->container); + + return $legacyController->process($request, $response, $legacyGetRoutes[0]); + } + + return null; + } +} diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php index e5c9ddac..05b4f095 100644 --- a/application/front/controller/visitor/DailyController.php +++ b/application/front/controller/visitor/DailyController.php @@ -7,6 +7,7 @@ namespace Shaarli\Front\Controller\Visitor; use DateTime; use DateTimeImmutable; use Shaarli\Bookmark\Bookmark; +use Shaarli\Render\TemplatePage; use Slim\Http\Request; use Slim\Http\Response; @@ -85,7 +86,7 @@ class DailyController extends ShaarliVisitorController t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle ); - return $response->write($this->render('daily')); + return $response->write($this->render(TemplatePage::DAILY)); } /** @@ -152,7 +153,7 @@ class DailyController extends ShaarliVisitorController $this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false)); $this->assignView('days', $dataPerDay); - $rssContent = $this->render('dailyrss'); + $rssContent = $this->render(TemplatePage::DAILY_RSS); $cache->cache($rssContent); diff --git a/application/front/controller/visitor/LoginController.php b/application/front/controller/visitor/LoginController.php index 0db1f463..a257766f 100644 --- a/application/front/controller/visitor/LoginController.php +++ b/application/front/controller/visitor/LoginController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shaarli\Front\Controller\Visitor; use Shaarli\Front\Exception\LoginBannedException; +use Shaarli\Render\TemplatePage; use Slim\Http\Request; use Slim\Http\Response; @@ -41,6 +42,6 @@ class LoginController extends ShaarliVisitorController ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli')) ; - return $response->write($this->render('loginform')); + return $response->write($this->render(TemplatePage::LOGIN)); } } diff --git a/application/front/controller/visitor/OpenSearchController.php b/application/front/controller/visitor/OpenSearchController.php index 0fd68db6..36d60acf 100644 --- a/application/front/controller/visitor/OpenSearchController.php +++ b/application/front/controller/visitor/OpenSearchController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shaarli\Front\Controller\Visitor; +use Shaarli\Render\TemplatePage; use Slim\Http\Request; use Slim\Http\Response; @@ -21,6 +22,6 @@ class OpenSearchController extends ShaarliVisitorController $this->assignView('serverurl', index_url($this->container->environment)); - return $response->write($this->render('opensearch')); + return $response->write($this->render(TemplatePage::OPEN_SEARCH)); } } diff --git a/application/front/controller/visitor/PictureWallController.php b/application/front/controller/visitor/PictureWallController.php index 4e1dce8c..5ef2cb17 100644 --- a/application/front/controller/visitor/PictureWallController.php +++ b/application/front/controller/visitor/PictureWallController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shaarli\Front\Controller\Visitor; use Shaarli\Front\Exception\ThumbnailsDisabledException; +use Shaarli\Render\TemplatePage; use Shaarli\Thumbnailer; use Slim\Http\Request; use Slim\Http\Response; @@ -46,7 +47,7 @@ class PictureWallController extends ShaarliVisitorController $this->assignView($key, $value); } - return $response->write($this->render('picwall')); + return $response->write($this->render(TemplatePage::PICTURE_WALL)); } /** -- cgit v1.2.3 From c4ad3d4f061d05a01db25aa54dda830ba776792d Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 7 Jul 2020 10:15:56 +0200 Subject: Process Shaarli install through Slim controller --- .../front/controller/visitor/InstallController.php | 173 +++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 application/front/controller/visitor/InstallController.php (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php new file mode 100644 index 00000000..aa032860 --- /dev/null +++ b/application/front/controller/visitor/InstallController.php @@ -0,0 +1,173 @@ +container->conf->getConfigFileExt())) { + throw new AlreadyInstalledException(); + } + } + + /** + * Display the install template page. + * Also test file permissions and sessions beforehand. + */ + public function index(Request $request, Response $response): Response + { + // Before installation, we'll make sure that permissions are set properly, and sessions are working. + $this->checkPermissions(); + + if (static::SESSION_TEST_VALUE + !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY) + ) { + $this->container->sessionManager->setSessionParameter(static::SESSION_TEST_KEY, static::SESSION_TEST_VALUE); + + return $this->redirect($response, '/install/session-test'); + } + + [$continents, $cities] = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); + + $this->assignView('continents', $continents); + $this->assignView('cities', $cities); + $this->assignView('languages', Languages::getAvailableLanguages()); + + return $response->write($this->render('install')); + } + + /** + * Route checking that the session parameter has been properly saved between two distinct requests. + * If the session parameter is preserved, redirect to install template page, otherwise displays error. + */ + public function sessionTest(Request $request, Response $response): Response + { + // This part makes sure sessions works correctly. + // (Because on some hosts, session.save_path may not be set correctly, + // or we may not have write access to it.) + if (static::SESSION_TEST_VALUE + !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY) + ) { + // Step 2: Check if data in session is correct. + $msg = t( + '
Sessions do not seem to work correctly on your server.
'. + 'Make sure the variable "session.save_path" is set correctly in your PHP config, '. + 'and that you have write access to it.
'. + 'It currently points to %s.
'. + 'On some browsers, accessing your server via a hostname like \'localhost\' '. + 'or any custom hostname without a dot causes cookie storage to fail. '. + 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.
' + ); + $msg = sprintf($msg, $this->container->sessionManager->getSavePath()); + + $this->assignView('message', $msg); + + return $response->write($this->render('error')); + } + + return $this->redirect($response, '/install'); + } + + /** + * Save installation form and initialize config file and datastore if necessary. + */ + public function save(Request $request, Response $response): Response + { + $timezone = 'UTC'; + if (!empty($request->getParam('continent')) + && !empty($request->getParam('city')) + && isTimeZoneValid($request->getParam('continent'), $request->getParam('city')) + ) { + $timezone = $request->getParam('continent') . '/' . $request->getParam('city'); + } + $this->container->conf->set('general.timezone', $timezone); + + $login = $request->getParam('setlogin'); + $this->container->conf->set('credentials.login', $login); + $salt = sha1(uniqid('', true) .'_'. mt_rand()); + $this->container->conf->set('credentials.salt', $salt); + $this->container->conf->set('credentials.hash', sha1($request->getParam('setpassword') . $login . $salt)); + + if (!empty($request->getParam('title'))) { + $this->container->conf->set('general.title', escape($request->getParam('title'))); + } else { + $this->container->conf->set( + 'general.title', + 'Shared bookmarks on '.escape(index_url($this->container->environment)) + ); + } + + $this->container->conf->set('translation.language', escape($request->getParam('language'))); + $this->container->conf->set('updates.check_updates', !empty($request->getParam('updateCheck'))); + $this->container->conf->set('api.enabled', !empty($request->getParam('enableApi'))); + $this->container->conf->set( + 'api.secret', + generate_api_secret( + $this->container->conf->get('credentials.login'), + $this->container->conf->get('credentials.salt') + ) + ); + + try { + // Everything is ok, let's create config file. + $this->container->conf->write($this->container->loginManager->isLoggedIn()); + } catch (\Exception $e) { + $this->assignView('message', $e->getMessage()); + $this->assignView('stacktrace', $e->getTraceAsString()); + + return $response->write($this->render('error')); + } + + if ($this->container->bookmarkService->count(BookmarkFilter::$ALL) === 0) { + $this->container->bookmarkService->initialize(); + } + + $this->container->sessionManager->setSessionParameter( + SessionManager::KEY_SUCCESS_MESSAGES, + [t('Shaarli is now configured. Please login and start shaaring your bookmarks!')] + ); + + return $this->redirect($response, '/'); + } + + protected function checkPermissions(): bool + { + // Ensure Shaarli has proper access to its resources + $errors = ApplicationUtils::checkResourcePermissions($this->container->conf); + + if (empty($errors)) { + return true; + } + + // FIXME! Do not insert HTML here. + $message = '

'. t('Insufficient permissions:') .'

    '; + + foreach ($errors as $error) { + $message .= '
  • '.$error.'
  • '; + } + $message .= '
'; + + throw new ResourcePermissionException($message); + } +} -- cgit v1.2.3 From a8c11451e8d885a243c1ad52012093ba8d121e2c Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 21 Jul 2020 20:33:33 +0200 Subject: Process login through Slim controller --- .../front/controller/visitor/LoginController.php | 127 +++++++++++++++++++-- 1 file changed, 117 insertions(+), 10 deletions(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/LoginController.php b/application/front/controller/visitor/LoginController.php index a257766f..c40b8cc4 100644 --- a/application/front/controller/visitor/LoginController.php +++ b/application/front/controller/visitor/LoginController.php @@ -4,8 +4,12 @@ declare(strict_types=1); namespace Shaarli\Front\Controller\Visitor; +use Shaarli\Front\Exception\CantLoginException; use Shaarli\Front\Exception\LoginBannedException; +use Shaarli\Front\Exception\WrongTokenException; use Shaarli\Render\TemplatePage; +use Shaarli\Security\CookieManager; +use Shaarli\Security\SessionManager; use Slim\Http\Request; use Slim\Http\Response; @@ -19,29 +23,132 @@ use Slim\Http\Response; */ class LoginController extends ShaarliVisitorController { + /** + * GET /login - Display the login page. + */ public function index(Request $request, Response $response): Response { - if ($this->container->loginManager->isLoggedIn() - || $this->container->conf->get('security.open_shaarli', false) - ) { + try { + $this->checkLoginState(); + } catch (CantLoginException $e) { return $this->redirect($response, '/'); } - $userCanLogin = $this->container->loginManager->canLogin($request->getServerParams()); - if ($userCanLogin !== true) { - throw new LoginBannedException(); + if ($request->getParam('login') !== null) { + $this->assignView('username', escape($request->getParam('login'))); } - if ($request->getParam('username') !== null) { - $this->assignView('username', escape($request->getParam('username'))); - } + $returnUrl = $request->getParam('returnurl') ?? $this->container->environment['HTTP_REFERER'] ?? null; $this - ->assignView('returnurl', escape($request->getServerParam('HTTP_REFERER'))) + ->assignView('returnurl', escape($returnUrl)) ->assignView('remember_user_default', $this->container->conf->get('privacy.remember_user_default', true)) ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli')) ; return $response->write($this->render(TemplatePage::LOGIN)); } + + /** + * POST /login - Process login + */ + public function login(Request $request, Response $response): Response + { + if (!$this->container->sessionManager->checkToken($request->getParam('token'))) { + throw new WrongTokenException(); + } + + try { + $this->checkLoginState(); + } catch (CantLoginException $e) { + return $this->redirect($response, '/'); + } + + if (!$this->container->loginManager->checkCredentials( + $this->container->environment['REMOTE_ADDR'], + client_ip_id($this->container->environment), + $request->getParam('login'), + $request->getParam('password') + ) + ) { + $this->container->loginManager->handleFailedLogin($this->container->environment); + + $this->container->sessionManager->setSessionParameter( + SessionManager::KEY_ERROR_MESSAGES, + [t('Wrong login/password.')] + ); + + // Call controller directly instead of unnecessary redirection + return $this->index($request, $response); + } + + $this->container->loginManager->handleSuccessfulLogin($this->container->environment); + + $cookiePath = $this->container->basePath . '/'; + $expirationTime = $this->saveLongLastingSession($request, $cookiePath); + $this->renewUserSession($cookiePath, $expirationTime); + + // Force referer from given return URL + $this->container->environment['HTTP_REFERER'] = $request->getParam('returnurl'); + + return $this->redirectFromReferer($request, $response, ['login']); + } + + /** + * Make sure that the user is allowed to login and/or displaying the login page: + * - not already logged in + * - not open shaarli + * - not banned + */ + protected function checkLoginState(): bool + { + if ($this->container->loginManager->isLoggedIn() + || $this->container->conf->get('security.open_shaarli', false) + ) { + throw new CantLoginException(); + } + + if (true !== $this->container->loginManager->canLogin($this->container->environment)) { + throw new LoginBannedException(); + } + + return true; + } + + /** + * @return int Session duration in seconds + */ + protected function saveLongLastingSession(Request $request, string $cookiePath): int + { + if (empty($request->getParam('longlastingsession'))) { + // Standard session expiration (=when browser closes) + $expirationTime = 0; + } else { + // Keep the session cookie even after the browser closes + $this->container->sessionManager->setStaySignedIn(true); + $expirationTime = $this->container->sessionManager->extendSession(); + } + + $this->container->cookieManager->setCookieParameter( + CookieManager::STAY_SIGNED_IN, + $this->container->loginManager->getStaySignedInToken(), + $expirationTime, + $cookiePath + ); + + return $expirationTime; + } + + protected function renewUserSession(string $cookiePath, int $expirationTime): void + { + // Send cookie with the new expiration date to the browser + $this->container->sessionManager->destroy(); + $this->container->sessionManager->cookieParameters( + $expirationTime, + $cookiePath, + $this->container->environment['SERVER_NAME'] + ); + $this->container->sessionManager->start(); + $this->container->sessionManager->regenerateId(true); + } } -- cgit v1.2.3 From 3ee8351e438f13ccf36062ce956e0b4a4d5f4a29 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 23 Jul 2020 16:41:32 +0200 Subject: Multiple small fixes --- application/front/controller/visitor/InstallController.php | 13 +++++-------- application/front/controller/visitor/TagController.php | 2 -- 2 files changed, 5 insertions(+), 10 deletions(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php index aa032860..94ebb4ae 100644 --- a/application/front/controller/visitor/InstallController.php +++ b/application/front/controller/visitor/InstallController.php @@ -128,13 +128,14 @@ class InstallController extends ShaarliVisitorController $this->container->conf->get('credentials.salt') ) ); + $this->container->conf->set('general.header_link', $this->container->basePath); try { // Everything is ok, let's create config file. $this->container->conf->write($this->container->loginManager->isLoggedIn()); } catch (\Exception $e) { - $this->assignView('message', $e->getMessage()); - $this->assignView('stacktrace', $e->getTraceAsString()); + $this->assignView('message', t('Error while writing config file after configuration update.')); + $this->assignView('stacktrace', $e->getMessage() . PHP_EOL . $e->getTraceAsString()); return $response->write($this->render('error')); } @@ -155,18 +156,14 @@ class InstallController extends ShaarliVisitorController { // Ensure Shaarli has proper access to its resources $errors = ApplicationUtils::checkResourcePermissions($this->container->conf); - if (empty($errors)) { return true; } - // FIXME! Do not insert HTML here. - $message = '

'. t('Insufficient permissions:') .'

    '; - + $message = t('Insufficient permissions:') . PHP_EOL; foreach ($errors as $error) { - $message .= '
  • '.$error.'
  • '; + $message .= PHP_EOL . $error; } - $message .= '
'; throw new ResourcePermissionException($message); } diff --git a/application/front/controller/visitor/TagController.php b/application/front/controller/visitor/TagController.php index c176f43f..de4e7ea2 100644 --- a/application/front/controller/visitor/TagController.php +++ b/application/front/controller/visitor/TagController.php @@ -11,8 +11,6 @@ use Slim\Http\Response; * Class TagController * * Slim controller handle tags. - * - * TODO: check redirections with new helper */ class TagController extends ShaarliVisitorController { -- cgit v1.2.3 From 87ae3c4f08431e02869376cb57add257747910d1 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Fri, 24 Jul 2020 10:30:47 +0200 Subject: Fix default link and redirection in install controller --- application/front/controller/visitor/InstallController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php index 94ebb4ae..5e3152c7 100644 --- a/application/front/controller/visitor/InstallController.php +++ b/application/front/controller/visitor/InstallController.php @@ -128,7 +128,7 @@ class InstallController extends ShaarliVisitorController $this->container->conf->get('credentials.salt') ) ); - $this->container->conf->set('general.header_link', $this->container->basePath); + $this->container->conf->set('general.header_link', $this->container->basePath . '/'); try { // Everything is ok, let's create config file. @@ -149,7 +149,7 @@ class InstallController extends ShaarliVisitorController [t('Shaarli is now configured. Please login and start shaaring your bookmarks!')] ); - return $this->redirect($response, '/'); + return $this->redirect($response, '/login'); } protected function checkPermissions(): bool -- cgit v1.2.3 From 204035bd3c91b9a5c39fcb6fc470e108b032dbd9 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Fri, 24 Jul 2020 12:48:53 +0200 Subject: Fix: visitor are allowed to chose nb of links per page --- .../visitor/PublicSessionFilterController.php | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 application/front/controller/visitor/PublicSessionFilterController.php (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/PublicSessionFilterController.php b/application/front/controller/visitor/PublicSessionFilterController.php new file mode 100644 index 00000000..35da0c5f --- /dev/null +++ b/application/front/controller/visitor/PublicSessionFilterController.php @@ -0,0 +1,33 @@ +getParam('nb') ?? null; + if (null === $linksPerPage || false === is_numeric($linksPerPage)) { + $linksPerPage = $this->container->conf->get('general.links_per_page', 20); + } + + $this->container->sessionManager->setSessionParameter( + SessionManager::KEY_LINKS_PER_PAGE, + abs(intval($linksPerPage)) + ); + + return $this->redirectFromReferer($request, $response, ['linksperpage'], ['nb']); + } +} -- cgit v1.2.3 From 9fbc42294e7667c5ef19cafa0d1fcfbc1c0f36a9 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sun, 26 Jul 2020 14:43:10 +0200 Subject: New basePath: fix officiel plugin paths and vintage template --- .../controller/visitor/BookmarkListController.php | 16 ++--------- .../front/controller/visitor/DailyController.php | 22 ++------------- .../front/controller/visitor/FeedController.php | 21 +------------- .../controller/visitor/PictureWallController.php | 23 ++------------- .../visitor/ShaarliVisitorController.php | 33 ++++++++++++---------- .../controller/visitor/TagCloudController.php | 24 ++-------------- 6 files changed, 29 insertions(+), 110 deletions(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php index a37a7f6b..23c4fbae 100644 --- a/application/front/controller/visitor/BookmarkListController.php +++ b/application/front/controller/visitor/BookmarkListController.php @@ -124,7 +124,7 @@ class BookmarkListController extends ShaarliVisitorController $data['pagetitle'] = ($data['pagetitle'] ?? '') . $this->container->conf->get('general.title', 'Shaarli'); - $this->executeHooks($data); + $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST); $this->assignAllView($data); return $response->write($this->render(TemplatePage::LINKLIST)); @@ -153,7 +153,7 @@ class BookmarkListController extends ShaarliVisitorController ] ); - $this->executeHooks($data); + $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST); $this->assignAllView($data); return $response->write($this->render(TemplatePage::LINKLIST)); @@ -182,18 +182,6 @@ class BookmarkListController extends ShaarliVisitorController return false; } - /** - * @param mixed[] $data Template vars to process in plugins, passed as reference. - */ - protected function executeHooks(array &$data): void - { - $this->container->pluginManager->executeHooks( - 'render_linklist', - $data, - ['loggedin' => $this->container->loginManager->isLoggedIn()] - ); - } - /** * @return string[] Default template variables without values. */ diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php index 05b4f095..808ca5f7 100644 --- a/application/front/controller/visitor/DailyController.php +++ b/application/front/controller/visitor/DailyController.php @@ -72,13 +72,11 @@ class DailyController extends ShaarliVisitorController ]; // Hooks are called before column construction so that plugins don't have to deal with columns. - $data = $this->executeHooks($data); + $this->executePageHooks('render_daily', $data, TemplatePage::DAILY); $data['cols'] = $this->calculateColumns($data['linksToDisplay']); - foreach ($data as $key => $value) { - $this->assignView($key, $value); - } + $this->assignAllView($data); $mainTitle = $this->container->conf->get('general.title', 'Shaarli'); $this->assignView( @@ -190,20 +188,4 @@ class DailyController extends ShaarliVisitorController return $columns; } - - /** - * @param mixed[] $data Variables passed to the template engine - * - * @return mixed[] Template data after active plugins render_picwall hook execution. - */ - protected function executeHooks(array $data): array - { - $this->container->pluginManager->executeHooks( - 'render_daily', - $data, - ['loggedin' => $this->container->loginManager->isLoggedIn()] - ); - - return $data; - } } diff --git a/application/front/controller/visitor/FeedController.php b/application/front/controller/visitor/FeedController.php index f76f55fd..da2848c2 100644 --- a/application/front/controller/visitor/FeedController.php +++ b/application/front/controller/visitor/FeedController.php @@ -46,7 +46,7 @@ class FeedController extends ShaarliVisitorController $data = $this->container->feedBuilder->buildData($feedType, $request->getParams()); - $data = $this->executeHooks($data, $feedType); + $this->executePageHooks('render_feed', $data, $feedType); $this->assignAllView($data); $content = $this->render('feed.'. $feedType); @@ -55,23 +55,4 @@ class FeedController extends ShaarliVisitorController return $response->write($content); } - - /** - * @param mixed[] $data Template data - * - * @return mixed[] Template data after active plugins hook execution. - */ - protected function executeHooks(array $data, string $feedType): array - { - $this->container->pluginManager->executeHooks( - 'render_feed', - $data, - [ - 'loggedin' => $this->container->loginManager->isLoggedIn(), - 'target' => $feedType, - ] - ); - - return $data; - } } diff --git a/application/front/controller/visitor/PictureWallController.php b/application/front/controller/visitor/PictureWallController.php index 5ef2cb17..3c57f8dd 100644 --- a/application/front/controller/visitor/PictureWallController.php +++ b/application/front/controller/visitor/PictureWallController.php @@ -42,30 +42,13 @@ class PictureWallController extends ShaarliVisitorController } } - $data = $this->executeHooks($linksToDisplay); + $data = ['linksToDisplay' => $linksToDisplay]; + $this->executePageHooks('render_picwall', $data, TemplatePage::PICTURE_WALL); + foreach ($data as $key => $value) { $this->assignView($key, $value); } return $response->write($this->render(TemplatePage::PICTURE_WALL)); } - - /** - * @param mixed[] $linksToDisplay List of formatted bookmarks - * - * @return mixed[] Template data after active plugins render_picwall hook execution. - */ - protected function executeHooks(array $linksToDisplay): array - { - $data = [ - 'linksToDisplay' => $linksToDisplay, - ]; - $this->container->pluginManager->executeHooks( - 'render_picwall', - $data, - ['loggedin' => $this->container->loginManager->isLoggedIn()] - ); - - return $data; - } } diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php index b494a8e6..47057d97 100644 --- a/application/front/controller/visitor/ShaarliVisitorController.php +++ b/application/front/controller/visitor/ShaarliVisitorController.php @@ -60,22 +60,9 @@ abstract class ShaarliVisitorController $this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE)); $this->assignView('plugin_errors', $this->container->pluginManager->getErrors()); - /* - * Define base path (if Shaarli is installed in a domain's subfolder, e.g. `/shaarli`) - * and the asset path (subfolder/tpl/default for default theme). - * These MUST be used to create an internal link or to include an asset in templates. - */ - $this->assignView('base_path', $this->container->basePath); - $this->assignView( - 'asset_path', - $this->container->basePath . '/' . - rtrim($this->container->conf->get('resource.raintpl_tpl', 'tpl'), '/') . '/' . - $this->container->conf->get('resource.theme', 'default') - ); - $this->executeDefaultHooks($template); - return $this->container->pageBuilder->render($template); + return $this->container->pageBuilder->render($template, $this->container->basePath); } /** @@ -97,13 +84,29 @@ abstract class ShaarliVisitorController $pluginData, [ 'target' => $template, - 'loggedin' => $this->container->loginManager->isLoggedIn() + 'loggedin' => $this->container->loginManager->isLoggedIn(), + 'basePath' => $this->container->basePath, ] ); $this->assignView('plugins_' . $name, $pluginData); } } + protected function executePageHooks(string $hook, array &$data, string $template = null): void + { + $params = [ + 'target' => $template, + 'loggedin' => $this->container->loginManager->isLoggedIn(), + 'basePath' => $this->container->basePath, + ]; + + $this->container->pluginManager->executeHooks( + $hook, + $data, + $params + ); + } + /** * Simple helper which prepend the base path to redirect path. * diff --git a/application/front/controller/visitor/TagCloudController.php b/application/front/controller/visitor/TagCloudController.php index 15b6d7b7..f9c529bc 100644 --- a/application/front/controller/visitor/TagCloudController.php +++ b/application/front/controller/visitor/TagCloudController.php @@ -71,10 +71,8 @@ class TagCloudController extends ShaarliVisitorController 'search_tags' => $searchTags, 'tags' => $tags, ]; - $data = $this->executeHooks('tag' . $type, $data); - foreach ($data as $key => $value) { - $this->assignView($key, $value); - } + $this->executePageHooks('render_tag' . $type, $data, 'tag.' . $type); + $this->assignAllView($data); $searchTags = !empty($searchTags) ? $searchTags .' - ' : ''; $this->assignView( @@ -82,7 +80,7 @@ class TagCloudController extends ShaarliVisitorController $searchTags . t('Tag '. $type) .' - '. $this->container->conf->get('general.title', 'Shaarli') ); - return $response->write($this->render('tag.'. $type)); + return $response->write($this->render('tag.' . $type)); } /** @@ -112,20 +110,4 @@ class TagCloudController extends ShaarliVisitorController return $tagList; } - - /** - * @param mixed[] $data Template data - * - * @return mixed[] Template data after active plugins hook execution. - */ - protected function executeHooks(string $template, array $data): array - { - $this->container->pluginManager->executeHooks( - 'render_'. $template, - $data, - ['loggedin' => $this->container->loginManager->isLoggedIn()] - ); - - return $data; - } } -- cgit v1.2.3 From a285668ec4456c4d413c1d6dec275f1d18bf3f15 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Mon, 27 Jul 2020 12:34:17 +0200 Subject: Fix redirection after post install login --- application/front/controller/visitor/LoginController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/LoginController.php b/application/front/controller/visitor/LoginController.php index c40b8cc4..121ba40b 100644 --- a/application/front/controller/visitor/LoginController.php +++ b/application/front/controller/visitor/LoginController.php @@ -91,7 +91,7 @@ class LoginController extends ShaarliVisitorController // Force referer from given return URL $this->container->environment['HTTP_REFERER'] = $request->getParam('returnurl'); - return $this->redirectFromReferer($request, $response, ['login']); + return $this->redirectFromReferer($request, $response, ['login', 'install']); } /** -- cgit v1.2.3 From 301c7ab1a079d937ab41c6f52b8804e5731008e6 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 28 Jul 2020 20:46:11 +0200 Subject: Better support for notes permalink --- application/front/controller/visitor/BookmarkListController.php | 6 +++++- application/front/controller/visitor/DailyController.php | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php index 23c4fbae..2988bee6 100644 --- a/application/front/controller/visitor/BookmarkListController.php +++ b/application/front/controller/visitor/BookmarkListController.php @@ -32,6 +32,7 @@ class BookmarkListController extends ShaarliVisitorController } $formatter = $this->container->formatterFactory->getFormatter(); + $formatter->addContextData('base_path', $this->container->basePath); $searchTags = escape(normalize_spaces($request->getParam('searchtags') ?? '')); $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));; @@ -145,11 +146,14 @@ class BookmarkListController extends ShaarliVisitorController $this->updateThumbnail($bookmark); + $formatter = $this->container->formatterFactory->getFormatter(); + $formatter->addContextData('base_path', $this->container->basePath); + $data = array_merge( $this->initializeTemplateVars(), [ 'pagetitle' => $bookmark->getTitle() .' - '. $this->container->conf->get('general.title', 'Shaarli'), - 'links' => [$this->container->formatterFactory->getFormatter()->format($bookmark)], + 'links' => [$formatter->format($bookmark)], ] ); diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php index 808ca5f7..54a4778f 100644 --- a/application/front/controller/visitor/DailyController.php +++ b/application/front/controller/visitor/DailyController.php @@ -54,6 +54,7 @@ class DailyController extends ShaarliVisitorController } $formatter = $this->container->formatterFactory->getFormatter(); + $formatter->addContextData('base_path', $this->container->basePath); // We pre-format some fields for proper output. foreach ($linksToDisplay as $key => $bookmark) { $linksToDisplay[$key] = $formatter->format($bookmark); -- cgit v1.2.3 From d6e5f04d3987e498c5cb859eed6bff33d67949df Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 1 Aug 2020 11:10:57 +0200 Subject: Remove anonymous permission and initialize bookmarks on login --- application/front/controller/visitor/InstallController.php | 5 ----- 1 file changed, 5 deletions(-) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php index 5e3152c7..7cb32777 100644 --- a/application/front/controller/visitor/InstallController.php +++ b/application/front/controller/visitor/InstallController.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Shaarli\Front\Controller\Visitor; use Shaarli\ApplicationUtils; -use Shaarli\Bookmark\BookmarkFilter; use Shaarli\Container\ShaarliContainer; use Shaarli\Front\Exception\AlreadyInstalledException; use Shaarli\Front\Exception\ResourcePermissionException; @@ -140,10 +139,6 @@ class InstallController extends ShaarliVisitorController return $response->write($this->render('error')); } - if ($this->container->bookmarkService->count(BookmarkFilter::$ALL) === 0) { - $this->container->bookmarkService->initialize(); - } - $this->container->sessionManager->setSessionParameter( SessionManager::KEY_SUCCESS_MESSAGES, [t('Shaarli is now configured. Please login and start shaaring your bookmarks!')] -- cgit v1.2.3 From bedbb845eec20363b928b424143787dbe988eefe Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 13 Aug 2020 11:08:13 +0200 Subject: Move all admin controller into a dedicated group Also handle authentication check in a new middleware for the admin group. --- .../controller/visitor/PublicSessionFilterController.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/PublicSessionFilterController.php b/application/front/controller/visitor/PublicSessionFilterController.php index 35da0c5f..1a66362d 100644 --- a/application/front/controller/visitor/PublicSessionFilterController.php +++ b/application/front/controller/visitor/PublicSessionFilterController.php @@ -30,4 +30,17 @@ class PublicSessionFilterController extends ShaarliVisitorController return $this->redirectFromReferer($request, $response, ['linksperpage'], ['nb']); } + + /** + * GET /untagged-only: allows to display only bookmarks without any tag + */ + public function untaggedOnly(Request $request, Response $response): Response + { + $this->container->sessionManager->setSessionParameter( + SessionManager::KEY_UNTAGGED_ONLY, + empty($this->container->sessionManager->getSessionParameter(SessionManager::KEY_UNTAGGED_ONLY)) + ); + + return $this->redirectFromReferer($request, $response, ['untaggedonly', 'untagged-only']); + } } -- cgit v1.2.3 From 0c6fdbe12bbbb336348666b14b82096f24d5858b Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Fri, 21 Aug 2020 10:50:44 +0200 Subject: Move error handling to dedicated controller instead of middleware --- .../front/controller/visitor/ErrorController.php | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 application/front/controller/visitor/ErrorController.php (limited to 'application/front/controller/visitor') diff --git a/application/front/controller/visitor/ErrorController.php b/application/front/controller/visitor/ErrorController.php new file mode 100644 index 00000000..10aa84c8 --- /dev/null +++ b/application/front/controller/visitor/ErrorController.php @@ -0,0 +1,45 @@ +container->pageBuilder->reset(); + + if ($throwable instanceof ShaarliFrontException) { + // Functional error + $this->assignView('message', nl2br($throwable->getMessage())); + + $response = $response->withStatus($throwable->getCode()); + } else { + // Internal error (any other Throwable) + if ($this->container->conf->get('dev.debug', false)) { + $this->assignView('message', $throwable->getMessage()); + $this->assignView( + 'stacktrace', + nl2br(get_class($throwable) .': '. PHP_EOL . $throwable->getTraceAsString()) + ); + } else { + $this->assignView('message', t('An unexpected error occurred.')); + } + + $response = $response->withStatus(500); + } + + + return $response->write($this->render('error')); + } +} -- cgit v1.2.3