diff options
25 files changed, 1125 insertions, 528 deletions
diff --git a/application/front/controller/admin/ManageShaareController.php b/application/front/controller/admin/ManageShaareController.php deleted file mode 100644 index e490f85a..00000000 --- a/application/front/controller/admin/ManageShaareController.php +++ /dev/null | |||
@@ -1,386 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Formatter\BookmarkMarkdownFormatter; | ||
10 | use Shaarli\Render\TemplatePage; | ||
11 | use Shaarli\Thumbnailer; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | /** | ||
16 | * Class PostBookmarkController | ||
17 | * | ||
18 | * Slim controller used to handle Shaarli create or edit bookmarks. | ||
19 | */ | ||
20 | class ManageShaareController extends ShaarliAdminController | ||
21 | { | ||
22 | /** | ||
23 | * GET /admin/add-shaare - Displays the form used to create a new bookmark from an URL | ||
24 | */ | ||
25 | public function addShaare(Request $request, Response $response): Response | ||
26 | { | ||
27 | $this->assignView( | ||
28 | 'pagetitle', | ||
29 | t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
30 | ); | ||
31 | |||
32 | return $response->write($this->render(TemplatePage::ADDLINK)); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * GET /admin/shaare - Displays the bookmark form for creation. | ||
37 | * Note that if the URL is found in existing bookmarks, then it will be in edit mode. | ||
38 | */ | ||
39 | public function displayCreateForm(Request $request, Response $response): Response | ||
40 | { | ||
41 | $url = cleanup_url($request->getParam('post')); | ||
42 | |||
43 | $linkIsNew = false; | ||
44 | // Check if URL is not already in database (in this case, we will edit the existing link) | ||
45 | $bookmark = $this->container->bookmarkService->findByUrl($url); | ||
46 | if (null === $bookmark) { | ||
47 | $linkIsNew = true; | ||
48 | // Get shaare data if it was provided in URL (e.g.: by the bookmarklet). | ||
49 | $title = $request->getParam('title'); | ||
50 | $description = $request->getParam('description'); | ||
51 | $tags = $request->getParam('tags'); | ||
52 | $private = filter_var($request->getParam('private'), FILTER_VALIDATE_BOOLEAN); | ||
53 | |||
54 | // If this is an HTTP(S) link, we try go get the page to extract | ||
55 | // the title (otherwise we will to straight to the edit form.) | ||
56 | if (true !== $this->container->conf->get('general.enable_async_metadata', true) | ||
57 | && empty($title) | ||
58 | && strpos(get_url_scheme($url) ?: '', 'http') !== false | ||
59 | ) { | ||
60 | $metadata = $this->container->metadataRetriever->retrieve($url); | ||
61 | } | ||
62 | |||
63 | if (empty($url)) { | ||
64 | $metadata['title'] = $this->container->conf->get('general.default_note_title', t('Note: ')); | ||
65 | } | ||
66 | |||
67 | $link = [ | ||
68 | 'title' => $title ?? $metadata['title'] ?? '', | ||
69 | 'url' => $url ?? '', | ||
70 | 'description' => $description ?? $metadata['description'] ?? '', | ||
71 | 'tags' => $tags ?? $metadata['tags'] ?? '', | ||
72 | 'private' => $private, | ||
73 | ]; | ||
74 | } else { | ||
75 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
76 | $link = $formatter->format($bookmark); | ||
77 | } | ||
78 | |||
79 | return $this->displayForm($link, $linkIsNew, $request, $response); | ||
80 | } | ||
81 | |||
82 | /** | ||
83 | * GET /admin/shaare/{id} - Displays the bookmark form in edition mode. | ||
84 | */ | ||
85 | public function displayEditForm(Request $request, Response $response, array $args): Response | ||
86 | { | ||
87 | $id = $args['id'] ?? ''; | ||
88 | try { | ||
89 | if (false === ctype_digit($id)) { | ||
90 | throw new BookmarkNotFoundException(); | ||
91 | } | ||
92 | $bookmark = $this->container->bookmarkService->get((int) $id); // Read database | ||
93 | } catch (BookmarkNotFoundException $e) { | ||
94 | $this->saveErrorMessage(sprintf( | ||
95 | t('Bookmark with identifier %s could not be found.'), | ||
96 | $id | ||
97 | )); | ||
98 | |||
99 | return $this->redirect($response, '/'); | ||
100 | } | ||
101 | |||
102 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
103 | $link = $formatter->format($bookmark); | ||
104 | |||
105 | return $this->displayForm($link, false, $request, $response); | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * POST /admin/shaare | ||
110 | */ | ||
111 | public function save(Request $request, Response $response): Response | ||
112 | { | ||
113 | $this->checkToken($request); | ||
114 | |||
115 | // lf_id should only be present if the link exists. | ||
116 | $id = $request->getParam('lf_id') !== null ? intval(escape($request->getParam('lf_id'))) : null; | ||
117 | if (null !== $id && true === $this->container->bookmarkService->exists($id)) { | ||
118 | // Edit | ||
119 | $bookmark = $this->container->bookmarkService->get($id); | ||
120 | } else { | ||
121 | // New link | ||
122 | $bookmark = new Bookmark(); | ||
123 | } | ||
124 | |||
125 | $bookmark->setTitle($request->getParam('lf_title')); | ||
126 | $bookmark->setDescription($request->getParam('lf_description')); | ||
127 | $bookmark->setUrl($request->getParam('lf_url'), $this->container->conf->get('security.allowed_protocols', [])); | ||
128 | $bookmark->setPrivate(filter_var($request->getParam('lf_private'), FILTER_VALIDATE_BOOLEAN)); | ||
129 | $bookmark->setTagsString($request->getParam('lf_tags')); | ||
130 | |||
131 | if ($this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE | ||
132 | && true !== $this->container->conf->get('general.enable_async_metadata', true) | ||
133 | && $bookmark->shouldUpdateThumbnail() | ||
134 | ) { | ||
135 | $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); | ||
136 | } | ||
137 | $this->container->bookmarkService->addOrSet($bookmark, false); | ||
138 | |||
139 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
140 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
141 | $data = $formatter->format($bookmark); | ||
142 | $this->executePageHooks('save_link', $data); | ||
143 | |||
144 | $bookmark->fromArray($data); | ||
145 | $this->container->bookmarkService->set($bookmark); | ||
146 | |||
147 | // If we are called from the bookmarklet, we must close the popup: | ||
148 | if ($request->getParam('source') === 'bookmarklet') { | ||
149 | return $response->write('<script>self.close();</script>'); | ||
150 | } | ||
151 | |||
152 | if (!empty($request->getParam('returnurl'))) { | ||
153 | $this->container->environment['HTTP_REFERER'] = escape($request->getParam('returnurl')); | ||
154 | } | ||
155 | |||
156 | return $this->redirectFromReferer( | ||
157 | $request, | ||
158 | $response, | ||
159 | ['/admin/add-shaare', '/admin/shaare'], ['addlink', 'post', 'edit_link'], | ||
160 | $bookmark->getShortUrl() | ||
161 | ); | ||
162 | } | ||
163 | |||
164 | /** | ||
165 | * GET /admin/shaare/delete - Delete one or multiple bookmarks (depending on `id` query parameter). | ||
166 | */ | ||
167 | public function deleteBookmark(Request $request, Response $response): Response | ||
168 | { | ||
169 | $this->checkToken($request); | ||
170 | |||
171 | $ids = escape(trim($request->getParam('id') ?? '')); | ||
172 | if (empty($ids) || strpos($ids, ' ') !== false) { | ||
173 | // multiple, space-separated ids provided | ||
174 | $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit')); | ||
175 | } else { | ||
176 | $ids = [$ids]; | ||
177 | } | ||
178 | |||
179 | // assert at least one id is given | ||
180 | if (0 === count($ids)) { | ||
181 | $this->saveErrorMessage(t('Invalid bookmark ID provided.')); | ||
182 | |||
183 | return $this->redirectFromReferer($request, $response, [], ['delete-shaare']); | ||
184 | } | ||
185 | |||
186 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
187 | $count = 0; | ||
188 | foreach ($ids as $id) { | ||
189 | try { | ||
190 | $bookmark = $this->container->bookmarkService->get((int) $id); | ||
191 | } catch (BookmarkNotFoundException $e) { | ||
192 | $this->saveErrorMessage(sprintf( | ||
193 | t('Bookmark with identifier %s could not be found.'), | ||
194 | $id | ||
195 | )); | ||
196 | |||
197 | continue; | ||
198 | } | ||
199 | |||
200 | $data = $formatter->format($bookmark); | ||
201 | $this->executePageHooks('delete_link', $data); | ||
202 | $this->container->bookmarkService->remove($bookmark, false); | ||
203 | ++ $count; | ||
204 | } | ||
205 | |||
206 | if ($count > 0) { | ||
207 | $this->container->bookmarkService->save(); | ||
208 | } | ||
209 | |||
210 | // If we are called from the bookmarklet, we must close the popup: | ||
211 | if ($request->getParam('source') === 'bookmarklet') { | ||
212 | return $response->write('<script>self.close();</script>'); | ||
213 | } | ||
214 | |||
215 | // Don't redirect to where we were previously because the datastore has changed. | ||
216 | return $this->redirect($response, '/'); | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * GET /admin/shaare/visibility | ||
221 | * | ||
222 | * Change visibility (public/private) of one or multiple bookmarks (depending on `id` query parameter). | ||
223 | */ | ||
224 | public function changeVisibility(Request $request, Response $response): Response | ||
225 | { | ||
226 | $this->checkToken($request); | ||
227 | |||
228 | $ids = trim(escape($request->getParam('id') ?? '')); | ||
229 | if (empty($ids) || strpos($ids, ' ') !== false) { | ||
230 | // multiple, space-separated ids provided | ||
231 | $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit')); | ||
232 | } else { | ||
233 | // only a single id provided | ||
234 | $ids = [$ids]; | ||
235 | } | ||
236 | |||
237 | // assert at least one id is given | ||
238 | if (0 === count($ids)) { | ||
239 | $this->saveErrorMessage(t('Invalid bookmark ID provided.')); | ||
240 | |||
241 | return $this->redirectFromReferer($request, $response, [], ['change_visibility']); | ||
242 | } | ||
243 | |||
244 | // assert that the visibility is valid | ||
245 | $visibility = $request->getParam('newVisibility'); | ||
246 | if (null === $visibility || false === in_array($visibility, ['public', 'private'], true)) { | ||
247 | $this->saveErrorMessage(t('Invalid visibility provided.')); | ||
248 | |||
249 | return $this->redirectFromReferer($request, $response, [], ['change_visibility']); | ||
250 | } else { | ||
251 | $isPrivate = $visibility === 'private'; | ||
252 | } | ||
253 | |||
254 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
255 | $count = 0; | ||
256 | |||
257 | foreach ($ids as $id) { | ||
258 | try { | ||
259 | $bookmark = $this->container->bookmarkService->get((int) $id); | ||
260 | } catch (BookmarkNotFoundException $e) { | ||
261 | $this->saveErrorMessage(sprintf( | ||
262 | t('Bookmark with identifier %s could not be found.'), | ||
263 | $id | ||
264 | )); | ||
265 | |||
266 | continue; | ||
267 | } | ||
268 | |||
269 | $bookmark->setPrivate($isPrivate); | ||
270 | |||
271 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
272 | $data = $formatter->format($bookmark); | ||
273 | $this->executePageHooks('save_link', $data); | ||
274 | $bookmark->fromArray($data); | ||
275 | |||
276 | $this->container->bookmarkService->set($bookmark, false); | ||
277 | ++$count; | ||
278 | } | ||
279 | |||
280 | if ($count > 0) { | ||
281 | $this->container->bookmarkService->save(); | ||
282 | } | ||
283 | |||
284 | return $this->redirectFromReferer($request, $response, ['/visibility'], ['change_visibility']); | ||
285 | } | ||
286 | |||
287 | /** | ||
288 | * GET /admin/shaare/{id}/pin - Pin or unpin a bookmark. | ||
289 | */ | ||
290 | public function pinBookmark(Request $request, Response $response, array $args): Response | ||
291 | { | ||
292 | $this->checkToken($request); | ||
293 | |||
294 | $id = $args['id'] ?? ''; | ||
295 | try { | ||
296 | if (false === ctype_digit($id)) { | ||
297 | throw new BookmarkNotFoundException(); | ||
298 | } | ||
299 | $bookmark = $this->container->bookmarkService->get((int) $id); // Read database | ||
300 | } catch (BookmarkNotFoundException $e) { | ||
301 | $this->saveErrorMessage(sprintf( | ||
302 | t('Bookmark with identifier %s could not be found.'), | ||
303 | $id | ||
304 | )); | ||
305 | |||
306 | return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']); | ||
307 | } | ||
308 | |||
309 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
310 | |||
311 | $bookmark->setSticky(!$bookmark->isSticky()); | ||
312 | |||
313 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
314 | $data = $formatter->format($bookmark); | ||
315 | $this->executePageHooks('save_link', $data); | ||
316 | $bookmark->fromArray($data); | ||
317 | |||
318 | $this->container->bookmarkService->set($bookmark); | ||
319 | |||
320 | return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']); | ||
321 | } | ||
322 | |||
323 | /** | ||
324 | * GET /admin/shaare/private/{hash} - Attach a private key to given bookmark, then redirect to the sharing URL. | ||
325 | */ | ||
326 | public function sharePrivate(Request $request, Response $response, array $args): Response | ||
327 | { | ||
328 | $this->checkToken($request); | ||
329 | |||
330 | $hash = $args['hash'] ?? ''; | ||
331 | $bookmark = $this->container->bookmarkService->findByHash($hash); | ||
332 | |||
333 | if ($bookmark->isPrivate() !== true) { | ||
334 | return $this->redirect($response, '/shaare/' . $hash); | ||
335 | } | ||
336 | |||
337 | if (empty($bookmark->getAdditionalContentEntry('private_key'))) { | ||
338 | $privateKey = bin2hex(random_bytes(16)); | ||
339 | $bookmark->addAdditionalContentEntry('private_key', $privateKey); | ||
340 | $this->container->bookmarkService->set($bookmark); | ||
341 | } | ||
342 | |||
343 | return $this->redirect( | ||
344 | $response, | ||
345 | '/shaare/' . $hash . '?key=' . $bookmark->getAdditionalContentEntry('private_key') | ||
346 | ); | ||
347 | } | ||
348 | |||
349 | /** | ||
350 | * Helper function used to display the shaare form whether it's a new or existing bookmark. | ||
351 | * | ||
352 | * @param array $link data used in template, either from parameters or from the data store | ||
353 | */ | ||
354 | protected function displayForm(array $link, bool $isNew, Request $request, Response $response): Response | ||
355 | { | ||
356 | $tags = $this->container->bookmarkService->bookmarksCountPerTag(); | ||
357 | if ($this->container->conf->get('formatter') === 'markdown') { | ||
358 | $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1; | ||
359 | } | ||
360 | |||
361 | $data = escape([ | ||
362 | 'link' => $link, | ||
363 | 'link_is_new' => $isNew, | ||
364 | 'http_referer' => $this->container->environment['HTTP_REFERER'] ?? '', | ||
365 | 'source' => $request->getParam('source') ?? '', | ||
366 | 'tags' => $tags, | ||
367 | 'default_private_links' => $this->container->conf->get('privacy.default_private_links', false), | ||
368 | 'async_metadata' => $this->container->conf->get('general.enable_async_metadata', true), | ||
369 | 'retrieve_description' => $this->container->conf->get('general.retrieve_description', false), | ||
370 | ]); | ||
371 | |||
372 | $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK); | ||
373 | |||
374 | foreach ($data as $key => $value) { | ||
375 | $this->assignView($key, $value); | ||
376 | } | ||
377 | |||
378 | $editLabel = false === $isNew ? t('Edit') .' ' : ''; | ||
379 | $this->assignView( | ||
380 | 'pagetitle', | ||
381 | $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
382 | ); | ||
383 | |||
384 | return $response->write($this->render(TemplatePage::EDIT_LINK)); | ||
385 | } | ||
386 | } | ||
diff --git a/application/front/controller/admin/ShaareAddController.php b/application/front/controller/admin/ShaareAddController.php new file mode 100644 index 00000000..8dc386b2 --- /dev/null +++ b/application/front/controller/admin/ShaareAddController.php | |||
@@ -0,0 +1,34 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Formatter\BookmarkMarkdownFormatter; | ||
8 | use Shaarli\Render\TemplatePage; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | class ShaareAddController extends ShaarliAdminController | ||
13 | { | ||
14 | /** | ||
15 | * GET /admin/add-shaare - Displays the form used to create a new bookmark from an URL | ||
16 | */ | ||
17 | public function addShaare(Request $request, Response $response): Response | ||
18 | { | ||
19 | $tags = $this->container->bookmarkService->bookmarksCountPerTag(); | ||
20 | if ($this->container->conf->get('formatter') === 'markdown') { | ||
21 | $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1; | ||
22 | } | ||
23 | |||
24 | $this->assignView( | ||
25 | 'pagetitle', | ||
26 | t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
27 | ); | ||
28 | $this->assignView('tags', $tags); | ||
29 | $this->assignView('default_private_links', $this->container->conf->get('privacy.default_private_links', false)); | ||
30 | $this->assignView('async_metadata', $this->container->conf->get('general.enable_async_metadata', true)); | ||
31 | |||
32 | return $response->write($this->render(TemplatePage::ADDLINK)); | ||
33 | } | ||
34 | } | ||
diff --git a/application/front/controller/admin/ShaareManageController.php b/application/front/controller/admin/ShaareManageController.php new file mode 100644 index 00000000..7ceb8d8a --- /dev/null +++ b/application/front/controller/admin/ShaareManageController.php | |||
@@ -0,0 +1,202 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | /** | ||
12 | * Class PostBookmarkController | ||
13 | * | ||
14 | * Slim controller used to handle Shaarli create or edit bookmarks. | ||
15 | */ | ||
16 | class ShaareManageController extends ShaarliAdminController | ||
17 | { | ||
18 | /** | ||
19 | * GET /admin/shaare/delete - Delete one or multiple bookmarks (depending on `id` query parameter). | ||
20 | */ | ||
21 | public function deleteBookmark(Request $request, Response $response): Response | ||
22 | { | ||
23 | $this->checkToken($request); | ||
24 | |||
25 | $ids = escape(trim($request->getParam('id') ?? '')); | ||
26 | if (empty($ids) || strpos($ids, ' ') !== false) { | ||
27 | // multiple, space-separated ids provided | ||
28 | $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit')); | ||
29 | } else { | ||
30 | $ids = [$ids]; | ||
31 | } | ||
32 | |||
33 | // assert at least one id is given | ||
34 | if (0 === count($ids)) { | ||
35 | $this->saveErrorMessage(t('Invalid bookmark ID provided.')); | ||
36 | |||
37 | return $this->redirectFromReferer($request, $response, [], ['delete-shaare']); | ||
38 | } | ||
39 | |||
40 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
41 | $count = 0; | ||
42 | foreach ($ids as $id) { | ||
43 | try { | ||
44 | $bookmark = $this->container->bookmarkService->get((int) $id); | ||
45 | } catch (BookmarkNotFoundException $e) { | ||
46 | $this->saveErrorMessage(sprintf( | ||
47 | t('Bookmark with identifier %s could not be found.'), | ||
48 | $id | ||
49 | )); | ||
50 | |||
51 | continue; | ||
52 | } | ||
53 | |||
54 | $data = $formatter->format($bookmark); | ||
55 | $this->executePageHooks('delete_link', $data); | ||
56 | $this->container->bookmarkService->remove($bookmark, false); | ||
57 | ++ $count; | ||
58 | } | ||
59 | |||
60 | if ($count > 0) { | ||
61 | $this->container->bookmarkService->save(); | ||
62 | } | ||
63 | |||
64 | // If we are called from the bookmarklet, we must close the popup: | ||
65 | if ($request->getParam('source') === 'bookmarklet') { | ||
66 | return $response->write('<script>self.close();</script>'); | ||
67 | } | ||
68 | |||
69 | // Don't redirect to where we were previously because the datastore has changed. | ||
70 | return $this->redirect($response, '/'); | ||
71 | } | ||
72 | |||
73 | /** | ||
74 | * GET /admin/shaare/visibility | ||
75 | * | ||
76 | * Change visibility (public/private) of one or multiple bookmarks (depending on `id` query parameter). | ||
77 | */ | ||
78 | public function changeVisibility(Request $request, Response $response): Response | ||
79 | { | ||
80 | $this->checkToken($request); | ||
81 | |||
82 | $ids = trim(escape($request->getParam('id') ?? '')); | ||
83 | if (empty($ids) || strpos($ids, ' ') !== false) { | ||
84 | // multiple, space-separated ids provided | ||
85 | $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit')); | ||
86 | } else { | ||
87 | // only a single id provided | ||
88 | $ids = [$ids]; | ||
89 | } | ||
90 | |||
91 | // assert at least one id is given | ||
92 | if (0 === count($ids)) { | ||
93 | $this->saveErrorMessage(t('Invalid bookmark ID provided.')); | ||
94 | |||
95 | return $this->redirectFromReferer($request, $response, [], ['change_visibility']); | ||
96 | } | ||
97 | |||
98 | // assert that the visibility is valid | ||
99 | $visibility = $request->getParam('newVisibility'); | ||
100 | if (null === $visibility || false === in_array($visibility, ['public', 'private'], true)) { | ||
101 | $this->saveErrorMessage(t('Invalid visibility provided.')); | ||
102 | |||
103 | return $this->redirectFromReferer($request, $response, [], ['change_visibility']); | ||
104 | } else { | ||
105 | $isPrivate = $visibility === 'private'; | ||
106 | } | ||
107 | |||
108 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
109 | $count = 0; | ||
110 | |||
111 | foreach ($ids as $id) { | ||
112 | try { | ||
113 | $bookmark = $this->container->bookmarkService->get((int) $id); | ||
114 | } catch (BookmarkNotFoundException $e) { | ||
115 | $this->saveErrorMessage(sprintf( | ||
116 | t('Bookmark with identifier %s could not be found.'), | ||
117 | $id | ||
118 | )); | ||
119 | |||
120 | continue; | ||
121 | } | ||
122 | |||
123 | $bookmark->setPrivate($isPrivate); | ||
124 | |||
125 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
126 | $data = $formatter->format($bookmark); | ||
127 | $this->executePageHooks('save_link', $data); | ||
128 | $bookmark->fromArray($data); | ||
129 | |||
130 | $this->container->bookmarkService->set($bookmark, false); | ||
131 | ++$count; | ||
132 | } | ||
133 | |||
134 | if ($count > 0) { | ||
135 | $this->container->bookmarkService->save(); | ||
136 | } | ||
137 | |||
138 | return $this->redirectFromReferer($request, $response, ['/visibility'], ['change_visibility']); | ||
139 | } | ||
140 | |||
141 | /** | ||
142 | * GET /admin/shaare/{id}/pin - Pin or unpin a bookmark. | ||
143 | */ | ||
144 | public function pinBookmark(Request $request, Response $response, array $args): Response | ||
145 | { | ||
146 | $this->checkToken($request); | ||
147 | |||
148 | $id = $args['id'] ?? ''; | ||
149 | try { | ||
150 | if (false === ctype_digit($id)) { | ||
151 | throw new BookmarkNotFoundException(); | ||
152 | } | ||
153 | $bookmark = $this->container->bookmarkService->get((int) $id); // Read database | ||
154 | } catch (BookmarkNotFoundException $e) { | ||
155 | $this->saveErrorMessage(sprintf( | ||
156 | t('Bookmark with identifier %s could not be found.'), | ||
157 | $id | ||
158 | )); | ||
159 | |||
160 | return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']); | ||
161 | } | ||
162 | |||
163 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
164 | |||
165 | $bookmark->setSticky(!$bookmark->isSticky()); | ||
166 | |||
167 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
168 | $data = $formatter->format($bookmark); | ||
169 | $this->executePageHooks('save_link', $data); | ||
170 | $bookmark->fromArray($data); | ||
171 | |||
172 | $this->container->bookmarkService->set($bookmark); | ||
173 | |||
174 | return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']); | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * GET /admin/shaare/private/{hash} - Attach a private key to given bookmark, then redirect to the sharing URL. | ||
179 | */ | ||
180 | public function sharePrivate(Request $request, Response $response, array $args): Response | ||
181 | { | ||
182 | $this->checkToken($request); | ||
183 | |||
184 | $hash = $args['hash'] ?? ''; | ||
185 | $bookmark = $this->container->bookmarkService->findByHash($hash); | ||
186 | |||
187 | if ($bookmark->isPrivate() !== true) { | ||
188 | return $this->redirect($response, '/shaare/' . $hash); | ||
189 | } | ||
190 | |||
191 | if (empty($bookmark->getAdditionalContentEntry('private_key'))) { | ||
192 | $privateKey = bin2hex(random_bytes(16)); | ||
193 | $bookmark->addAdditionalContentEntry('private_key', $privateKey); | ||
194 | $this->container->bookmarkService->set($bookmark); | ||
195 | } | ||
196 | |||
197 | return $this->redirect( | ||
198 | $response, | ||
199 | '/shaare/' . $hash . '?key=' . $bookmark->getAdditionalContentEntry('private_key') | ||
200 | ); | ||
201 | } | ||
202 | } | ||
diff --git a/application/front/controller/admin/ShaarePublishController.php b/application/front/controller/admin/ShaarePublishController.php new file mode 100644 index 00000000..ddcffdc7 --- /dev/null +++ b/application/front/controller/admin/ShaarePublishController.php | |||
@@ -0,0 +1,263 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Formatter\BookmarkFormatter; | ||
10 | use Shaarli\Formatter\BookmarkMarkdownFormatter; | ||
11 | use Shaarli\Render\TemplatePage; | ||
12 | use Shaarli\Thumbnailer; | ||
13 | use Slim\Http\Request; | ||
14 | use Slim\Http\Response; | ||
15 | |||
16 | class ShaarePublishController extends ShaarliAdminController | ||
17 | { | ||
18 | /** | ||
19 | * @var BookmarkFormatter[] Statically cached instances of formatters | ||
20 | */ | ||
21 | protected $formatters = []; | ||
22 | |||
23 | /** | ||
24 | * @var array Statically cached bookmark's tags counts | ||
25 | */ | ||
26 | protected $tags; | ||
27 | |||
28 | /** | ||
29 | * GET /admin/shaare - Displays the bookmark form for creation. | ||
30 | * Note that if the URL is found in existing bookmarks, then it will be in edit mode. | ||
31 | */ | ||
32 | public function displayCreateForm(Request $request, Response $response): Response | ||
33 | { | ||
34 | $url = cleanup_url($request->getParam('post')); | ||
35 | $link = $this->buildLinkDataFromUrl($request, $url); | ||
36 | |||
37 | return $this->displayForm($link, $link['linkIsNew'], $request, $response); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * POST /admin/shaare-batch - Displays multiple creation/edit forms from bulk add in add-link page. | ||
42 | */ | ||
43 | public function displayCreateBatchForms(Request $request, Response $response): Response | ||
44 | { | ||
45 | $urls = array_map('cleanup_url', explode(PHP_EOL, $request->getParam('urls'))); | ||
46 | |||
47 | $links = []; | ||
48 | foreach ($urls as $url) { | ||
49 | if (empty($url)) { | ||
50 | continue; | ||
51 | } | ||
52 | $link = $this->buildLinkDataFromUrl($request, $url); | ||
53 | $data = $this->buildFormData($link, $link['linkIsNew'], $request); | ||
54 | $data['token'] = $this->container->sessionManager->generateToken(); | ||
55 | $data['source'] = 'batch'; | ||
56 | |||
57 | $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK); | ||
58 | |||
59 | $links[] = $data; | ||
60 | } | ||
61 | |||
62 | $this->assignView('links', $links); | ||
63 | $this->assignView('batch_mode', true); | ||
64 | $this->assignView('async_metadata', $this->container->conf->get('general.enable_async_metadata', true)); | ||
65 | |||
66 | return $response->write($this->render(TemplatePage::EDIT_LINK_BATCH)); | ||
67 | } | ||
68 | |||
69 | /** | ||
70 | * GET /admin/shaare/{id} - Displays the bookmark form in edition mode. | ||
71 | */ | ||
72 | public function displayEditForm(Request $request, Response $response, array $args): Response | ||
73 | { | ||
74 | $id = $args['id'] ?? ''; | ||
75 | try { | ||
76 | if (false === ctype_digit($id)) { | ||
77 | throw new BookmarkNotFoundException(); | ||
78 | } | ||
79 | $bookmark = $this->container->bookmarkService->get((int) $id); // Read database | ||
80 | } catch (BookmarkNotFoundException $e) { | ||
81 | $this->saveErrorMessage(sprintf( | ||
82 | t('Bookmark with identifier %s could not be found.'), | ||
83 | $id | ||
84 | )); | ||
85 | |||
86 | return $this->redirect($response, '/'); | ||
87 | } | ||
88 | |||
89 | $formatter = $this->getFormatter('raw'); | ||
90 | $link = $formatter->format($bookmark); | ||
91 | |||
92 | return $this->displayForm($link, false, $request, $response); | ||
93 | } | ||
94 | |||
95 | /** | ||
96 | * POST /admin/shaare | ||
97 | */ | ||
98 | public function save(Request $request, Response $response): Response | ||
99 | { | ||
100 | $this->checkToken($request); | ||
101 | |||
102 | // lf_id should only be present if the link exists. | ||
103 | $id = $request->getParam('lf_id') !== null ? intval(escape($request->getParam('lf_id'))) : null; | ||
104 | if (null !== $id && true === $this->container->bookmarkService->exists($id)) { | ||
105 | // Edit | ||
106 | $bookmark = $this->container->bookmarkService->get($id); | ||
107 | } else { | ||
108 | // New link | ||
109 | $bookmark = new Bookmark(); | ||
110 | } | ||
111 | |||
112 | $bookmark->setTitle($request->getParam('lf_title')); | ||
113 | $bookmark->setDescription($request->getParam('lf_description')); | ||
114 | $bookmark->setUrl($request->getParam('lf_url'), $this->container->conf->get('security.allowed_protocols', [])); | ||
115 | $bookmark->setPrivate(filter_var($request->getParam('lf_private'), FILTER_VALIDATE_BOOLEAN)); | ||
116 | $bookmark->setTagsString($request->getParam('lf_tags')); | ||
117 | |||
118 | if ($this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE | ||
119 | && true !== $this->container->conf->get('general.enable_async_metadata', true) | ||
120 | && $bookmark->shouldUpdateThumbnail() | ||
121 | ) { | ||
122 | $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); | ||
123 | } | ||
124 | $this->container->bookmarkService->addOrSet($bookmark, false); | ||
125 | |||
126 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
127 | $formatter = $this->getFormatter('raw'); | ||
128 | $data = $formatter->format($bookmark); | ||
129 | $this->executePageHooks('save_link', $data); | ||
130 | |||
131 | $bookmark->fromArray($data); | ||
132 | $this->container->bookmarkService->set($bookmark); | ||
133 | |||
134 | // If we are called from the bookmarklet, we must close the popup: | ||
135 | if ($request->getParam('source') === 'bookmarklet') { | ||
136 | return $response->write('<script>self.close();</script>'); | ||
137 | } elseif ($request->getParam('source') === 'batch') { | ||
138 | return $response; | ||
139 | } | ||
140 | |||
141 | if (!empty($request->getParam('returnurl'))) { | ||
142 | $this->container->environment['HTTP_REFERER'] = escape($request->getParam('returnurl')); | ||
143 | } | ||
144 | |||
145 | return $this->redirectFromReferer( | ||
146 | $request, | ||
147 | $response, | ||
148 | ['/admin/add-shaare', '/admin/shaare'], ['addlink', 'post', 'edit_link'], | ||
149 | $bookmark->getShortUrl() | ||
150 | ); | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * Helper function used to display the shaare form whether it's a new or existing bookmark. | ||
155 | * | ||
156 | * @param array $link data used in template, either from parameters or from the data store | ||
157 | */ | ||
158 | protected function displayForm(array $link, bool $isNew, Request $request, Response $response): Response | ||
159 | { | ||
160 | $data = $this->buildFormData($link, $isNew, $request); | ||
161 | |||
162 | $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK); | ||
163 | |||
164 | foreach ($data as $key => $value) { | ||
165 | $this->assignView($key, $value); | ||
166 | } | ||
167 | |||
168 | $editLabel = false === $isNew ? t('Edit') .' ' : ''; | ||
169 | $this->assignView( | ||
170 | 'pagetitle', | ||
171 | $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
172 | ); | ||
173 | |||
174 | return $response->write($this->render(TemplatePage::EDIT_LINK)); | ||
175 | } | ||
176 | |||
177 | protected function buildLinkDataFromUrl(Request $request, string $url): array | ||
178 | { | ||
179 | // Check if URL is not already in database (in this case, we will edit the existing link) | ||
180 | $bookmark = $this->container->bookmarkService->findByUrl($url); | ||
181 | if (null === $bookmark) { | ||
182 | // Get shaare data if it was provided in URL (e.g.: by the bookmarklet). | ||
183 | $title = $request->getParam('title'); | ||
184 | $description = $request->getParam('description'); | ||
185 | $tags = $request->getParam('tags'); | ||
186 | if ($request->getParam('private') !== null) { | ||
187 | $private = filter_var($request->getParam('private'), FILTER_VALIDATE_BOOLEAN); | ||
188 | } else { | ||
189 | $private = $this->container->conf->get('privacy.default_private_links', false); | ||
190 | } | ||
191 | |||
192 | // If this is an HTTP(S) link, we try go get the page to extract | ||
193 | // the title (otherwise we will to straight to the edit form.) | ||
194 | if (true !== $this->container->conf->get('general.enable_async_metadata', true) | ||
195 | && empty($title) | ||
196 | && strpos(get_url_scheme($url) ?: '', 'http') !== false | ||
197 | ) { | ||
198 | $metadata = $this->container->metadataRetriever->retrieve($url); | ||
199 | } | ||
200 | |||
201 | if (empty($url)) { | ||
202 | $metadata['title'] = $this->container->conf->get('general.default_note_title', t('Note: ')); | ||
203 | } | ||
204 | |||
205 | return [ | ||
206 | 'title' => $title ?? $metadata['title'] ?? '', | ||
207 | 'url' => $url ?? '', | ||
208 | 'description' => $description ?? $metadata['description'] ?? '', | ||
209 | 'tags' => $tags ?? $metadata['tags'] ?? '', | ||
210 | 'private' => $private, | ||
211 | 'linkIsNew' => true, | ||
212 | ]; | ||
213 | } | ||
214 | |||
215 | $formatter = $this->getFormatter('raw'); | ||
216 | $link = $formatter->format($bookmark); | ||
217 | $link['linkIsNew'] = false; | ||
218 | |||
219 | return $link; | ||
220 | } | ||
221 | |||
222 | protected function buildFormData(array $link, bool $isNew, Request $request): array | ||
223 | { | ||
224 | return escape([ | ||
225 | 'link' => $link, | ||
226 | 'link_is_new' => $isNew, | ||
227 | 'http_referer' => $this->container->environment['HTTP_REFERER'] ?? '', | ||
228 | 'source' => $request->getParam('source') ?? '', | ||
229 | 'tags' => $this->getTags(), | ||
230 | 'default_private_links' => $this->container->conf->get('privacy.default_private_links', false), | ||
231 | 'async_metadata' => $this->container->conf->get('general.enable_async_metadata', true), | ||
232 | 'retrieve_description' => $this->container->conf->get('general.retrieve_description', false), | ||
233 | ]); | ||
234 | } | ||
235 | |||
236 | /** | ||
237 | * Memoize formatterFactory->getFormatter() calls. | ||
238 | */ | ||
239 | protected function getFormatter(string $type): BookmarkFormatter | ||
240 | { | ||
241 | if (!array_key_exists($type, $this->formatters) || $this->formatters[$type] === null) { | ||
242 | $this->formatters[$type] = $this->container->formatterFactory->getFormatter($type); | ||
243 | } | ||
244 | |||
245 | return $this->formatters[$type]; | ||
246 | } | ||
247 | |||
248 | /** | ||
249 | * Memoize bookmarkService->bookmarksCountPerTag() calls. | ||
250 | */ | ||
251 | protected function getTags(): array | ||
252 | { | ||
253 | if ($this->tags === null) { | ||
254 | $this->tags = $this->container->bookmarkService->bookmarksCountPerTag(); | ||
255 | |||
256 | if ($this->container->conf->get('formatter') === 'markdown') { | ||
257 | $this->tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1; | ||
258 | } | ||
259 | } | ||
260 | |||
261 | return $this->tags; | ||
262 | } | ||
263 | } | ||
diff --git a/application/render/TemplatePage.php b/application/render/TemplatePage.php index 8af8228a..03b424f3 100644 --- a/application/render/TemplatePage.php +++ b/application/render/TemplatePage.php | |||
@@ -14,6 +14,7 @@ interface TemplatePage | |||
14 | public const DAILY = 'daily'; | 14 | public const DAILY = 'daily'; |
15 | public const DAILY_RSS = 'dailyrss'; | 15 | public const DAILY_RSS = 'dailyrss'; |
16 | public const EDIT_LINK = 'editlink'; | 16 | public const EDIT_LINK = 'editlink'; |
17 | public const EDIT_LINK_BATCH = 'editlink.batch'; | ||
17 | public const ERROR = 'error'; | 18 | public const ERROR = 'error'; |
18 | public const EXPORT = 'export'; | 19 | public const EXPORT = 'export'; |
19 | public const NETSCAPE_EXPORT_BOOKMARKS = 'export.bookmarks'; | 20 | public const NETSCAPE_EXPORT_BOOKMARKS = 'export.bookmarks'; |
diff --git a/assets/common/js/metadata.js b/assets/common/js/metadata.js index 2b013364..d5a28a35 100644 --- a/assets/common/js/metadata.js +++ b/assets/common/js/metadata.js | |||
@@ -56,37 +56,41 @@ function updateThumb(basePath, divElement, id) { | |||
56 | 56 | ||
57 | (() => { | 57 | (() => { |
58 | const basePath = document.querySelector('input[name="js_base_path"]').value; | 58 | const basePath = document.querySelector('input[name="js_base_path"]').value; |
59 | const loaders = document.querySelectorAll('.loading-input'); | ||
60 | 59 | ||
61 | /* | 60 | /* |
62 | * METADATA FOR EDIT BOOKMARK PAGE | 61 | * METADATA FOR EDIT BOOKMARK PAGE |
63 | */ | 62 | */ |
64 | const inputTitle = document.querySelector('input[name="lf_title"]'); | 63 | const inputTitles = document.querySelectorAll('input[name="lf_title"]'); |
65 | if (inputTitle != null) { | 64 | if (inputTitles != null) { |
66 | if (inputTitle.value.length > 0) { | 65 | [...inputTitles].forEach((inputTitle) => { |
67 | clearLoaders(loaders); | 66 | const form = inputTitle.closest('form[name="linkform"]'); |
68 | return; | 67 | const loaders = form.querySelectorAll('.loading-input'); |
69 | } | 68 | |
69 | if (inputTitle.value.length > 0) { | ||
70 | clearLoaders(loaders); | ||
71 | return; | ||
72 | } | ||
70 | 73 | ||
71 | const url = document.querySelector('input[name="lf_url"]').value; | 74 | const url = form.querySelector('input[name="lf_url"]').value; |
72 | 75 | ||
73 | const xhr = new XMLHttpRequest(); | 76 | const xhr = new XMLHttpRequest(); |
74 | xhr.open('GET', `${basePath}/admin/metadata?url=${encodeURI(url)}`, true); | 77 | xhr.open('GET', `${basePath}/admin/metadata?url=${encodeURI(url)}`, true); |
75 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | 78 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
76 | xhr.onload = () => { | 79 | xhr.onload = () => { |
77 | const result = JSON.parse(xhr.response); | 80 | const result = JSON.parse(xhr.response); |
78 | Object.keys(result).forEach((key) => { | 81 | Object.keys(result).forEach((key) => { |
79 | if (result[key] !== null && result[key].length) { | 82 | if (result[key] !== null && result[key].length) { |
80 | const element = document.querySelector(`input[name="lf_${key}"], textarea[name="lf_${key}"]`); | 83 | const element = form.querySelector(`input[name="lf_${key}"], textarea[name="lf_${key}"]`); |
81 | if (element != null && element.value.length === 0) { | 84 | if (element != null && element.value.length === 0) { |
82 | element.value = he.decode(result[key]); | 85 | element.value = he.decode(result[key]); |
86 | } | ||
83 | } | 87 | } |
84 | } | 88 | }); |
85 | }); | 89 | clearLoaders(loaders); |
86 | clearLoaders(loaders); | 90 | }; |
87 | }; | ||
88 | 91 | ||
89 | xhr.send(); | 92 | xhr.send(); |
93 | }); | ||
90 | } | 94 | } |
91 | 95 | ||
92 | /* | 96 | /* |
diff --git a/assets/common/js/shaare-batch.js b/assets/common/js/shaare-batch.js new file mode 100644 index 00000000..557325ee --- /dev/null +++ b/assets/common/js/shaare-batch.js | |||
@@ -0,0 +1,121 @@ | |||
1 | const sendBookmarkForm = (basePath, formElement) => { | ||
2 | const inputs = formElement | ||
3 | .querySelectorAll('input[type="text"], textarea, input[type="checkbox"], input[type="hidden"]'); | ||
4 | |||
5 | const formData = new FormData(); | ||
6 | [...inputs].forEach((input) => { | ||
7 | formData.append(input.getAttribute('name'), input.value); | ||
8 | }); | ||
9 | |||
10 | return new Promise((resolve, reject) => { | ||
11 | const xhr = new XMLHttpRequest(); | ||
12 | xhr.open('POST', `${basePath}/admin/shaare`); | ||
13 | xhr.onload = () => { | ||
14 | if (xhr.status !== 200) { | ||
15 | alert(`An error occurred. Return code: ${xhr.status}`); | ||
16 | reject(); | ||
17 | } else { | ||
18 | formElement.closest('.edit-link-container').remove(); | ||
19 | resolve(); | ||
20 | } | ||
21 | }; | ||
22 | xhr.send(formData); | ||
23 | }); | ||
24 | }; | ||
25 | |||
26 | const sendBookmarkDelete = (buttonElement, formElement) => ( | ||
27 | new Promise((resolve, reject) => { | ||
28 | const xhr = new XMLHttpRequest(); | ||
29 | xhr.open('GET', buttonElement.href); | ||
30 | xhr.onload = () => { | ||
31 | if (xhr.status !== 200) { | ||
32 | alert(`An error occurred. Return code: ${xhr.status}`); | ||
33 | reject(); | ||
34 | } else { | ||
35 | formElement.closest('.edit-link-container').remove(); | ||
36 | resolve(); | ||
37 | } | ||
38 | }; | ||
39 | xhr.send(); | ||
40 | }) | ||
41 | ); | ||
42 | |||
43 | const redirectIfEmptyBatch = (basePath, formElements, path) => { | ||
44 | if (formElements == null || formElements.length === 0) { | ||
45 | window.location.href = `${basePath}${path}`; | ||
46 | } | ||
47 | }; | ||
48 | |||
49 | (() => { | ||
50 | const basePath = document.querySelector('input[name="js_base_path"]').value; | ||
51 | const getForms = () => document.querySelectorAll('form[name="linkform"]'); | ||
52 | |||
53 | const cancelButtons = document.querySelectorAll('[name="cancel-batch-link"]'); | ||
54 | if (cancelButtons != null) { | ||
55 | [...cancelButtons].forEach((cancelButton) => { | ||
56 | cancelButton.addEventListener('click', (e) => { | ||
57 | e.preventDefault(); | ||
58 | e.target.closest('form[name="linkform"]').remove(); | ||
59 | redirectIfEmptyBatch(basePath, getForms(), '/admin/add-shaare'); | ||
60 | }); | ||
61 | }); | ||
62 | } | ||
63 | |||
64 | const saveButtons = document.querySelectorAll('[name="save_edit"]'); | ||
65 | if (saveButtons != null) { | ||
66 | [...saveButtons].forEach((saveButton) => { | ||
67 | saveButton.addEventListener('click', (e) => { | ||
68 | e.preventDefault(); | ||
69 | |||
70 | const formElement = e.target.closest('form[name="linkform"]'); | ||
71 | sendBookmarkForm(basePath, formElement) | ||
72 | .then(() => redirectIfEmptyBatch(basePath, getForms(), '/')); | ||
73 | }); | ||
74 | }); | ||
75 | } | ||
76 | |||
77 | const saveAllButtons = document.querySelectorAll('[name="save_edit_batch"]'); | ||
78 | if (saveAllButtons != null) { | ||
79 | [...saveAllButtons].forEach((saveAllButton) => { | ||
80 | saveAllButton.addEventListener('click', (e) => { | ||
81 | e.preventDefault(); | ||
82 | |||
83 | const forms = [...getForms()]; | ||
84 | const nbForm = forms.length; | ||
85 | let current = 0; | ||
86 | const progressBar = document.querySelector('.progressbar > div'); | ||
87 | const progressBarCurrent = document.querySelector('.progressbar-current'); | ||
88 | |||
89 | document.querySelector('.dark-layer').style.display = 'block'; | ||
90 | document.querySelector('.progressbar-max').innerHTML = nbForm; | ||
91 | progressBarCurrent.innerHTML = current; | ||
92 | |||
93 | const promises = []; | ||
94 | forms.forEach((formElement) => { | ||
95 | promises.push(sendBookmarkForm(basePath, formElement).then(() => { | ||
96 | current += 1; | ||
97 | progressBar.style.width = `${(current * 100) / nbForm}%`; | ||
98 | progressBarCurrent.innerHTML = current; | ||
99 | })); | ||
100 | }); | ||
101 | |||
102 | Promise.all(promises).then(() => { | ||
103 | window.location.href = basePath || '/'; | ||
104 | }); | ||
105 | }); | ||
106 | }); | ||
107 | } | ||
108 | |||
109 | const deleteButtons = document.querySelectorAll('[name="delete_link"]'); | ||
110 | if (deleteButtons != null) { | ||
111 | [...deleteButtons].forEach((deleteButton) => { | ||
112 | deleteButton.addEventListener('click', (e) => { | ||
113 | e.preventDefault(); | ||
114 | |||
115 | const formElement = e.target.closest('form[name="linkform"]'); | ||
116 | sendBookmarkDelete(e.target, formElement) | ||
117 | .then(() => redirectIfEmptyBatch(basePath, getForms(), '/')); | ||
118 | }); | ||
119 | }); | ||
120 | } | ||
121 | })(); | ||
diff --git a/assets/default/js/base.js b/assets/default/js/base.js index 7f6b9637..4163577d 100644 --- a/assets/default/js/base.js +++ b/assets/default/js/base.js | |||
@@ -634,4 +634,33 @@ function init(description) { | |||
634 | }); | 634 | }); |
635 | }); | 635 | }); |
636 | } | 636 | } |
637 | |||
638 | const bulkCreationButton = document.querySelector('.addlink-batch-show-more-block'); | ||
639 | if (bulkCreationButton != null) { | ||
640 | const toggleBulkCreationVisibility = (showMoreBlockElement, formElement) => { | ||
641 | if (bulkCreationButton.classList.contains('pure-u-0')) { | ||
642 | showMoreBlockElement.classList.remove('pure-u-0'); | ||
643 | formElement.classList.add('pure-u-0'); | ||
644 | } else { | ||
645 | showMoreBlockElement.classList.add('pure-u-0'); | ||
646 | formElement.classList.remove('pure-u-0'); | ||
647 | } | ||
648 | }; | ||
649 | |||
650 | const bulkCreationForm = document.querySelector('.addlink-batch-form-block'); | ||
651 | |||
652 | toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm); | ||
653 | bulkCreationButton.querySelector('a').addEventListener('click', (e) => { | ||
654 | e.preventDefault(); | ||
655 | toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm); | ||
656 | }); | ||
657 | |||
658 | // Force to send falsy value if the checkbox is not checked. | ||
659 | const privateButton = bulkCreationForm.querySelector('input[type="checkbox"][name="private"]'); | ||
660 | const privateHiddenButton = bulkCreationForm.querySelector('input[type="hidden"][name="private"]'); | ||
661 | privateButton.addEventListener('click', () => { | ||
662 | privateHiddenButton.disabled = !privateHiddenButton.disabled; | ||
663 | }); | ||
664 | privateHiddenButton.disabled = privateButton.checked; | ||
665 | } | ||
637 | })(); | 666 | })(); |
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss index 7dc61903..a7f091e9 100644 --- a/assets/default/scss/shaarli.scss +++ b/assets/default/scss/shaarli.scss | |||
@@ -1023,6 +1023,10 @@ body, | |||
1023 | &.button-red { | 1023 | &.button-red { |
1024 | background: $red; | 1024 | background: $red; |
1025 | } | 1025 | } |
1026 | |||
1027 | &.button-grey { | ||
1028 | background: $light-grey; | ||
1029 | } | ||
1026 | } | 1030 | } |
1027 | 1031 | ||
1028 | .submit-buttons { | 1032 | .submit-buttons { |
@@ -1083,6 +1087,11 @@ body, | |||
1083 | position: absolute; | 1087 | position: absolute; |
1084 | right: 5%; | 1088 | right: 5%; |
1085 | } | 1089 | } |
1090 | |||
1091 | &.button-grey { | ||
1092 | position: absolute; | ||
1093 | left: 5%; | ||
1094 | } | ||
1086 | } | 1095 | } |
1087 | } | 1096 | } |
1088 | } | 1097 | } |
@@ -1750,6 +1759,69 @@ form { | |||
1750 | } | 1759 | } |
1751 | } | 1760 | } |
1752 | 1761 | ||
1762 | // Batch creation | ||
1763 | input[name='save_edit_batch'] { | ||
1764 | @extend %page-form-button; | ||
1765 | } | ||
1766 | |||
1767 | .addlink-batch-show-more { | ||
1768 | display: flex; | ||
1769 | align-items: center; | ||
1770 | margin: 20px 0 8px; | ||
1771 | |||
1772 | a { | ||
1773 | color: var(--main-color); | ||
1774 | text-decoration: none; | ||
1775 | } | ||
1776 | |||
1777 | &::before, | ||
1778 | &::after { | ||
1779 | content: ""; | ||
1780 | flex-grow: 1; | ||
1781 | background: rgba(0, 0, 0, 0.35); | ||
1782 | height: 1px; | ||
1783 | font-size: 0; | ||
1784 | line-height: 0; | ||
1785 | } | ||
1786 | |||
1787 | &::before { | ||
1788 | margin: 0 16px 0 0; | ||
1789 | } | ||
1790 | |||
1791 | &::after { | ||
1792 | margin: 0 0 0 16px; | ||
1793 | } | ||
1794 | } | ||
1795 | |||
1796 | .dark-layer { | ||
1797 | display: none; | ||
1798 | position: fixed; | ||
1799 | height: 100%; | ||
1800 | width: 100%; | ||
1801 | z-index: 998; | ||
1802 | background-color: rgba(0, 0, 0, .75); | ||
1803 | color: #fff; | ||
1804 | |||
1805 | .screen-center { | ||
1806 | display: flex; | ||
1807 | flex-direction: column; | ||
1808 | justify-content: center; | ||
1809 | align-items: center; | ||
1810 | text-align: center; | ||
1811 | min-height: 100vh; | ||
1812 | } | ||
1813 | |||
1814 | .progressbar { | ||
1815 | width: 33%; | ||
1816 | } | ||
1817 | } | ||
1818 | |||
1819 | .addlink-batch-form-block { | ||
1820 | .pure-alert { | ||
1821 | margin: 25px 0 0 0; | ||
1822 | } | ||
1823 | } | ||
1824 | |||
1753 | // Print rules | 1825 | // Print rules |
1754 | @media print { | 1826 | @media print { |
1755 | .shaarli-menu { | 1827 | .shaarli-menu { |
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po index 6d4ff0bd..60ea7a97 100644 --- a/inc/languages/fr/LC_MESSAGES/shaarli.po +++ b/inc/languages/fr/LC_MESSAGES/shaarli.po | |||
@@ -347,43 +347,16 @@ msgstr "" | |||
347 | "le serveur web peut accepter (%s). Merci de l'envoyer en parties plus " | 347 | "le serveur web peut accepter (%s). Merci de l'envoyer en parties plus " |
348 | "légères." | 348 | "légères." |
349 | 349 | ||
350 | #: application/front/controller/admin/ManageShaareController.php:29 | ||
351 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
352 | msgid "Shaare a new link" | ||
353 | msgstr "Partager un nouveau lien" | ||
354 | |||
355 | #: application/front/controller/admin/ManageShaareController.php:64 | 350 | #: application/front/controller/admin/ManageShaareController.php:64 |
356 | msgid "Note: " | ||
357 | msgstr "Note : " | ||
358 | |||
359 | #: application/front/controller/admin/ManageShaareController.php:95 | 351 | #: application/front/controller/admin/ManageShaareController.php:95 |
360 | #: application/front/controller/admin/ManageShaareController.php:193 | 352 | #: application/front/controller/admin/ManageShaareController.php:193 |
361 | #: application/front/controller/admin/ManageShaareController.php:262 | 353 | #: application/front/controller/admin/ManageShaareController.php:262 |
362 | #: application/front/controller/admin/ManageShaareController.php:302 | 354 | #: application/front/controller/admin/ManageShaareController.php:302 |
363 | #, php-format | ||
364 | msgid "Bookmark with identifier %s could not be found." | ||
365 | msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé." | ||
366 | |||
367 | #: application/front/controller/admin/ManageShaareController.php:181 | 355 | #: application/front/controller/admin/ManageShaareController.php:181 |
368 | #: application/front/controller/admin/ManageShaareController.php:239 | 356 | #: application/front/controller/admin/ManageShaareController.php:239 |
369 | msgid "Invalid bookmark ID provided." | ||
370 | msgstr "ID du lien non valide." | ||
371 | |||
372 | #: application/front/controller/admin/ManageShaareController.php:247 | 357 | #: application/front/controller/admin/ManageShaareController.php:247 |
373 | msgid "Invalid visibility provided." | ||
374 | msgstr "Visibilité du lien non valide." | ||
375 | |||
376 | #: application/front/controller/admin/ManageShaareController.php:378 | 358 | #: application/front/controller/admin/ManageShaareController.php:378 |
377 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 | ||
378 | msgid "Edit" | ||
379 | msgstr "Modifier" | ||
380 | |||
381 | #: application/front/controller/admin/ManageShaareController.php:381 | 359 | #: application/front/controller/admin/ManageShaareController.php:381 |
382 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
383 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 | ||
384 | msgid "Shaare" | ||
385 | msgstr "Shaare" | ||
386 | |||
387 | #: application/front/controller/admin/ManageTagController.php:29 | 360 | #: application/front/controller/admin/ManageTagController.php:29 |
388 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | 361 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 |
389 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | 362 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 |
@@ -456,6 +429,29 @@ msgstr "Le cache des miniatures a été vidé." | |||
456 | msgid "Shaarli's cache folder has been cleared!" | 429 | msgid "Shaarli's cache folder has been cleared!" |
457 | msgstr "Le dossier de cache de Shaarli a été vidé !" | 430 | msgstr "Le dossier de cache de Shaarli a été vidé !" |
458 | 431 | ||
432 | #, php-format | ||
433 | msgid "Bookmark with identifier %s could not be found." | ||
434 | msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé." | ||
435 | |||
436 | #: application/front/controller/admin/ShaareManageController.php:101 | ||
437 | msgid "Invalid visibility provided." | ||
438 | msgstr "Visibilité du lien non valide." | ||
439 | |||
440 | #: application/front/controller/admin/ShaarePublishController.php:154 | ||
441 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 | ||
442 | msgid "Edit" | ||
443 | msgstr "Modifier" | ||
444 | |||
445 | #: application/front/controller/admin/ShaarePublishController.php:157 | ||
446 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
447 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 | ||
448 | msgid "Shaare" | ||
449 | msgstr "Shaare" | ||
450 | |||
451 | #: application/front/controller/admin/ShaarePublishController.php:184 | ||
452 | msgid "Note: " | ||
453 | msgstr "Note : " | ||
454 | |||
459 | #: application/front/controller/admin/ThumbnailsController.php:37 | 455 | #: application/front/controller/admin/ThumbnailsController.php:37 |
460 | #: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 456 | #: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 |
461 | msgid "Thumbnails update" | 457 | msgid "Thumbnails update" |
@@ -941,6 +937,48 @@ msgstr "Désolé, il y a rien à voir ici." | |||
941 | msgid "URL or leave empty to post a note" | 937 | msgid "URL or leave empty to post a note" |
942 | msgstr "URL ou laisser vide pour créer une note" | 938 | msgstr "URL ou laisser vide pour créer une note" |
943 | 939 | ||
940 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
941 | msgid "BULK CREATION" | ||
942 | msgstr "CRÉATION DE MASSE" | ||
943 | |||
944 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40 | ||
945 | msgid "Metadata asynchronous retrieval is disabled." | ||
946 | msgstr "La récupération asynchrone des meta-données est désactivée." | ||
947 | |||
948 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | ||
949 | msgid "" | ||
950 | "We recommend that you enable the setting <em>general > " | ||
951 | "enable_async_metadata</em> in your configuration file to use bulk link " | ||
952 | "creation." | ||
953 | msgstr "" | ||
954 | "Nous recommandons d'activer le paramètre <em>general > " | ||
955 | "enable_async_metadata</em> dans votre fichier de configuration pour utiliser " | ||
956 | "la création de masse." | ||
957 | |||
958 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 | ||
959 | msgid "Shaare multiple new links" | ||
960 | msgstr "Partagez plusieurs nouveaux liens" | ||
961 | |||
962 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59 | ||
963 | msgid "Add one URL per line to create multiple bookmarks." | ||
964 | msgstr "Ajouter une URL par ligne pour créer plusieurs marque-pages." | ||
965 | |||
966 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63 | ||
967 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67 | ||
968 | msgid "Tags" | ||
969 | msgstr "Tags" | ||
970 | |||
971 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:73 | ||
972 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 | ||
973 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | ||
974 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | ||
975 | msgid "Private" | ||
976 | msgstr "Privé" | ||
977 | |||
978 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78 | ||
979 | msgid "Add links" | ||
980 | msgstr "Ajouter des liens" | ||
981 | |||
944 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | 982 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 |
945 | msgid "Current password" | 983 | msgid "Current password" |
946 | msgstr "Mot de passe actuel" | 984 | msgstr "Mot de passe actuel" |
@@ -1187,15 +1225,7 @@ msgid "Description" | |||
1187 | msgstr "Description" | 1225 | msgstr "Description" |
1188 | 1226 | ||
1189 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 | 1227 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 |
1190 | msgid "Tags" | ||
1191 | msgstr "Tags" | ||
1192 | |||
1193 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | 1228 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 |
1194 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | ||
1195 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | ||
1196 | msgid "Private" | ||
1197 | msgstr "Privé" | ||
1198 | |||
1199 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80 | 1229 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80 |
1200 | msgid "Description will be rendered with" | 1230 | msgid "Description will be rendered with" |
1201 | msgstr "La description sera générée avec" | 1231 | msgstr "La description sera générée avec" |
@@ -1209,9 +1239,18 @@ msgid "Markdown syntax" | |||
1209 | msgstr "la syntaxe Markdown" | 1239 | msgstr "la syntaxe Markdown" |
1210 | 1240 | ||
1211 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 | 1241 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 |
1242 | msgid "Cancel" | ||
1243 | msgstr "Annuler" | ||
1244 | |||
1245 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121 | ||
1212 | msgid "Apply Changes" | 1246 | msgid "Apply Changes" |
1213 | msgstr "Appliquer les changements" | 1247 | msgstr "Appliquer les changements" |
1214 | 1248 | ||
1249 | #: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12 | ||
1250 | #: tmp/editlink.batch.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | ||
1251 | msgid "Save all" | ||
1252 | msgstr "Tout enregistrer" | ||
1253 | |||
1215 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:107 | 1254 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:107 |
1216 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 | 1255 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 |
1217 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 | 1256 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 |
@@ -125,14 +125,15 @@ $app->group('/admin', function () { | |||
125 | $this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save'); | 125 | $this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save'); |
126 | $this->get('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index'); | 126 | $this->get('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index'); |
127 | $this->post('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save'); | 127 | $this->post('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save'); |
128 | $this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:addShaare'); | 128 | $this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\ShaareAddController:addShaare'); |
129 | $this->get('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayCreateForm'); | 129 | $this->get('/shaare', '\Shaarli\Front\Controller\Admin\ShaarePublishController:displayCreateForm'); |
130 | $this->get('/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayEditForm'); | 130 | $this->get('/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\ShaarePublishController:displayEditForm'); |
131 | $this->get('/shaare/private/{hash}', '\Shaarli\Front\Controller\Admin\ManageShaareController:sharePrivate'); | 131 | $this->get('/shaare/private/{hash}', '\Shaarli\Front\Controller\Admin\ShaareManageController:sharePrivate'); |
132 | $this->post('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:save'); | 132 | $this->post('/shaare-batch', '\Shaarli\Front\Controller\Admin\ShaarePublishController:displayCreateBatchForms'); |
133 | $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark'); | 133 | $this->post('/shaare', '\Shaarli\Front\Controller\Admin\ShaarePublishController:save'); |
134 | $this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility'); | 134 | $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ShaareManageController:deleteBookmark'); |
135 | $this->get('/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ManageShaareController:pinBookmark'); | 135 | $this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ShaareManageController:changeVisibility'); |
136 | $this->get('/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ShaareManageController:pinBookmark'); | ||
136 | $this->patch( | 137 | $this->patch( |
137 | '/shaare/{id:[0-9]+}/update-thumbnail', | 138 | '/shaare/{id:[0-9]+}/update-thumbnail', |
138 | '\Shaarli\Front\Controller\Admin\ThumbnailsController:ajaxUpdate' | 139 | '\Shaarli\Front\Controller\Admin\ThumbnailsController:ajaxUpdate' |
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/AddShaareTest.php b/tests/front/controller/admin/ManageShaareControllerTest/AddShaareTest.php deleted file mode 100644 index 0f27ec2f..00000000 --- a/tests/front/controller/admin/ManageShaareControllerTest/AddShaareTest.php +++ /dev/null | |||
@@ -1,47 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
8 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
9 | use Shaarli\Http\HttpAccess; | ||
10 | use Shaarli\TestCase; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | |||
14 | class AddShaareTest extends TestCase | ||
15 | { | ||
16 | use FrontAdminControllerMockHelper; | ||
17 | |||
18 | /** @var ManageShaareController */ | ||
19 | protected $controller; | ||
20 | |||
21 | public function setUp(): void | ||
22 | { | ||
23 | $this->createContainer(); | ||
24 | |||
25 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
26 | $this->controller = new ManageShaareController($this->container); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Test displaying add link page | ||
31 | */ | ||
32 | public function testAddShaare(): void | ||
33 | { | ||
34 | $assignedVariables = []; | ||
35 | $this->assignTemplateVars($assignedVariables); | ||
36 | |||
37 | $request = $this->createMock(Request::class); | ||
38 | $response = new Response(); | ||
39 | |||
40 | $result = $this->controller->addShaare($request, $response); | ||
41 | |||
42 | static::assertSame(200, $result->getStatusCode()); | ||
43 | static::assertSame('addlink', (string) $result->getBody()); | ||
44 | |||
45 | static::assertSame('Shaare a new link - Shaarli', $assignedVariables['pagetitle']); | ||
46 | } | ||
47 | } | ||
diff --git a/tests/front/controller/admin/ShaareAddControllerTest.php b/tests/front/controller/admin/ShaareAddControllerTest.php new file mode 100644 index 00000000..a27ebe64 --- /dev/null +++ b/tests/front/controller/admin/ShaareAddControllerTest.php | |||
@@ -0,0 +1,97 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Formatter\BookmarkMarkdownFormatter; | ||
9 | use Shaarli\Http\HttpAccess; | ||
10 | use Shaarli\TestCase; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | |||
14 | class ShaareAddControllerTest extends TestCase | ||
15 | { | ||
16 | use FrontAdminControllerMockHelper; | ||
17 | |||
18 | /** @var ShaareAddController */ | ||
19 | protected $controller; | ||
20 | |||
21 | public function setUp(): void | ||
22 | { | ||
23 | $this->createContainer(); | ||
24 | |||
25 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
26 | $this->controller = new ShaareAddController($this->container); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Test displaying add link page | ||
31 | */ | ||
32 | public function testAddShaare(): void | ||
33 | { | ||
34 | $assignedVariables = []; | ||
35 | $this->assignTemplateVars($assignedVariables); | ||
36 | |||
37 | $request = $this->createMock(Request::class); | ||
38 | $response = new Response(); | ||
39 | |||
40 | $expectedTags = [ | ||
41 | 'tag1' => 32, | ||
42 | 'tag2' => 24, | ||
43 | 'tag3' => 1, | ||
44 | ]; | ||
45 | $this->container->bookmarkService | ||
46 | ->expects(static::once()) | ||
47 | ->method('bookmarksCountPerTag') | ||
48 | ->willReturn($expectedTags) | ||
49 | ; | ||
50 | $expectedTags = array_merge($expectedTags, [BookmarkMarkdownFormatter::NO_MD_TAG => 1]); | ||
51 | |||
52 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
53 | $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { | ||
54 | return $key === 'formatter' ? 'markdown' : $default; | ||
55 | }); | ||
56 | |||
57 | $result = $this->controller->addShaare($request, $response); | ||
58 | |||
59 | static::assertSame(200, $result->getStatusCode()); | ||
60 | static::assertSame('addlink', (string) $result->getBody()); | ||
61 | |||
62 | static::assertSame('Shaare a new link - Shaarli', $assignedVariables['pagetitle']); | ||
63 | static::assertFalse($assignedVariables['default_private_links']); | ||
64 | static::assertTrue($assignedVariables['async_metadata']); | ||
65 | static::assertSame($expectedTags, $assignedVariables['tags']); | ||
66 | } | ||
67 | |||
68 | /** | ||
69 | * Test displaying add link page | ||
70 | */ | ||
71 | public function testAddShaareWithoutMd(): void | ||
72 | { | ||
73 | $assignedVariables = []; | ||
74 | $this->assignTemplateVars($assignedVariables); | ||
75 | |||
76 | $request = $this->createMock(Request::class); | ||
77 | $response = new Response(); | ||
78 | |||
79 | $expectedTags = [ | ||
80 | 'tag1' => 32, | ||
81 | 'tag2' => 24, | ||
82 | 'tag3' => 1, | ||
83 | ]; | ||
84 | $this->container->bookmarkService | ||
85 | ->expects(static::once()) | ||
86 | ->method('bookmarksCountPerTag') | ||
87 | ->willReturn($expectedTags) | ||
88 | ; | ||
89 | |||
90 | $result = $this->controller->addShaare($request, $response); | ||
91 | |||
92 | static::assertSame(200, $result->getStatusCode()); | ||
93 | static::assertSame('addlink', (string) $result->getBody()); | ||
94 | |||
95 | static::assertSame($expectedTags, $assignedVariables['tags']); | ||
96 | } | ||
97 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/ChangeVisibilityBookmarkTest.php b/tests/front/controller/admin/ShaareManageControllerTest/ChangeVisibilityBookmarkTest.php index 096d0774..28b1c023 100644 --- a/tests/front/controller/admin/ManageShaareControllerTest/ChangeVisibilityBookmarkTest.php +++ b/tests/front/controller/admin/ShaareManageControllerTest/ChangeVisibilityBookmarkTest.php | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | declare(strict_types=1); | 3 | declare(strict_types=1); |
4 | 4 | ||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | 5 | namespace Shaarli\Front\Controller\Admin\ShaareManageControllerTest; |
6 | 6 | ||
7 | use Shaarli\Bookmark\Bookmark; | 7 | use Shaarli\Bookmark\Bookmark; |
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | 8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; |
@@ -10,7 +10,7 @@ use Shaarli\Formatter\BookmarkFormatter; | |||
10 | use Shaarli\Formatter\BookmarkRawFormatter; | 10 | use Shaarli\Formatter\BookmarkRawFormatter; |
11 | use Shaarli\Formatter\FormatterFactory; | 11 | use Shaarli\Formatter\FormatterFactory; |
12 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | 12 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; |
13 | use Shaarli\Front\Controller\Admin\ManageShaareController; | 13 | use Shaarli\Front\Controller\Admin\ShaareManageController; |
14 | use Shaarli\Http\HttpAccess; | 14 | use Shaarli\Http\HttpAccess; |
15 | use Shaarli\Security\SessionManager; | 15 | use Shaarli\Security\SessionManager; |
16 | use Shaarli\TestCase; | 16 | use Shaarli\TestCase; |
@@ -21,7 +21,7 @@ class ChangeVisibilityBookmarkTest extends TestCase | |||
21 | { | 21 | { |
22 | use FrontAdminControllerMockHelper; | 22 | use FrontAdminControllerMockHelper; |
23 | 23 | ||
24 | /** @var ManageShaareController */ | 24 | /** @var ShaareManageController */ |
25 | protected $controller; | 25 | protected $controller; |
26 | 26 | ||
27 | public function setUp(): void | 27 | public function setUp(): void |
@@ -29,7 +29,7 @@ class ChangeVisibilityBookmarkTest extends TestCase | |||
29 | $this->createContainer(); | 29 | $this->createContainer(); |
30 | 30 | ||
31 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | 31 | $this->container->httpAccess = $this->createMock(HttpAccess::class); |
32 | $this->controller = new ManageShaareController($this->container); | 32 | $this->controller = new ShaareManageController($this->container); |
33 | } | 33 | } |
34 | 34 | ||
35 | /** | 35 | /** |
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php b/tests/front/controller/admin/ShaareManageControllerTest/DeleteBookmarkTest.php index 83bbee7c..770a16d7 100644 --- a/tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php +++ b/tests/front/controller/admin/ShaareManageControllerTest/DeleteBookmarkTest.php | |||
@@ -2,14 +2,14 @@ | |||
2 | 2 | ||
3 | declare(strict_types=1); | 3 | declare(strict_types=1); |
4 | 4 | ||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | 5 | namespace Shaarli\Front\Controller\Admin\ShaareManageControllerTest; |
6 | 6 | ||
7 | use Shaarli\Bookmark\Bookmark; | 7 | use Shaarli\Bookmark\Bookmark; |
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | 8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; |
9 | use Shaarli\Formatter\BookmarkFormatter; | 9 | use Shaarli\Formatter\BookmarkFormatter; |
10 | use Shaarli\Formatter\FormatterFactory; | 10 | use Shaarli\Formatter\FormatterFactory; |
11 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | 11 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; |
12 | use Shaarli\Front\Controller\Admin\ManageShaareController; | 12 | use Shaarli\Front\Controller\Admin\ShaareManageController; |
13 | use Shaarli\Http\HttpAccess; | 13 | use Shaarli\Http\HttpAccess; |
14 | use Shaarli\Security\SessionManager; | 14 | use Shaarli\Security\SessionManager; |
15 | use Shaarli\TestCase; | 15 | use Shaarli\TestCase; |
@@ -20,7 +20,7 @@ class DeleteBookmarkTest extends TestCase | |||
20 | { | 20 | { |
21 | use FrontAdminControllerMockHelper; | 21 | use FrontAdminControllerMockHelper; |
22 | 22 | ||
23 | /** @var ManageShaareController */ | 23 | /** @var ShaareManageController */ |
24 | protected $controller; | 24 | protected $controller; |
25 | 25 | ||
26 | public function setUp(): void | 26 | public function setUp(): void |
@@ -28,7 +28,7 @@ class DeleteBookmarkTest extends TestCase | |||
28 | $this->createContainer(); | 28 | $this->createContainer(); |
29 | 29 | ||
30 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | 30 | $this->container->httpAccess = $this->createMock(HttpAccess::class); |
31 | $this->controller = new ManageShaareController($this->container); | 31 | $this->controller = new ShaareManageController($this->container); |
32 | } | 32 | } |
33 | 33 | ||
34 | /** | 34 | /** |
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/PinBookmarkTest.php b/tests/front/controller/admin/ShaareManageControllerTest/PinBookmarkTest.php index 50ce7df1..b89206ce 100644 --- a/tests/front/controller/admin/ManageShaareControllerTest/PinBookmarkTest.php +++ b/tests/front/controller/admin/ShaareManageControllerTest/PinBookmarkTest.php | |||
@@ -2,12 +2,12 @@ | |||
2 | 2 | ||
3 | declare(strict_types=1); | 3 | declare(strict_types=1); |
4 | 4 | ||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | 5 | namespace Shaarli\Front\Controller\Admin\ShaareManageControllerTest; |
6 | 6 | ||
7 | use Shaarli\Bookmark\Bookmark; | 7 | use Shaarli\Bookmark\Bookmark; |
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | 8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; |
9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | 9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; |
10 | use Shaarli\Front\Controller\Admin\ManageShaareController; | 10 | use Shaarli\Front\Controller\Admin\ShaareManageController; |
11 | use Shaarli\Http\HttpAccess; | 11 | use Shaarli\Http\HttpAccess; |
12 | use Shaarli\Security\SessionManager; | 12 | use Shaarli\Security\SessionManager; |
13 | use Shaarli\TestCase; | 13 | use Shaarli\TestCase; |
@@ -18,7 +18,7 @@ class PinBookmarkTest extends TestCase | |||
18 | { | 18 | { |
19 | use FrontAdminControllerMockHelper; | 19 | use FrontAdminControllerMockHelper; |
20 | 20 | ||
21 | /** @var ManageShaareController */ | 21 | /** @var ShaareManageController */ |
22 | protected $controller; | 22 | protected $controller; |
23 | 23 | ||
24 | public function setUp(): void | 24 | public function setUp(): void |
@@ -26,7 +26,7 @@ class PinBookmarkTest extends TestCase | |||
26 | $this->createContainer(); | 26 | $this->createContainer(); |
27 | 27 | ||
28 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | 28 | $this->container->httpAccess = $this->createMock(HttpAccess::class); |
29 | $this->controller = new ManageShaareController($this->container); | 29 | $this->controller = new ShaareManageController($this->container); |
30 | } | 30 | } |
31 | 31 | ||
32 | /** | 32 | /** |
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/SharePrivateTest.php b/tests/front/controller/admin/ShaareManageControllerTest/SharePrivateTest.php index 1e7877c7..ae61dfb7 100644 --- a/tests/front/controller/admin/ManageShaareControllerTest/SharePrivateTest.php +++ b/tests/front/controller/admin/ShaareManageControllerTest/SharePrivateTest.php | |||
@@ -2,11 +2,11 @@ | |||
2 | 2 | ||
3 | declare(strict_types=1); | 3 | declare(strict_types=1); |
4 | 4 | ||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | 5 | namespace Shaarli\Front\Controller\Admin\ShaareManageControllerTest; |
6 | 6 | ||
7 | use Shaarli\Bookmark\Bookmark; | 7 | use Shaarli\Bookmark\Bookmark; |
8 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | 8 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; |
9 | use Shaarli\Front\Controller\Admin\ManageShaareController; | 9 | use Shaarli\Front\Controller\Admin\ShaareManageController; |
10 | use Shaarli\Http\HttpAccess; | 10 | use Shaarli\Http\HttpAccess; |
11 | use Shaarli\TestCase; | 11 | use Shaarli\TestCase; |
12 | use Slim\Http\Request; | 12 | use Slim\Http\Request; |
@@ -19,7 +19,7 @@ class SharePrivateTest extends TestCase | |||
19 | { | 19 | { |
20 | use FrontAdminControllerMockHelper; | 20 | use FrontAdminControllerMockHelper; |
21 | 21 | ||
22 | /** @var ManageShaareController */ | 22 | /** @var ShaareManageController */ |
23 | protected $controller; | 23 | protected $controller; |
24 | 24 | ||
25 | public function setUp(): void | 25 | public function setUp(): void |
@@ -27,7 +27,7 @@ class SharePrivateTest extends TestCase | |||
27 | $this->createContainer(); | 27 | $this->createContainer(); |
28 | 28 | ||
29 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | 29 | $this->container->httpAccess = $this->createMock(HttpAccess::class); |
30 | $this->controller = new ManageShaareController($this->container); | 30 | $this->controller = new ShaareManageController($this->container); |
31 | } | 31 | } |
32 | 32 | ||
33 | /** | 33 | /** |
diff --git a/tests/front/controller/admin/ShaarePublishControllerTest/DisplayCreateBatchFormTest.php b/tests/front/controller/admin/ShaarePublishControllerTest/DisplayCreateBatchFormTest.php new file mode 100644 index 00000000..ce8e112b --- /dev/null +++ b/tests/front/controller/admin/ShaarePublishControllerTest/DisplayCreateBatchFormTest.php | |||
@@ -0,0 +1,63 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ShaarePublishControllerTest; | ||
6 | |||
7 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
8 | use Shaarli\Front\Controller\Admin\ShaarePublishController; | ||
9 | use Shaarli\Http\HttpAccess; | ||
10 | use Shaarli\Http\MetadataRetriever; | ||
11 | use Shaarli\TestCase; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | class DisplayCreateBatchFormTest extends TestCase | ||
16 | { | ||
17 | use FrontAdminControllerMockHelper; | ||
18 | |||
19 | /** @var ShaarePublishController */ | ||
20 | protected $controller; | ||
21 | |||
22 | public function setUp(): void | ||
23 | { | ||
24 | $this->createContainer(); | ||
25 | |||
26 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
27 | $this->container->metadataRetriever = $this->createMock(MetadataRetriever::class); | ||
28 | $this->controller = new ShaarePublishController($this->container); | ||
29 | } | ||
30 | |||
31 | /** | ||
32 | * TODO | ||
33 | */ | ||
34 | public function testDisplayCreateFormBatch(): void | ||
35 | { | ||
36 | $urls = [ | ||
37 | 'https://domain1.tld/url1', | ||
38 | 'https://domain2.tld/url2', | ||
39 | ' ', | ||
40 | 'https://domain3.tld/url3', | ||
41 | ]; | ||
42 | |||
43 | $request = $this->createMock(Request::class); | ||
44 | $request->method('getParam')->willReturnCallback(function (string $key) use ($urls): ?string { | ||
45 | return $key === 'urls' ? implode(PHP_EOL, $urls) : null; | ||
46 | }); | ||
47 | $response = new Response(); | ||
48 | |||
49 | $assignedVariables = []; | ||
50 | $this->assignTemplateVars($assignedVariables); | ||
51 | |||
52 | $result = $this->controller->displayCreateBatchForms($request, $response); | ||
53 | |||
54 | static::assertSame(200, $result->getStatusCode()); | ||
55 | static::assertSame('editlink.batch', (string) $result->getBody()); | ||
56 | |||
57 | static::assertTrue($assignedVariables['batch_mode']); | ||
58 | static::assertCount(3, $assignedVariables['links']); | ||
59 | static::assertSame($urls[0], $assignedVariables['links'][0]['link']['url']); | ||
60 | static::assertSame($urls[1], $assignedVariables['links'][1]['link']['url']); | ||
61 | static::assertSame($urls[3], $assignedVariables['links'][2]['link']['url']); | ||
62 | } | ||
63 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/DisplayCreateFormTest.php b/tests/front/controller/admin/ShaarePublishControllerTest/DisplayCreateFormTest.php index eafa54eb..f20b1def 100644 --- a/tests/front/controller/admin/ManageShaareControllerTest/DisplayCreateFormTest.php +++ b/tests/front/controller/admin/ShaarePublishControllerTest/DisplayCreateFormTest.php | |||
@@ -2,12 +2,12 @@ | |||
2 | 2 | ||
3 | declare(strict_types=1); | 3 | declare(strict_types=1); |
4 | 4 | ||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | 5 | namespace Shaarli\Front\Controller\Admin\ShaarePublishControllerTest; |
6 | 6 | ||
7 | use Shaarli\Bookmark\Bookmark; | 7 | use Shaarli\Bookmark\Bookmark; |
8 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | 9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; |
10 | use Shaarli\Front\Controller\Admin\ManageShaareController; | 10 | use Shaarli\Front\Controller\Admin\ShaarePublishController; |
11 | use Shaarli\Http\HttpAccess; | 11 | use Shaarli\Http\HttpAccess; |
12 | use Shaarli\Http\MetadataRetriever; | 12 | use Shaarli\Http\MetadataRetriever; |
13 | use Shaarli\TestCase; | 13 | use Shaarli\TestCase; |
@@ -18,7 +18,7 @@ class DisplayCreateFormTest extends TestCase | |||
18 | { | 18 | { |
19 | use FrontAdminControllerMockHelper; | 19 | use FrontAdminControllerMockHelper; |
20 | 20 | ||
21 | /** @var ManageShaareController */ | 21 | /** @var ShaarePublishController */ |
22 | protected $controller; | 22 | protected $controller; |
23 | 23 | ||
24 | public function setUp(): void | 24 | public function setUp(): void |
@@ -27,7 +27,7 @@ class DisplayCreateFormTest extends TestCase | |||
27 | 27 | ||
28 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | 28 | $this->container->httpAccess = $this->createMock(HttpAccess::class); |
29 | $this->container->metadataRetriever = $this->createMock(MetadataRetriever::class); | 29 | $this->container->metadataRetriever = $this->createMock(MetadataRetriever::class); |
30 | $this->controller = new ManageShaareController($this->container); | 30 | $this->controller = new ShaarePublishController($this->container); |
31 | } | 31 | } |
32 | 32 | ||
33 | /** | 33 | /** |
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/DisplayEditFormTest.php b/tests/front/controller/admin/ShaarePublishControllerTest/DisplayEditFormTest.php index 2dc3f41c..da393e49 100644 --- a/tests/front/controller/admin/ManageShaareControllerTest/DisplayEditFormTest.php +++ b/tests/front/controller/admin/ShaarePublishControllerTest/DisplayEditFormTest.php | |||
@@ -2,12 +2,12 @@ | |||
2 | 2 | ||
3 | declare(strict_types=1); | 3 | declare(strict_types=1); |
4 | 4 | ||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | 5 | namespace Shaarli\Front\Controller\Admin\ShaarePublishControllerTest; |
6 | 6 | ||
7 | use Shaarli\Bookmark\Bookmark; | 7 | use Shaarli\Bookmark\Bookmark; |
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | 8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; |
9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | 9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; |
10 | use Shaarli\Front\Controller\Admin\ManageShaareController; | 10 | use Shaarli\Front\Controller\Admin\ShaarePublishController; |
11 | use Shaarli\Http\HttpAccess; | 11 | use Shaarli\Http\HttpAccess; |
12 | use Shaarli\Security\SessionManager; | 12 | use Shaarli\Security\SessionManager; |
13 | use Shaarli\TestCase; | 13 | use Shaarli\TestCase; |
@@ -18,7 +18,7 @@ class DisplayEditFormTest extends TestCase | |||
18 | { | 18 | { |
19 | use FrontAdminControllerMockHelper; | 19 | use FrontAdminControllerMockHelper; |
20 | 20 | ||
21 | /** @var ManageShaareController */ | 21 | /** @var ShaarePublishController */ |
22 | protected $controller; | 22 | protected $controller; |
23 | 23 | ||
24 | public function setUp(): void | 24 | public function setUp(): void |
@@ -26,7 +26,7 @@ class DisplayEditFormTest extends TestCase | |||
26 | $this->createContainer(); | 26 | $this->createContainer(); |
27 | 27 | ||
28 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | 28 | $this->container->httpAccess = $this->createMock(HttpAccess::class); |
29 | $this->controller = new ManageShaareController($this->container); | 29 | $this->controller = new ShaarePublishController($this->container); |
30 | } | 30 | } |
31 | 31 | ||
32 | /** | 32 | /** |
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php b/tests/front/controller/admin/ShaarePublishControllerTest/SaveBookmarkTest.php index 1adeef5a..b6a861bc 100644 --- a/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php +++ b/tests/front/controller/admin/ShaarePublishControllerTest/SaveBookmarkTest.php | |||
@@ -2,12 +2,12 @@ | |||
2 | 2 | ||
3 | declare(strict_types=1); | 3 | declare(strict_types=1); |
4 | 4 | ||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | 5 | namespace Shaarli\Front\Controller\Admin\ShaarePublishControllerTest; |
6 | 6 | ||
7 | use Shaarli\Bookmark\Bookmark; | 7 | use Shaarli\Bookmark\Bookmark; |
8 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | 9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; |
10 | use Shaarli\Front\Controller\Admin\ManageShaareController; | 10 | use Shaarli\Front\Controller\Admin\ShaarePublishController; |
11 | use Shaarli\Front\Exception\WrongTokenException; | 11 | use Shaarli\Front\Exception\WrongTokenException; |
12 | use Shaarli\Http\HttpAccess; | 12 | use Shaarli\Http\HttpAccess; |
13 | use Shaarli\Security\SessionManager; | 13 | use Shaarli\Security\SessionManager; |
@@ -20,7 +20,7 @@ class SaveBookmarkTest extends TestCase | |||
20 | { | 20 | { |
21 | use FrontAdminControllerMockHelper; | 21 | use FrontAdminControllerMockHelper; |
22 | 22 | ||
23 | /** @var ManageShaareController */ | 23 | /** @var ShaarePublishController */ |
24 | protected $controller; | 24 | protected $controller; |
25 | 25 | ||
26 | public function setUp(): void | 26 | public function setUp(): void |
@@ -28,7 +28,7 @@ class SaveBookmarkTest extends TestCase | |||
28 | $this->createContainer(); | 28 | $this->createContainer(); |
29 | 29 | ||
30 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | 30 | $this->container->httpAccess = $this->createMock(HttpAccess::class); |
31 | $this->controller = new ManageShaareController($this->container); | 31 | $this->controller = new ShaarePublishController($this->container); |
32 | } | 32 | } |
33 | 33 | ||
34 | /** | 34 | /** |
diff --git a/tpl/default/addlink.html b/tpl/default/addlink.html index 67d3ebd1..4aac7ff1 100644 --- a/tpl/default/addlink.html +++ b/tpl/default/addlink.html | |||
@@ -20,6 +20,62 @@ | |||
20 | </form> | 20 | </form> |
21 | </div> | 21 | </div> |
22 | </div> | 22 | </div> |
23 | |||
24 | <div class="pure-g addlink-batch-show-more-block pure-u-0"> | ||
25 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> | ||
26 | <div class="pure-u-lg-1-3 pure-u-22-24 addlink-batch-show-more"> | ||
27 | <a href="#">{'BULK CREATION'|t} <i class="fa fa-plus-circle" aria-hidden="true"></i></a> | ||
28 | </div> | ||
29 | </div> | ||
30 | |||
31 | <div class="addlink-batch-form-block"> | ||
32 | {if="empty($async_metadata)"} | ||
33 | <div class="pure-g pure-alert pure-alert-warning pure-alert-closable"> | ||
34 | <div class="pure-u-2-24"></div> | ||
35 | <div class="pure-u-20-24"> | ||
36 | <p> | ||
37 | {'Metadata asynchronous retrieval is disabled.'|t} | ||
38 | {'We recommend that you enable the setting <em>general > enable_async_metadata</em> in your configuration file to use bulk link creation.'|t} | ||
39 | </p> | ||
40 | </div> | ||
41 | <div class="pure-u-2-24"> | ||
42 | <i class="fa fa-times pure-alert-close"></i> | ||
43 | </div> | ||
44 | </div> | ||
45 | {/if} | ||
46 | |||
47 | <div class="pure-g"> | ||
48 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> | ||
49 | <div id="batch-addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24"> | ||
50 | <h2 class="window-title">{"Shaare multiple new links"|t}</h2> | ||
51 | <form method="POST" action="{$base_path}/admin/shaare-batch" name="batch-addform" class="batch-addform"> | ||
52 | <div> | ||
53 | <label for="urls">{'Add one URL per line to create multiple bookmarks.'|t}</label> | ||
54 | <textarea name="urls" id="urls"></textarea> | ||
55 | |||
56 | <div> | ||
57 | <label for="tags">{'Tags'|t}</label> | ||
58 | </div> | ||
59 | <div> | ||
60 | <input type="text" name="tags" id="tags" class="lf_input" | ||
61 | data-list="{loop="$tags"}{$key}, {/loop}" data-multiple data-autofirst autocomplete="off"> | ||
62 | </div> | ||
63 | |||
64 | <div> | ||
65 | <input type="hidden" name="private" value="0"> | ||
66 | <input type="checkbox" name="private" {if="$default_private_links"} checked="checked"{/if}> | ||
67 | <label for="lf_private">{'Private'|t}</label> | ||
68 | </div> | ||
69 | </div> | ||
70 | <div> | ||
71 | <input type="hidden" name="token" value="{$token}"> | ||
72 | <input type="submit" value="{'Add links'|t}"> | ||
73 | </div> | ||
74 | </form> | ||
75 | </div> | ||
76 | </div> | ||
77 | </div> | ||
78 | |||
23 | {include="page.footer"} | 79 | {include="page.footer"} |
24 | </body> | 80 | </body> |
25 | </html> | 81 | </html> |
diff --git a/tpl/default/editlink.batch.html b/tpl/default/editlink.batch.html new file mode 100644 index 00000000..b1f8e5bd --- /dev/null +++ b/tpl/default/editlink.batch.html | |||
@@ -0,0 +1,32 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html{if="$language !== 'auto'"} lang="{$language}"{/if}> | ||
3 | <head> | ||
4 | {include="includes"} | ||
5 | </head> | ||
6 | <body> | ||
7 | <div class="dark-layer"> | ||
8 | <div class="screen-center"> | ||
9 | <div><span class="progressbar-current"></span> / <span class="progressbar-max"></span></div> | ||
10 | <div class="progressbar"> | ||
11 | <div></div> | ||
12 | </div> | ||
13 | </div> | ||
14 | </div> | ||
15 | |||
16 | {include="page.header"} | ||
17 | |||
18 | <div class="center"> | ||
19 | <input type="submit" name="save_edit_batch" class="pure-button-shaarli" value="{'Save all'|t}"> | ||
20 | </div> | ||
21 | |||
22 | {loop="$links"} | ||
23 | {include="editlink"} | ||
24 | {/loop} | ||
25 | |||
26 | <div class="center"> | ||
27 | <input type="submit" name="save_edit_batch" class="pure-button-shaarli" value="{'Save all'|t}"> | ||
28 | </div> | ||
29 | |||
30 | {include="page.footer"} | ||
31 | {if="$async_metadata"}<script src="{$asset_path}/js/metadata.min.js?v={$version_hash}#"></script>{/if} | ||
32 | <script src="{$asset_path}/js/shaare_batch.min.js?v={$version_hash}#"></script> | ||
diff --git a/tpl/default/editlink.html b/tpl/default/editlink.html index 7ab7e1fe..83e541fd 100644 --- a/tpl/default/editlink.html +++ b/tpl/default/editlink.html | |||
@@ -1,3 +1,4 @@ | |||
1 | {if="empty($batch_mode)"} | ||
1 | <!DOCTYPE html> | 2 | <!DOCTYPE html> |
2 | <html{if="$language !== 'auto'"} lang="{$language}"{/if}> | 3 | <html{if="$language !== 'auto'"} lang="{$language}"{/if}> |
3 | <head> | 4 | <head> |
@@ -5,6 +6,10 @@ | |||
5 | </head> | 6 | </head> |
6 | <body> | 7 | <body> |
7 | {include="page.header"} | 8 | {include="page.header"} |
9 | {else} | ||
10 | {ignore}Lil hack: when included in a loop in batch mode, `$value` is assigned by RainTPL with template vars.{/ignore} | ||
11 | {function="extract($value) ? '' : ''"} | ||
12 | {/if} | ||
8 | <div id="editlinkform" class="edit-link-container" class="pure-g"> | 13 | <div id="editlinkform" class="edit-link-container" class="pure-g"> |
9 | <div class="pure-u-lg-1-5 pure-u-1-24"></div> | 14 | <div class="pure-u-lg-1-5 pure-u-1-24"></div> |
10 | <form method="post" | 15 | <form method="post" |
@@ -60,7 +65,7 @@ | |||
60 | 65 | ||
61 | <div> | 66 | <div> |
62 | <input type="checkbox" name="lf_private" id="lf_private" | 67 | <input type="checkbox" name="lf_private" id="lf_private" |
63 | {if="($link_is_new && $default_private_links || $link.private == true)"} | 68 | {if="$link.private === true"} |
64 | checked="checked" | 69 | checked="checked" |
65 | {/if}> | 70 | {/if}> |
66 | <label for="lf_private">{'Private'|t}</label> | 71 | <label for="lf_private">{'Private'|t}</label> |
@@ -83,6 +88,13 @@ | |||
83 | 88 | ||
84 | 89 | ||
85 | <div class="submit-buttons center"> | 90 | <div class="submit-buttons center"> |
91 | {if="!empty($batch_mode)"} | ||
92 | <a href="#" class="button button-grey" name="cancel-batch-link" | ||
93 | title="{'Remove this bookmark from batch creation/modification.'}" | ||
94 | > | ||
95 | {'Cancel'|t} | ||
96 | </a> | ||
97 | {/if} | ||
86 | <input type="submit" name="save_edit" class="" id="button-save-edit" | 98 | <input type="submit" name="save_edit" class="" id="button-save-edit" |
87 | value="{if="$link_is_new"}{'Save'|t}{else}{'Apply Changes'|t}{/if}"> | 99 | value="{if="$link_is_new"}{'Save'|t}{else}{'Apply Changes'|t}{/if}"> |
88 | {if="!$link_is_new"} | 100 | {if="!$link_is_new"} |
@@ -100,7 +112,10 @@ | |||
100 | {/if} | 112 | {/if} |
101 | </form> | 113 | </form> |
102 | </div> | 114 | </div> |
115 | |||
116 | {if="empty($batch_mode)"} | ||
103 | {include="page.footer"} | 117 | {include="page.footer"} |
104 | {if="$link_is_new && $async_metadata"}<script src="{$asset_path}/js/metadata.min.js?v={$version_hash}#"></script>{/if} | 118 | {if="$link_is_new && $async_metadata"}<script src="{$asset_path}/js/metadata.min.js?v={$version_hash}#"></script>{/if} |
105 | </body> | 119 | </body> |
106 | </html> | 120 | </html> |
121 | {/if} | ||
diff --git a/webpack.config.js b/webpack.config.js index 8e3d1470..a4aa633e 100644 --- a/webpack.config.js +++ b/webpack.config.js | |||
@@ -18,6 +18,7 @@ module.exports = [ | |||
18 | { | 18 | { |
19 | mode: 'production', | 19 | mode: 'production', |
20 | entry: { | 20 | entry: { |
21 | shaare_batch: './assets/common/js/shaare-batch.js', | ||
21 | thumbnails: './assets/common/js/thumbnails.js', | 22 | thumbnails: './assets/common/js/thumbnails.js', |
22 | thumbnails_update: './assets/common/js/thumbnails-update.js', | 23 | thumbnails_update: './assets/common/js/thumbnails-update.js', |
23 | metadata: './assets/common/js/metadata.js', | 24 | metadata: './assets/common/js/metadata.js', |