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