3 declare(strict_types
=1);
5 namespace Shaarli\Front\Controller\Admin
;
7 use Shaarli\Bookmark\Bookmark
;
8 use Shaarli\Bookmark\Exception\BookmarkNotFoundException
;
9 use Shaarli\Formatter\BookmarkMarkdownFormatter
;
10 use Shaarli\Thumbnailer
;
11 use Slim\Http\Request
;
12 use Slim\Http\Response
;
15 * Class PostBookmarkController
17 * Slim controller used to handle Shaarli create or edit bookmarks.
19 class PostBookmarkController
extends ShaarliAdminController
22 * GET /admin/add-shaare - Displays the form used to create a new bookmark from an URL
24 public function addShaare(Request
$request, Response
$response): Response
28 t('Shaare a new link') .' - '. $this->container
->conf
->get('general.title', 'Shaarli')
31 return $response->write($this->render('addlink'));
35 * GET /admin/shaare - Displays the bookmark form for creation.
36 * Note that if the URL is found in existing bookmarks, then it will be in edit mode.
38 public function displayCreateForm(Request
$request, Response
$response): Response
40 $url = cleanup_url($request->getParam('post'));
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) {
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
);
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(
61 $this->container
->conf
->get('general.download_timeout', 30),
62 $this->container
->conf
->get('general.download_max_size', 4194304),
63 $this->container
->httpAccess
->getCurlDownloadCallback(
71 if (! empty($title) && strtolower($charset) !== 'utf-8') {
72 $title = mb_convert_encoding($title, 'utf-8', $charset);
76 if (empty($url) && empty($title)) {
77 $title = $this->container
->conf
->get('general.default_note_title', t('Note: '));
83 'description' => $description ?? '',
84 'tags' => $tags ?? '',
85 'private' => $private,
88 $formatter = $this->container
->formatterFactory
->getFormatter('raw');
89 $link = $formatter->format($bookmark);
92 return $this->displayForm($link, $linkIsNew, $request, $response);
96 * GET /admin/shaare/{id} - Displays the bookmark form in edition mode.
98 public function displayEditForm(Request
$request, Response
$response, array $args): Response
102 if (false === ctype_digit($id)) {
103 throw new BookmarkNotFoundException();
105 $bookmark = $this->container
->bookmarkService
->get($id); // Read database
106 } catch (BookmarkNotFoundException
$e) {
107 $this->saveErrorMessage(t('Bookmark not found'));
109 return $this->redirect($response, '/');
112 $formatter = $this->container
->formatterFactory
->getFormatter('raw');
113 $link = $formatter->format($bookmark);
115 return $this->displayForm($link, false, $request, $response);
121 public function save(Request
$request, Response
$response): Response
123 $this->checkToken($request);
125 // lf_id should only be present if the link exists.
126 $id = $request->getParam('lf_id') ? intval(escape($request->getParam('lf_id'))) : null;
127 if (null !== $id && true === $this->container
->bookmarkService
->exists($id)) {
129 $bookmark = $this->container
->bookmarkService
->get($id);
132 $bookmark = new Bookmark();
135 $bookmark->setTitle($request->getParam('lf_title'));
136 $bookmark->setDescription($request->getParam('lf_description'));
137 $bookmark->setUrl($request->getParam('lf_url'), $this->container
->conf
->get('security.allowed_protocols', []));
138 $bookmark->setPrivate(filter_var($request->getParam('lf_private'), FILTER_VALIDATE_BOOLEAN
));
139 $bookmark->setTagsString($request->getParam('lf_tags'));
141 if ($this->container
->conf
->get('thumbnails.mode', Thumbnailer
::MODE_NONE
) !== Thumbnailer
::MODE_NONE
142 && false === $bookmark->isNote()
144 $bookmark->setThumbnail($this->container
->thumbnailer
->get($bookmark->getUrl()));
146 $this->container
->bookmarkService
->addOrSet($bookmark, false);
148 // To preserve backward compatibility with 3rd parties, plugins still use arrays
149 $formatter = $this->container
->formatterFactory
->getFormatter('raw');
150 $data = $formatter->format($bookmark);
151 $data = $this->executeHooks('save_link', $data);
153 $bookmark->fromArray($data);
154 $this->container
->bookmarkService
->set($bookmark);
156 // If we are called from the bookmarklet, we must close the popup:
157 if ($request->getParam('source') === 'bookmarklet') {
158 return $response->write('<script>self.close();</script>');
161 if (!empty($request->getParam('returnurl'))) {
162 $this->container
->environment
['HTTP_REFERER'] = escape($request->getParam('returnurl'));
165 return $this->redirectFromReferer(
168 ['add-shaare', 'shaare'], ['addlink', 'post', 'edit_link'],
169 $bookmark->getShortUrl()
174 * GET /admin/shaare/delete
176 public function deleteBookmark(Request
$request, Response
$response): Response
178 $this->checkToken($request);
180 $ids = escape(trim($request->getParam('id')));
181 if (strpos($ids, ' ') !== false) {
182 // multiple, space-separated ids provided
183 $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'strlen'));
188 // assert at least one id is given
189 if (0 === count($ids)) {
190 $this->saveErrorMessage(t('Invalid bookmark ID provided.'));
192 return $this->redirectFromReferer($request, $response, [], ['delete-shaare']);
195 $formatter = $this->container
->formatterFactory
->getFormatter('raw');
196 foreach ($ids as $id) {
198 // TODO: check if it exists
199 $bookmark = $this->container
->bookmarkService
->get($id);
200 $data = $formatter->format($bookmark);
201 $this->container
->pluginManager
->executeHooks('delete_link', $data);
202 $this->container
->bookmarkService
->remove($bookmark, false);
205 $this->container
->bookmarkService
->save();
207 // If we are called from the bookmarklet, we must close the popup:
208 if ($request->getParam('source') === 'bookmarklet') {
209 return $response->write('<script>self.close();</script>');
212 // Don't redirect to where we were previously because the datastore has changed.
213 return $this->redirect($response, '/');
216 protected function displayForm(array $link, bool $isNew, Request
$request, Response
$response): Response
218 $tags = $this->container
->bookmarkService
->bookmarksCountPerTag();
219 if ($this->container
->conf
->get('formatter') === 'markdown') {
220 $tags[BookmarkMarkdownFormatter
::NO_MD_TAG
] = 1;
225 'link_is_new' => $isNew,
226 'http_referer' => escape($this->container
->environment
['HTTP_REFERER'] ?? ''),
227 'source' => $request->getParam('source') ?? '',
229 'default_private_links' => $this->container
->conf
->get('privacy.default_private_links', false),
232 $data = $this->executeHooks('render_editlink', $data);
234 foreach ($data as $key => $value) {
235 $this->assignView($key, $value);
238 $editLabel = false === $isNew ? t('Edit') .' ' : '';
241 $editLabel . t('Shaare') .' - '. $this->container
->conf
->get('general.title', 'Shaarli')
244 return $response->write($this->render('editlink'));
248 * @param mixed[] $data Variables passed to the template engine
250 * @return mixed[] Template data after active plugins render_picwall hook execution.
252 protected function executeHooks(string $hook, array $data): array
254 $this->container
->pluginManager
->executeHooks(