3 declare(strict_types
=1);
5 namespace Shaarli\Front\Controller\Visitor
;
7 use Shaarli\Bookmark\Bookmark
;
8 use Shaarli\Bookmark\Exception\BookmarkNotFoundException
;
9 use Shaarli\Legacy\LegacyController
;
10 use Shaarli\Legacy\UnknowLegacyRouteException
;
11 use Shaarli\Render\TemplatePage
;
12 use Shaarli\Thumbnailer
;
13 use Slim\Http\Request
;
14 use Slim\Http\Response
;
17 * Class BookmarkListController
19 * Slim controller used to render the bookmark list, the home page of Shaarli.
20 * It also displays permalinks, and process legacy routes based on GET parameters.
22 class BookmarkListController
extends ShaarliVisitorController
25 * GET / - Displays the bookmark list, with optional filter parameters.
27 public function index(Request
$request, Response
$response): Response
29 $legacyResponse = $this->processLegacyController($request, $response);
30 if (null !== $legacyResponse) {
31 return $legacyResponse;
34 $formatter = $this->container
->formatterFactory
->getFormatter();
35 $formatter->addContextData('base_path', $this->container
->basePath
);
37 $searchTags = escape(normalize_spaces($request->getParam('searchtags') ?? ''));
38 $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));;
40 // Filter bookmarks according search parameters.
41 $visibility = $this->container
->sessionManager
->getSessionParameter('visibility');
43 'searchtags' => $searchTags,
44 'searchterm' => $searchTerm,
46 $linksToDisplay = $this->container
->bookmarkService
->search(
50 !!$this->container
->sessionManager
->getSessionParameter('untaggedonly')
53 // ---- Handle paging.
55 foreach ($linksToDisplay as $key => $value) {
59 $linksPerPage = $this->container
->sessionManager
->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20;
61 // Select articles according to paging.
62 $pageCount = (int) ceil(count($keys) / $linksPerPage) ?: 1;
63 $page = (int) $request->getParam('page') ?? 1;
64 $page = $page < 1 ? 1 : $page;
65 $page = $page > $pageCount ? $pageCount : $page;
68 $i = ($page - 1) * $linksPerPage;
69 $end = $i +
$linksPerPage;
73 while ($i < $end && $i < count($keys)) {
74 $save = $this->updateThumbnail($linksToDisplay[$keys[$i]], false) || $save;
75 $link = $formatter->format($linksToDisplay[$keys[$i]]);
77 $linkDisp[$keys[$i]] = $link;
82 $this->container
->bookmarkService
->save();
85 // Compute paging navigation
86 $searchtagsUrl = $searchTags === '' ? '' : '&searchtags=' . urlencode($searchTags);
87 $searchtermUrl = $searchTerm === '' ? '' : '&searchterm=' . urlencode($searchTerm);
89 $previous_page_url = '';
90 if ($i !== count($keys)) {
91 $previous_page_url = '?page=' . ($page +
1) . $searchtermUrl . $searchtagsUrl;
95 $next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl;
98 // Fill all template fields.
100 $this->initializeTemplateVars(),
102 'previous_page_url' => $previous_page_url,
103 'next_page_url' => $next_page_url,
104 'page_current' => $page,
105 'page_max' => $pageCount,
106 'result_count' => count($linksToDisplay),
107 'search_term' => $searchTerm,
108 'search_tags' => $searchTags,
109 'visibility' => $visibility,
110 'links' => $linkDisp,
114 if (!empty($searchTerm) || !empty($searchTags)) {
115 $data['pagetitle'] = t('Search: ');
116 $data['pagetitle'] .= ! empty($searchTerm) ? $searchTerm . ' ' : '';
117 $bracketWrap = function ($tag) {
118 return '[' . $tag . ']';
120 $data['pagetitle'] .= ! empty($searchTags)
121 ? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchTags))) . ' '
123 $data['pagetitle'] .= '- ';
126 $data['pagetitle'] = ($data['pagetitle'] ?? '') . $this->container
->conf
->get('general.title', 'Shaarli');
128 $this->executePageHooks('render_linklist', $data, TemplatePage
::LINKLIST
);
129 $this->assignAllView($data);
131 return $response->write($this->render(TemplatePage
::LINKLIST
));
135 * GET /shaare/{hash} - Display a single shaare
137 public function permalink(Request
$request, Response
$response, array $args): Response
140 $bookmark = $this->container
->bookmarkService
->findByHash($args['hash']);
141 } catch (BookmarkNotFoundException
$e) {
142 $this->assignView('error_message', $e->getMessage());
144 return $response->write($this->render(TemplatePage
::ERROR_404
));
147 $this->updateThumbnail($bookmark);
149 $formatter = $this->container
->formatterFactory
->getFormatter();
150 $formatter->addContextData('base_path', $this->container
->basePath
);
153 $this->initializeTemplateVars(),
155 'pagetitle' => $bookmark->getTitle() .' - '. $this->container
->conf
->get('general.title', 'Shaarli'),
156 'links' => [$formatter->format($bookmark)],
160 $this->executePageHooks('render_linklist', $data, TemplatePage
::LINKLIST
);
161 $this->assignAllView($data);
163 return $response->write($this->render(TemplatePage
::LINKLIST
));
167 * Update the thumbnail of a single bookmark if necessary.
169 protected function updateThumbnail(Bookmark
$bookmark, bool $writeDatastore = true): bool
171 // Logged in, thumbnails enabled, not a note, is HTTP
172 // and (never retrieved yet or no valid cache file)
173 if ($this->container
->loginManager
->isLoggedIn()
174 && $this->container
->conf
->get('thumbnails.mode', Thumbnailer
::MODE_NONE
) !== Thumbnailer
::MODE_NONE
175 && false !== $bookmark->getThumbnail()
176 && !$bookmark->isNote()
177 && (null === $bookmark->getThumbnail() || !is_file($bookmark->getThumbnail()))
178 && startsWith(strtolower($bookmark->getUrl()), 'http')
180 $bookmark->setThumbnail($this->container
->thumbnailer
->get($bookmark->getUrl()));
181 $this->container
->bookmarkService
->set($bookmark, $writeDatastore);
190 * @return string[] Default template variables without values.
192 protected function initializeTemplateVars(): array
195 'previous_page_url' => '',
196 'next_page_url' => '',
199 'result_count' => '',
204 * Process legacy routes if necessary. They used query parameters.
205 * If no legacy routes is passed, return null.
207 protected function processLegacyController(Request
$request, Response
$response): ?Response
209 // Legacy smallhash filter
210 $queryString = $this->container
->environment
['QUERY_STRING'] ?? null;
211 if (null !== $queryString && 1 === preg_match('/^([a-zA-Z0-9-_@]{6})($|&|#)/', $queryString, $match)) {
212 return $this->redirect($response, '/shaare/' . $match[1]);
215 // Legacy controllers (mostly used for redirections)
216 if (null !== $request->getQueryParam('do')) {
217 $legacyController = new LegacyController($this->container
);
220 return $legacyController->process($request, $response, $request->getQueryParam('do'));
221 } catch (UnknowLegacyRouteException
$e) {
222 // We ignore legacy 404
227 // Legacy GET admin routes
228 $legacyGetRoutes = array_intersect(
229 LegacyController
::LEGACY_GET_ROUTES
,
230 array_keys($request->getQueryParams() ?? [])
232 if (1 === count($legacyGetRoutes)) {
233 $legacyController = new LegacyController($this->container
);
235 return $legacyController->process($request, $response, $legacyGetRoutes[0]);