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