]> git.immae.eu Git - github/shaarli/Shaarli.git/blame - application/front/controller/admin/ManageShaareController.php
Pin bookmarks through Slim controller
[github/shaarli/Shaarli.git] / application / front / controller / admin / ManageShaareController.php
CommitLineData
c22fa57a
A
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin;
6
7use Shaarli\Bookmark\Bookmark;
8use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
9use Shaarli\Formatter\BookmarkMarkdownFormatter;
10use Shaarli\Thumbnailer;
11use Slim\Http\Request;
12use Slim\Http\Response;
13
14/**
15 * Class PostBookmarkController
16 *
17 * Slim controller used to handle Shaarli create or edit bookmarks.
18 */
baa69791 19class ManageShaareController extends ShaarliAdminController
c22fa57a
A
20{
21 /**
9c75f877 22 * GET /admin/add-shaare - Displays the form used to create a new bookmark from an URL
c22fa57a
A
23 */
24 public function addShaare(Request $request, Response $response): Response
25 {
26 $this->assignView(
27 'pagetitle',
28 t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli')
29 );
30
31 return $response->write($this->render('addlink'));
32 }
33
34 /**
9c75f877 35 * GET /admin/shaare - Displays the bookmark form for creation.
baa69791 36 * Note that if the URL is found in existing bookmarks, then it will be in edit mode.
c22fa57a
A
37 */
38 public function displayCreateForm(Request $request, Response $response): Response
39 {
40 $url = cleanup_url($request->getParam('post'));
41
42 $linkIsNew = false;
43 // Check if URL is not already in database (in this case, we will edit the existing link)
44 $bookmark = $this->container->bookmarkService->findByUrl($url);
45 if (null === $bookmark) {
46 $linkIsNew = true;
47 // Get shaare data if it was provided in URL (e.g.: by the bookmarklet).
48 $title = $request->getParam('title');
49 $description = $request->getParam('description');
50 $tags = $request->getParam('tags');
51 $private = filter_var($request->getParam('private'), FILTER_VALIDATE_BOOLEAN);
52
53 // If this is an HTTP(S) link, we try go get the page to extract
54 // the title (otherwise we will to straight to the edit form.)
55 if (empty($title) && strpos(get_url_scheme($url) ?: '', 'http') !== false) {
56 $retrieveDescription = $this->container->conf->get('general.retrieve_description');
57 // Short timeout to keep the application responsive
58 // The callback will fill $charset and $title with data from the downloaded page.
59 $this->container->httpAccess->getHttpResponse(
60 $url,
61 $this->container->conf->get('general.download_timeout', 30),
62 $this->container->conf->get('general.download_max_size', 4194304),
63 $this->container->httpAccess->getCurlDownloadCallback(
64 $charset,
65 $title,
66 $description,
67 $tags,
68 $retrieveDescription
69 )
70 );
71 if (! empty($title) && strtolower($charset) !== 'utf-8') {
72 $title = mb_convert_encoding($title, 'utf-8', $charset);
73 }
74 }
75
76 if (empty($url) && empty($title)) {
77 $title = $this->container->conf->get('general.default_note_title', t('Note: '));
78 }
79
80 $link = escape([
81 'title' => $title,
82 'url' => $url ?? '',
83 'description' => $description ?? '',
84 'tags' => $tags ?? '',
85 'private' => $private,
86 ]);
87 } else {
88 $formatter = $this->container->formatterFactory->getFormatter('raw');
89 $link = $formatter->format($bookmark);
90 }
91
92 return $this->displayForm($link, $linkIsNew, $request, $response);
93 }
94
95 /**
9c75f877 96 * GET /admin/shaare/{id} - Displays the bookmark form in edition mode.
c22fa57a
A
97 */
98 public function displayEditForm(Request $request, Response $response, array $args): Response
99 {
baa69791 100 $id = $args['id'] ?? '';
c22fa57a
A
101 try {
102 if (false === ctype_digit($id)) {
103 throw new BookmarkNotFoundException();
104 }
baa69791 105 $bookmark = $this->container->bookmarkService->get((int) $id); // Read database
c22fa57a 106 } catch (BookmarkNotFoundException $e) {
baa69791
A
107 $this->saveErrorMessage(sprintf(
108 t('Bookmark with identifier %s could not be found.'),
109 $id
110 ));
c22fa57a 111
9c75f877 112 return $this->redirect($response, '/');
c22fa57a
A
113 }
114
115 $formatter = $this->container->formatterFactory->getFormatter('raw');
116 $link = $formatter->format($bookmark);
117
118 return $this->displayForm($link, false, $request, $response);
119 }
120
121 /**
9c75f877 122 * POST /admin/shaare
c22fa57a
A
123 */
124 public function save(Request $request, Response $response): Response
125 {
126 $this->checkToken($request);
127
128 // lf_id should only be present if the link exists.
129 $id = $request->getParam('lf_id') ? intval(escape($request->getParam('lf_id'))) : null;
130 if (null !== $id && true === $this->container->bookmarkService->exists($id)) {
131 // Edit
132 $bookmark = $this->container->bookmarkService->get($id);
133 } else {
134 // New link
135 $bookmark = new Bookmark();
136 }
137
138 $bookmark->setTitle($request->getParam('lf_title'));
139 $bookmark->setDescription($request->getParam('lf_description'));
140 $bookmark->setUrl($request->getParam('lf_url'), $this->container->conf->get('security.allowed_protocols', []));
141 $bookmark->setPrivate(filter_var($request->getParam('lf_private'), FILTER_VALIDATE_BOOLEAN));
142 $bookmark->setTagsString($request->getParam('lf_tags'));
143
144 if ($this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
145 && false === $bookmark->isNote()
146 ) {
147 $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
148 }
149 $this->container->bookmarkService->addOrSet($bookmark, false);
150
151 // To preserve backward compatibility with 3rd parties, plugins still use arrays
152 $formatter = $this->container->formatterFactory->getFormatter('raw');
153 $data = $formatter->format($bookmark);
154 $data = $this->executeHooks('save_link', $data);
155
156 $bookmark->fromArray($data);
157 $this->container->bookmarkService->set($bookmark);
158
159 // If we are called from the bookmarklet, we must close the popup:
160 if ($request->getParam('source') === 'bookmarklet') {
161 return $response->write('<script>self.close();</script>');
162 }
163
164 if (!empty($request->getParam('returnurl'))) {
165 $this->container->environment['HTTP_REFERER'] = escape($request->getParam('returnurl'));
166 }
167
168 return $this->redirectFromReferer(
169 $request,
170 $response,
171 ['add-shaare', 'shaare'], ['addlink', 'post', 'edit_link'],
172 $bookmark->getShortUrl()
173 );
174 }
175
9c75f877 176 /**
7b8a6f28 177 * GET /admin/shaare/delete - Delete one or multiple bookmarks (depending on `id` query parameter).
9c75f877 178 */
c22fa57a
A
179 public function deleteBookmark(Request $request, Response $response): Response
180 {
181 $this->checkToken($request);
182
baa69791
A
183 $ids = escape(trim($request->getParam('id') ?? ''));
184 if (empty($ids) || strpos($ids, ' ') !== false) {
c22fa57a 185 // multiple, space-separated ids provided
baa69791 186 $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit'));
c22fa57a
A
187 } else {
188 $ids = [$ids];
189 }
190
191 // assert at least one id is given
192 if (0 === count($ids)) {
193 $this->saveErrorMessage(t('Invalid bookmark ID provided.'));
194
195 return $this->redirectFromReferer($request, $response, [], ['delete-shaare']);
196 }
197
198 $formatter = $this->container->formatterFactory->getFormatter('raw');
baa69791 199 $count = 0;
c22fa57a 200 foreach ($ids as $id) {
baa69791
A
201 try {
202 $bookmark = $this->container->bookmarkService->get((int) $id);
203 } catch (BookmarkNotFoundException $e) {
204 $this->saveErrorMessage(sprintf(
205 t('Bookmark with identifier %s could not be found.'),
206 $id
207 ));
208
209 continue;
210 }
211
c22fa57a
A
212 $data = $formatter->format($bookmark);
213 $this->container->pluginManager->executeHooks('delete_link', $data);
214 $this->container->bookmarkService->remove($bookmark, false);
baa69791 215 ++ $count;
c22fa57a
A
216 }
217
baa69791
A
218 if ($count > 0) {
219 $this->container->bookmarkService->save();
220 }
c22fa57a
A
221
222 // If we are called from the bookmarklet, we must close the popup:
223 if ($request->getParam('source') === 'bookmarklet') {
224 return $response->write('<script>self.close();</script>');
225 }
226
227 // Don't redirect to where we were previously because the datastore has changed.
9c75f877 228 return $this->redirect($response, '/');
c22fa57a
A
229 }
230
7b8a6f28
A
231 /**
232 * GET /admin/shaare/visibility
233 *
234 * Change visibility (public/private) of one or multiple bookmarks (depending on `id` query parameter).
235 */
236 public function changeVisibility(Request $request, Response $response): Response
237 {
238 $this->checkToken($request);
239
240 $ids = trim(escape($request->getParam('id') ?? ''));
241 if (empty($ids) || strpos($ids, ' ') !== false) {
242 // multiple, space-separated ids provided
243 $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit'));
244 } else {
245 // only a single id provided
246 $ids = [$ids];
247 }
248
249 // assert at least one id is given
250 if (0 === count($ids)) {
251 $this->saveErrorMessage(t('Invalid bookmark ID provided.'));
252
253 return $this->redirectFromReferer($request, $response, [], ['change_visibility']);
254 }
255
256 // assert that the visibility is valid
257 $visibility = $request->getParam('newVisibility');
258 if (null === $visibility || false === in_array($visibility, ['public', 'private'], true)) {
259 $this->saveErrorMessage(t('Invalid visibility provided.'));
260
261 return $this->redirectFromReferer($request, $response, [], ['change_visibility']);
262 } else {
263 $isPrivate = $visibility === 'private';
264 }
265
266 $formatter = $this->container->formatterFactory->getFormatter('raw');
267 $count = 0;
268
269 foreach ($ids as $id) {
270 try {
271 $bookmark = $this->container->bookmarkService->get((int) $id);
272 } catch (BookmarkNotFoundException $e) {
273 $this->saveErrorMessage(sprintf(
274 t('Bookmark with identifier %s could not be found.'),
275 $id
276 ));
277
278 continue;
279 }
280
281 $bookmark->setPrivate($isPrivate);
282
283 // To preserve backward compatibility with 3rd parties, plugins still use arrays
284 $data = $formatter->format($bookmark);
285 $this->container->pluginManager->executeHooks('save_link', $data);
286 $bookmark->fromArray($data);
287
288 $this->container->bookmarkService->set($bookmark, false);
289 ++$count;
290 }
291
292 if ($count > 0) {
293 $this->container->bookmarkService->save();
294 }
295
296 return $this->redirectFromReferer($request, $response, ['/visibility'], ['change_visibility']);
297 }
298
3447d888
A
299 /**
300 * GET /admin/shaare/{id}/pin - Pin or unpin a bookmark.
301 */
302 public function pinBookmark(Request $request, Response $response, array $args): Response
303 {
304 $this->checkToken($request);
305
306 $id = $args['id'] ?? '';
307 try {
308 if (false === ctype_digit($id)) {
309 throw new BookmarkNotFoundException();
310 }
311 $bookmark = $this->container->bookmarkService->get((int) $id); // Read database
312 } catch (BookmarkNotFoundException $e) {
313 $this->saveErrorMessage(sprintf(
314 t('Bookmark with identifier %s could not be found.'),
315 $id
316 ));
317
318 return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']);
319 }
320
321 $formatter = $this->container->formatterFactory->getFormatter('raw');
322
323 $bookmark->setSticky(!$bookmark->isSticky());
324
325 // To preserve backward compatibility with 3rd parties, plugins still use arrays
326 $data = $formatter->format($bookmark);
327 $this->container->pluginManager->executeHooks('save_link', $data);
328 $bookmark->fromArray($data);
329
330 $this->container->bookmarkService->set($bookmark);
331
332 return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']);
333 }
334
baa69791
A
335 /**
336 * Helper function used to display the shaare form whether it's a new or existing bookmark.
337 *
338 * @param array $link data used in template, either from parameters or from the data store
339 */
c22fa57a
A
340 protected function displayForm(array $link, bool $isNew, Request $request, Response $response): Response
341 {
342 $tags = $this->container->bookmarkService->bookmarksCountPerTag();
343 if ($this->container->conf->get('formatter') === 'markdown') {
344 $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1;
345 }
346
347 $data = [
348 'link' => $link,
349 'link_is_new' => $isNew,
350 'http_referer' => escape($this->container->environment['HTTP_REFERER'] ?? ''),
351 'source' => $request->getParam('source') ?? '',
352 'tags' => $tags,
353 'default_private_links' => $this->container->conf->get('privacy.default_private_links', false),
354 ];
355
356 $data = $this->executeHooks('render_editlink', $data);
357
358 foreach ($data as $key => $value) {
359 $this->assignView($key, $value);
360 }
361
362 $editLabel = false === $isNew ? t('Edit') .' ' : '';
363 $this->assignView(
364 'pagetitle',
365 $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli')
366 );
367
368 return $response->write($this->render('editlink'));
369 }
370
371 /**
372 * @param mixed[] $data Variables passed to the template engine
373 *
374 * @return mixed[] Template data after active plugins render_picwall hook execution.
375 */
376 protected function executeHooks(string $hook, array $data): array
377 {
378 $this->container->pluginManager->executeHooks(
379 $hook,
380 $data
381 );
382
383 return $data;
384 }
385}