]> git.immae.eu Git - github/shaarli/Shaarli.git/blame - application/front/controller/visitor/BookmarkListController.php
Merge pull request #1697 from ArthurHoaro/feature/pagination
[github/shaarli/Shaarli.git] / application / front / controller / visitor / BookmarkListController.php
CommitLineData
1a8ac737
A
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Visitor;
6
7use Shaarli\Bookmark\Bookmark;
8use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
9use Shaarli\Legacy\LegacyController;
10use Shaarli\Legacy\UnknowLegacyRouteException;
11use Shaarli\Render\TemplatePage;
12use Shaarli\Thumbnailer;
13use Slim\Http\Request;
14use Slim\Http\Response;
15
16/**
17 * Class BookmarkListController
18 *
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.
21 */
22class BookmarkListController extends ShaarliVisitorController
23{
24 /**
25 * GET / - Displays the bookmark list, with optional filter parameters.
26 */
27 public function index(Request $request, Response $response): Response
28 {
29 $legacyResponse = $this->processLegacyController($request, $response);
30 if (null !== $legacyResponse) {
31 return $legacyResponse;
32 }
33
34 $formatter = $this->container->formatterFactory->getFormatter();
301c7ab1 35 $formatter->addContextData('base_path', $this->container->basePath);
9ef8555a 36 $formatter->addContextData('index_url', index_url($this->container->environment));
1a8ac737 37
72fbbcd6 38 $searchTags = normalize_spaces($request->getParam('searchtags') ?? '');
53054b2b 39 $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));
1a8ac737
A
40
41 // Filter bookmarks according search parameters.
42 $visibility = $this->container->sessionManager->getSessionParameter('visibility');
43 $search = [
44 'searchtags' => $searchTags,
45 'searchterm' => $searchTerm,
46 ];
1a8ac737
A
47
48 // Select articles according to paging.
9b8c0a45 49 $page = (int) ($request->getParam('page') ?? 1);
1a8ac737 50 $page = $page < 1 ? 1 : $page;
9b8c0a45 51 $linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20;
1a8ac737 52
9b8c0a45
A
53 $searchResult = $this->container->bookmarkService->search(
54 $search,
55 $visibility,
56 false,
57 !!$this->container->sessionManager->getSessionParameter('untaggedonly'),
58 false,
59 ['offset' => $linksPerPage * ($page - 1), 'limit' => $linksPerPage]
60 ) ?? [];
1a8ac737 61
1a8ac737 62 $save = false;
9b8c0a45
A
63 $links = [];
64 foreach ($searchResult->getBookmarks() as $key => $bookmark) {
65 $save = $this->updateThumbnail($bookmark, false) || $save;
66 $links[$key] = $formatter->format($bookmark);
1a8ac737
A
67 }
68
69 if ($save) {
70 $this->container->bookmarkService->save();
71 }
72
73 // Compute paging navigation
74 $searchtagsUrl = $searchTags === '' ? '' : '&searchtags=' . urlencode($searchTags);
75 $searchtermUrl = $searchTerm === '' ? '' : '&searchterm=' . urlencode($searchTerm);
9b8c0a45 76 $page = $searchResult->getPage();
1a8ac737 77
9b8c0a45
A
78 $previousPageUrl = !$searchResult->isLastPage() ? '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl : '';
79 $nextPageUrl = !$searchResult->isFirstPage() ? '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl : '';
1a8ac737 80
b3bd8c3e
A
81 $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
82 $searchTagsUrlEncoded = array_map('urlencode', tags_str2array($searchTags, $tagsSeparator));
83 $searchTags = !empty($searchTags) ? trim($searchTags, $tagsSeparator) . $tagsSeparator : '';
84
1a8ac737
A
85 // Fill all template fields.
86 $data = array_merge(
87 $this->initializeTemplateVars(),
88 [
9b8c0a45
A
89 'previous_page_url' => $previousPageUrl,
90 'next_page_url' => $nextPageUrl,
1a8ac737 91 'page_current' => $page,
9b8c0a45
A
92 'page_max' => $searchResult->getLastPage(),
93 'result_count' => $searchResult->getTotalCount(),
72fbbcd6
A
94 'search_term' => escape($searchTerm),
95 'search_tags' => escape($searchTags),
b3bd8c3e 96 'search_tags_url' => $searchTagsUrlEncoded,
1a8ac737 97 'visibility' => $visibility,
9b8c0a45 98 'links' => $links,
1a8ac737
A
99 ]
100 );
101
102 if (!empty($searchTerm) || !empty($searchTags)) {
103 $data['pagetitle'] = t('Search: ');
104 $data['pagetitle'] .= ! empty($searchTerm) ? $searchTerm . ' ' : '';
105 $bracketWrap = function ($tag) {
106 return '[' . $tag . ']';
107 };
108 $data['pagetitle'] .= ! empty($searchTags)
b3bd8c3e
A
109 ? implode(' ', array_map($bracketWrap, tags_str2array($searchTags, $tagsSeparator))) . ' '
110 : ''
111 ;
1a8ac737
A
112 $data['pagetitle'] .= '- ';
113 }
114
115 $data['pagetitle'] = ($data['pagetitle'] ?? '') . $this->container->conf->get('general.title', 'Shaarli');
116
9fbc4229 117 $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST);
1a8ac737
A
118 $this->assignAllView($data);
119
120 return $response->write($this->render(TemplatePage::LINKLIST));
121 }
122
123 /**
124 * GET /shaare/{hash} - Display a single shaare
125 */
126 public function permalink(Request $request, Response $response, array $args): Response
127 {
9c04921a
A
128 $privateKey = $request->getParam('key');
129
1a8ac737 130 try {
9c04921a 131 $bookmark = $this->container->bookmarkService->findByHash($args['hash'], $privateKey);
1a8ac737
A
132 } catch (BookmarkNotFoundException $e) {
133 $this->assignView('error_message', $e->getMessage());
134
135 return $response->write($this->render(TemplatePage::ERROR_404));
136 }
137
138 $this->updateThumbnail($bookmark);
139
301c7ab1
A
140 $formatter = $this->container->formatterFactory->getFormatter();
141 $formatter->addContextData('base_path', $this->container->basePath);
9ef8555a 142 $formatter->addContextData('index_url', index_url($this->container->environment));
301c7ab1 143
1a8ac737
A
144 $data = array_merge(
145 $this->initializeTemplateVars(),
146 [
53054b2b 147 'pagetitle' => $bookmark->getTitle() . ' - ' . $this->container->conf->get('general.title', 'Shaarli'),
301c7ab1 148 'links' => [$formatter->format($bookmark)],
1a8ac737
A
149 ]
150 );
151
9fbc4229 152 $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST);
1a8ac737
A
153 $this->assignAllView($data);
154
155 return $response->write($this->render(TemplatePage::LINKLIST));
156 }
157
158 /**
159 * Update the thumbnail of a single bookmark if necessary.
160 */
161 protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool
162 {
0cf76ccb
A
163 if (false === $this->container->loginManager->isLoggedIn()) {
164 return false;
165 }
166
167 // If thumbnail should be updated, we reset it to null
168 if ($bookmark->shouldUpdateThumbnail()) {
169 $bookmark->setThumbnail(null);
170
171 // Requires an update, not async retrieval, thumbnails enabled
53054b2b
A
172 if (
173 $bookmark->shouldUpdateThumbnail()
0cf76ccb
A
174 && true !== $this->container->conf->get('general.enable_async_metadata', true)
175 && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
176 ) {
177 $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
178 $this->container->bookmarkService->set($bookmark, $writeDatastore);
179
180 return true;
181 }
1a8ac737
A
182 }
183
184 return false;
185 }
186
1a8ac737
A
187 /**
188 * @return string[] Default template variables without values.
189 */
190 protected function initializeTemplateVars(): array
191 {
192 return [
193 'previous_page_url' => '',
194 'next_page_url' => '',
195 'page_max' => '',
196 'search_tags' => '',
197 'result_count' => '',
21e72da9 198 'async_metadata' => $this->container->conf->get('general.enable_async_metadata', true)
1a8ac737
A
199 ];
200 }
201
202 /**
203 * Process legacy routes if necessary. They used query parameters.
204 * If no legacy routes is passed, return null.
205 */
206 protected function processLegacyController(Request $request, Response $response): ?Response
207 {
208 // Legacy smallhash filter
209 $queryString = $this->container->environment['QUERY_STRING'] ?? null;
210 if (null !== $queryString && 1 === preg_match('/^([a-zA-Z0-9-_@]{6})($|&|#)/', $queryString, $match)) {
211 return $this->redirect($response, '/shaare/' . $match[1]);
212 }
213
214 // Legacy controllers (mostly used for redirections)
215 if (null !== $request->getQueryParam('do')) {
216 $legacyController = new LegacyController($this->container);
217
218 try {
219 return $legacyController->process($request, $response, $request->getQueryParam('do'));
220 } catch (UnknowLegacyRouteException $e) {
221 // We ignore legacy 404
222 return null;
223 }
224 }
225
226 // Legacy GET admin routes
227 $legacyGetRoutes = array_intersect(
228 LegacyController::LEGACY_GET_ROUTES,
229 array_keys($request->getQueryParams() ?? [])
230 );
231 if (1 === count($legacyGetRoutes)) {
232 $legacyController = new LegacyController($this->container);
233
234 return $legacyController->process($request, $response, $legacyGetRoutes[0]);
235 }
236
237 return null;
238 }
239}