diff options
65 files changed, 3616 insertions, 910 deletions
@@ -44,6 +44,7 @@ RUN apk --update --no-cache add \ | |||
44 | php7-openssl \ | 44 | php7-openssl \ |
45 | php7-session \ | 45 | php7-session \ |
46 | php7-xml \ | 46 | php7-xml \ |
47 | php7-simplexml \ | ||
47 | php7-zlib \ | 48 | php7-zlib \ |
48 | s6 | 49 | s6 |
49 | 50 | ||
diff --git a/application/History.php b/application/History.php index 4fd2f294..bd5c1bf7 100644 --- a/application/History.php +++ b/application/History.php | |||
@@ -4,6 +4,7 @@ namespace Shaarli; | |||
4 | use DateTime; | 4 | use DateTime; |
5 | use Exception; | 5 | use Exception; |
6 | use Shaarli\Bookmark\Bookmark; | 6 | use Shaarli\Bookmark\Bookmark; |
7 | use Shaarli\Helper\FileUtils; | ||
7 | 8 | ||
8 | /** | 9 | /** |
9 | * Class History | 10 | * Class History |
diff --git a/application/Utils.php b/application/Utils.php index bc1c9f5d..db046893 100644 --- a/application/Utils.php +++ b/application/Utils.php | |||
@@ -327,6 +327,23 @@ function format_date($date, $time = true, $intl = true) | |||
327 | } | 327 | } |
328 | 328 | ||
329 | /** | 329 | /** |
330 | * Format the date month according to the locale. | ||
331 | * | ||
332 | * @param DateTimeInterface $date to format. | ||
333 | * | ||
334 | * @return bool|string Formatted date, or false if the input is invalid. | ||
335 | */ | ||
336 | function format_month(DateTimeInterface $date) | ||
337 | { | ||
338 | if (! $date instanceof DateTimeInterface) { | ||
339 | return false; | ||
340 | } | ||
341 | |||
342 | return strftime('%B', $date->getTimestamp()); | ||
343 | } | ||
344 | |||
345 | |||
346 | /** | ||
330 | * Check if the input is an integer, no matter its real type. | 347 | * Check if the input is an integer, no matter its real type. |
331 | * | 348 | * |
332 | * PHP is a bit messy regarding this: | 349 | * PHP is a bit messy regarding this: |
@@ -454,16 +471,20 @@ function alphabetical_sort(&$data, $reverse = false, $byKeys = false) | |||
454 | * Wrapper function for translation which match the API | 471 | * Wrapper function for translation which match the API |
455 | * of gettext()/_() and ngettext(). | 472 | * of gettext()/_() and ngettext(). |
456 | * | 473 | * |
457 | * @param string $text Text to translate. | 474 | * @param string $text Text to translate. |
458 | * @param string $nText The plural message ID. | 475 | * @param string $nText The plural message ID. |
459 | * @param int $nb The number of items for plural forms. | 476 | * @param int $nb The number of items for plural forms. |
460 | * @param string $domain The domain where the translation is stored (default: shaarli). | 477 | * @param string $domain The domain where the translation is stored (default: shaarli). |
478 | * @param array $variables Associative array of variables to replace in translated text. | ||
479 | * @param bool $fixCase Apply `ucfirst` on the translated string, might be useful for strings with variables. | ||
461 | * | 480 | * |
462 | * @return string Text translated. | 481 | * @return string Text translated. |
463 | */ | 482 | */ |
464 | function t($text, $nText = '', $nb = 1, $domain = 'shaarli') | 483 | function t($text, $nText = '', $nb = 1, $domain = 'shaarli', $variables = [], $fixCase = false) |
465 | { | 484 | { |
466 | return dn__($domain, $text, $nText, $nb); | 485 | $postFunction = $fixCase ? 'ucfirst' : function ($input) { return $input; }; |
486 | |||
487 | return $postFunction(dn__($domain, $text, $nText, $nb, $variables)); | ||
467 | } | 488 | } |
468 | 489 | ||
469 | /** | 490 | /** |
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php index 73a1b84e..6bf529e4 100644 --- a/application/api/controllers/Links.php +++ b/application/api/controllers/Links.php | |||
@@ -131,7 +131,7 @@ class Links extends ApiController | |||
131 | 131 | ||
132 | $this->bookmarkService->add($bookmark); | 132 | $this->bookmarkService->add($bookmark); |
133 | $out = ApiUtils::formatLink($bookmark, index_url($this->ci['environment'])); | 133 | $out = ApiUtils::formatLink($bookmark, index_url($this->ci['environment'])); |
134 | $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $bookmark->getId()]); | 134 | $redirect = $this->ci->router->pathFor('getLink', ['id' => $bookmark->getId()]); |
135 | return $response->withAddedHeader('Location', $redirect) | 135 | return $response->withAddedHeader('Location', $redirect) |
136 | ->withJson($out, 201, $this->jsonStyle); | 136 | ->withJson($out, 201, $this->jsonStyle); |
137 | } | 137 | } |
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php index eb7899bf..3ea98a45 100644 --- a/application/bookmark/BookmarkFileService.php +++ b/application/bookmark/BookmarkFileService.php | |||
@@ -97,13 +97,16 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
97 | /** | 97 | /** |
98 | * @inheritDoc | 98 | * @inheritDoc |
99 | */ | 99 | */ |
100 | public function findByHash(string $hash): Bookmark | 100 | public function findByHash(string $hash, string $privateKey = null): Bookmark |
101 | { | 101 | { |
102 | $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash); | 102 | $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash); |
103 | // PHP 7.3 introduced array_key_first() to avoid this hack | 103 | // PHP 7.3 introduced array_key_first() to avoid this hack |
104 | $first = reset($bookmark); | 104 | $first = reset($bookmark); |
105 | if (! $this->isLoggedIn && $first->isPrivate()) { | 105 | if (!$this->isLoggedIn |
106 | throw new Exception('Not authorized'); | 106 | && $first->isPrivate() |
107 | && (empty($privateKey) || $privateKey !== $first->getAdditionalContentEntry('private_key')) | ||
108 | ) { | ||
109 | throw new BookmarkNotFoundException(); | ||
107 | } | 110 | } |
108 | 111 | ||
109 | return $first; | 112 | return $first; |
@@ -340,26 +343,42 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
340 | /** | 343 | /** |
341 | * @inheritDoc | 344 | * @inheritDoc |
342 | */ | 345 | */ |
343 | public function days(): array | 346 | public function findByDate( |
344 | { | 347 | \DateTimeInterface $from, |
345 | $bookmarkDays = []; | 348 | \DateTimeInterface $to, |
346 | foreach ($this->search() as $bookmark) { | 349 | ?\DateTimeInterface &$previous, |
347 | $bookmarkDays[$bookmark->getCreated()->format('Ymd')] = 0; | 350 | ?\DateTimeInterface &$next |
351 | ): array { | ||
352 | $out = []; | ||
353 | $previous = null; | ||
354 | $next = null; | ||
355 | |||
356 | foreach ($this->search([], null, false, false, true) as $bookmark) { | ||
357 | if ($to < $bookmark->getCreated()) { | ||
358 | $next = $bookmark->getCreated(); | ||
359 | } else if ($from < $bookmark->getCreated() && $to > $bookmark->getCreated()) { | ||
360 | $out[] = $bookmark; | ||
361 | } else { | ||
362 | if ($previous !== null) { | ||
363 | break; | ||
364 | } | ||
365 | $previous = $bookmark->getCreated(); | ||
366 | } | ||
348 | } | 367 | } |
349 | $bookmarkDays = array_keys($bookmarkDays); | ||
350 | sort($bookmarkDays); | ||
351 | 368 | ||
352 | return array_map('strval', $bookmarkDays); | 369 | return $out; |
353 | } | 370 | } |
354 | 371 | ||
355 | /** | 372 | /** |
356 | * @inheritDoc | 373 | * @inheritDoc |
357 | */ | 374 | */ |
358 | public function filterDay(string $request) | 375 | public function getLatest(): ?Bookmark |
359 | { | 376 | { |
360 | $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC; | 377 | foreach ($this->search([], null, false, false, true) as $bookmark) { |
378 | return $bookmark; | ||
379 | } | ||
361 | 380 | ||
362 | return $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_DAY, $request, false, $visibility); | 381 | return null; |
363 | } | 382 | } |
364 | 383 | ||
365 | /** | 384 | /** |
diff --git a/application/bookmark/BookmarkServiceInterface.php b/application/bookmark/BookmarkServiceInterface.php index 37a54d03..08cdbb4e 100644 --- a/application/bookmark/BookmarkServiceInterface.php +++ b/application/bookmark/BookmarkServiceInterface.php | |||
@@ -20,13 +20,14 @@ interface BookmarkServiceInterface | |||
20 | /** | 20 | /** |
21 | * Find a bookmark by hash | 21 | * Find a bookmark by hash |
22 | * | 22 | * |
23 | * @param string $hash | 23 | * @param string $hash Bookmark's hash |
24 | * @param string|null $privateKey Optional key used to access private links while logged out | ||
24 | * | 25 | * |
25 | * @return Bookmark | 26 | * @return Bookmark |
26 | * | 27 | * |
27 | * @throws \Exception | 28 | * @throws \Exception |
28 | */ | 29 | */ |
29 | public function findByHash(string $hash): Bookmark; | 30 | public function findByHash(string $hash, string $privateKey = null); |
30 | 31 | ||
31 | /** | 32 | /** |
32 | * @param $url | 33 | * @param $url |
@@ -155,22 +156,29 @@ interface BookmarkServiceInterface | |||
155 | public function bookmarksCountPerTag(array $filteringTags = [], ?string $visibility = null): array; | 156 | public function bookmarksCountPerTag(array $filteringTags = [], ?string $visibility = null): array; |
156 | 157 | ||
157 | /** | 158 | /** |
158 | * Returns the list of days containing articles (oldest first) | 159 | * Return a list of bookmark matching provided period of time. |
160 | * It also update directly previous and next date outside of given period found in the datastore. | ||
159 | * | 161 | * |
160 | * @return array containing days (in format YYYYMMDD). | 162 | * @param \DateTimeInterface $from Starting date. |
163 | * @param \DateTimeInterface $to Ending date. | ||
164 | * @param \DateTimeInterface|null $previous (by reference) updated with first created date found before $from. | ||
165 | * @param \DateTimeInterface|null $next (by reference) updated with first created date found after $to. | ||
166 | * | ||
167 | * @return array List of bookmarks matching provided period of time. | ||
161 | */ | 168 | */ |
162 | public function days(): array; | 169 | public function findByDate( |
170 | \DateTimeInterface $from, | ||
171 | \DateTimeInterface $to, | ||
172 | ?\DateTimeInterface &$previous, | ||
173 | ?\DateTimeInterface &$next | ||
174 | ): array; | ||
163 | 175 | ||
164 | /** | 176 | /** |
165 | * Returns the list of articles for a given day. | 177 | * Returns the latest bookmark by creation date. |
166 | * | ||
167 | * @param string $request day to filter. Format: YYYYMMDD. | ||
168 | * | 178 | * |
169 | * @return Bookmark[] list of shaare found. | 179 | * @return Bookmark|null Found Bookmark or null if the datastore is empty. |
170 | * | ||
171 | * @throws BookmarkNotFoundException | ||
172 | */ | 180 | */ |
173 | public function filterDay(string $request); | 181 | public function getLatest(): ?Bookmark; |
174 | 182 | ||
175 | /** | 183 | /** |
176 | * Creates the default database after a fresh install. | 184 | * Creates the default database after a fresh install. |
diff --git a/application/config/ConfigJson.php b/application/config/ConfigJson.php index c0c0dab9..23b22269 100644 --- a/application/config/ConfigJson.php +++ b/application/config/ConfigJson.php | |||
@@ -19,7 +19,7 @@ class ConfigJson implements ConfigIO | |||
19 | $data = file_get_contents($filepath); | 19 | $data = file_get_contents($filepath); |
20 | $data = str_replace(self::getPhpHeaders(), '', $data); | 20 | $data = str_replace(self::getPhpHeaders(), '', $data); |
21 | $data = str_replace(self::getPhpSuffix(), '', $data); | 21 | $data = str_replace(self::getPhpSuffix(), '', $data); |
22 | $data = json_decode($data, true); | 22 | $data = json_decode(trim($data), true); |
23 | if ($data === null) { | 23 | if ($data === null) { |
24 | $errorCode = json_last_error(); | 24 | $errorCode = json_last_error(); |
25 | $error = sprintf( | 25 | $error = sprintf( |
@@ -73,7 +73,7 @@ class ConfigJson implements ConfigIO | |||
73 | */ | 73 | */ |
74 | public static function getPhpHeaders() | 74 | public static function getPhpHeaders() |
75 | { | 75 | { |
76 | return '<?php /*'. PHP_EOL; | 76 | return '<?php /*'; |
77 | } | 77 | } |
78 | 78 | ||
79 | /** | 79 | /** |
@@ -85,6 +85,6 @@ class ConfigJson implements ConfigIO | |||
85 | */ | 85 | */ |
86 | public static function getPhpSuffix() | 86 | public static function getPhpSuffix() |
87 | { | 87 | { |
88 | return PHP_EOL . '*/ ?>'; | 88 | return '*/ ?>'; |
89 | } | 89 | } |
90 | } | 90 | } |
diff --git a/application/front/controller/admin/ManageShaareController.php b/application/front/controller/admin/ManageShaareController.php deleted file mode 100644 index 908ebae3..00000000 --- a/application/front/controller/admin/ManageShaareController.php +++ /dev/null | |||
@@ -1,360 +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 | * Helper function used to display the shaare form whether it's a new or existing bookmark. | ||
325 | * | ||
326 | * @param array $link data used in template, either from parameters or from the data store | ||
327 | */ | ||
328 | protected function displayForm(array $link, bool $isNew, Request $request, Response $response): Response | ||
329 | { | ||
330 | $tags = $this->container->bookmarkService->bookmarksCountPerTag(); | ||
331 | if ($this->container->conf->get('formatter') === 'markdown') { | ||
332 | $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1; | ||
333 | } | ||
334 | |||
335 | $data = escape([ | ||
336 | 'link' => $link, | ||
337 | 'link_is_new' => $isNew, | ||
338 | 'http_referer' => $this->container->environment['HTTP_REFERER'] ?? '', | ||
339 | 'source' => $request->getParam('source') ?? '', | ||
340 | 'tags' => $tags, | ||
341 | 'default_private_links' => $this->container->conf->get('privacy.default_private_links', false), | ||
342 | 'async_metadata' => $this->container->conf->get('general.enable_async_metadata', true), | ||
343 | 'retrieve_description' => $this->container->conf->get('general.retrieve_description', false), | ||
344 | ]); | ||
345 | |||
346 | $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK); | ||
347 | |||
348 | foreach ($data as $key => $value) { | ||
349 | $this->assignView($key, $value); | ||
350 | } | ||
351 | |||
352 | $editLabel = false === $isNew ? t('Edit') .' ' : ''; | ||
353 | $this->assignView( | ||
354 | 'pagetitle', | ||
355 | $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
356 | ); | ||
357 | |||
358 | return $response->write($this->render(TemplatePage::EDIT_LINK)); | ||
359 | } | ||
360 | } | ||
diff --git a/application/front/controller/admin/ServerController.php b/application/front/controller/admin/ServerController.php new file mode 100644 index 00000000..bfc99422 --- /dev/null +++ b/application/front/controller/admin/ServerController.php | |||
@@ -0,0 +1,87 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Helper\ApplicationUtils; | ||
8 | use Shaarli\Helper\FileUtils; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Slim controller used to handle Server administration page, and actions. | ||
14 | */ | ||
15 | class ServerController extends ShaarliAdminController | ||
16 | { | ||
17 | /** @var string Cache type - main - by default pagecache/ and tmp/ */ | ||
18 | protected const CACHE_MAIN = 'main'; | ||
19 | |||
20 | /** @var string Cache type - thumbnails - by default cache/ */ | ||
21 | protected const CACHE_THUMB = 'thumbnails'; | ||
22 | |||
23 | /** | ||
24 | * GET /admin/server - Display page Server administration | ||
25 | */ | ||
26 | public function index(Request $request, Response $response): Response | ||
27 | { | ||
28 | $latestVersion = 'v' . ApplicationUtils::getVersion( | ||
29 | ApplicationUtils::$GIT_RAW_URL . '/latest/' . ApplicationUtils::$VERSION_FILE | ||
30 | ); | ||
31 | $currentVersion = ApplicationUtils::getVersion('./shaarli_version.php'); | ||
32 | $currentVersion = $currentVersion === 'dev' ? $currentVersion : 'v' . $currentVersion; | ||
33 | $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION)); | ||
34 | |||
35 | $this->assignView('php_version', PHP_VERSION); | ||
36 | $this->assignView('php_eol', format_date($phpEol, false)); | ||
37 | $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); | ||
38 | $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); | ||
39 | $this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf)); | ||
40 | $this->assignView('release_url', ApplicationUtils::$GITHUB_URL . '/releases/tag/' . $latestVersion); | ||
41 | $this->assignView('latest_version', $latestVersion); | ||
42 | $this->assignView('current_version', $currentVersion); | ||
43 | $this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode')); | ||
44 | $this->assignView('index_url', index_url($this->container->environment)); | ||
45 | $this->assignView('client_ip', client_ip_id($this->container->environment)); | ||
46 | $this->assignView('trusted_proxies', $this->container->conf->get('security.trusted_proxies', [])); | ||
47 | |||
48 | $this->assignView( | ||
49 | 'pagetitle', | ||
50 | t('Server administration') . ' - ' . $this->container->conf->get('general.title', 'Shaarli') | ||
51 | ); | ||
52 | |||
53 | return $response->write($this->render('server')); | ||
54 | } | ||
55 | |||
56 | /** | ||
57 | * GET /admin/clear-cache?type={$type} - Action to trigger cache folder clearing (either main or thumbnails). | ||
58 | */ | ||
59 | public function clearCache(Request $request, Response $response): Response | ||
60 | { | ||
61 | $exclude = ['.htaccess']; | ||
62 | |||
63 | if ($request->getQueryParam('type') === static::CACHE_THUMB) { | ||
64 | $folders = [$this->container->conf->get('resource.thumbnails_cache')]; | ||
65 | |||
66 | $this->saveWarningMessage( | ||
67 | t('Thumbnails cache has been cleared.') . ' ' . | ||
68 | '<a href="'. $this->container->basePath .'/admin/thumbnails">' . t('Please synchronize them.') .'</a>' | ||
69 | ); | ||
70 | } else { | ||
71 | $folders = [ | ||
72 | $this->container->conf->get('resource.page_cache'), | ||
73 | $this->container->conf->get('resource.raintpl_tmp'), | ||
74 | ]; | ||
75 | |||
76 | $this->saveSuccessMessage(t('Shaarli\'s cache folder has been cleared!')); | ||
77 | } | ||
78 | |||
79 | // Make sure that we don't delete root cache folder | ||
80 | $folders = array_map('realpath', array_values(array_filter(array_map('trim', $folders)))); | ||
81 | foreach ($folders as $folder) { | ||
82 | FileUtils::clearFolder($folder, false, $exclude); | ||
83 | } | ||
84 | |||
85 | return $this->redirect($response, '/admin/server'); | ||
86 | } | ||
87 | } | ||
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..18afc2d1 --- /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'] = $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/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php index a8019ead..78c474c9 100644 --- a/application/front/controller/visitor/BookmarkListController.php +++ b/application/front/controller/visitor/BookmarkListController.php | |||
@@ -137,8 +137,10 @@ class BookmarkListController extends ShaarliVisitorController | |||
137 | */ | 137 | */ |
138 | public function permalink(Request $request, Response $response, array $args): Response | 138 | public function permalink(Request $request, Response $response, array $args): Response |
139 | { | 139 | { |
140 | $privateKey = $request->getParam('key'); | ||
141 | |||
140 | try { | 142 | try { |
141 | $bookmark = $this->container->bookmarkService->findByHash($args['hash']); | 143 | $bookmark = $this->container->bookmarkService->findByHash($args['hash'], $privateKey); |
142 | } catch (BookmarkNotFoundException $e) { | 144 | } catch (BookmarkNotFoundException $e) { |
143 | $this->assignView('error_message', $e->getMessage()); | 145 | $this->assignView('error_message', $e->getMessage()); |
144 | 146 | ||
@@ -169,16 +171,24 @@ class BookmarkListController extends ShaarliVisitorController | |||
169 | */ | 171 | */ |
170 | protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool | 172 | protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool |
171 | { | 173 | { |
172 | // Logged in, not async retrieval, thumbnails enabled, and thumbnail should be updated | 174 | if (false === $this->container->loginManager->isLoggedIn()) { |
173 | if ($this->container->loginManager->isLoggedIn() | 175 | return false; |
174 | && true !== $this->container->conf->get('general.enable_async_metadata', true) | 176 | } |
175 | && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE | 177 | |
176 | && $bookmark->shouldUpdateThumbnail() | 178 | // If thumbnail should be updated, we reset it to null |
177 | ) { | 179 | if ($bookmark->shouldUpdateThumbnail()) { |
178 | $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); | 180 | $bookmark->setThumbnail(null); |
179 | $this->container->bookmarkService->set($bookmark, $writeDatastore); | 181 | |
180 | 182 | // Requires an update, not async retrieval, thumbnails enabled | |
181 | return true; | 183 | if ($bookmark->shouldUpdateThumbnail() |
184 | && true !== $this->container->conf->get('general.enable_async_metadata', true) | ||
185 | && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE | ||
186 | ) { | ||
187 | $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); | ||
188 | $this->container->bookmarkService->set($bookmark, $writeDatastore); | ||
189 | |||
190 | return true; | ||
191 | } | ||
182 | } | 192 | } |
183 | 193 | ||
184 | return false; | 194 | return false; |
diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php index 07617cf1..728bc2d8 100644 --- a/application/front/controller/visitor/DailyController.php +++ b/application/front/controller/visitor/DailyController.php | |||
@@ -5,8 +5,8 @@ declare(strict_types=1); | |||
5 | namespace Shaarli\Front\Controller\Visitor; | 5 | namespace Shaarli\Front\Controller\Visitor; |
6 | 6 | ||
7 | use DateTime; | 7 | use DateTime; |
8 | use DateTimeImmutable; | ||
9 | use Shaarli\Bookmark\Bookmark; | 8 | use Shaarli\Bookmark\Bookmark; |
9 | use Shaarli\Helper\DailyPageHelper; | ||
10 | use Shaarli\Render\TemplatePage; | 10 | use Shaarli\Render\TemplatePage; |
11 | use Slim\Http\Request; | 11 | use Slim\Http\Request; |
12 | use Slim\Http\Response; | 12 | use Slim\Http\Response; |
@@ -26,32 +26,20 @@ class DailyController extends ShaarliVisitorController | |||
26 | */ | 26 | */ |
27 | public function index(Request $request, Response $response): Response | 27 | public function index(Request $request, Response $response): Response |
28 | { | 28 | { |
29 | $day = $request->getQueryParam('day') ?? date('Ymd'); | 29 | $type = DailyPageHelper::extractRequestedType($request); |
30 | 30 | $format = DailyPageHelper::getFormatByType($type); | |
31 | $availableDates = $this->container->bookmarkService->days(); | 31 | $latestBookmark = $this->container->bookmarkService->getLatest(); |
32 | $nbAvailableDates = count($availableDates); | 32 | $dateTime = DailyPageHelper::extractRequestedDateTime($type, $request->getQueryParam($type), $latestBookmark); |
33 | $index = array_search($day, $availableDates); | 33 | $start = DailyPageHelper::getStartDateTimeByType($type, $dateTime); |
34 | 34 | $end = DailyPageHelper::getEndDateTimeByType($type, $dateTime); | |
35 | if ($index === false) { | 35 | $dailyDesc = DailyPageHelper::getDescriptionByType($type, $dateTime); |
36 | // no bookmarks for day, but at least one day with bookmarks | 36 | |
37 | $day = $availableDates[$nbAvailableDates - 1] ?? $day; | 37 | $linksToDisplay = $this->container->bookmarkService->findByDate( |
38 | $previousDay = $availableDates[$nbAvailableDates - 2] ?? ''; | 38 | $start, |
39 | } else { | 39 | $end, |
40 | $previousDay = $availableDates[$index - 1] ?? ''; | 40 | $previousDay, |
41 | $nextDay = $availableDates[$index + 1] ?? ''; | 41 | $nextDay |
42 | } | 42 | ); |
43 | |||
44 | if ($day === date('Ymd')) { | ||
45 | $this->assignView('dayDesc', t('Today')); | ||
46 | } elseif ($day === date('Ymd', strtotime('-1 days'))) { | ||
47 | $this->assignView('dayDesc', t('Yesterday')); | ||
48 | } | ||
49 | |||
50 | try { | ||
51 | $linksToDisplay = $this->container->bookmarkService->filterDay($day); | ||
52 | } catch (\Exception $exc) { | ||
53 | $linksToDisplay = []; | ||
54 | } | ||
55 | 43 | ||
56 | $formatter = $this->container->formatterFactory->getFormatter(); | 44 | $formatter = $this->container->formatterFactory->getFormatter(); |
57 | $formatter->addContextData('base_path', $this->container->basePath); | 45 | $formatter->addContextData('base_path', $this->container->basePath); |
@@ -63,13 +51,15 @@ class DailyController extends ShaarliVisitorController | |||
63 | $linksToDisplay[$key]['description'] = $bookmark->getDescription(); | 51 | $linksToDisplay[$key]['description'] = $bookmark->getDescription(); |
64 | } | 52 | } |
65 | 53 | ||
66 | $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); | ||
67 | $data = [ | 54 | $data = [ |
68 | 'linksToDisplay' => $linksToDisplay, | 55 | 'linksToDisplay' => $linksToDisplay, |
69 | 'day' => $dayDate->getTimestamp(), | 56 | 'dayDate' => $start, |
70 | 'dayDate' => $dayDate, | 57 | 'day' => $start->getTimestamp(), |
71 | 'previousday' => $previousDay ?? '', | 58 | 'previousday' => $previousDay ? $previousDay->format($format) : '', |
72 | 'nextday' => $nextDay ?? '', | 59 | 'nextday' => $nextDay ? $nextDay->format($format) : '', |
60 | 'dayDesc' => $dailyDesc, | ||
61 | 'type' => $type, | ||
62 | 'localizedType' => $this->translateType($type), | ||
73 | ]; | 63 | ]; |
74 | 64 | ||
75 | // Hooks are called before column construction so that plugins don't have to deal with columns. | 65 | // Hooks are called before column construction so that plugins don't have to deal with columns. |
@@ -82,7 +72,7 @@ class DailyController extends ShaarliVisitorController | |||
82 | $mainTitle = $this->container->conf->get('general.title', 'Shaarli'); | 72 | $mainTitle = $this->container->conf->get('general.title', 'Shaarli'); |
83 | $this->assignView( | 73 | $this->assignView( |
84 | 'pagetitle', | 74 | 'pagetitle', |
85 | t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle | 75 | $data['localizedType'] . ' - ' . $data['dayDesc'] . ' - ' . $mainTitle |
86 | ); | 76 | ); |
87 | 77 | ||
88 | return $response->write($this->render(TemplatePage::DAILY)); | 78 | return $response->write($this->render(TemplatePage::DAILY)); |
@@ -106,11 +96,14 @@ class DailyController extends ShaarliVisitorController | |||
106 | } | 96 | } |
107 | 97 | ||
108 | $days = []; | 98 | $days = []; |
99 | $type = DailyPageHelper::extractRequestedType($request); | ||
100 | $format = DailyPageHelper::getFormatByType($type); | ||
101 | $length = DailyPageHelper::getRssLengthByType($type); | ||
109 | foreach ($this->container->bookmarkService->search() as $bookmark) { | 102 | foreach ($this->container->bookmarkService->search() as $bookmark) { |
110 | $day = $bookmark->getCreated()->format('Ymd'); | 103 | $day = $bookmark->getCreated()->format($format); |
111 | 104 | ||
112 | // Stop iterating after DAILY_RSS_NB_DAYS entries | 105 | // Stop iterating after DAILY_RSS_NB_DAYS entries |
113 | if (count($days) === static::$DAILY_RSS_NB_DAYS && !isset($days[$day])) { | 106 | if (count($days) === $length && !isset($days[$day])) { |
114 | break; | 107 | break; |
115 | } | 108 | } |
116 | 109 | ||
@@ -127,12 +120,19 @@ class DailyController extends ShaarliVisitorController | |||
127 | 120 | ||
128 | /** @var Bookmark[] $bookmarks */ | 121 | /** @var Bookmark[] $bookmarks */ |
129 | foreach ($days as $day => $bookmarks) { | 122 | foreach ($days as $day => $bookmarks) { |
130 | $dayDatetime = DateTimeImmutable::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); | 123 | $dayDateTime = DailyPageHelper::extractRequestedDateTime($type, (string) $day); |
124 | $endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dayDateTime); | ||
125 | |||
126 | // We only want the RSS entry to be published when the period is over. | ||
127 | if (new DateTime() < $endDateTime) { | ||
128 | continue; | ||
129 | } | ||
130 | |||
131 | $dataPerDay[$day] = [ | 131 | $dataPerDay[$day] = [ |
132 | 'date' => $dayDatetime, | 132 | 'date' => $endDateTime, |
133 | 'date_rss' => $dayDatetime->format(DateTime::RSS), | 133 | 'date_rss' => $endDateTime->format(DateTime::RSS), |
134 | 'date_human' => format_date($dayDatetime, false, true), | 134 | 'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime), |
135 | 'absolute_url' => $indexUrl . 'daily?day=' . $day, | 135 | 'absolute_url' => $indexUrl . 'daily?'. $type .'=' . $day, |
136 | 'links' => [], | 136 | 'links' => [], |
137 | ]; | 137 | ]; |
138 | 138 | ||
@@ -141,16 +141,20 @@ class DailyController extends ShaarliVisitorController | |||
141 | 141 | ||
142 | // Make permalink URL absolute | 142 | // Make permalink URL absolute |
143 | if ($bookmark->isNote()) { | 143 | if ($bookmark->isNote()) { |
144 | $dataPerDay[$day]['links'][$key]['url'] = $indexUrl . $bookmark->getUrl(); | 144 | $dataPerDay[$day]['links'][$key]['url'] = rtrim($indexUrl, '/') . $bookmark->getUrl(); |
145 | } | 145 | } |
146 | } | 146 | } |
147 | } | 147 | } |
148 | 148 | ||
149 | $this->assignView('title', $this->container->conf->get('general.title', 'Shaarli')); | 149 | $this->assignAllView([ |
150 | $this->assignView('index_url', $indexUrl); | 150 | 'title' => $this->container->conf->get('general.title', 'Shaarli'), |
151 | $this->assignView('page_url', $pageUrl); | 151 | 'index_url' => $indexUrl, |
152 | $this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false)); | 152 | 'page_url' => $pageUrl, |
153 | $this->assignView('days', $dataPerDay); | 153 | 'hide_timestamps' => $this->container->conf->get('privacy.hide_timestamps', false), |
154 | 'days' => $dataPerDay, | ||
155 | 'type' => $type, | ||
156 | 'localizedType' => $this->translateType($type), | ||
157 | ]); | ||
154 | 158 | ||
155 | $rssContent = $this->render(TemplatePage::DAILY_RSS); | 159 | $rssContent = $this->render(TemplatePage::DAILY_RSS); |
156 | 160 | ||
@@ -189,4 +193,13 @@ class DailyController extends ShaarliVisitorController | |||
189 | 193 | ||
190 | return $columns; | 194 | return $columns; |
191 | } | 195 | } |
196 | |||
197 | protected function translateType($type): string | ||
198 | { | ||
199 | return [ | ||
200 | t('day') => t('Daily'), | ||
201 | t('week') => t('Weekly'), | ||
202 | t('month') => t('Monthly'), | ||
203 | ][t($type)] ?? t('Daily'); | ||
204 | } | ||
192 | } | 205 | } |
diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php index 7cb32777..22329294 100644 --- a/application/front/controller/visitor/InstallController.php +++ b/application/front/controller/visitor/InstallController.php | |||
@@ -4,10 +4,10 @@ declare(strict_types=1); | |||
4 | 4 | ||
5 | namespace Shaarli\Front\Controller\Visitor; | 5 | namespace Shaarli\Front\Controller\Visitor; |
6 | 6 | ||
7 | use Shaarli\ApplicationUtils; | ||
8 | use Shaarli\Container\ShaarliContainer; | 7 | use Shaarli\Container\ShaarliContainer; |
9 | use Shaarli\Front\Exception\AlreadyInstalledException; | 8 | use Shaarli\Front\Exception\AlreadyInstalledException; |
10 | use Shaarli\Front\Exception\ResourcePermissionException; | 9 | use Shaarli\Front\Exception\ResourcePermissionException; |
10 | use Shaarli\Helper\ApplicationUtils; | ||
11 | use Shaarli\Languages; | 11 | use Shaarli\Languages; |
12 | use Shaarli\Security\SessionManager; | 12 | use Shaarli\Security\SessionManager; |
13 | use Slim\Http\Request; | 13 | use Slim\Http\Request; |
@@ -53,6 +53,16 @@ class InstallController extends ShaarliVisitorController | |||
53 | $this->assignView('cities', $cities); | 53 | $this->assignView('cities', $cities); |
54 | $this->assignView('languages', Languages::getAvailableLanguages()); | 54 | $this->assignView('languages', Languages::getAvailableLanguages()); |
55 | 55 | ||
56 | $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION)); | ||
57 | |||
58 | $this->assignView('php_version', PHP_VERSION); | ||
59 | $this->assignView('php_eol', format_date($phpEol, false)); | ||
60 | $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); | ||
61 | $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); | ||
62 | $this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf)); | ||
63 | |||
64 | $this->assignView('pagetitle', t('Install Shaarli')); | ||
65 | |||
56 | return $response->write($this->render('install')); | 66 | return $response->write($this->render('install')); |
57 | } | 67 | } |
58 | 68 | ||
@@ -150,7 +160,7 @@ class InstallController extends ShaarliVisitorController | |||
150 | protected function checkPermissions(): bool | 160 | protected function checkPermissions(): bool |
151 | { | 161 | { |
152 | // Ensure Shaarli has proper access to its resources | 162 | // Ensure Shaarli has proper access to its resources |
153 | $errors = ApplicationUtils::checkResourcePermissions($this->container->conf); | 163 | $errors = ApplicationUtils::checkResourcePermissions($this->container->conf, true); |
154 | if (empty($errors)) { | 164 | if (empty($errors)) { |
155 | return true; | 165 | return true; |
156 | } | 166 | } |
diff --git a/application/ApplicationUtils.php b/application/helper/ApplicationUtils.php index 3aa21829..4b34e114 100644 --- a/application/ApplicationUtils.php +++ b/application/helper/ApplicationUtils.php | |||
@@ -1,5 +1,5 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli; | 2 | namespace Shaarli\Helper; |
3 | 3 | ||
4 | use Exception; | 4 | use Exception; |
5 | use Shaarli\Config\ConfigManager; | 5 | use Shaarli\Config\ConfigManager; |
@@ -14,8 +14,9 @@ class ApplicationUtils | |||
14 | */ | 14 | */ |
15 | public static $VERSION_FILE = 'shaarli_version.php'; | 15 | public static $VERSION_FILE = 'shaarli_version.php'; |
16 | 16 | ||
17 | private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli'; | 17 | public static $GITHUB_URL = 'https://github.com/shaarli/Shaarli'; |
18 | private static $GIT_BRANCHES = array('latest', 'stable'); | 18 | public static $GIT_RAW_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli'; |
19 | public static $GIT_BRANCHES = array('latest', 'stable'); | ||
19 | private static $VERSION_START_TAG = '<?php /* '; | 20 | private static $VERSION_START_TAG = '<?php /* '; |
20 | private static $VERSION_END_TAG = ' */ ?>'; | 21 | private static $VERSION_END_TAG = ' */ ?>'; |
21 | 22 | ||
@@ -125,7 +126,7 @@ class ApplicationUtils | |||
125 | // Late Static Binding allows overriding within tests | 126 | // Late Static Binding allows overriding within tests |
126 | // See http://php.net/manual/en/language.oop5.late-static-bindings.php | 127 | // See http://php.net/manual/en/language.oop5.late-static-bindings.php |
127 | $latestVersion = static::getVersion( | 128 | $latestVersion = static::getVersion( |
128 | self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE | 129 | self::$GIT_RAW_URL . '/' . $branch . '/' . self::$VERSION_FILE |
129 | ); | 130 | ); |
130 | 131 | ||
131 | if (!$latestVersion) { | 132 | if (!$latestVersion) { |
@@ -171,35 +172,45 @@ class ApplicationUtils | |||
171 | /** | 172 | /** |
172 | * Checks Shaarli has the proper access permissions to its resources | 173 | * Checks Shaarli has the proper access permissions to its resources |
173 | * | 174 | * |
174 | * @param ConfigManager $conf Configuration Manager instance. | 175 | * @param ConfigManager $conf Configuration Manager instance. |
176 | * @param bool $minimalMode In minimal mode we only check permissions to be able to display a template. | ||
177 | * Currently we only need to be able to read the theme and write in raintpl cache. | ||
175 | * | 178 | * |
176 | * @return array A list of the detected configuration issues | 179 | * @return array A list of the detected configuration issues |
177 | */ | 180 | */ |
178 | public static function checkResourcePermissions($conf) | 181 | public static function checkResourcePermissions(ConfigManager $conf, bool $minimalMode = false): array |
179 | { | 182 | { |
180 | $errors = array(); | 183 | $errors = []; |
181 | $rainTplDir = rtrim($conf->get('resource.raintpl_tpl'), '/'); | 184 | $rainTplDir = rtrim($conf->get('resource.raintpl_tpl'), '/'); |
182 | 185 | ||
183 | // Check script and template directories are readable | 186 | // Check script and template directories are readable |
184 | foreach (array( | 187 | foreach ([ |
185 | 'application', | 188 | 'application', |
186 | 'inc', | 189 | 'inc', |
187 | 'plugins', | 190 | 'plugins', |
188 | $rainTplDir, | 191 | $rainTplDir, |
189 | $rainTplDir . '/' . $conf->get('resource.theme'), | 192 | $rainTplDir . '/' . $conf->get('resource.theme'), |
190 | ) as $path) { | 193 | ] as $path) { |
191 | if (!is_readable(realpath($path))) { | 194 | if (!is_readable(realpath($path))) { |
192 | $errors[] = '"' . $path . '" ' . t('directory is not readable'); | 195 | $errors[] = '"' . $path . '" ' . t('directory is not readable'); |
193 | } | 196 | } |
194 | } | 197 | } |
195 | 198 | ||
196 | // Check cache and data directories are readable and writable | 199 | // Check cache and data directories are readable and writable |
197 | foreach (array( | 200 | if ($minimalMode) { |
198 | $conf->get('resource.thumbnails_cache'), | 201 | $folders = [ |
199 | $conf->get('resource.data_dir'), | 202 | $conf->get('resource.raintpl_tmp'), |
200 | $conf->get('resource.page_cache'), | 203 | ]; |
201 | $conf->get('resource.raintpl_tmp'), | 204 | } else { |
202 | ) as $path) { | 205 | $folders = [ |
206 | $conf->get('resource.thumbnails_cache'), | ||
207 | $conf->get('resource.data_dir'), | ||
208 | $conf->get('resource.page_cache'), | ||
209 | $conf->get('resource.raintpl_tmp'), | ||
210 | ]; | ||
211 | } | ||
212 | |||
213 | foreach ($folders as $path) { | ||
203 | if (!is_readable(realpath($path))) { | 214 | if (!is_readable(realpath($path))) { |
204 | $errors[] = '"' . $path . '" ' . t('directory is not readable'); | 215 | $errors[] = '"' . $path . '" ' . t('directory is not readable'); |
205 | } | 216 | } |
@@ -208,6 +219,10 @@ class ApplicationUtils | |||
208 | } | 219 | } |
209 | } | 220 | } |
210 | 221 | ||
222 | if ($minimalMode) { | ||
223 | return $errors; | ||
224 | } | ||
225 | |||
211 | // Check configuration files are readable and writable | 226 | // Check configuration files are readable and writable |
212 | foreach (array( | 227 | foreach (array( |
213 | $conf->getConfigFileExt(), | 228 | $conf->getConfigFileExt(), |
@@ -246,4 +261,54 @@ class ApplicationUtils | |||
246 | { | 261 | { |
247 | return hash_hmac('sha256', $currentVersion, $salt); | 262 | return hash_hmac('sha256', $currentVersion, $salt); |
248 | } | 263 | } |
264 | |||
265 | /** | ||
266 | * Get a list of PHP extensions used by Shaarli. | ||
267 | * | ||
268 | * @return array[] List of extension with following keys: | ||
269 | * - name: extension name | ||
270 | * - required: whether the extension is required to use Shaarli | ||
271 | * - desc: short description of extension usage in Shaarli | ||
272 | * - loaded: whether the extension is properly loaded or not | ||
273 | */ | ||
274 | public static function getPhpExtensionsRequirement(): array | ||
275 | { | ||
276 | $extensions = [ | ||
277 | ['name' => 'json', 'required' => true, 'desc' => t('Configuration parsing')], | ||
278 | ['name' => 'simplexml', 'required' => true, 'desc' => t('Slim Framework (routing, etc.)')], | ||
279 | ['name' => 'mbstring', 'required' => true, 'desc' => t('Multibyte (Unicode) string support')], | ||
280 | ['name' => 'gd', 'required' => false, 'desc' => t('Required to use thumbnails')], | ||
281 | ['name' => 'intl', 'required' => false, 'desc' => t('Localized text sorting (e.g. e->è->f)')], | ||
282 | ['name' => 'curl', 'required' => false, 'desc' => t('Better retrieval of bookmark metadata and thumbnail')], | ||
283 | ['name' => 'gettext', 'required' => false, 'desc' => t('Use the translation system in gettext mode')], | ||
284 | ['name' => 'ldap', 'required' => false, 'desc' => t('Login using LDAP server')], | ||
285 | ]; | ||
286 | |||
287 | foreach ($extensions as &$extension) { | ||
288 | $extension['loaded'] = extension_loaded($extension['name']); | ||
289 | } | ||
290 | |||
291 | return $extensions; | ||
292 | } | ||
293 | |||
294 | /** | ||
295 | * Return the EOL date of given PHP version. If the version is unknown, | ||
296 | * we return today + 2 years. | ||
297 | * | ||
298 | * @param string $fullVersion PHP version, e.g. 7.4.7 | ||
299 | * | ||
300 | * @return string Date format: YYYY-MM-DD | ||
301 | */ | ||
302 | public static function getPhpEol(string $fullVersion): string | ||
303 | { | ||
304 | preg_match('/(\d+\.\d+)\.\d+/', $fullVersion, $matches); | ||
305 | |||
306 | return [ | ||
307 | '7.1' => '2019-12-01', | ||
308 | '7.2' => '2020-11-30', | ||
309 | '7.3' => '2021-12-06', | ||
310 | '7.4' => '2022-11-28', | ||
311 | '8.0' => '2023-12-01', | ||
312 | ][$matches[1]] ?? (new \DateTime('+2 year'))->format('Y-m-d'); | ||
313 | } | ||
249 | } | 314 | } |
diff --git a/application/helper/DailyPageHelper.php b/application/helper/DailyPageHelper.php new file mode 100644 index 00000000..5fabc907 --- /dev/null +++ b/application/helper/DailyPageHelper.php | |||
@@ -0,0 +1,208 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Helper; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Slim\Http\Request; | ||
9 | |||
10 | class DailyPageHelper | ||
11 | { | ||
12 | public const MONTH = 'month'; | ||
13 | public const WEEK = 'week'; | ||
14 | public const DAY = 'day'; | ||
15 | |||
16 | /** | ||
17 | * Extracts the type of the daily to display from the HTTP request parameters | ||
18 | * | ||
19 | * @param Request $request HTTP request | ||
20 | * | ||
21 | * @return string month/week/day | ||
22 | */ | ||
23 | public static function extractRequestedType(Request $request): string | ||
24 | { | ||
25 | if ($request->getQueryParam(static::MONTH) !== null) { | ||
26 | return static::MONTH; | ||
27 | } elseif ($request->getQueryParam(static::WEEK) !== null) { | ||
28 | return static::WEEK; | ||
29 | } | ||
30 | |||
31 | return static::DAY; | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Extracts a DateTimeImmutable from provided HTTP request. | ||
36 | * If no parameter is provided, we rely on the creation date of the latest provided created bookmark. | ||
37 | * If the datastore is empty or no bookmark is provided, we use the current date. | ||
38 | * | ||
39 | * @param string $type month/week/day | ||
40 | * @param string|null $requestedDate Input string extracted from the request | ||
41 | * @param Bookmark|null $latestBookmark Latest bookmark found in the datastore (by date) | ||
42 | * | ||
43 | * @return \DateTimeImmutable from input or latest bookmark. | ||
44 | * | ||
45 | * @throws \Exception Type not supported. | ||
46 | */ | ||
47 | public static function extractRequestedDateTime( | ||
48 | string $type, | ||
49 | ?string $requestedDate, | ||
50 | Bookmark $latestBookmark = null | ||
51 | ): \DateTimeImmutable { | ||
52 | $format = static::getFormatByType($type); | ||
53 | if (empty($requestedDate)) { | ||
54 | return $latestBookmark instanceof Bookmark | ||
55 | ? new \DateTimeImmutable($latestBookmark->getCreated()->format(\DateTime::ATOM)) | ||
56 | : new \DateTimeImmutable() | ||
57 | ; | ||
58 | } | ||
59 | |||
60 | // W is not supported by createFromFormat... | ||
61 | if ($type === static::WEEK) { | ||
62 | return (new \DateTimeImmutable()) | ||
63 | ->setISODate((int) substr($requestedDate, 0, 4), (int) substr($requestedDate, 4, 2)) | ||
64 | ; | ||
65 | } | ||
66 | |||
67 | return \DateTimeImmutable::createFromFormat($format, $requestedDate); | ||
68 | } | ||
69 | |||
70 | /** | ||
71 | * Get the DateTime format used by provided type | ||
72 | * Examples: | ||
73 | * - day: 20201016 (<year><month><day>) | ||
74 | * - week: 202041 (<year><week number>) | ||
75 | * - month: 202010 (<year><month>) | ||
76 | * | ||
77 | * @param string $type month/week/day | ||
78 | * | ||
79 | * @return string DateTime compatible format | ||
80 | * | ||
81 | * @see https://www.php.net/manual/en/datetime.format.php | ||
82 | * | ||
83 | * @throws \Exception Type not supported. | ||
84 | */ | ||
85 | public static function getFormatByType(string $type): string | ||
86 | { | ||
87 | switch ($type) { | ||
88 | case static::MONTH: | ||
89 | return 'Ym'; | ||
90 | case static::WEEK: | ||
91 | return 'YW'; | ||
92 | case static::DAY: | ||
93 | return 'Ymd'; | ||
94 | default: | ||
95 | throw new \Exception('Unsupported daily format type'); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | /** | ||
100 | * Get the first DateTime of the time period depending on given datetime and type. | ||
101 | * Note: DateTimeImmutable is required because we rely heavily on DateTime->modify() syntax | ||
102 | * and we don't want to alter original datetime. | ||
103 | * | ||
104 | * @param string $type month/week/day | ||
105 | * @param \DateTimeImmutable $requested DateTime extracted from request input | ||
106 | * (should come from extractRequestedDateTime) | ||
107 | * | ||
108 | * @return \DateTimeInterface First DateTime of the time period | ||
109 | * | ||
110 | * @throws \Exception Type not supported. | ||
111 | */ | ||
112 | public static function getStartDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface | ||
113 | { | ||
114 | switch ($type) { | ||
115 | case static::MONTH: | ||
116 | return $requested->modify('first day of this month midnight'); | ||
117 | case static::WEEK: | ||
118 | return $requested->modify('Monday this week midnight'); | ||
119 | case static::DAY: | ||
120 | return $requested->modify('Today midnight'); | ||
121 | default: | ||
122 | throw new \Exception('Unsupported daily format type'); | ||
123 | } | ||
124 | } | ||
125 | |||
126 | /** | ||
127 | * Get the last DateTime of the time period depending on given datetime and type. | ||
128 | * Note: DateTimeImmutable is required because we rely heavily on DateTime->modify() syntax | ||
129 | * and we don't want to alter original datetime. | ||
130 | * | ||
131 | * @param string $type month/week/day | ||
132 | * @param \DateTimeImmutable $requested DateTime extracted from request input | ||
133 | * (should come from extractRequestedDateTime) | ||
134 | * | ||
135 | * @return \DateTimeInterface Last DateTime of the time period | ||
136 | * | ||
137 | * @throws \Exception Type not supported. | ||
138 | */ | ||
139 | public static function getEndDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface | ||
140 | { | ||
141 | switch ($type) { | ||
142 | case static::MONTH: | ||
143 | return $requested->modify('last day of this month 23:59:59'); | ||
144 | case static::WEEK: | ||
145 | return $requested->modify('Sunday this week 23:59:59'); | ||
146 | case static::DAY: | ||
147 | return $requested->modify('Today 23:59:59'); | ||
148 | default: | ||
149 | throw new \Exception('Unsupported daily format type'); | ||
150 | } | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * Get localized description of the time period depending on given datetime and type. | ||
155 | * Example: for a month period, it returns `October, 2020`. | ||
156 | * | ||
157 | * @param string $type month/week/day | ||
158 | * @param \DateTimeImmutable $requested DateTime extracted from request input | ||
159 | * (should come from extractRequestedDateTime) | ||
160 | * | ||
161 | * @return string Localized time period description | ||
162 | * | ||
163 | * @throws \Exception Type not supported. | ||
164 | */ | ||
165 | public static function getDescriptionByType(string $type, \DateTimeImmutable $requested): string | ||
166 | { | ||
167 | switch ($type) { | ||
168 | case static::MONTH: | ||
169 | return $requested->format('F') . ', ' . $requested->format('Y'); | ||
170 | case static::WEEK: | ||
171 | $requested = $requested->modify('Monday this week'); | ||
172 | return t('Week') . ' ' . $requested->format('W') . ' (' . format_date($requested, false) . ')'; | ||
173 | case static::DAY: | ||
174 | $out = ''; | ||
175 | if ($requested->format('Ymd') === date('Ymd')) { | ||
176 | $out = t('Today') . ' - '; | ||
177 | } elseif ($requested->format('Ymd') === date('Ymd', strtotime('-1 days'))) { | ||
178 | $out = t('Yesterday') . ' - '; | ||
179 | } | ||
180 | return $out . format_date($requested, false); | ||
181 | default: | ||
182 | throw new \Exception('Unsupported daily format type'); | ||
183 | } | ||
184 | } | ||
185 | |||
186 | /** | ||
187 | * Get the number of items to display in the RSS feed depending on the given type. | ||
188 | * | ||
189 | * @param string $type month/week/day | ||
190 | * | ||
191 | * @return int number of elements | ||
192 | * | ||
193 | * @throws \Exception Type not supported. | ||
194 | */ | ||
195 | public static function getRssLengthByType(string $type): int | ||
196 | { | ||
197 | switch ($type) { | ||
198 | case static::MONTH: | ||
199 | return 12; // 1 year | ||
200 | case static::WEEK: | ||
201 | return 26; // ~6 months | ||
202 | case static::DAY: | ||
203 | return 30; // ~1 month | ||
204 | default: | ||
205 | throw new \Exception('Unsupported daily format type'); | ||
206 | } | ||
207 | } | ||
208 | } | ||
diff --git a/application/FileUtils.php b/application/helper/FileUtils.php index 30560bfc..2eac0793 100644 --- a/application/FileUtils.php +++ b/application/helper/FileUtils.php | |||
@@ -1,6 +1,6 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | namespace Shaarli; | 3 | namespace Shaarli\Helper; |
4 | 4 | ||
5 | use Shaarli\Exceptions\IOException; | 5 | use Shaarli\Exceptions\IOException; |
6 | 6 | ||
@@ -81,4 +81,60 @@ class FileUtils | |||
81 | ) | 81 | ) |
82 | ); | 82 | ); |
83 | } | 83 | } |
84 | |||
85 | /** | ||
86 | * Recursively deletes a folder content, and deletes itself optionally. | ||
87 | * If an excluded file is found, folders won't be deleted. | ||
88 | * | ||
89 | * Additional security: raise an exception if it tries to delete a folder outside of Shaarli directory. | ||
90 | * | ||
91 | * @param string $path | ||
92 | * @param bool $selfDelete Delete the provided folder if true, only its content if false. | ||
93 | * @param array $exclude | ||
94 | */ | ||
95 | public static function clearFolder(string $path, bool $selfDelete, array $exclude = []): bool | ||
96 | { | ||
97 | $skipped = false; | ||
98 | |||
99 | if (!is_dir($path)) { | ||
100 | throw new IOException(t('Provided path is not a directory.')); | ||
101 | } | ||
102 | |||
103 | if (!static::isPathInShaarliFolder($path)) { | ||
104 | throw new IOException(t('Trying to delete a folder outside of Shaarli path.')); | ||
105 | } | ||
106 | |||
107 | foreach (new \DirectoryIterator($path) as $file) { | ||
108 | if($file->isDot()) { | ||
109 | continue; | ||
110 | } | ||
111 | |||
112 | if (in_array($file->getBasename(), $exclude, true)) { | ||
113 | $skipped = true; | ||
114 | continue; | ||
115 | } | ||
116 | |||
117 | if ($file->isFile()) { | ||
118 | unlink($file->getPathname()); | ||
119 | } elseif($file->isDir()) { | ||
120 | $skipped = static::clearFolder($file->getRealPath(), true, $exclude) || $skipped; | ||
121 | } | ||
122 | } | ||
123 | |||
124 | if ($selfDelete && !$skipped) { | ||
125 | rmdir($path); | ||
126 | } | ||
127 | |||
128 | return $skipped; | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * Checks that the given path is inside Shaarli directory. | ||
133 | */ | ||
134 | public static function isPathInShaarliFolder(string $path): bool | ||
135 | { | ||
136 | $rootDirectory = dirname(dirname(dirname(__FILE__))); | ||
137 | |||
138 | return strpos(realpath($path), $rootDirectory) !== false; | ||
139 | } | ||
84 | } | 140 | } |
diff --git a/application/legacy/LegacyLinkDB.php b/application/legacy/LegacyLinkDB.php index 7bf76fd4..5c02a21b 100644 --- a/application/legacy/LegacyLinkDB.php +++ b/application/legacy/LegacyLinkDB.php | |||
@@ -8,7 +8,7 @@ use DateTime; | |||
8 | use Iterator; | 8 | use Iterator; |
9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | 9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; |
10 | use Shaarli\Exceptions\IOException; | 10 | use Shaarli\Exceptions\IOException; |
11 | use Shaarli\FileUtils; | 11 | use Shaarli\Helper\FileUtils; |
12 | use Shaarli\Render\PageCacheManager; | 12 | use Shaarli\Render\PageCacheManager; |
13 | 13 | ||
14 | /** | 14 | /** |
diff --git a/application/legacy/LegacyUpdater.php b/application/legacy/LegacyUpdater.php index 0ab3a55b..fe1a286f 100644 --- a/application/legacy/LegacyUpdater.php +++ b/application/legacy/LegacyUpdater.php | |||
@@ -7,7 +7,6 @@ use RainTPL; | |||
7 | use ReflectionClass; | 7 | use ReflectionClass; |
8 | use ReflectionException; | 8 | use ReflectionException; |
9 | use ReflectionMethod; | 9 | use ReflectionMethod; |
10 | use Shaarli\ApplicationUtils; | ||
11 | use Shaarli\Bookmark\Bookmark; | 10 | use Shaarli\Bookmark\Bookmark; |
12 | use Shaarli\Bookmark\BookmarkArray; | 11 | use Shaarli\Bookmark\BookmarkArray; |
13 | use Shaarli\Bookmark\BookmarkFilter; | 12 | use Shaarli\Bookmark\BookmarkFilter; |
@@ -17,6 +16,7 @@ use Shaarli\Config\ConfigJson; | |||
17 | use Shaarli\Config\ConfigManager; | 16 | use Shaarli\Config\ConfigManager; |
18 | use Shaarli\Config\ConfigPhp; | 17 | use Shaarli\Config\ConfigPhp; |
19 | use Shaarli\Exceptions\IOException; | 18 | use Shaarli\Exceptions\IOException; |
19 | use Shaarli\Helper\ApplicationUtils; | ||
20 | use Shaarli\Thumbnailer; | 20 | use Shaarli\Thumbnailer; |
21 | use Shaarli\Updater\Exception\UpdaterException; | 21 | use Shaarli\Updater\Exception\UpdaterException; |
22 | 22 | ||
diff --git a/application/render/PageBuilder.php b/application/render/PageBuilder.php index 512bb79e..c2fae705 100644 --- a/application/render/PageBuilder.php +++ b/application/render/PageBuilder.php | |||
@@ -5,9 +5,9 @@ namespace Shaarli\Render; | |||
5 | use Exception; | 5 | use Exception; |
6 | use Psr\Log\LoggerInterface; | 6 | use Psr\Log\LoggerInterface; |
7 | use RainTPL; | 7 | use RainTPL; |
8 | use Shaarli\ApplicationUtils; | ||
9 | use Shaarli\Bookmark\BookmarkServiceInterface; | 8 | use Shaarli\Bookmark\BookmarkServiceInterface; |
10 | use Shaarli\Config\ConfigManager; | 9 | use Shaarli\Config\ConfigManager; |
10 | use Shaarli\Helper\ApplicationUtils; | ||
11 | use Shaarli\Security\SessionManager; | 11 | use Shaarli\Security\SessionManager; |
12 | use Shaarli\Thumbnailer; | 12 | use Shaarli\Thumbnailer; |
13 | 13 | ||
@@ -160,7 +160,7 @@ class PageBuilder | |||
160 | 160 | ||
161 | $this->tpl->assign('formatter', $this->conf->get('formatter', 'default')); | 161 | $this->tpl->assign('formatter', $this->conf->get('formatter', 'default')); |
162 | 162 | ||
163 | $this->tpl->assign('links_per_page', $this->session['LINKS_PER_PAGE']); | 163 | $this->tpl->assign('links_per_page', $this->session['LINKS_PER_PAGE'] ?? 20); |
164 | 164 | ||
165 | // To be removed with a proper theme configuration. | 165 | // To be removed with a proper theme configuration. |
166 | $this->tpl->assign('conf', $this->conf); | 166 | $this->tpl->assign('conf', $this->conf); |
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/application/security/BanManager.php b/application/security/BanManager.php index f72c8b7b..288cbde0 100644 --- a/application/security/BanManager.php +++ b/application/security/BanManager.php | |||
@@ -4,7 +4,7 @@ | |||
4 | namespace Shaarli\Security; | 4 | namespace Shaarli\Security; |
5 | 5 | ||
6 | use Psr\Log\LoggerInterface; | 6 | use Psr\Log\LoggerInterface; |
7 | use Shaarli\FileUtils; | 7 | use Shaarli\Helper\FileUtils; |
8 | 8 | ||
9 | /** | 9 | /** |
10 | * Class BanManager | 10 | * Class BanManager |
diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php index 36df8c1c..96bf193c 100644 --- a/application/security/SessionManager.php +++ b/application/security/SessionManager.php | |||
@@ -293,9 +293,12 @@ class SessionManager | |||
293 | return session_start(); | 293 | return session_start(); |
294 | } | 294 | } |
295 | 295 | ||
296 | public function cookieParameters(int $lifeTime, string $path, string $domain): bool | 296 | /** |
297 | * Be careful, return type of session_set_cookie_params() changed between PHP 7.1 and 7.2. | ||
298 | */ | ||
299 | public function cookieParameters(int $lifeTime, string $path, string $domain): void | ||
297 | { | 300 | { |
298 | return session_set_cookie_params($lifeTime, $path, $domain); | 301 | session_set_cookie_params($lifeTime, $path, $domain); |
299 | } | 302 | } |
300 | 303 | ||
301 | public function regenerateId(bool $deleteOldSession = false): bool | 304 | public function regenerateId(bool $deleteOldSession = false): bool |
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 286ac83b..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 { |
@@ -1047,7 +1051,7 @@ body, | |||
1047 | } | 1051 | } |
1048 | 1052 | ||
1049 | table { | 1053 | table { |
1050 | margin: auto; | 1054 | margin: 10px auto 25px auto; |
1051 | width: 90%; | 1055 | width: 90%; |
1052 | 1056 | ||
1053 | .order { | 1057 | .order { |
@@ -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 | } |
@@ -1696,6 +1705,123 @@ form { | |||
1696 | } | 1705 | } |
1697 | } | 1706 | } |
1698 | 1707 | ||
1708 | // SERVER PAGE | ||
1709 | |||
1710 | .server-tables-page, | ||
1711 | .server-tables { | ||
1712 | .window-subtitle { | ||
1713 | &::before { | ||
1714 | display: block; | ||
1715 | margin: 8px auto; | ||
1716 | background: linear-gradient(to right, var(--background-color), $dark-grey, var(--background-color)); | ||
1717 | width: 50%; | ||
1718 | height: 1px; | ||
1719 | content: ''; | ||
1720 | } | ||
1721 | } | ||
1722 | |||
1723 | .server-row { | ||
1724 | p { | ||
1725 | height: 25px; | ||
1726 | padding: 0 10px; | ||
1727 | } | ||
1728 | } | ||
1729 | |||
1730 | .server-label { | ||
1731 | text-align: right; | ||
1732 | font-weight: bold; | ||
1733 | } | ||
1734 | |||
1735 | i { | ||
1736 | &.fa-color-green { | ||
1737 | color: $main-green; | ||
1738 | } | ||
1739 | |||
1740 | &.fa-color-orange { | ||
1741 | color: $orange; | ||
1742 | } | ||
1743 | |||
1744 | &.fa-color-red { | ||
1745 | color: $red; | ||
1746 | } | ||
1747 | } | ||
1748 | |||
1749 | @media screen and (max-width: 64em) { | ||
1750 | .server-label { | ||
1751 | text-align: center; | ||
1752 | } | ||
1753 | |||
1754 | .server-row { | ||
1755 | p { | ||
1756 | text-align: center; | ||
1757 | } | ||
1758 | } | ||
1759 | } | ||
1760 | } | ||
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 | |||
1699 | // Print rules | 1825 | // Print rules |
1700 | @media print { | 1826 | @media print { |
1701 | .shaarli-menu { | 1827 | .shaarli-menu { |
diff --git a/composer.json b/composer.json index 64f0025e..94492586 100644 --- a/composer.json +++ b/composer.json | |||
@@ -59,6 +59,7 @@ | |||
59 | "Shaarli\\Front\\Controller\\Admin\\": "application/front/controller/admin", | 59 | "Shaarli\\Front\\Controller\\Admin\\": "application/front/controller/admin", |
60 | "Shaarli\\Front\\Controller\\Visitor\\": "application/front/controller/visitor", | 60 | "Shaarli\\Front\\Controller\\Visitor\\": "application/front/controller/visitor", |
61 | "Shaarli\\Front\\Exception\\": "application/front/exceptions", | 61 | "Shaarli\\Front\\Exception\\": "application/front/exceptions", |
62 | "Shaarli\\Helper\\": "application/helper", | ||
62 | "Shaarli\\Http\\": "application/http", | 63 | "Shaarli\\Http\\": "application/http", |
63 | "Shaarli\\Legacy\\": "application/legacy", | 64 | "Shaarli\\Legacy\\": "application/legacy", |
64 | "Shaarli\\Netscape\\": "application/netscape", | 65 | "Shaarli\\Netscape\\": "application/netscape", |
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po index f7baedfb..60ea7a97 100644 --- a/inc/languages/fr/LC_MESSAGES/shaarli.po +++ b/inc/languages/fr/LC_MESSAGES/shaarli.po | |||
@@ -1,8 +1,8 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: Shaarli\n" | 3 | "Project-Id-Version: Shaarli\n" |
4 | "POT-Creation-Date: 2020-10-16 20:01+0200\n" | 4 | "POT-Creation-Date: 2020-10-27 19:44+0100\n" |
5 | "PO-Revision-Date: 2020-10-16 20:02+0200\n" | 5 | "PO-Revision-Date: 2020-10-27 19:44+0100\n" |
6 | "Last-Translator: \n" | 6 | "Last-Translator: \n" |
7 | "Language-Team: Shaarli\n" | 7 | "Language-Team: Shaarli\n" |
8 | "Language: fr_FR\n" | 8 | "Language: fr_FR\n" |
@@ -20,38 +20,11 @@ msgstr "" | |||
20 | "X-Poedit-SearchPath-3: init.php\n" | 20 | "X-Poedit-SearchPath-3: init.php\n" |
21 | "X-Poedit-SearchPath-4: plugins\n" | 21 | "X-Poedit-SearchPath-4: plugins\n" |
22 | 22 | ||
23 | #: application/ApplicationUtils.php:161 | 23 | #: application/History.php:180 |
24 | #, php-format | ||
25 | msgid "" | ||
26 | "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " | ||
27 | "cannot run. Your PHP version has known security vulnerabilities and should " | ||
28 | "be updated as soon as possible." | ||
29 | msgstr "" | ||
30 | "Votre version de PHP est obsolète ! Shaarli nécessite au moins PHP %s, et ne " | ||
31 | "peut donc pas fonctionner. Votre version de PHP a des failles de sécurités " | ||
32 | "connues et devrait être mise à jour au plus tôt." | ||
33 | |||
34 | #: application/ApplicationUtils.php:192 application/ApplicationUtils.php:204 | ||
35 | msgid "directory is not readable" | ||
36 | msgstr "le répertoire n'est pas accessible en lecture" | ||
37 | |||
38 | #: application/ApplicationUtils.php:207 | ||
39 | msgid "directory is not writable" | ||
40 | msgstr "le répertoire n'est pas accessible en écriture" | ||
41 | |||
42 | #: application/ApplicationUtils.php:225 | ||
43 | msgid "file is not readable" | ||
44 | msgstr "le fichier n'est pas accessible en lecture" | ||
45 | |||
46 | #: application/ApplicationUtils.php:228 | ||
47 | msgid "file is not writable" | ||
48 | msgstr "le fichier n'est pas accessible en écriture" | ||
49 | |||
50 | #: application/History.php:179 | ||
51 | msgid "History file isn't readable or writable" | 24 | msgid "History file isn't readable or writable" |
52 | msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture" | 25 | msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture" |
53 | 26 | ||
54 | #: application/History.php:190 | 27 | #: application/History.php:191 |
55 | msgid "Could not parse history file" | 28 | msgid "Could not parse history file" |
56 | msgstr "Format incorrect pour le fichier d'historique" | 29 | msgstr "Format incorrect pour le fichier d'historique" |
57 | 30 | ||
@@ -83,40 +56,40 @@ msgstr "" | |||
83 | "l'extension php-gd doit être chargée pour utiliser les miniatures. Les " | 56 | "l'extension php-gd doit être chargée pour utiliser les miniatures. Les " |
84 | "miniatures sont désormais désactivées. Rechargez la page." | 57 | "miniatures sont désormais désactivées. Rechargez la page." |
85 | 58 | ||
86 | #: application/Utils.php:383 | 59 | #: application/Utils.php:402 |
87 | msgid "Setting not set" | 60 | msgid "Setting not set" |
88 | msgstr "Paramètre non défini" | 61 | msgstr "Paramètre non défini" |
89 | 62 | ||
90 | #: application/Utils.php:390 | 63 | #: application/Utils.php:409 |
91 | msgid "Unlimited" | 64 | msgid "Unlimited" |
92 | msgstr "Illimité" | 65 | msgstr "Illimité" |
93 | 66 | ||
94 | #: application/Utils.php:393 | 67 | #: application/Utils.php:412 |
95 | msgid "B" | 68 | msgid "B" |
96 | msgstr "o" | 69 | msgstr "o" |
97 | 70 | ||
98 | #: application/Utils.php:393 | 71 | #: application/Utils.php:412 |
99 | msgid "kiB" | 72 | msgid "kiB" |
100 | msgstr "ko" | 73 | msgstr "ko" |
101 | 74 | ||
102 | #: application/Utils.php:393 | 75 | #: application/Utils.php:412 |
103 | msgid "MiB" | 76 | msgid "MiB" |
104 | msgstr "Mo" | 77 | msgstr "Mo" |
105 | 78 | ||
106 | #: application/Utils.php:393 | 79 | #: application/Utils.php:412 |
107 | msgid "GiB" | 80 | msgid "GiB" |
108 | msgstr "Go" | 81 | msgstr "Go" |
109 | 82 | ||
110 | #: application/bookmark/BookmarkFileService.php:180 | 83 | #: application/bookmark/BookmarkFileService.php:183 |
111 | #: application/bookmark/BookmarkFileService.php:202 | 84 | #: application/bookmark/BookmarkFileService.php:205 |
112 | #: application/bookmark/BookmarkFileService.php:224 | 85 | #: application/bookmark/BookmarkFileService.php:227 |
113 | #: application/bookmark/BookmarkFileService.php:238 | 86 | #: application/bookmark/BookmarkFileService.php:241 |
114 | msgid "You're not authorized to alter the datastore" | 87 | msgid "You're not authorized to alter the datastore" |
115 | msgstr "Vous n'êtes pas autorisé à modifier les données" | 88 | msgstr "Vous n'êtes pas autorisé à modifier les données" |
116 | 89 | ||
117 | #: application/bookmark/BookmarkFileService.php:205 | 90 | #: application/bookmark/BookmarkFileService.php:208 |
118 | msgid "This bookmarks already exists" | 91 | msgid "This bookmarks already exists" |
119 | msgstr "Ce marque-page existe déjà ." | 92 | msgstr "Ce marque-page existe déjà " |
120 | 93 | ||
121 | #: application/bookmark/BookmarkInitializer.php:39 | 94 | #: application/bookmark/BookmarkInitializer.php:39 |
122 | msgid "(private bookmark with thumbnail demo)" | 95 | msgid "(private bookmark with thumbnail demo)" |
@@ -314,7 +287,8 @@ msgid "Direct link" | |||
314 | msgstr "Liens directs" | 287 | msgstr "Liens directs" |
315 | 288 | ||
316 | #: application/feed/FeedBuilder.php:181 | 289 | #: application/feed/FeedBuilder.php:181 |
317 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 | 290 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 |
291 | #: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | ||
318 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179 | 292 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179 |
319 | msgid "Permalink" | 293 | msgid "Permalink" |
320 | msgstr "Permalien" | 294 | msgstr "Permalien" |
@@ -330,12 +304,13 @@ msgid "You have enabled or changed thumbnails mode." | |||
330 | msgstr "Vous avez activé ou changé le mode de miniatures." | 304 | msgstr "Vous avez activé ou changé le mode de miniatures." |
331 | 305 | ||
332 | #: application/front/controller/admin/ConfigureController.php:103 | 306 | #: application/front/controller/admin/ConfigureController.php:103 |
307 | #: application/front/controller/admin/ServerController.php:68 | ||
333 | #: application/legacy/LegacyUpdater.php:538 | 308 | #: application/legacy/LegacyUpdater.php:538 |
334 | msgid "Please synchronize them." | 309 | msgid "Please synchronize them." |
335 | msgstr "Merci de les synchroniser." | 310 | msgstr "Merci de les synchroniser." |
336 | 311 | ||
337 | #: application/front/controller/admin/ConfigureController.php:113 | 312 | #: application/front/controller/admin/ConfigureController.php:113 |
338 | #: application/front/controller/visitor/InstallController.php:136 | 313 | #: application/front/controller/visitor/InstallController.php:146 |
339 | msgid "Error while writing config file after configuration update." | 314 | msgid "Error while writing config file after configuration update." |
340 | msgstr "" | 315 | msgstr "" |
341 | "Une erreur s'est produite lors de la sauvegarde du fichier de configuration." | 316 | "Une erreur s'est produite lors de la sauvegarde du fichier de configuration." |
@@ -372,46 +347,19 @@ msgstr "" | |||
372 | "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 " |
373 | "légères." | 348 | "légères." |
374 | 349 | ||
375 | #: application/front/controller/admin/ManageShaareController.php:29 | 350 | #: application/front/controller/admin/ManageShaareController.php:64 |
376 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | 351 | #: application/front/controller/admin/ManageShaareController.php:95 |
377 | msgid "Shaare a new link" | 352 | #: application/front/controller/admin/ManageShaareController.php:193 |
378 | msgstr "Partager un nouveau lien" | 353 | #: application/front/controller/admin/ManageShaareController.php:262 |
379 | 354 | #: application/front/controller/admin/ManageShaareController.php:302 | |
380 | #: application/front/controller/admin/ManageShaareController.php:78 | 355 | #: application/front/controller/admin/ManageShaareController.php:181 |
381 | msgid "Note: " | 356 | #: application/front/controller/admin/ManageShaareController.php:239 |
382 | msgstr "Note : " | 357 | #: application/front/controller/admin/ManageShaareController.php:247 |
383 | 358 | #: application/front/controller/admin/ManageShaareController.php:378 | |
384 | #: application/front/controller/admin/ManageShaareController.php:109 | 359 | #: application/front/controller/admin/ManageShaareController.php:381 |
385 | #: application/front/controller/admin/ManageShaareController.php:206 | ||
386 | #: application/front/controller/admin/ManageShaareController.php:275 | ||
387 | #: application/front/controller/admin/ManageShaareController.php:315 | ||
388 | #, php-format | ||
389 | msgid "Bookmark with identifier %s could not be found." | ||
390 | msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé." | ||
391 | |||
392 | #: application/front/controller/admin/ManageShaareController.php:194 | ||
393 | #: application/front/controller/admin/ManageShaareController.php:252 | ||
394 | msgid "Invalid bookmark ID provided." | ||
395 | msgstr "ID du lien non valide." | ||
396 | |||
397 | #: application/front/controller/admin/ManageShaareController.php:260 | ||
398 | msgid "Invalid visibility provided." | ||
399 | msgstr "Visibilité du lien non valide." | ||
400 | |||
401 | #: application/front/controller/admin/ManageShaareController.php:363 | ||
402 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 | ||
403 | msgid "Edit" | ||
404 | msgstr "Modifier" | ||
405 | |||
406 | #: application/front/controller/admin/ManageShaareController.php:366 | ||
407 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
408 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 | ||
409 | msgid "Shaare" | ||
410 | msgstr "Shaare" | ||
411 | |||
412 | #: application/front/controller/admin/ManageTagController.php:29 | 360 | #: application/front/controller/admin/ManageTagController.php:29 |
413 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | 361 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 |
414 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 362 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 |
415 | msgid "Manage tags" | 363 | msgid "Manage tags" |
416 | msgstr "Gérer les tags" | 364 | msgstr "Gérer les tags" |
417 | 365 | ||
@@ -435,7 +383,7 @@ msgstr[1] "Le tag a été renommé dans %d liens." | |||
435 | 383 | ||
436 | #: application/front/controller/admin/PasswordController.php:28 | 384 | #: application/front/controller/admin/PasswordController.php:28 |
437 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | 385 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 |
438 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | 386 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 |
439 | msgid "Change password" | 387 | msgid "Change password" |
440 | msgstr "Modifier le mot de passe" | 388 | msgstr "Modifier le mot de passe" |
441 | 389 | ||
@@ -467,6 +415,43 @@ msgstr "" | |||
467 | "Une erreur s'est produite lors de la sauvegarde de la configuration des " | 415 | "Une erreur s'est produite lors de la sauvegarde de la configuration des " |
468 | "plugins : " | 416 | "plugins : " |
469 | 417 | ||
418 | #: application/front/controller/admin/ServerController.php:50 | ||
419 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
420 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
421 | msgid "Server administration" | ||
422 | msgstr "Administration serveur" | ||
423 | |||
424 | #: application/front/controller/admin/ServerController.php:67 | ||
425 | msgid "Thumbnails cache has been cleared." | ||
426 | msgstr "Le cache des miniatures a été vidé." | ||
427 | |||
428 | #: application/front/controller/admin/ServerController.php:76 | ||
429 | msgid "Shaarli's cache folder has been cleared!" | ||
430 | msgstr "Le dossier de cache de Shaarli a été vidé !" | ||
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 | |||
470 | #: application/front/controller/admin/ThumbnailsController.php:37 | 455 | #: application/front/controller/admin/ThumbnailsController.php:37 |
471 | #: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 456 | #: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 |
472 | msgid "Thumbnails update" | 457 | msgid "Thumbnails update" |
@@ -482,29 +467,50 @@ msgstr "Outils" | |||
482 | msgid "Search: " | 467 | msgid "Search: " |
483 | msgstr "Recherche : " | 468 | msgstr "Recherche : " |
484 | 469 | ||
485 | #: application/front/controller/visitor/DailyController.php:45 | 470 | #: application/front/controller/visitor/DailyController.php:200 |
486 | msgid "Today" | 471 | msgid "day" |
487 | msgstr "Aujourd'hui" | 472 | msgstr "jour" |
488 | |||
489 | #: application/front/controller/visitor/DailyController.php:47 | ||
490 | msgid "Yesterday" | ||
491 | msgstr "Hier" | ||
492 | 473 | ||
493 | #: application/front/controller/visitor/DailyController.php:85 | 474 | #: application/front/controller/visitor/DailyController.php:200 |
475 | #: application/front/controller/visitor/DailyController.php:203 | ||
476 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
494 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | 477 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 |
495 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:48 | 478 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:48 |
496 | msgid "Daily" | 479 | msgid "Daily" |
497 | msgstr "Quotidien" | 480 | msgstr "Quotidien" |
498 | 481 | ||
499 | #: application/front/controller/visitor/ErrorController.php:36 | 482 | #: application/front/controller/visitor/DailyController.php:201 |
483 | msgid "week" | ||
484 | msgstr "semaine" | ||
485 | |||
486 | #: application/front/controller/visitor/DailyController.php:201 | ||
487 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
488 | msgid "Weekly" | ||
489 | msgstr "Hebdomadaire" | ||
490 | |||
491 | #: application/front/controller/visitor/DailyController.php:202 | ||
492 | msgid "month" | ||
493 | msgstr "mois" | ||
494 | |||
495 | #: application/front/controller/visitor/DailyController.php:202 | ||
496 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
497 | msgid "Monthly" | ||
498 | msgstr "Mensuel" | ||
499 | |||
500 | #: application/front/controller/visitor/ErrorController.php:33 | ||
500 | msgid "An unexpected error occurred." | 501 | msgid "An unexpected error occurred." |
501 | msgstr "Une erreur inattendue s'est produite." | 502 | msgstr "Une erreur inattendue s'est produite." |
502 | 503 | ||
503 | #: application/front/controller/visitor/ErrorNotFoundController.php:25 | 504 | #: application/front/controller/visitor/ErrorNotFoundController.php:25 |
504 | msgid "Requested page could not be found." | 505 | msgid "Requested page could not be found." |
505 | msgstr "" | 506 | msgstr "La page demandée n'a pas pu être trouvée." |
507 | |||
508 | #: application/front/controller/visitor/InstallController.php:64 | ||
509 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
510 | msgid "Install Shaarli" | ||
511 | msgstr "Installation de Shaarli" | ||
506 | 512 | ||
507 | #: application/front/controller/visitor/InstallController.php:73 | 513 | #: application/front/controller/visitor/InstallController.php:83 |
508 | #, php-format | 514 | #, php-format |
509 | msgid "" | 515 | msgid "" |
510 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " | 516 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " |
@@ -523,14 +529,14 @@ msgstr "" | |||
523 | "des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " | 529 | "des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " |
524 | "adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>" | 530 | "adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>" |
525 | 531 | ||
526 | #: application/front/controller/visitor/InstallController.php:144 | 532 | #: application/front/controller/visitor/InstallController.php:154 |
527 | msgid "" | 533 | msgid "" |
528 | "Shaarli is now configured. Please login and start shaaring your bookmarks!" | 534 | "Shaarli is now configured. Please login and start shaaring your bookmarks!" |
529 | msgstr "" | 535 | msgstr "" |
530 | "Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à " | 536 | "Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à " |
531 | "shaare vos liens !" | 537 | "shaare vos liens !" |
532 | 538 | ||
533 | #: application/front/controller/visitor/InstallController.php:158 | 539 | #: application/front/controller/visitor/InstallController.php:168 |
534 | msgid "Insufficient permissions:" | 540 | msgid "Insufficient permissions:" |
535 | msgstr "Permissions insuffisantes :" | 541 | msgstr "Permissions insuffisantes :" |
536 | 542 | ||
@@ -544,7 +550,7 @@ msgstr "Permissions insuffisantes :" | |||
544 | msgid "Login" | 550 | msgid "Login" |
545 | msgstr "Connexion" | 551 | msgstr "Connexion" |
546 | 552 | ||
547 | #: application/front/controller/visitor/LoginController.php:78 | 553 | #: application/front/controller/visitor/LoginController.php:77 |
548 | msgid "Wrong login/password." | 554 | msgid "Wrong login/password." |
549 | msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." | 555 | msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." |
550 | 556 | ||
@@ -556,7 +562,7 @@ msgstr "Mur d'images" | |||
556 | 562 | ||
557 | #: application/front/controller/visitor/TagCloudController.php:88 | 563 | #: application/front/controller/visitor/TagCloudController.php:88 |
558 | msgid "Tag " | 564 | msgid "Tag " |
559 | msgstr "Tag" | 565 | msgstr "Tag " |
560 | 566 | ||
561 | #: application/front/exceptions/AlreadyInstalledException.php:11 | 567 | #: application/front/exceptions/AlreadyInstalledException.php:11 |
562 | msgid "Shaarli has already been installed. Login to edit the configuration." | 568 | msgid "Shaarli has already been installed. Login to edit the configuration." |
@@ -584,6 +590,86 @@ msgstr "" | |||
584 | msgid "Wrong token." | 590 | msgid "Wrong token." |
585 | msgstr "Jeton invalide." | 591 | msgstr "Jeton invalide." |
586 | 592 | ||
593 | #: application/helper/ApplicationUtils.php:162 | ||
594 | #, php-format | ||
595 | msgid "" | ||
596 | "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " | ||
597 | "cannot run. Your PHP version has known security vulnerabilities and should " | ||
598 | "be updated as soon as possible." | ||
599 | msgstr "" | ||
600 | "Votre version de PHP est obsolète ! Shaarli nécessite au moins PHP %s, et ne " | ||
601 | "peut donc pas fonctionner. Votre version de PHP a des failles de sécurités " | ||
602 | "connues et devrait être mise à jour au plus tôt." | ||
603 | |||
604 | #: application/helper/ApplicationUtils.php:195 | ||
605 | #: application/helper/ApplicationUtils.php:215 | ||
606 | msgid "directory is not readable" | ||
607 | msgstr "le répertoire n'est pas accessible en lecture" | ||
608 | |||
609 | #: application/helper/ApplicationUtils.php:218 | ||
610 | msgid "directory is not writable" | ||
611 | msgstr "le répertoire n'est pas accessible en écriture" | ||
612 | |||
613 | #: application/helper/ApplicationUtils.php:240 | ||
614 | msgid "file is not readable" | ||
615 | msgstr "le fichier n'est pas accessible en lecture" | ||
616 | |||
617 | #: application/helper/ApplicationUtils.php:243 | ||
618 | msgid "file is not writable" | ||
619 | msgstr "le fichier n'est pas accessible en écriture" | ||
620 | |||
621 | #: application/helper/ApplicationUtils.php:277 | ||
622 | msgid "Configuration parsing" | ||
623 | msgstr "Chargement de la configuration" | ||
624 | |||
625 | #: application/helper/ApplicationUtils.php:278 | ||
626 | msgid "Slim Framework (routing, etc.)" | ||
627 | msgstr "Slim Framwork (routage, etc.)" | ||
628 | |||
629 | #: application/helper/ApplicationUtils.php:279 | ||
630 | msgid "Multibyte (Unicode) string support" | ||
631 | msgstr "Support des chaînes de caractère multibytes (Unicode)" | ||
632 | |||
633 | #: application/helper/ApplicationUtils.php:280 | ||
634 | msgid "Required to use thumbnails" | ||
635 | msgstr "Obligatoire pour utiliser les miniatures" | ||
636 | |||
637 | #: application/helper/ApplicationUtils.php:281 | ||
638 | msgid "Localized text sorting (e.g. e->è->f)" | ||
639 | msgstr "Tri des textes traduits (ex : e->è->f)" | ||
640 | |||
641 | #: application/helper/ApplicationUtils.php:282 | ||
642 | msgid "Better retrieval of bookmark metadata and thumbnail" | ||
643 | msgstr "Meilleure récupération des meta-données des marque-pages et minatures" | ||
644 | |||
645 | #: application/helper/ApplicationUtils.php:283 | ||
646 | msgid "Use the translation system in gettext mode" | ||
647 | msgstr "Utiliser le système de traduction en mode gettext" | ||
648 | |||
649 | #: application/helper/ApplicationUtils.php:284 | ||
650 | msgid "Login using LDAP server" | ||
651 | msgstr "Authentification via un serveur LDAP" | ||
652 | |||
653 | #: application/helper/DailyPageHelper.php:172 | ||
654 | msgid "Week" | ||
655 | msgstr "Semaine" | ||
656 | |||
657 | #: application/helper/DailyPageHelper.php:176 | ||
658 | msgid "Today" | ||
659 | msgstr "Aujourd'hui" | ||
660 | |||
661 | #: application/helper/DailyPageHelper.php:178 | ||
662 | msgid "Yesterday" | ||
663 | msgstr "Hier" | ||
664 | |||
665 | #: application/helper/FileUtils.php:100 | ||
666 | msgid "Provided path is not a directory." | ||
667 | msgstr "Le chemin fourni n'est pas un dossier." | ||
668 | |||
669 | #: application/helper/FileUtils.php:104 | ||
670 | msgid "Trying to delete a folder outside of Shaarli path." | ||
671 | msgstr "Tentative de supprimer un dossier en dehors du chemin de Shaarli." | ||
672 | |||
587 | #: application/legacy/LegacyLinkDB.php:131 | 673 | #: application/legacy/LegacyLinkDB.php:131 |
588 | msgid "You are not authorized to add a link." | 674 | msgid "You are not authorized to add a link." |
589 | msgstr "Vous n'êtes pas autorisé à ajouter un lien." | 675 | msgstr "Vous n'êtes pas autorisé à ajouter un lien." |
@@ -678,7 +764,7 @@ msgstr "Impossible de purger %s : le répertoire n'existe pas" | |||
678 | msgid "An error occurred while running the update " | 764 | msgid "An error occurred while running the update " |
679 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " | 765 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " |
680 | 766 | ||
681 | #: index.php:65 | 767 | #: index.php:80 |
682 | msgid "Shared bookmarks on " | 768 | msgid "Shared bookmarks on " |
683 | msgstr "Liens partagés sur " | 769 | msgstr "Liens partagés sur " |
684 | 770 | ||
@@ -851,6 +937,48 @@ msgstr "Désolé, il y a rien à voir ici." | |||
851 | msgid "URL or leave empty to post a note" | 937 | msgid "URL or leave empty to post a note" |
852 | msgstr "URL ou laisser vide pour créer une note" | 938 | msgstr "URL ou laisser vide pour créer une note" |
853 | 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 | |||
854 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | 982 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 |
855 | msgid "Current password" | 983 | msgid "Current password" |
856 | msgstr "Mot de passe actuel" | 984 | msgstr "Mot de passe actuel" |
@@ -1016,71 +1144,79 @@ msgstr "" | |||
1016 | "miniatures." | 1144 | "miniatures." |
1017 | 1145 | ||
1018 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328 | 1146 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328 |
1019 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 | 1147 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122 |
1020 | msgid "Synchronize thumbnails" | 1148 | msgid "Synchronize thumbnails" |
1021 | msgstr "Synchroniser les miniatures" | 1149 | msgstr "Synchroniser les miniatures" |
1022 | 1150 | ||
1023 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339 | 1151 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339 |
1024 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 | 1152 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 |
1153 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 | ||
1025 | msgid "All" | 1154 | msgid "All" |
1026 | msgstr "Tous" | 1155 | msgstr "Tous" |
1027 | 1156 | ||
1028 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343 | 1157 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343 |
1158 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106 | ||
1029 | msgid "Only common media hosts" | 1159 | msgid "Only common media hosts" |
1030 | msgstr "Seulement les hébergeurs de média connus" | 1160 | msgstr "Seulement les hébergeurs de média connus" |
1031 | 1161 | ||
1032 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347 | 1162 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347 |
1163 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110 | ||
1033 | msgid "None" | 1164 | msgid "None" |
1034 | msgstr "Aucune" | 1165 | msgstr "Aucune" |
1035 | 1166 | ||
1036 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:355 | 1167 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:355 |
1037 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 | 1168 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 |
1038 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 | 1169 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 |
1039 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 | 1170 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 |
1040 | msgid "Save" | 1171 | msgid "Save" |
1041 | msgstr "Enregistrer" | 1172 | msgstr "Enregistrer" |
1042 | 1173 | ||
1043 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | 1174 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 |
1044 | msgid "The Daily Shaarli" | 1175 | msgid "1 RSS entry per :type" |
1045 | msgstr "Le Quotidien Shaarli" | 1176 | msgid_plural "" |
1046 | 1177 | msgstr[0] "1 entrée RSS par :type" | |
1047 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17 | 1178 | msgstr[1] "" |
1048 | msgid "1 RSS entry per day" | 1179 | |
1049 | msgstr "1 entrée RSS par jour" | 1180 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49 |
1050 | 1181 | msgid "Previous :type" | |
1051 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37 | 1182 | msgid_plural "" |
1052 | msgid "Previous day" | 1183 | msgstr[0] ":type précédent" |
1053 | msgstr "Jour précédent" | 1184 | msgstr[1] "Jour précédent" |
1054 | 1185 | ||
1055 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | 1186 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 |
1056 | msgid "All links of one day in a single page." | 1187 | #: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7 |
1057 | msgstr "Tous les liens d'un jour sur une page." | 1188 | msgid "All links of one :type in a single page." |
1058 | 1189 | msgid_plural "" | |
1059 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51 | 1190 | msgstr[0] "Tous les liens d'un :type sur une page." |
1060 | msgid "Next day" | 1191 | msgstr[1] "Tous les liens d'un jour sur une page." |
1061 | msgstr "Jour suivant" | 1192 | |
1062 | 1193 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63 | |
1063 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 | 1194 | msgid "Next :type" |
1195 | msgid_plural "" | ||
1196 | msgstr[0] ":type suivant" | ||
1197 | msgstr[1] "" | ||
1198 | |||
1199 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21 | ||
1064 | msgid "Edit Shaare" | 1200 | msgid "Edit Shaare" |
1065 | msgstr "Modifier le Shaare" | 1201 | msgstr "Modifier le Shaare" |
1066 | 1202 | ||
1067 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 | 1203 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21 |
1068 | msgid "New Shaare" | 1204 | msgid "New Shaare" |
1069 | msgstr "Nouveau Shaare" | 1205 | msgstr "Nouveau Shaare" |
1070 | 1206 | ||
1071 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | 1207 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 |
1072 | msgid "Created:" | 1208 | msgid "Created:" |
1073 | msgstr "Création :" | 1209 | msgstr "Création :" |
1074 | 1210 | ||
1075 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | 1211 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32 |
1076 | msgid "URL" | 1212 | msgid "URL" |
1077 | msgstr "URL" | 1213 | msgstr "URL" |
1078 | 1214 | ||
1079 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | 1215 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38 |
1080 | msgid "Title" | 1216 | msgid "Title" |
1081 | msgstr "Titre" | 1217 | msgstr "Titre" |
1082 | 1218 | ||
1083 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | 1219 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49 |
1084 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | 1220 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 |
1085 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 | 1221 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 |
1086 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 | 1222 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 |
@@ -1088,33 +1224,34 @@ msgstr "Titre" | |||
1088 | msgid "Description" | 1224 | msgid "Description" |
1089 | msgstr "Description" | 1225 | msgstr "Description" |
1090 | 1226 | ||
1091 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 | 1227 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 |
1092 | msgid "Tags" | 1228 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 |
1093 | msgstr "Tags" | 1229 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80 |
1094 | |||
1095 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60 | ||
1096 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | ||
1097 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | ||
1098 | msgid "Private" | ||
1099 | msgstr "Privé" | ||
1100 | |||
1101 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66 | ||
1102 | msgid "Description will be rendered with" | 1230 | msgid "Description will be rendered with" |
1103 | msgstr "La description sera générée avec" | 1231 | msgstr "La description sera générée avec" |
1104 | 1232 | ||
1105 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68 | 1233 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82 |
1106 | msgid "Markdown syntax documentation" | 1234 | msgid "Markdown syntax documentation" |
1107 | msgstr "Documentation sur la syntaxe Markdown" | 1235 | msgstr "Documentation sur la syntaxe Markdown" |
1108 | 1236 | ||
1109 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69 | 1237 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 |
1110 | msgid "Markdown syntax" | 1238 | msgid "Markdown syntax" |
1111 | msgstr "la syntaxe Markdown" | 1239 | msgstr "la syntaxe Markdown" |
1112 | 1240 | ||
1113 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 | 1241 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 |
1242 | msgid "Cancel" | ||
1243 | msgstr "Annuler" | ||
1244 | |||
1245 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121 | ||
1114 | msgid "Apply Changes" | 1246 | msgid "Apply Changes" |
1115 | msgstr "Appliquer les changements" | 1247 | msgstr "Appliquer les changements" |
1116 | 1248 | ||
1117 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:93 | 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 | |||
1254 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:107 | ||
1118 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 | 1255 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 |
1119 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 | 1256 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 |
1120 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147 | 1257 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147 |
@@ -1179,10 +1316,6 @@ msgstr "Les doublons s'appuient sur les URL" | |||
1179 | msgid "Add default tags" | 1316 | msgid "Add default tags" |
1180 | msgstr "Ajouter des tags par défaut" | 1317 | msgstr "Ajouter des tags par défaut" |
1181 | 1318 | ||
1182 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
1183 | msgid "Install Shaarli" | ||
1184 | msgstr "Installation de Shaarli" | ||
1185 | |||
1186 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 | 1319 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 |
1187 | msgid "It looks like it's the first time you run Shaarli. Please configure it." | 1320 | msgid "It looks like it's the first time you run Shaarli. Please configure it." |
1188 | msgstr "" | 1321 | msgstr "" |
@@ -1215,6 +1348,10 @@ msgstr "Mes liens" | |||
1215 | msgid "Install" | 1348 | msgid "Install" |
1216 | msgstr "Installer" | 1349 | msgstr "Installer" |
1217 | 1350 | ||
1351 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:190 | ||
1352 | msgid "Server requirements" | ||
1353 | msgstr "Pré-requis serveur" | ||
1354 | |||
1218 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 1355 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 |
1219 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79 | 1356 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79 |
1220 | msgid "shaare" | 1357 | msgid "shaare" |
@@ -1313,6 +1450,10 @@ msgstr "Changer statut épinglé" | |||
1313 | msgid "Sticky" | 1450 | msgid "Sticky" |
1314 | msgstr "Épinglé" | 1451 | msgstr "Épinglé" |
1315 | 1452 | ||
1453 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189 | ||
1454 | msgid "Share a private link" | ||
1455 | msgstr "Partager un lien privé" | ||
1456 | |||
1316 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5 | 1457 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5 |
1317 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:5 | 1458 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:5 |
1318 | msgid "Filters" | 1459 | msgid "Filters" |
@@ -1511,6 +1652,100 @@ msgstr "Configuration des extensions" | |||
1511 | msgid "No parameter available." | 1652 | msgid "No parameter available." |
1512 | msgstr "Aucun paramètre disponible." | 1653 | msgstr "Aucun paramètre disponible." |
1513 | 1654 | ||
1655 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
1656 | msgid "General" | ||
1657 | msgstr "Général" | ||
1658 | |||
1659 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20 | ||
1660 | msgid "Index URL" | ||
1661 | msgstr "URL de l'index" | ||
1662 | |||
1663 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
1664 | msgid "Base path" | ||
1665 | msgstr "Chemin de base" | ||
1666 | |||
1667 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
1668 | msgid "Client IP" | ||
1669 | msgstr "IP du client" | ||
1670 | |||
1671 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | ||
1672 | msgid "Trusted reverse proxies" | ||
1673 | msgstr "Reverse proxies de confiance" | ||
1674 | |||
1675 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 | ||
1676 | msgid "N/A" | ||
1677 | msgstr "N/A" | ||
1678 | |||
1679 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84 | ||
1680 | msgid "Visit releases page on Github" | ||
1681 | msgstr "Visiter la page des releases sur Github" | ||
1682 | |||
1683 | #: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121 | ||
1684 | msgid "Synchronize all link thumbnails" | ||
1685 | msgstr "Synchroniser toutes les miniatures" | ||
1686 | |||
1687 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:2 | ||
1688 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:2 | ||
1689 | msgid "Permissions" | ||
1690 | msgstr "Permissions" | ||
1691 | |||
1692 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:8 | ||
1693 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:8 | ||
1694 | msgid "There are permissions that need to be fixed." | ||
1695 | msgstr "Il y a des permissions qui doivent être corrigées." | ||
1696 | |||
1697 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | ||
1698 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:23 | ||
1699 | msgid "All read/write permissions are properly set." | ||
1700 | msgstr "Toutes les permissions de lecture/écriture sont définies correctement." | ||
1701 | |||
1702 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32 | ||
1703 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:32 | ||
1704 | msgid "Running PHP" | ||
1705 | msgstr "Fonctionnant avec PHP" | ||
1706 | |||
1707 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
1708 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:36 | ||
1709 | msgid "End of life: " | ||
1710 | msgstr "Fin de vie : " | ||
1711 | |||
1712 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | ||
1713 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:48 | ||
1714 | msgid "Extension" | ||
1715 | msgstr "Extension" | ||
1716 | |||
1717 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49 | ||
1718 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:49 | ||
1719 | msgid "Usage" | ||
1720 | msgstr "Utilisation" | ||
1721 | |||
1722 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:50 | ||
1723 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:50 | ||
1724 | msgid "Status" | ||
1725 | msgstr "Statut" | ||
1726 | |||
1727 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51 | ||
1728 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66 | ||
1729 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:51 | ||
1730 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:66 | ||
1731 | msgid "Loaded" | ||
1732 | msgstr "Chargé" | ||
1733 | |||
1734 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60 | ||
1735 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:60 | ||
1736 | msgid "Required" | ||
1737 | msgstr "Obligatoire" | ||
1738 | |||
1739 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60 | ||
1740 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:60 | ||
1741 | msgid "Optional" | ||
1742 | msgstr "Optionnel" | ||
1743 | |||
1744 | #: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:70 | ||
1745 | #: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:70 | ||
1746 | msgid "Not loaded" | ||
1747 | msgstr "Non chargé" | ||
1748 | |||
1514 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | 1749 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 |
1515 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | 1750 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 |
1516 | msgid "tags" | 1751 | msgid "tags" |
@@ -1561,15 +1796,19 @@ msgstr "Configurer Shaarli" | |||
1561 | msgid "Enable, disable and configure plugins" | 1796 | msgid "Enable, disable and configure plugins" |
1562 | msgstr "Activer, désactiver et configurer les extensions" | 1797 | msgstr "Activer, désactiver et configurer les extensions" |
1563 | 1798 | ||
1564 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | 1799 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:27 |
1800 | msgid "Check instance's server configuration" | ||
1801 | msgstr "Vérifier la configuration serveur de l'instance" | ||
1802 | |||
1803 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | ||
1565 | msgid "Change your password" | 1804 | msgid "Change your password" |
1566 | msgstr "Modifier le mot de passe" | 1805 | msgstr "Modifier le mot de passe" |
1567 | 1806 | ||
1568 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | 1807 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 |
1569 | msgid "Rename or delete a tag in all links" | 1808 | msgid "Rename or delete a tag in all links" |
1570 | msgstr "Renommer ou supprimer un tag dans tous les liens" | 1809 | msgstr "Renommer ou supprimer un tag dans tous les liens" |
1571 | 1810 | ||
1572 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | 1811 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 |
1573 | msgid "" | 1812 | msgid "" |
1574 | "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, " | 1813 | "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, " |
1575 | "delicious...)" | 1814 | "delicious...)" |
@@ -1577,11 +1816,11 @@ msgstr "" | |||
1577 | "Importer des marques pages au format Netscape HTML (comme exportés depuis " | 1816 | "Importer des marques pages au format Netscape HTML (comme exportés depuis " |
1578 | "Firefox, Chrome, Opera, delicious...)" | 1817 | "Firefox, Chrome, Opera, delicious...)" |
1579 | 1818 | ||
1580 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | 1819 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 |
1581 | msgid "Import links" | 1820 | msgid "Import links" |
1582 | msgstr "Importer des liens" | 1821 | msgstr "Importer des liens" |
1583 | 1822 | ||
1584 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 | 1823 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53 |
1585 | msgid "" | 1824 | msgid "" |
1586 | "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, " | 1825 | "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, " |
1587 | "Opera, delicious...)" | 1826 | "Opera, delicious...)" |
@@ -1589,15 +1828,11 @@ msgstr "" | |||
1589 | "Exporter les marques pages au format Netscape HTML (comme exportés depuis " | 1828 | "Exporter les marques pages au format Netscape HTML (comme exportés depuis " |
1590 | "Firefox, Chrome, Opera, delicious...)" | 1829 | "Firefox, Chrome, Opera, delicious...)" |
1591 | 1830 | ||
1592 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | 1831 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:54 |
1593 | msgid "Export database" | 1832 | msgid "Export database" |
1594 | msgstr "Exporter les données" | 1833 | msgstr "Exporter les données" |
1595 | 1834 | ||
1596 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:55 | 1835 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 |
1597 | msgid "Synchronize all link thumbnails" | ||
1598 | msgstr "Synchroniser toutes les miniatures" | ||
1599 | |||
1600 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81 | ||
1601 | msgid "" | 1836 | msgid "" |
1602 | "Drag one of these button to your bookmarks toolbar or right-click it and " | 1837 | "Drag one of these button to your bookmarks toolbar or right-click it and " |
1603 | "\"Bookmark This Link\"" | 1838 | "\"Bookmark This Link\"" |
@@ -1605,13 +1840,13 @@ msgstr "" | |||
1605 | "Glisser un de ces boutons dans votre barre de favoris ou cliquer droit " | 1840 | "Glisser un de ces boutons dans votre barre de favoris ou cliquer droit " |
1606 | "dessus et « Ajouter aux favoris »" | 1841 | "dessus et « Ajouter aux favoris »" |
1607 | 1842 | ||
1608 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82 | 1843 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78 |
1609 | msgid "then click on the bookmarklet in any page you want to share." | 1844 | msgid "then click on the bookmarklet in any page you want to share." |
1610 | msgstr "" | 1845 | msgstr "" |
1611 | "puis cliquer sur le marque-page depuis un site que vous souhaitez partager." | 1846 | "puis cliquer sur le marque-page depuis un site que vous souhaitez partager." |
1612 | 1847 | ||
1613 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 | 1848 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82 |
1614 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110 | 1849 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106 |
1615 | msgid "" | 1850 | msgid "" |
1616 | "Drag this link to your bookmarks toolbar or right-click it and Bookmark This " | 1851 | "Drag this link to your bookmarks toolbar or right-click it and Bookmark This " |
1617 | "Link" | 1852 | "Link" |
@@ -1619,40 +1854,40 @@ msgstr "" | |||
1619 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " | 1854 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " |
1620 | "Ajouter aux favoris »" | 1855 | "Ajouter aux favoris »" |
1621 | 1856 | ||
1622 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87 | 1857 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 |
1623 | msgid "then click ✚Shaare link button in any page you want to share" | 1858 | msgid "then click ✚Shaare link button in any page you want to share" |
1624 | msgstr "puis cliquer sur ✚Shaare depuis un site que vous souhaitez partager" | 1859 | msgstr "puis cliquer sur ✚Shaare depuis un site que vous souhaitez partager" |
1625 | 1860 | ||
1626 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 | 1861 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92 |
1627 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:118 | 1862 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114 |
1628 | msgid "The selected text is too long, it will be truncated." | 1863 | msgid "The selected text is too long, it will be truncated." |
1629 | msgstr "Le texte sélectionné est trop long, il sera tronqué." | 1864 | msgstr "Le texte sélectionné est trop long, il sera tronqué." |
1630 | 1865 | ||
1631 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106 | 1866 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 |
1632 | msgid "Shaare link" | 1867 | msgid "Shaare link" |
1633 | msgstr "Shaare" | 1868 | msgstr "Shaare" |
1634 | 1869 | ||
1635 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111 | 1870 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:107 |
1636 | msgid "" | 1871 | msgid "" |
1637 | "Then click ✚Add Note button anytime to start composing a private Note (text " | 1872 | "Then click ✚Add Note button anytime to start composing a private Note (text " |
1638 | "post) to your Shaarli" | 1873 | "post) to your Shaarli" |
1639 | msgstr "" | 1874 | msgstr "" |
1640 | "Puis cliquer sur ✚Add Note pour commencer à rédiger une Note sur Shaarli" | 1875 | "Puis cliquer sur ✚Add Note pour commencer à rédiger une Note sur Shaarli" |
1641 | 1876 | ||
1642 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:127 | 1877 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123 |
1643 | msgid "Add Note" | 1878 | msgid "Add Note" |
1644 | msgstr "Ajouter une Note" | 1879 | msgstr "Ajouter une Note" |
1645 | 1880 | ||
1646 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:136 | 1881 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:132 |
1647 | msgid "3rd party" | 1882 | msgid "3rd party" |
1648 | msgstr "Applications tierces" | 1883 | msgstr "Applications tierces" |
1649 | 1884 | ||
1650 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 | 1885 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:135 |
1651 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144 | 1886 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:140 |
1652 | msgid "plugin" | 1887 | msgid "plugin" |
1653 | msgstr "extension" | 1888 | msgstr "extension" |
1654 | 1889 | ||
1655 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | 1890 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:165 |
1656 | msgid "" | 1891 | msgid "" |
1657 | "Drag this link to your bookmarks toolbar, or right-click it and choose " | 1892 | "Drag this link to your bookmarks toolbar, or right-click it and choose " |
1658 | "Bookmark This Link" | 1893 | "Bookmark This Link" |
@@ -1660,11 +1895,11 @@ msgstr "" | |||
1660 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " | 1895 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " |
1661 | "Ajouter aux favoris »" | 1896 | "Ajouter aux favoris »" |
1662 | 1897 | ||
1663 | #~ msgid "Provided data is invalid" | 1898 | #~ msgid "Display:" |
1664 | #~ msgstr "Les informations fournies ne sont pas valides" | 1899 | #~ msgstr "Afficher :" |
1665 | 1900 | ||
1666 | #~ msgid "Rename" | 1901 | #~ msgid "The Daily Shaarli" |
1667 | #~ msgstr "Renommer" | 1902 | #~ msgstr "Le Quotidien Shaarli" |
1668 | 1903 | ||
1669 | #, fuzzy | 1904 | #, fuzzy |
1670 | #~| msgid "Selection" | 1905 | #~| msgid "Selection" |
@@ -125,13 +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->post('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:save'); | 131 | $this->get('/shaare/private/{hash}', '\Shaarli\Front\Controller\Admin\ShaareManageController:sharePrivate'); |
132 | $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark'); | 132 | $this->post('/shaare-batch', '\Shaarli\Front\Controller\Admin\ShaarePublishController:displayCreateBatchForms'); |
133 | $this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility'); | 133 | $this->post('/shaare', '\Shaarli\Front\Controller\Admin\ShaarePublishController:save'); |
134 | $this->get('/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ManageShaareController:pinBookmark'); | 134 | $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ShaareManageController:deleteBookmark'); |
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'); | ||
135 | $this->patch( | 137 | $this->patch( |
136 | '/shaare/{id:[0-9]+}/update-thumbnail', | 138 | '/shaare/{id:[0-9]+}/update-thumbnail', |
137 | '\Shaarli\Front\Controller\Admin\ThumbnailsController:ajaxUpdate' | 139 | '\Shaarli\Front\Controller\Admin\ThumbnailsController:ajaxUpdate' |
@@ -143,6 +145,8 @@ $app->group('/admin', function () { | |||
143 | $this->get('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:index'); | 145 | $this->get('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:index'); |
144 | $this->post('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:save'); | 146 | $this->post('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:save'); |
145 | $this->get('/token', '\Shaarli\Front\Controller\Admin\TokenController:getToken'); | 147 | $this->get('/token', '\Shaarli\Front\Controller\Admin\TokenController:getToken'); |
148 | $this->get('/server', '\Shaarli\Front\Controller\Admin\ServerController:index'); | ||
149 | $this->get('/clear-cache', '\Shaarli\Front\Controller\Admin\ServerController:clearCache'); | ||
146 | $this->get('/thumbnails', '\Shaarli\Front\Controller\Admin\ThumbnailsController:index'); | 150 | $this->get('/thumbnails', '\Shaarli\Front\Controller\Admin\ThumbnailsController:index'); |
147 | $this->get('/metadata', '\Shaarli\Front\Controller\Admin\MetadataController:ajaxRetrieveTitle'); | 151 | $this->get('/metadata', '\Shaarli\Front\Controller\Admin\MetadataController:ajaxRetrieveTitle'); |
148 | $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); | 152 | $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); |
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | require_once __DIR__ . '/vendor/autoload.php'; | 3 | require_once __DIR__ . '/vendor/autoload.php'; |
4 | 4 | ||
5 | use Shaarli\ApplicationUtils; | 5 | use Shaarli\Helper\ApplicationUtils; |
6 | use Shaarli\Security\SessionManager; | 6 | use Shaarli\Security\SessionManager; |
7 | 7 | ||
8 | // Set 'UTC' as the default timezone if it is not defined in php.ini | 8 | // Set 'UTC' as the default timezone if it is not defined in php.ini |
diff --git a/tests/api/controllers/links/PostLinkTest.php b/tests/api/controllers/links/PostLinkTest.php index 7ff92f5c..e12f803b 100644 --- a/tests/api/controllers/links/PostLinkTest.php +++ b/tests/api/controllers/links/PostLinkTest.php | |||
@@ -92,8 +92,8 @@ class PostLinkTest extends TestCase | |||
92 | 92 | ||
93 | $mock = $this->createMock(Router::class); | 93 | $mock = $this->createMock(Router::class); |
94 | $mock->expects($this->any()) | 94 | $mock->expects($this->any()) |
95 | ->method('relativePathFor') | 95 | ->method('pathFor') |
96 | ->willReturn('api/v1/bookmarks/1'); | 96 | ->willReturn('/api/v1/bookmarks/1'); |
97 | 97 | ||
98 | // affect @property-read... seems to work | 98 | // affect @property-read... seems to work |
99 | $this->controller->getCi()->router = $mock; | 99 | $this->controller->getCi()->router = $mock; |
@@ -128,7 +128,7 @@ class PostLinkTest extends TestCase | |||
128 | 128 | ||
129 | $response = $this->controller->postLink($request, new Response()); | 129 | $response = $this->controller->postLink($request, new Response()); |
130 | $this->assertEquals(201, $response->getStatusCode()); | 130 | $this->assertEquals(201, $response->getStatusCode()); |
131 | $this->assertEquals('api/v1/bookmarks/1', $response->getHeader('Location')[0]); | 131 | $this->assertEquals('/api/v1/bookmarks/1', $response->getHeader('Location')[0]); |
132 | $data = json_decode((string) $response->getBody(), true); | 132 | $data = json_decode((string) $response->getBody(), true); |
133 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | 133 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); |
134 | $this->assertEquals(43, $data['id']); | 134 | $this->assertEquals(43, $data['id']); |
@@ -175,7 +175,7 @@ class PostLinkTest extends TestCase | |||
175 | $response = $this->controller->postLink($request, new Response()); | 175 | $response = $this->controller->postLink($request, new Response()); |
176 | 176 | ||
177 | $this->assertEquals(201, $response->getStatusCode()); | 177 | $this->assertEquals(201, $response->getStatusCode()); |
178 | $this->assertEquals('api/v1/bookmarks/1', $response->getHeader('Location')[0]); | 178 | $this->assertEquals('/api/v1/bookmarks/1', $response->getHeader('Location')[0]); |
179 | $data = json_decode((string) $response->getBody(), true); | 179 | $data = json_decode((string) $response->getBody(), true); |
180 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | 180 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); |
181 | $this->assertEquals(43, $data['id']); | 181 | $this->assertEquals(43, $data['id']); |
diff --git a/tests/bookmark/BookmarkFileServiceTest.php b/tests/bookmark/BookmarkFileServiceTest.php index daafd250..f619aff3 100644 --- a/tests/bookmark/BookmarkFileServiceTest.php +++ b/tests/bookmark/BookmarkFileServiceTest.php | |||
@@ -686,22 +686,6 @@ class BookmarkFileServiceTest extends TestCase | |||
686 | } | 686 | } |
687 | 687 | ||
688 | /** | 688 | /** |
689 | * List the days for which bookmarks have been posted | ||
690 | */ | ||
691 | public function testDays() | ||
692 | { | ||
693 | $this->assertSame( | ||
694 | ['20100309', '20100310', '20121206', '20121207', '20130614', '20150310'], | ||
695 | $this->publicLinkDB->days() | ||
696 | ); | ||
697 | |||
698 | $this->assertSame( | ||
699 | ['20100309', '20100310', '20121206', '20121207', '20130614', '20141125', '20150310'], | ||
700 | $this->privateLinkDB->days() | ||
701 | ); | ||
702 | } | ||
703 | |||
704 | /** | ||
705 | * The URL corresponds to an existing entry in the DB | 689 | * The URL corresponds to an existing entry in the DB |
706 | */ | 690 | */ |
707 | public function testGetKnownLinkFromURL() | 691 | public function testGetKnownLinkFromURL() |
@@ -898,6 +882,37 @@ class BookmarkFileServiceTest extends TestCase | |||
898 | } | 882 | } |
899 | 883 | ||
900 | /** | 884 | /** |
885 | * Test filterHash() on a private bookmark while logged out. | ||
886 | */ | ||
887 | public function testFilterHashPrivateWhileLoggedOut() | ||
888 | { | ||
889 | $this->expectException(BookmarkNotFoundException::class); | ||
890 | $this->expectExceptionMessage('The link you are trying to reach does not exist or has been deleted'); | ||
891 | |||
892 | $hash = smallHash('20141125_084734' . 6); | ||
893 | |||
894 | $this->publicLinkDB->findByHash($hash); | ||
895 | } | ||
896 | |||
897 | /** | ||
898 | * Test filterHash() with private key. | ||
899 | */ | ||
900 | public function testFilterHashWithPrivateKey() | ||
901 | { | ||
902 | $hash = smallHash('20141125_084734' . 6); | ||
903 | $privateKey = 'this is usually auto generated'; | ||
904 | |||
905 | $bookmark = $this->privateLinkDB->findByHash($hash); | ||
906 | $bookmark->addAdditionalContentEntry('private_key', $privateKey); | ||
907 | $this->privateLinkDB->save(); | ||
908 | |||
909 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, false); | ||
910 | $bookmark = $this->privateLinkDB->findByHash($hash, $privateKey); | ||
911 | |||
912 | static::assertSame(6, $bookmark->getId()); | ||
913 | } | ||
914 | |||
915 | /** | ||
901 | * Test linksCountPerTag all tags without filter. | 916 | * Test linksCountPerTag all tags without filter. |
902 | * Equal occurrences should be sorted alphabetically. | 917 | * Equal occurrences should be sorted alphabetically. |
903 | */ | 918 | */ |
@@ -1043,33 +1058,105 @@ class BookmarkFileServiceTest extends TestCase | |||
1043 | } | 1058 | } |
1044 | 1059 | ||
1045 | /** | 1060 | /** |
1046 | * Test filterDay while logged in | 1061 | * Test find by dates in the middle of the datastore (sorted by dates) with a single bookmark as a result. |
1047 | */ | 1062 | */ |
1048 | public function testFilterDayLoggedIn(): void | 1063 | public function testFilterByDateMidTimePeriodSingleBookmark(): void |
1049 | { | 1064 | { |
1050 | $bookmarks = $this->privateLinkDB->filterDay('20121206'); | 1065 | $bookmarks = $this->privateLinkDB->findByDate( |
1051 | $expectedIds = [4, 9, 1, 0]; | 1066 | DateTime::createFromFormat('Ymd_His', '20121206_150000'), |
1067 | DateTime::createFromFormat('Ymd_His', '20121206_160000'), | ||
1068 | $before, | ||
1069 | $after | ||
1070 | ); | ||
1052 | 1071 | ||
1053 | static::assertCount(4, $bookmarks); | 1072 | static::assertCount(1, $bookmarks); |
1054 | foreach ($bookmarks as $bookmark) { | 1073 | |
1055 | $i = ($i ?? -1) + 1; | 1074 | static::assertSame(9, $bookmarks[0]->getId()); |
1056 | static::assertSame($expectedIds[$i], $bookmark->getId()); | 1075 | static::assertEquals(DateTime::createFromFormat('Ymd_His', '20121206_142300'), $before); |
1057 | } | 1076 | static::assertEquals(DateTime::createFromFormat('Ymd_His', '20121206_172539'), $after); |
1058 | } | 1077 | } |
1059 | 1078 | ||
1060 | /** | 1079 | /** |
1061 | * Test filterDay while logged out | 1080 | * Test find by dates in the middle of the datastore (sorted by dates) with a multiple bookmarks as a result. |
1062 | */ | 1081 | */ |
1063 | public function testFilterDayLoggedOut(): void | 1082 | public function testFilterByDateMidTimePeriodMultipleBookmarks(): void |
1064 | { | 1083 | { |
1065 | $bookmarks = $this->publicLinkDB->filterDay('20121206'); | 1084 | $bookmarks = $this->privateLinkDB->findByDate( |
1066 | $expectedIds = [4, 9, 1]; | 1085 | DateTime::createFromFormat('Ymd_His', '20121206_150000'), |
1086 | DateTime::createFromFormat('Ymd_His', '20121206_180000'), | ||
1087 | $before, | ||
1088 | $after | ||
1089 | ); | ||
1067 | 1090 | ||
1068 | static::assertCount(3, $bookmarks); | 1091 | static::assertCount(2, $bookmarks); |
1069 | foreach ($bookmarks as $bookmark) { | 1092 | |
1070 | $i = ($i ?? -1) + 1; | 1093 | static::assertSame(1, $bookmarks[0]->getId()); |
1071 | static::assertSame($expectedIds[$i], $bookmark->getId()); | 1094 | static::assertSame(9, $bookmarks[1]->getId()); |
1072 | } | 1095 | static::assertEquals(DateTime::createFromFormat('Ymd_His', '20121206_142300'), $before); |
1096 | static::assertEquals(DateTime::createFromFormat('Ymd_His', '20121206_182539'), $after); | ||
1097 | } | ||
1098 | |||
1099 | /** | ||
1100 | * Test find by dates at the end of the datastore (sorted by dates). | ||
1101 | */ | ||
1102 | public function testFilterByDateLastTimePeriod(): void | ||
1103 | { | ||
1104 | $after = new DateTime(); | ||
1105 | $bookmarks = $this->privateLinkDB->findByDate( | ||
1106 | DateTime::createFromFormat('Ymd_His', '20150310_114640'), | ||
1107 | DateTime::createFromFormat('Ymd_His', '20450101_010101'), | ||
1108 | $before, | ||
1109 | $after | ||
1110 | ); | ||
1111 | |||
1112 | static::assertCount(1, $bookmarks); | ||
1113 | |||
1114 | static::assertSame(41, $bookmarks[0]->getId()); | ||
1115 | static::assertEquals(DateTime::createFromFormat('Ymd_His', '20150310_114633'), $before); | ||
1116 | static::assertNull($after); | ||
1117 | } | ||
1118 | |||
1119 | /** | ||
1120 | * Test find by dates at the beginning of the datastore (sorted by dates). | ||
1121 | */ | ||
1122 | public function testFilterByDateFirstTimePeriod(): void | ||
1123 | { | ||
1124 | $before = new DateTime(); | ||
1125 | $bookmarks = $this->privateLinkDB->findByDate( | ||
1126 | DateTime::createFromFormat('Ymd_His', '20000101_101010'), | ||
1127 | DateTime::createFromFormat('Ymd_His', '20100309_110000'), | ||
1128 | $before, | ||
1129 | $after | ||
1130 | ); | ||
1131 | |||
1132 | static::assertCount(1, $bookmarks); | ||
1133 | |||
1134 | static::assertSame(11, $bookmarks[0]->getId()); | ||
1135 | static::assertNull($before); | ||
1136 | static::assertEquals(DateTime::createFromFormat('Ymd_His', '20100310_101010'), $after); | ||
1137 | } | ||
1138 | |||
1139 | /** | ||
1140 | * Test getLatest with a sticky bookmark: it should be ignored and return the latest by creation date instead. | ||
1141 | */ | ||
1142 | public function testGetLatestWithSticky(): void | ||
1143 | { | ||
1144 | $bookmark = $this->publicLinkDB->getLatest(); | ||
1145 | |||
1146 | static::assertSame(41, $bookmark->getId()); | ||
1147 | } | ||
1148 | |||
1149 | /** | ||
1150 | * Test getLatest with a sticky bookmark: it should be ignored and return the latest by creation date instead. | ||
1151 | */ | ||
1152 | public function testGetLatestEmptyDatastore(): void | ||
1153 | { | ||
1154 | unlink($this->conf->get('resource.datastore')); | ||
1155 | $this->publicLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, false); | ||
1156 | |||
1157 | $bookmark = $this->publicLinkDB->getLatest(); | ||
1158 | |||
1159 | static::assertNull($bookmark); | ||
1073 | } | 1160 | } |
1074 | 1161 | ||
1075 | /** | 1162 | /** |
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/ServerControllerTest.php b/tests/front/controller/admin/ServerControllerTest.php new file mode 100644 index 00000000..355cce7d --- /dev/null +++ b/tests/front/controller/admin/ServerControllerTest.php | |||
@@ -0,0 +1,184 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Security\SessionManager; | ||
9 | use Shaarli\TestCase; | ||
10 | use Slim\Http\Request; | ||
11 | use Slim\Http\Response; | ||
12 | |||
13 | /** | ||
14 | * Test Server administration controller. | ||
15 | */ | ||
16 | class ServerControllerTest extends TestCase | ||
17 | { | ||
18 | use FrontAdminControllerMockHelper; | ||
19 | |||
20 | /** @var ServerController */ | ||
21 | protected $controller; | ||
22 | |||
23 | public function setUp(): void | ||
24 | { | ||
25 | $this->createContainer(); | ||
26 | |||
27 | $this->controller = new ServerController($this->container); | ||
28 | |||
29 | // initialize dummy cache | ||
30 | @mkdir('sandbox/'); | ||
31 | foreach (['pagecache', 'tmp', 'cache'] as $folder) { | ||
32 | @mkdir('sandbox/' . $folder); | ||
33 | @touch('sandbox/' . $folder . '/.htaccess'); | ||
34 | @touch('sandbox/' . $folder . '/1'); | ||
35 | @touch('sandbox/' . $folder . '/2'); | ||
36 | } | ||
37 | } | ||
38 | |||
39 | public function tearDown(): void | ||
40 | { | ||
41 | foreach (['pagecache', 'tmp', 'cache'] as $folder) { | ||
42 | @unlink('sandbox/' . $folder . '/.htaccess'); | ||
43 | @unlink('sandbox/' . $folder . '/1'); | ||
44 | @unlink('sandbox/' . $folder . '/2'); | ||
45 | @rmdir('sandbox/' . $folder); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * Test default display of server administration page. | ||
51 | */ | ||
52 | public function testIndex(): void | ||
53 | { | ||
54 | $request = $this->createMock(Request::class); | ||
55 | $response = new Response(); | ||
56 | |||
57 | // Save RainTPL assigned variables | ||
58 | $assignedVariables = []; | ||
59 | $this->assignTemplateVars($assignedVariables); | ||
60 | |||
61 | $result = $this->controller->index($request, $response); | ||
62 | |||
63 | static::assertSame(200, $result->getStatusCode()); | ||
64 | static::assertSame('server', (string) $result->getBody()); | ||
65 | |||
66 | static::assertSame(PHP_VERSION, $assignedVariables['php_version']); | ||
67 | static::assertArrayHasKey('php_has_reached_eol', $assignedVariables); | ||
68 | static::assertArrayHasKey('php_eol', $assignedVariables); | ||
69 | static::assertArrayHasKey('php_extensions', $assignedVariables); | ||
70 | static::assertArrayHasKey('permissions', $assignedVariables); | ||
71 | static::assertEmpty($assignedVariables['permissions']); | ||
72 | |||
73 | static::assertRegExp( | ||
74 | '#https://github\.com/shaarli/Shaarli/releases/tag/v\d+\.\d+\.\d+#', | ||
75 | $assignedVariables['release_url'] | ||
76 | ); | ||
77 | static::assertRegExp('#v\d+\.\d+\.\d+#', $assignedVariables['latest_version']); | ||
78 | static::assertRegExp('#(v\d+\.\d+\.\d+|dev)#', $assignedVariables['current_version']); | ||
79 | static::assertArrayHasKey('index_url', $assignedVariables); | ||
80 | static::assertArrayHasKey('client_ip', $assignedVariables); | ||
81 | static::assertArrayHasKey('trusted_proxies', $assignedVariables); | ||
82 | |||
83 | static::assertSame('Server administration - Shaarli', $assignedVariables['pagetitle']); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * Test clearing the main cache | ||
88 | */ | ||
89 | public function testClearMainCache(): void | ||
90 | { | ||
91 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
92 | $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { | ||
93 | if ($key === 'resource.page_cache') { | ||
94 | return 'sandbox/pagecache'; | ||
95 | } elseif ($key === 'resource.raintpl_tmp') { | ||
96 | return 'sandbox/tmp'; | ||
97 | } elseif ($key === 'resource.thumbnails_cache') { | ||
98 | return 'sandbox/cache'; | ||
99 | } else { | ||
100 | return $default; | ||
101 | } | ||
102 | }); | ||
103 | |||
104 | $this->container->sessionManager | ||
105 | ->expects(static::once()) | ||
106 | ->method('setSessionParameter') | ||
107 | ->with(SessionManager::KEY_SUCCESS_MESSAGES, ['Shaarli\'s cache folder has been cleared!']) | ||
108 | ; | ||
109 | |||
110 | $request = $this->createMock(Request::class); | ||
111 | $request->method('getQueryParam')->with('type')->willReturn('main'); | ||
112 | $response = new Response(); | ||
113 | |||
114 | $result = $this->controller->clearCache($request, $response); | ||
115 | |||
116 | static::assertSame(302, $result->getStatusCode()); | ||
117 | static::assertSame('/subfolder/admin/server', (string) $result->getHeaderLine('Location')); | ||
118 | |||
119 | static::assertFileNotExists('sandbox/pagecache/1'); | ||
120 | static::assertFileNotExists('sandbox/pagecache/2'); | ||
121 | static::assertFileNotExists('sandbox/tmp/1'); | ||
122 | static::assertFileNotExists('sandbox/tmp/2'); | ||
123 | |||
124 | static::assertFileExists('sandbox/pagecache/.htaccess'); | ||
125 | static::assertFileExists('sandbox/tmp/.htaccess'); | ||
126 | static::assertFileExists('sandbox/cache'); | ||
127 | static::assertFileExists('sandbox/cache/.htaccess'); | ||
128 | static::assertFileExists('sandbox/cache/1'); | ||
129 | static::assertFileExists('sandbox/cache/2'); | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * Test clearing thumbnails cache | ||
134 | */ | ||
135 | public function testClearThumbnailsCache(): void | ||
136 | { | ||
137 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
138 | $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { | ||
139 | if ($key === 'resource.page_cache') { | ||
140 | return 'sandbox/pagecache'; | ||
141 | } elseif ($key === 'resource.raintpl_tmp') { | ||
142 | return 'sandbox/tmp'; | ||
143 | } elseif ($key === 'resource.thumbnails_cache') { | ||
144 | return 'sandbox/cache'; | ||
145 | } else { | ||
146 | return $default; | ||
147 | } | ||
148 | }); | ||
149 | |||
150 | $this->container->sessionManager | ||
151 | ->expects(static::once()) | ||
152 | ->method('setSessionParameter') | ||
153 | ->willReturnCallback(function (string $key, array $value): SessionManager { | ||
154 | static::assertSame(SessionManager::KEY_WARNING_MESSAGES, $key); | ||
155 | static::assertCount(1, $value); | ||
156 | static::assertStringStartsWith('Thumbnails cache has been cleared.', $value[0]); | ||
157 | |||
158 | return $this->container->sessionManager; | ||
159 | }); | ||
160 | ; | ||
161 | |||
162 | $request = $this->createMock(Request::class); | ||
163 | $request->method('getQueryParam')->with('type')->willReturn('thumbnails'); | ||
164 | $response = new Response(); | ||
165 | |||
166 | $result = $this->controller->clearCache($request, $response); | ||
167 | |||
168 | static::assertSame(302, $result->getStatusCode()); | ||
169 | static::assertSame('/subfolder/admin/server', (string) $result->getHeaderLine('Location')); | ||
170 | |||
171 | static::assertFileNotExists('sandbox/cache/1'); | ||
172 | static::assertFileNotExists('sandbox/cache/2'); | ||
173 | |||
174 | static::assertFileExists('sandbox/cache/.htaccess'); | ||
175 | static::assertFileExists('sandbox/pagecache'); | ||
176 | static::assertFileExists('sandbox/pagecache/.htaccess'); | ||
177 | static::assertFileExists('sandbox/pagecache/1'); | ||
178 | static::assertFileExists('sandbox/pagecache/2'); | ||
179 | static::assertFileExists('sandbox/tmp'); | ||
180 | static::assertFileExists('sandbox/tmp/.htaccess'); | ||
181 | static::assertFileExists('sandbox/tmp/1'); | ||
182 | static::assertFileExists('sandbox/tmp/2'); | ||
183 | } | ||
184 | } | ||
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/ShaareManageControllerTest/SharePrivateTest.php b/tests/front/controller/admin/ShaareManageControllerTest/SharePrivateTest.php new file mode 100644 index 00000000..ae61dfb7 --- /dev/null +++ b/tests/front/controller/admin/ShaareManageControllerTest/SharePrivateTest.php | |||
@@ -0,0 +1,139 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ShaareManageControllerTest; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
9 | use Shaarli\Front\Controller\Admin\ShaareManageController; | ||
10 | use Shaarli\Http\HttpAccess; | ||
11 | use Shaarli\TestCase; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | /** | ||
16 | * Test GET /admin/shaare/private/{hash} | ||
17 | */ | ||
18 | class SharePrivateTest extends TestCase | ||
19 | { | ||
20 | use FrontAdminControllerMockHelper; | ||
21 | |||
22 | /** @var ShaareManageController */ | ||
23 | protected $controller; | ||
24 | |||
25 | public function setUp(): void | ||
26 | { | ||
27 | $this->createContainer(); | ||
28 | |||
29 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
30 | $this->controller = new ShaareManageController($this->container); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Test shaare private with a private bookmark which does not have a key yet. | ||
35 | */ | ||
36 | public function testSharePrivateWithNewPrivateBookmark(): void | ||
37 | { | ||
38 | $hash = 'abcdcef'; | ||
39 | $request = $this->createMock(Request::class); | ||
40 | $response = new Response(); | ||
41 | |||
42 | $bookmark = (new Bookmark()) | ||
43 | ->setId(123) | ||
44 | ->setUrl('http://domain.tld') | ||
45 | ->setTitle('Title 123') | ||
46 | ->setPrivate(true) | ||
47 | ; | ||
48 | |||
49 | $this->container->bookmarkService | ||
50 | ->expects(static::once()) | ||
51 | ->method('findByHash') | ||
52 | ->with($hash) | ||
53 | ->willReturn($bookmark) | ||
54 | ; | ||
55 | $this->container->bookmarkService | ||
56 | ->expects(static::once()) | ||
57 | ->method('set') | ||
58 | ->with($bookmark, true) | ||
59 | ->willReturnCallback(function (Bookmark $bookmark): Bookmark { | ||
60 | static::assertSame(32, strlen($bookmark->getAdditionalContentEntry('private_key'))); | ||
61 | |||
62 | return $bookmark; | ||
63 | }) | ||
64 | ; | ||
65 | |||
66 | $result = $this->controller->sharePrivate($request, $response, ['hash' => $hash]); | ||
67 | |||
68 | static::assertSame(302, $result->getStatusCode()); | ||
69 | static::assertRegExp('#/subfolder/shaare/' . $hash . '\?key=\w{32}#', $result->getHeaderLine('Location')); | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * Test shaare private with a private bookmark which does already have a key. | ||
74 | */ | ||
75 | public function testSharePrivateWithExistingPrivateBookmark(): void | ||
76 | { | ||
77 | $hash = 'abcdcef'; | ||
78 | $existingKey = 'this is a private key'; | ||
79 | $request = $this->createMock(Request::class); | ||
80 | $response = new Response(); | ||
81 | |||
82 | $bookmark = (new Bookmark()) | ||
83 | ->setId(123) | ||
84 | ->setUrl('http://domain.tld') | ||
85 | ->setTitle('Title 123') | ||
86 | ->setPrivate(true) | ||
87 | ->addAdditionalContentEntry('private_key', $existingKey) | ||
88 | ; | ||
89 | |||
90 | $this->container->bookmarkService | ||
91 | ->expects(static::once()) | ||
92 | ->method('findByHash') | ||
93 | ->with($hash) | ||
94 | ->willReturn($bookmark) | ||
95 | ; | ||
96 | $this->container->bookmarkService | ||
97 | ->expects(static::never()) | ||
98 | ->method('set') | ||
99 | ; | ||
100 | |||
101 | $result = $this->controller->sharePrivate($request, $response, ['hash' => $hash]); | ||
102 | |||
103 | static::assertSame(302, $result->getStatusCode()); | ||
104 | static::assertSame('/subfolder/shaare/' . $hash . '?key=' . $existingKey, $result->getHeaderLine('Location')); | ||
105 | } | ||
106 | |||
107 | /** | ||
108 | * Test shaare private with a public bookmark. | ||
109 | */ | ||
110 | public function testSharePrivateWithPublicBookmark(): void | ||
111 | { | ||
112 | $hash = 'abcdcef'; | ||
113 | $request = $this->createMock(Request::class); | ||
114 | $response = new Response(); | ||
115 | |||
116 | $bookmark = (new Bookmark()) | ||
117 | ->setId(123) | ||
118 | ->setUrl('http://domain.tld') | ||
119 | ->setTitle('Title 123') | ||
120 | ->setPrivate(false) | ||
121 | ; | ||
122 | |||
123 | $this->container->bookmarkService | ||
124 | ->expects(static::once()) | ||
125 | ->method('findByHash') | ||
126 | ->with($hash) | ||
127 | ->willReturn($bookmark) | ||
128 | ; | ||
129 | $this->container->bookmarkService | ||
130 | ->expects(static::never()) | ||
131 | ->method('set') | ||
132 | ; | ||
133 | |||
134 | $result = $this->controller->sharePrivate($request, $response, ['hash' => $hash]); | ||
135 | |||
136 | static::assertSame(302, $result->getStatusCode()); | ||
137 | static::assertSame('/subfolder/shaare/' . $hash, $result->getHeaderLine('Location')); | ||
138 | } | ||
139 | } | ||
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/tests/front/controller/visitor/BookmarkListControllerTest.php b/tests/front/controller/visitor/BookmarkListControllerTest.php index 5ca92507..5cbc8c73 100644 --- a/tests/front/controller/visitor/BookmarkListControllerTest.php +++ b/tests/front/controller/visitor/BookmarkListControllerTest.php | |||
@@ -292,6 +292,37 @@ class BookmarkListControllerTest extends TestCase | |||
292 | } | 292 | } |
293 | 293 | ||
294 | /** | 294 | /** |
295 | * Test GET /shaare/{hash}?key={key} - Find a link by hash using a private link. | ||
296 | */ | ||
297 | public function testPermalinkWithPrivateKey(): void | ||
298 | { | ||
299 | $hash = 'abcdef'; | ||
300 | $privateKey = 'this is a private key'; | ||
301 | |||
302 | $assignedVariables = []; | ||
303 | $this->assignTemplateVars($assignedVariables); | ||
304 | |||
305 | $request = $this->createMock(Request::class); | ||
306 | $request->method('getParam')->willReturnCallback(function (string $key, $default = null) use ($privateKey) { | ||
307 | return $key === 'key' ? $privateKey : $default; | ||
308 | }); | ||
309 | $response = new Response(); | ||
310 | |||
311 | $this->container->bookmarkService | ||
312 | ->expects(static::once()) | ||
313 | ->method('findByHash') | ||
314 | ->with($hash, $privateKey) | ||
315 | ->willReturn((new Bookmark())->setId(123)->setTitle('Title 1')->setUrl('http://url1.tld')) | ||
316 | ; | ||
317 | |||
318 | $result = $this->controller->permalink($request, $response, ['hash' => $hash]); | ||
319 | |||
320 | static::assertSame(200, $result->getStatusCode()); | ||
321 | static::assertSame('linklist', (string) $result->getBody()); | ||
322 | static::assertCount(1, $assignedVariables['links']); | ||
323 | } | ||
324 | |||
325 | /** | ||
295 | * Test getting link list with thumbnail updates. | 326 | * Test getting link list with thumbnail updates. |
296 | * -> 2 thumbnails update, only 1 datastore write | 327 | * -> 2 thumbnails update, only 1 datastore write |
297 | */ | 328 | */ |
diff --git a/tests/front/controller/visitor/DailyControllerTest.php b/tests/front/controller/visitor/DailyControllerTest.php index fc78bc13..70fbce54 100644 --- a/tests/front/controller/visitor/DailyControllerTest.php +++ b/tests/front/controller/visitor/DailyControllerTest.php | |||
@@ -28,52 +28,49 @@ class DailyControllerTest extends TestCase | |||
28 | public function testValidIndexControllerInvokeDefault(): void | 28 | public function testValidIndexControllerInvokeDefault(): void |
29 | { | 29 | { |
30 | $currentDay = new \DateTimeImmutable('2020-05-13'); | 30 | $currentDay = new \DateTimeImmutable('2020-05-13'); |
31 | $previousDate = new \DateTime('2 days ago 00:00:00'); | ||
32 | $nextDate = new \DateTime('today 00:00:00'); | ||
31 | 33 | ||
32 | $request = $this->createMock(Request::class); | 34 | $request = $this->createMock(Request::class); |
33 | $request->method('getQueryParam')->willReturn($currentDay->format('Ymd')); | 35 | $request->method('getQueryParam')->willReturnCallback(function (string $key) use ($currentDay): ?string { |
36 | return $key === 'day' ? $currentDay->format('Ymd') : null; | ||
37 | }); | ||
34 | $response = new Response(); | 38 | $response = new Response(); |
35 | 39 | ||
36 | // Save RainTPL assigned variables | 40 | // Save RainTPL assigned variables |
37 | $assignedVariables = []; | 41 | $assignedVariables = []; |
38 | $this->assignTemplateVars($assignedVariables); | 42 | $this->assignTemplateVars($assignedVariables); |
39 | 43 | ||
40 | // Links dataset: 2 links with thumbnails | ||
41 | $this->container->bookmarkService | ||
42 | ->expects(static::once()) | ||
43 | ->method('days') | ||
44 | ->willReturnCallback(function () use ($currentDay): array { | ||
45 | return [ | ||
46 | '20200510', | ||
47 | $currentDay->format('Ymd'), | ||
48 | '20200516', | ||
49 | ]; | ||
50 | }) | ||
51 | ; | ||
52 | $this->container->bookmarkService | 44 | $this->container->bookmarkService |
53 | ->expects(static::once()) | 45 | ->expects(static::once()) |
54 | ->method('filterDay') | 46 | ->method('findByDate') |
55 | ->willReturnCallback(function (): array { | 47 | ->willReturnCallback( |
56 | return [ | 48 | function ($from, $to, &$previous, &$next) use ($currentDay, $previousDate, $nextDate): array { |
57 | (new Bookmark()) | 49 | $previous = $previousDate; |
58 | ->setId(1) | 50 | $next = $nextDate; |
59 | ->setUrl('http://url.tld') | 51 | |
60 | ->setTitle(static::generateString(50)) | 52 | return [ |
61 | ->setDescription(static::generateString(500)) | 53 | (new Bookmark()) |
62 | , | 54 | ->setId(1) |
63 | (new Bookmark()) | 55 | ->setUrl('http://url.tld') |
64 | ->setId(2) | 56 | ->setTitle(static::generateString(50)) |
65 | ->setUrl('http://url2.tld') | 57 | ->setDescription(static::generateString(500)) |
66 | ->setTitle(static::generateString(50)) | 58 | , |
67 | ->setDescription(static::generateString(500)) | 59 | (new Bookmark()) |
68 | , | 60 | ->setId(2) |
69 | (new Bookmark()) | 61 | ->setUrl('http://url2.tld') |
70 | ->setId(3) | 62 | ->setTitle(static::generateString(50)) |
71 | ->setUrl('http://url3.tld') | 63 | ->setDescription(static::generateString(500)) |
72 | ->setTitle(static::generateString(50)) | 64 | , |
73 | ->setDescription(static::generateString(500)) | 65 | (new Bookmark()) |
74 | , | 66 | ->setId(3) |
75 | ]; | 67 | ->setUrl('http://url3.tld') |
76 | }) | 68 | ->setTitle(static::generateString(50)) |
69 | ->setDescription(static::generateString(500)) | ||
70 | , | ||
71 | ]; | ||
72 | } | ||
73 | ) | ||
77 | ; | 74 | ; |
78 | 75 | ||
79 | // Make sure that PluginManager hook is triggered | 76 | // Make sure that PluginManager hook is triggered |
@@ -81,20 +78,22 @@ class DailyControllerTest extends TestCase | |||
81 | ->expects(static::atLeastOnce()) | 78 | ->expects(static::atLeastOnce()) |
82 | ->method('executeHooks') | 79 | ->method('executeHooks') |
83 | ->withConsecutive(['render_daily']) | 80 | ->withConsecutive(['render_daily']) |
84 | ->willReturnCallback(function (string $hook, array $data, array $param) use ($currentDay): array { | 81 | ->willReturnCallback( |
85 | if ('render_daily' === $hook) { | 82 | function (string $hook, array $data, array $param) use ($currentDay, $previousDate, $nextDate): array { |
86 | static::assertArrayHasKey('linksToDisplay', $data); | 83 | if ('render_daily' === $hook) { |
87 | static::assertCount(3, $data['linksToDisplay']); | 84 | static::assertArrayHasKey('linksToDisplay', $data); |
88 | static::assertSame(1, $data['linksToDisplay'][0]['id']); | 85 | static::assertCount(3, $data['linksToDisplay']); |
89 | static::assertSame($currentDay->getTimestamp(), $data['day']); | 86 | static::assertSame(1, $data['linksToDisplay'][0]['id']); |
90 | static::assertSame('20200510', $data['previousday']); | 87 | static::assertSame($currentDay->getTimestamp(), $data['day']); |
91 | static::assertSame('20200516', $data['nextday']); | 88 | static::assertSame($previousDate->format('Ymd'), $data['previousday']); |
92 | 89 | static::assertSame($nextDate->format('Ymd'), $data['nextday']); | |
93 | static::assertArrayHasKey('loggedin', $param); | 90 | |
91 | static::assertArrayHasKey('loggedin', $param); | ||
92 | } | ||
93 | |||
94 | return $data; | ||
94 | } | 95 | } |
95 | 96 | ) | |
96 | return $data; | ||
97 | }) | ||
98 | ; | 97 | ; |
99 | 98 | ||
100 | $result = $this->controller->index($request, $response); | 99 | $result = $this->controller->index($request, $response); |
@@ -107,6 +106,11 @@ class DailyControllerTest extends TestCase | |||
107 | ); | 106 | ); |
108 | static::assertEquals($currentDay, $assignedVariables['dayDate']); | 107 | static::assertEquals($currentDay, $assignedVariables['dayDate']); |
109 | static::assertEquals($currentDay->getTimestamp(), $assignedVariables['day']); | 108 | static::assertEquals($currentDay->getTimestamp(), $assignedVariables['day']); |
109 | static::assertSame($previousDate->format('Ymd'), $assignedVariables['previousday']); | ||
110 | static::assertSame($nextDate->format('Ymd'), $assignedVariables['nextday']); | ||
111 | static::assertSame('day', $assignedVariables['type']); | ||
112 | static::assertSame('May 13, 2020', $assignedVariables['dayDesc']); | ||
113 | static::assertSame('Daily', $assignedVariables['localizedType']); | ||
110 | static::assertCount(3, $assignedVariables['linksToDisplay']); | 114 | static::assertCount(3, $assignedVariables['linksToDisplay']); |
111 | 115 | ||
112 | $link = $assignedVariables['linksToDisplay'][0]; | 116 | $link = $assignedVariables['linksToDisplay'][0]; |
@@ -171,27 +175,20 @@ class DailyControllerTest extends TestCase | |||
171 | $currentDay = new \DateTimeImmutable('2020-05-13'); | 175 | $currentDay = new \DateTimeImmutable('2020-05-13'); |
172 | 176 | ||
173 | $request = $this->createMock(Request::class); | 177 | $request = $this->createMock(Request::class); |
178 | $request->method('getQueryParam')->willReturnCallback(function (string $key) use ($currentDay): ?string { | ||
179 | return $key === 'day' ? $currentDay->format('Ymd') : null; | ||
180 | }); | ||
174 | $response = new Response(); | 181 | $response = new Response(); |
175 | 182 | ||
176 | // Save RainTPL assigned variables | 183 | // Save RainTPL assigned variables |
177 | $assignedVariables = []; | 184 | $assignedVariables = []; |
178 | $this->assignTemplateVars($assignedVariables); | 185 | $this->assignTemplateVars($assignedVariables); |
179 | 186 | ||
180 | // Links dataset: 2 links with thumbnails | ||
181 | $this->container->bookmarkService | 187 | $this->container->bookmarkService |
182 | ->expects(static::once()) | 188 | ->expects(static::once()) |
183 | ->method('days') | 189 | ->method('findByDate') |
184 | ->willReturnCallback(function () use ($currentDay): array { | 190 | ->willReturnCallback(function () use ($currentDay): array { |
185 | return [ | 191 | return [ |
186 | $currentDay->format($currentDay->format('Ymd')), | ||
187 | ]; | ||
188 | }) | ||
189 | ; | ||
190 | $this->container->bookmarkService | ||
191 | ->expects(static::once()) | ||
192 | ->method('filterDay') | ||
193 | ->willReturnCallback(function (): array { | ||
194 | return [ | ||
195 | (new Bookmark()) | 192 | (new Bookmark()) |
196 | ->setId(1) | 193 | ->setId(1) |
197 | ->setUrl('http://url.tld') | 194 | ->setUrl('http://url.tld') |
@@ -250,21 +247,11 @@ class DailyControllerTest extends TestCase | |||
250 | $assignedVariables = []; | 247 | $assignedVariables = []; |
251 | $this->assignTemplateVars($assignedVariables); | 248 | $this->assignTemplateVars($assignedVariables); |
252 | 249 | ||
253 | // Links dataset: 2 links with thumbnails | ||
254 | $this->container->bookmarkService | 250 | $this->container->bookmarkService |
255 | ->expects(static::once()) | 251 | ->expects(static::once()) |
256 | ->method('days') | 252 | ->method('findByDate') |
257 | ->willReturnCallback(function () use ($currentDay): array { | 253 | ->willReturnCallback(function () use ($currentDay): array { |
258 | return [ | 254 | return [ |
259 | $currentDay->format($currentDay->format('Ymd')), | ||
260 | ]; | ||
261 | }) | ||
262 | ; | ||
263 | $this->container->bookmarkService | ||
264 | ->expects(static::once()) | ||
265 | ->method('filterDay') | ||
266 | ->willReturnCallback(function (): array { | ||
267 | return [ | ||
268 | (new Bookmark())->setId(1)->setUrl('http://url.tld')->setTitle('title'), | 255 | (new Bookmark())->setId(1)->setUrl('http://url.tld')->setTitle('title'), |
269 | (new Bookmark()) | 256 | (new Bookmark()) |
270 | ->setId(2) | 257 | ->setId(2) |
@@ -320,14 +307,7 @@ class DailyControllerTest extends TestCase | |||
320 | // Links dataset: 2 links with thumbnails | 307 | // Links dataset: 2 links with thumbnails |
321 | $this->container->bookmarkService | 308 | $this->container->bookmarkService |
322 | ->expects(static::once()) | 309 | ->expects(static::once()) |
323 | ->method('days') | 310 | ->method('findByDate') |
324 | ->willReturnCallback(function (): array { | ||
325 | return []; | ||
326 | }) | ||
327 | ; | ||
328 | $this->container->bookmarkService | ||
329 | ->expects(static::once()) | ||
330 | ->method('filterDay') | ||
331 | ->willReturnCallback(function (): array { | 311 | ->willReturnCallback(function (): array { |
332 | return []; | 312 | return []; |
333 | }) | 313 | }) |
@@ -347,7 +327,7 @@ class DailyControllerTest extends TestCase | |||
347 | static::assertSame(200, $result->getStatusCode()); | 327 | static::assertSame(200, $result->getStatusCode()); |
348 | static::assertSame('daily', (string) $result->getBody()); | 328 | static::assertSame('daily', (string) $result->getBody()); |
349 | static::assertCount(0, $assignedVariables['linksToDisplay']); | 329 | static::assertCount(0, $assignedVariables['linksToDisplay']); |
350 | static::assertSame('Today', $assignedVariables['dayDesc']); | 330 | static::assertSame('Today - ' . (new \DateTime())->format('F j, Y'), $assignedVariables['dayDesc']); |
351 | static::assertEquals((new \DateTime())->setTime(0, 0)->getTimestamp(), $assignedVariables['day']); | 331 | static::assertEquals((new \DateTime())->setTime(0, 0)->getTimestamp(), $assignedVariables['day']); |
352 | static::assertEquals((new \DateTime())->setTime(0, 0), $assignedVariables['dayDate']); | 332 | static::assertEquals((new \DateTime())->setTime(0, 0), $assignedVariables['dayDate']); |
353 | } | 333 | } |
@@ -361,6 +341,7 @@ class DailyControllerTest extends TestCase | |||
361 | new \DateTimeImmutable('2020-05-17'), | 341 | new \DateTimeImmutable('2020-05-17'), |
362 | new \DateTimeImmutable('2020-05-15'), | 342 | new \DateTimeImmutable('2020-05-15'), |
363 | new \DateTimeImmutable('2020-05-13'), | 343 | new \DateTimeImmutable('2020-05-13'), |
344 | new \DateTimeImmutable('+1 month'), | ||
364 | ]; | 345 | ]; |
365 | 346 | ||
366 | $request = $this->createMock(Request::class); | 347 | $request = $this->createMock(Request::class); |
@@ -371,6 +352,7 @@ class DailyControllerTest extends TestCase | |||
371 | (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'), | 352 | (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'), |
372 | (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'), | 353 | (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'), |
373 | (new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'), | 354 | (new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'), |
355 | (new Bookmark())->setId(5)->setCreated($dates[3])->setUrl('http://domain.tld/5'), | ||
374 | ]); | 356 | ]); |
375 | 357 | ||
376 | $this->container->pageCacheManager | 358 | $this->container->pageCacheManager |
@@ -397,13 +379,14 @@ class DailyControllerTest extends TestCase | |||
397 | static::assertSame('http://shaarli/subfolder/', $assignedVariables['index_url']); | 379 | static::assertSame('http://shaarli/subfolder/', $assignedVariables['index_url']); |
398 | static::assertSame('http://shaarli/subfolder/daily-rss', $assignedVariables['page_url']); | 380 | static::assertSame('http://shaarli/subfolder/daily-rss', $assignedVariables['page_url']); |
399 | static::assertFalse($assignedVariables['hide_timestamps']); | 381 | static::assertFalse($assignedVariables['hide_timestamps']); |
400 | static::assertCount(2, $assignedVariables['days']); | 382 | static::assertCount(3, $assignedVariables['days']); |
401 | 383 | ||
402 | $day = $assignedVariables['days'][$dates[0]->format('Ymd')]; | 384 | $day = $assignedVariables['days'][$dates[0]->format('Ymd')]; |
385 | $date = $dates[0]->setTime(23, 59, 59); | ||
403 | 386 | ||
404 | static::assertEquals($dates[0], $day['date']); | 387 | static::assertEquals($date, $day['date']); |
405 | static::assertSame($dates[0]->format(\DateTime::RSS), $day['date_rss']); | 388 | static::assertSame($date->format(\DateTime::RSS), $day['date_rss']); |
406 | static::assertSame(format_date($dates[0], false), $day['date_human']); | 389 | static::assertSame(format_date($date, false), $day['date_human']); |
407 | static::assertSame('http://shaarli/subfolder/daily?day='. $dates[0]->format('Ymd'), $day['absolute_url']); | 390 | static::assertSame('http://shaarli/subfolder/daily?day='. $dates[0]->format('Ymd'), $day['absolute_url']); |
408 | static::assertCount(1, $day['links']); | 391 | static::assertCount(1, $day['links']); |
409 | static::assertSame(1, $day['links'][0]['id']); | 392 | static::assertSame(1, $day['links'][0]['id']); |
@@ -411,10 +394,11 @@ class DailyControllerTest extends TestCase | |||
411 | static::assertEquals($dates[0], $day['links'][0]['created']); | 394 | static::assertEquals($dates[0], $day['links'][0]['created']); |
412 | 395 | ||
413 | $day = $assignedVariables['days'][$dates[1]->format('Ymd')]; | 396 | $day = $assignedVariables['days'][$dates[1]->format('Ymd')]; |
397 | $date = $dates[1]->setTime(23, 59, 59); | ||
414 | 398 | ||
415 | static::assertEquals($dates[1], $day['date']); | 399 | static::assertEquals($date, $day['date']); |
416 | static::assertSame($dates[1]->format(\DateTime::RSS), $day['date_rss']); | 400 | static::assertSame($date->format(\DateTime::RSS), $day['date_rss']); |
417 | static::assertSame(format_date($dates[1], false), $day['date_human']); | 401 | static::assertSame(format_date($date, false), $day['date_human']); |
418 | static::assertSame('http://shaarli/subfolder/daily?day='. $dates[1]->format('Ymd'), $day['absolute_url']); | 402 | static::assertSame('http://shaarli/subfolder/daily?day='. $dates[1]->format('Ymd'), $day['absolute_url']); |
419 | static::assertCount(2, $day['links']); | 403 | static::assertCount(2, $day['links']); |
420 | 404 | ||
@@ -424,6 +408,18 @@ class DailyControllerTest extends TestCase | |||
424 | static::assertSame(3, $day['links'][1]['id']); | 408 | static::assertSame(3, $day['links'][1]['id']); |
425 | static::assertSame('http://domain.tld/3', $day['links'][1]['url']); | 409 | static::assertSame('http://domain.tld/3', $day['links'][1]['url']); |
426 | static::assertEquals($dates[1], $day['links'][1]['created']); | 410 | static::assertEquals($dates[1], $day['links'][1]['created']); |
411 | |||
412 | $day = $assignedVariables['days'][$dates[2]->format('Ymd')]; | ||
413 | $date = $dates[2]->setTime(23, 59, 59); | ||
414 | |||
415 | static::assertEquals($date, $day['date']); | ||
416 | static::assertSame($date->format(\DateTime::RSS), $day['date_rss']); | ||
417 | static::assertSame(format_date($date, false), $day['date_human']); | ||
418 | static::assertSame('http://shaarli/subfolder/daily?day='. $dates[2]->format('Ymd'), $day['absolute_url']); | ||
419 | static::assertCount(1, $day['links']); | ||
420 | static::assertSame(4, $day['links'][0]['id']); | ||
421 | static::assertSame('http://domain.tld/4', $day['links'][0]['url']); | ||
422 | static::assertEquals($dates[2], $day['links'][0]['created']); | ||
427 | } | 423 | } |
428 | 424 | ||
429 | /** | 425 | /** |
@@ -475,4 +471,246 @@ class DailyControllerTest extends TestCase | |||
475 | static::assertFalse($assignedVariables['hide_timestamps']); | 471 | static::assertFalse($assignedVariables['hide_timestamps']); |
476 | static::assertCount(0, $assignedVariables['days']); | 472 | static::assertCount(0, $assignedVariables['days']); |
477 | } | 473 | } |
474 | |||
475 | /** | ||
476 | * Test simple display index with week parameter | ||
477 | */ | ||
478 | public function testSimpleIndexWeekly(): void | ||
479 | { | ||
480 | $currentDay = new \DateTimeImmutable('2020-05-13'); | ||
481 | $expectedDay = new \DateTimeImmutable('2020-05-11'); | ||
482 | |||
483 | $request = $this->createMock(Request::class); | ||
484 | $request->method('getQueryParam')->willReturnCallback(function (string $key) use ($currentDay): ?string { | ||
485 | return $key === 'week' ? $currentDay->format('YW') : null; | ||
486 | }); | ||
487 | $response = new Response(); | ||
488 | |||
489 | // Save RainTPL assigned variables | ||
490 | $assignedVariables = []; | ||
491 | $this->assignTemplateVars($assignedVariables); | ||
492 | |||
493 | $this->container->bookmarkService | ||
494 | ->expects(static::once()) | ||
495 | ->method('findByDate') | ||
496 | ->willReturnCallback( | ||
497 | function (): array { | ||
498 | return [ | ||
499 | (new Bookmark()) | ||
500 | ->setId(1) | ||
501 | ->setUrl('http://url.tld') | ||
502 | ->setTitle(static::generateString(50)) | ||
503 | ->setDescription(static::generateString(500)) | ||
504 | , | ||
505 | (new Bookmark()) | ||
506 | ->setId(2) | ||
507 | ->setUrl('http://url2.tld') | ||
508 | ->setTitle(static::generateString(50)) | ||
509 | ->setDescription(static::generateString(500)) | ||
510 | , | ||
511 | ]; | ||
512 | } | ||
513 | ) | ||
514 | ; | ||
515 | |||
516 | $result = $this->controller->index($request, $response); | ||
517 | |||
518 | static::assertSame(200, $result->getStatusCode()); | ||
519 | static::assertSame('daily', (string) $result->getBody()); | ||
520 | static::assertSame( | ||
521 | 'Weekly - Week 20 (May 11, 2020) - Shaarli', | ||
522 | $assignedVariables['pagetitle'] | ||
523 | ); | ||
524 | |||
525 | static::assertCount(2, $assignedVariables['linksToDisplay']); | ||
526 | static::assertEquals($expectedDay->setTime(0, 0), $assignedVariables['dayDate']); | ||
527 | static::assertSame($expectedDay->setTime(0, 0)->getTimestamp(), $assignedVariables['day']); | ||
528 | static::assertSame('', $assignedVariables['previousday']); | ||
529 | static::assertSame('', $assignedVariables['nextday']); | ||
530 | static::assertSame('Week 20 (May 11, 2020)', $assignedVariables['dayDesc']); | ||
531 | static::assertSame('week', $assignedVariables['type']); | ||
532 | static::assertSame('Weekly', $assignedVariables['localizedType']); | ||
533 | } | ||
534 | |||
535 | /** | ||
536 | * Test simple display index with month parameter | ||
537 | */ | ||
538 | public function testSimpleIndexMonthly(): void | ||
539 | { | ||
540 | $currentDay = new \DateTimeImmutable('2020-05-13'); | ||
541 | $expectedDay = new \DateTimeImmutable('2020-05-01'); | ||
542 | |||
543 | $request = $this->createMock(Request::class); | ||
544 | $request->method('getQueryParam')->willReturnCallback(function (string $key) use ($currentDay): ?string { | ||
545 | return $key === 'month' ? $currentDay->format('Ym') : null; | ||
546 | }); | ||
547 | $response = new Response(); | ||
548 | |||
549 | // Save RainTPL assigned variables | ||
550 | $assignedVariables = []; | ||
551 | $this->assignTemplateVars($assignedVariables); | ||
552 | |||
553 | $this->container->bookmarkService | ||
554 | ->expects(static::once()) | ||
555 | ->method('findByDate') | ||
556 | ->willReturnCallback( | ||
557 | function (): array { | ||
558 | return [ | ||
559 | (new Bookmark()) | ||
560 | ->setId(1) | ||
561 | ->setUrl('http://url.tld') | ||
562 | ->setTitle(static::generateString(50)) | ||
563 | ->setDescription(static::generateString(500)) | ||
564 | , | ||
565 | (new Bookmark()) | ||
566 | ->setId(2) | ||
567 | ->setUrl('http://url2.tld') | ||
568 | ->setTitle(static::generateString(50)) | ||
569 | ->setDescription(static::generateString(500)) | ||
570 | , | ||
571 | ]; | ||
572 | } | ||
573 | ) | ||
574 | ; | ||
575 | |||
576 | $result = $this->controller->index($request, $response); | ||
577 | |||
578 | static::assertSame(200, $result->getStatusCode()); | ||
579 | static::assertSame('daily', (string) $result->getBody()); | ||
580 | static::assertSame( | ||
581 | 'Monthly - May, 2020 - Shaarli', | ||
582 | $assignedVariables['pagetitle'] | ||
583 | ); | ||
584 | |||
585 | static::assertCount(2, $assignedVariables['linksToDisplay']); | ||
586 | static::assertEquals($expectedDay->setTime(0, 0), $assignedVariables['dayDate']); | ||
587 | static::assertSame($expectedDay->setTime(0, 0)->getTimestamp(), $assignedVariables['day']); | ||
588 | static::assertSame('', $assignedVariables['previousday']); | ||
589 | static::assertSame('', $assignedVariables['nextday']); | ||
590 | static::assertSame('May, 2020', $assignedVariables['dayDesc']); | ||
591 | static::assertSame('month', $assignedVariables['type']); | ||
592 | static::assertSame('Monthly', $assignedVariables['localizedType']); | ||
593 | } | ||
594 | |||
595 | /** | ||
596 | * Test simple display RSS with week parameter | ||
597 | */ | ||
598 | public function testSimpleRssWeekly(): void | ||
599 | { | ||
600 | $dates = [ | ||
601 | new \DateTimeImmutable('2020-05-19'), | ||
602 | new \DateTimeImmutable('2020-05-13'), | ||
603 | ]; | ||
604 | $expectedDates = [ | ||
605 | new \DateTimeImmutable('2020-05-24 23:59:59'), | ||
606 | new \DateTimeImmutable('2020-05-17 23:59:59'), | ||
607 | ]; | ||
608 | |||
609 | $this->container->environment['QUERY_STRING'] = 'week'; | ||
610 | $request = $this->createMock(Request::class); | ||
611 | $request->method('getQueryParam')->willReturnCallback(function (string $key): ?string { | ||
612 | return $key === 'week' ? '' : null; | ||
613 | }); | ||
614 | $response = new Response(); | ||
615 | |||
616 | $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([ | ||
617 | (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'), | ||
618 | (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'), | ||
619 | (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'), | ||
620 | ]); | ||
621 | |||
622 | // Save RainTPL assigned variables | ||
623 | $assignedVariables = []; | ||
624 | $this->assignTemplateVars($assignedVariables); | ||
625 | |||
626 | $result = $this->controller->rss($request, $response); | ||
627 | |||
628 | static::assertSame(200, $result->getStatusCode()); | ||
629 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
630 | static::assertSame('dailyrss', (string) $result->getBody()); | ||
631 | static::assertSame('Shaarli', $assignedVariables['title']); | ||
632 | static::assertSame('http://shaarli/subfolder/', $assignedVariables['index_url']); | ||
633 | static::assertSame('http://shaarli/subfolder/daily-rss?week', $assignedVariables['page_url']); | ||
634 | static::assertFalse($assignedVariables['hide_timestamps']); | ||
635 | static::assertCount(2, $assignedVariables['days']); | ||
636 | |||
637 | $day = $assignedVariables['days'][$dates[0]->format('YW')]; | ||
638 | $date = $expectedDates[0]; | ||
639 | |||
640 | static::assertEquals($date, $day['date']); | ||
641 | static::assertSame($date->format(\DateTime::RSS), $day['date_rss']); | ||
642 | static::assertSame('Week 21 (May 18, 2020)', $day['date_human']); | ||
643 | static::assertSame('http://shaarli/subfolder/daily?week='. $dates[0]->format('YW'), $day['absolute_url']); | ||
644 | static::assertCount(1, $day['links']); | ||
645 | |||
646 | $day = $assignedVariables['days'][$dates[1]->format('YW')]; | ||
647 | $date = $expectedDates[1]; | ||
648 | |||
649 | static::assertEquals($date, $day['date']); | ||
650 | static::assertSame($date->format(\DateTime::RSS), $day['date_rss']); | ||
651 | static::assertSame('Week 20 (May 11, 2020)', $day['date_human']); | ||
652 | static::assertSame('http://shaarli/subfolder/daily?week='. $dates[1]->format('YW'), $day['absolute_url']); | ||
653 | static::assertCount(2, $day['links']); | ||
654 | } | ||
655 | |||
656 | /** | ||
657 | * Test simple display RSS with month parameter | ||
658 | */ | ||
659 | public function testSimpleRssMonthly(): void | ||
660 | { | ||
661 | $dates = [ | ||
662 | new \DateTimeImmutable('2020-05-19'), | ||
663 | new \DateTimeImmutable('2020-04-13'), | ||
664 | ]; | ||
665 | $expectedDates = [ | ||
666 | new \DateTimeImmutable('2020-05-31 23:59:59'), | ||
667 | new \DateTimeImmutable('2020-04-30 23:59:59'), | ||
668 | ]; | ||
669 | |||
670 | $this->container->environment['QUERY_STRING'] = 'month'; | ||
671 | $request = $this->createMock(Request::class); | ||
672 | $request->method('getQueryParam')->willReturnCallback(function (string $key): ?string { | ||
673 | return $key === 'month' ? '' : null; | ||
674 | }); | ||
675 | $response = new Response(); | ||
676 | |||
677 | $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([ | ||
678 | (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'), | ||
679 | (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'), | ||
680 | (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'), | ||
681 | ]); | ||
682 | |||
683 | // Save RainTPL assigned variables | ||
684 | $assignedVariables = []; | ||
685 | $this->assignTemplateVars($assignedVariables); | ||
686 | |||
687 | $result = $this->controller->rss($request, $response); | ||
688 | |||
689 | static::assertSame(200, $result->getStatusCode()); | ||
690 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
691 | static::assertSame('dailyrss', (string) $result->getBody()); | ||
692 | static::assertSame('Shaarli', $assignedVariables['title']); | ||
693 | static::assertSame('http://shaarli/subfolder/', $assignedVariables['index_url']); | ||
694 | static::assertSame('http://shaarli/subfolder/daily-rss?month', $assignedVariables['page_url']); | ||
695 | static::assertFalse($assignedVariables['hide_timestamps']); | ||
696 | static::assertCount(2, $assignedVariables['days']); | ||
697 | |||
698 | $day = $assignedVariables['days'][$dates[0]->format('Ym')]; | ||
699 | $date = $expectedDates[0]; | ||
700 | |||
701 | static::assertEquals($date, $day['date']); | ||
702 | static::assertSame($date->format(\DateTime::RSS), $day['date_rss']); | ||
703 | static::assertSame('May, 2020', $day['date_human']); | ||
704 | static::assertSame('http://shaarli/subfolder/daily?month='. $dates[0]->format('Ym'), $day['absolute_url']); | ||
705 | static::assertCount(1, $day['links']); | ||
706 | |||
707 | $day = $assignedVariables['days'][$dates[1]->format('Ym')]; | ||
708 | $date = $expectedDates[1]; | ||
709 | |||
710 | static::assertEquals($date, $day['date']); | ||
711 | static::assertSame($date->format(\DateTime::RSS), $day['date_rss']); | ||
712 | static::assertSame('April, 2020', $day['date_human']); | ||
713 | static::assertSame('http://shaarli/subfolder/daily?month='. $dates[1]->format('Ym'), $day['absolute_url']); | ||
714 | static::assertCount(2, $day['links']); | ||
715 | } | ||
478 | } | 716 | } |
diff --git a/tests/front/controller/visitor/InstallControllerTest.php b/tests/front/controller/visitor/InstallControllerTest.php index 345ad544..2105ed77 100644 --- a/tests/front/controller/visitor/InstallControllerTest.php +++ b/tests/front/controller/visitor/InstallControllerTest.php | |||
@@ -79,6 +79,15 @@ class InstallControllerTest extends TestCase | |||
79 | static::assertIsArray($assignedVariables['languages']); | 79 | static::assertIsArray($assignedVariables['languages']); |
80 | static::assertSame('Automatic', $assignedVariables['languages']['auto']); | 80 | static::assertSame('Automatic', $assignedVariables['languages']['auto']); |
81 | static::assertSame('French', $assignedVariables['languages']['fr']); | 81 | static::assertSame('French', $assignedVariables['languages']['fr']); |
82 | |||
83 | static::assertSame(PHP_VERSION, $assignedVariables['php_version']); | ||
84 | static::assertArrayHasKey('php_has_reached_eol', $assignedVariables); | ||
85 | static::assertArrayHasKey('php_eol', $assignedVariables); | ||
86 | static::assertArrayHasKey('php_extensions', $assignedVariables); | ||
87 | static::assertArrayHasKey('permissions', $assignedVariables); | ||
88 | static::assertEmpty($assignedVariables['permissions']); | ||
89 | |||
90 | static::assertSame('Install Shaarli', $assignedVariables['pagetitle']); | ||
82 | } | 91 | } |
83 | 92 | ||
84 | /** | 93 | /** |
diff --git a/tests/ApplicationUtilsTest.php b/tests/helper/ApplicationUtilsTest.php index a232b351..654857b9 100644 --- a/tests/ApplicationUtilsTest.php +++ b/tests/helper/ApplicationUtilsTest.php | |||
@@ -1,7 +1,8 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli; | 2 | namespace Shaarli\Helper; |
3 | 3 | ||
4 | use Shaarli\Config\ConfigManager; | 4 | use Shaarli\Config\ConfigManager; |
5 | use Shaarli\FakeApplicationUtils; | ||
5 | 6 | ||
6 | require_once 'tests/utils/FakeApplicationUtils.php'; | 7 | require_once 'tests/utils/FakeApplicationUtils.php'; |
7 | 8 | ||
@@ -340,6 +341,35 @@ class ApplicationUtilsTest extends \Shaarli\TestCase | |||
340 | } | 341 | } |
341 | 342 | ||
342 | /** | 343 | /** |
344 | * Checks resource permissions in minimal mode. | ||
345 | */ | ||
346 | public function testCheckCurrentResourcePermissionsErrorsMinimalMode(): void | ||
347 | { | ||
348 | $conf = new ConfigManager(''); | ||
349 | $conf->set('resource.thumbnails_cache', 'null/cache'); | ||
350 | $conf->set('resource.config', 'null/data/config.php'); | ||
351 | $conf->set('resource.data_dir', 'null/data'); | ||
352 | $conf->set('resource.datastore', 'null/data/store.php'); | ||
353 | $conf->set('resource.ban_file', 'null/data/ipbans.php'); | ||
354 | $conf->set('resource.log', 'null/data/log.txt'); | ||
355 | $conf->set('resource.page_cache', 'null/pagecache'); | ||
356 | $conf->set('resource.raintpl_tmp', 'null/tmp'); | ||
357 | $conf->set('resource.raintpl_tpl', 'null/tpl'); | ||
358 | $conf->set('resource.raintpl_theme', 'null/tpl/default'); | ||
359 | $conf->set('resource.update_check', 'null/data/lastupdatecheck.txt'); | ||
360 | |||
361 | static::assertSame( | ||
362 | [ | ||
363 | '"null/tpl" directory is not readable', | ||
364 | '"null/tpl/default" directory is not readable', | ||
365 | '"null/tmp" directory is not readable', | ||
366 | '"null/tmp" directory is not writable' | ||
367 | ], | ||
368 | ApplicationUtils::checkResourcePermissions($conf, true) | ||
369 | ); | ||
370 | } | ||
371 | |||
372 | /** | ||
343 | * Check update with 'dev' as curent version (master branch). | 373 | * Check update with 'dev' as curent version (master branch). |
344 | * It should always return false. | 374 | * It should always return false. |
345 | */ | 375 | */ |
@@ -349,4 +379,37 @@ class ApplicationUtilsTest extends \Shaarli\TestCase | |||
349 | ApplicationUtils::checkUpdate('dev', self::$testUpdateFile, 100, true, true) | 379 | ApplicationUtils::checkUpdate('dev', self::$testUpdateFile, 100, true, true) |
350 | ); | 380 | ); |
351 | } | 381 | } |
382 | |||
383 | /** | ||
384 | * Basic test of getPhpExtensionsRequirement() | ||
385 | */ | ||
386 | public function testGetPhpExtensionsRequirementSimple(): void | ||
387 | { | ||
388 | static::assertCount(8, ApplicationUtils::getPhpExtensionsRequirement()); | ||
389 | static::assertSame([ | ||
390 | 'name' => 'json', | ||
391 | 'required' => true, | ||
392 | 'desc' => 'Configuration parsing', | ||
393 | 'loaded' => true, | ||
394 | ], ApplicationUtils::getPhpExtensionsRequirement()[0]); | ||
395 | } | ||
396 | |||
397 | /** | ||
398 | * Test getPhpEol with a known version: 7.4 -> 2022 | ||
399 | */ | ||
400 | public function testGetKnownPhpEol(): void | ||
401 | { | ||
402 | static::assertSame('2022-11-28', ApplicationUtils::getPhpEol('7.4.7')); | ||
403 | } | ||
404 | |||
405 | /** | ||
406 | * Test getPhpEol with an unknown version: 7.4 -> 2022 | ||
407 | */ | ||
408 | public function testGetUnknownPhpEol(): void | ||
409 | { | ||
410 | static::assertSame( | ||
411 | (((int) (new \DateTime())->format('Y')) + 2) . (new \DateTime())->format('-m-d'), | ||
412 | ApplicationUtils::getPhpEol('7.51.34') | ||
413 | ); | ||
414 | } | ||
352 | } | 415 | } |
diff --git a/tests/helper/DailyPageHelperTest.php b/tests/helper/DailyPageHelperTest.php new file mode 100644 index 00000000..5255b7b1 --- /dev/null +++ b/tests/helper/DailyPageHelperTest.php | |||
@@ -0,0 +1,262 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Helper; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\TestCase; | ||
9 | use Slim\Http\Request; | ||
10 | |||
11 | class DailyPageHelperTest extends TestCase | ||
12 | { | ||
13 | /** | ||
14 | * @dataProvider getRequestedTypes | ||
15 | */ | ||
16 | public function testExtractRequestedType(array $queryParams, string $expectedType): void | ||
17 | { | ||
18 | $request = $this->createMock(Request::class); | ||
19 | $request->method('getQueryParam')->willReturnCallback(function ($key) use ($queryParams): ?string { | ||
20 | return $queryParams[$key] ?? null; | ||
21 | }); | ||
22 | |||
23 | $type = DailyPageHelper::extractRequestedType($request); | ||
24 | |||
25 | static::assertSame($type, $expectedType); | ||
26 | } | ||
27 | |||
28 | /** | ||
29 | * @dataProvider getRequestedDateTimes | ||
30 | */ | ||
31 | public function testExtractRequestedDateTime( | ||
32 | string $type, | ||
33 | string $input, | ||
34 | ?Bookmark $bookmark, | ||
35 | \DateTimeInterface $expectedDateTime, | ||
36 | string $compareFormat = 'Ymd' | ||
37 | ): void { | ||
38 | $dateTime = DailyPageHelper::extractRequestedDateTime($type, $input, $bookmark); | ||
39 | |||
40 | static::assertSame($dateTime->format($compareFormat), $expectedDateTime->format($compareFormat)); | ||
41 | } | ||
42 | |||
43 | public function testExtractRequestedDateTimeExceptionUnknownType(): void | ||
44 | { | ||
45 | $this->expectException(\Exception::class); | ||
46 | $this->expectExceptionMessage('Unsupported daily format type'); | ||
47 | |||
48 | DailyPageHelper::extractRequestedDateTime('nope', null, null); | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * @dataProvider getFormatsByType | ||
53 | */ | ||
54 | public function testGetFormatByType(string $type, string $expectedFormat): void | ||
55 | { | ||
56 | $format = DailyPageHelper::getFormatByType($type); | ||
57 | |||
58 | static::assertSame($expectedFormat, $format); | ||
59 | } | ||
60 | |||
61 | public function testGetFormatByTypeExceptionUnknownType(): void | ||
62 | { | ||
63 | $this->expectException(\Exception::class); | ||
64 | $this->expectExceptionMessage('Unsupported daily format type'); | ||
65 | |||
66 | DailyPageHelper::getFormatByType('nope'); | ||
67 | } | ||
68 | |||
69 | /** | ||
70 | * @dataProvider getStartDatesByType | ||
71 | */ | ||
72 | public function testGetStartDatesByType( | ||
73 | string $type, | ||
74 | \DateTimeImmutable $dateTime, | ||
75 | \DateTimeInterface $expectedDateTime | ||
76 | ): void { | ||
77 | $startDateTime = DailyPageHelper::getStartDateTimeByType($type, $dateTime); | ||
78 | |||
79 | static::assertEquals($expectedDateTime, $startDateTime); | ||
80 | } | ||
81 | |||
82 | public function testGetStartDatesByTypeExceptionUnknownType(): void | ||
83 | { | ||
84 | $this->expectException(\Exception::class); | ||
85 | $this->expectExceptionMessage('Unsupported daily format type'); | ||
86 | |||
87 | DailyPageHelper::getStartDateTimeByType('nope', new \DateTimeImmutable()); | ||
88 | } | ||
89 | |||
90 | /** | ||
91 | * @dataProvider getEndDatesByType | ||
92 | */ | ||
93 | public function testGetEndDatesByType( | ||
94 | string $type, | ||
95 | \DateTimeImmutable $dateTime, | ||
96 | \DateTimeInterface $expectedDateTime | ||
97 | ): void { | ||
98 | $endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dateTime); | ||
99 | |||
100 | static::assertEquals($expectedDateTime, $endDateTime); | ||
101 | } | ||
102 | |||
103 | public function testGetEndDatesByTypeExceptionUnknownType(): void | ||
104 | { | ||
105 | $this->expectException(\Exception::class); | ||
106 | $this->expectExceptionMessage('Unsupported daily format type'); | ||
107 | |||
108 | DailyPageHelper::getEndDateTimeByType('nope', new \DateTimeImmutable()); | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * @dataProvider getDescriptionsByType | ||
113 | */ | ||
114 | public function testGeDescriptionsByType( | ||
115 | string $type, | ||
116 | \DateTimeImmutable $dateTime, | ||
117 | string $expectedDescription | ||
118 | ): void { | ||
119 | $description = DailyPageHelper::getDescriptionByType($type, $dateTime); | ||
120 | |||
121 | static::assertEquals($expectedDescription, $description); | ||
122 | } | ||
123 | |||
124 | public function getDescriptionByTypeExceptionUnknownType(): void | ||
125 | { | ||
126 | $this->expectException(\Exception::class); | ||
127 | $this->expectExceptionMessage('Unsupported daily format type'); | ||
128 | |||
129 | DailyPageHelper::getDescriptionByType('nope', new \DateTimeImmutable()); | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * @dataProvider getRssLengthsByType | ||
134 | */ | ||
135 | public function testGeRssLengthsByType(string $type): void { | ||
136 | $length = DailyPageHelper::getRssLengthByType($type); | ||
137 | |||
138 | static::assertIsInt($length); | ||
139 | } | ||
140 | |||
141 | public function testGeRssLengthsByTypeExceptionUnknownType(): void | ||
142 | { | ||
143 | $this->expectException(\Exception::class); | ||
144 | $this->expectExceptionMessage('Unsupported daily format type'); | ||
145 | |||
146 | DailyPageHelper::getRssLengthByType('nope'); | ||
147 | } | ||
148 | |||
149 | /** | ||
150 | * Data provider for testExtractRequestedType() test method. | ||
151 | */ | ||
152 | public function getRequestedTypes(): array | ||
153 | { | ||
154 | return [ | ||
155 | [['month' => null], DailyPageHelper::DAY], | ||
156 | [['month' => ''], DailyPageHelper::MONTH], | ||
157 | [['month' => 'content'], DailyPageHelper::MONTH], | ||
158 | [['week' => null], DailyPageHelper::DAY], | ||
159 | [['week' => ''], DailyPageHelper::WEEK], | ||
160 | [['week' => 'content'], DailyPageHelper::WEEK], | ||
161 | [['day' => null], DailyPageHelper::DAY], | ||
162 | [['day' => ''], DailyPageHelper::DAY], | ||
163 | [['day' => 'content'], DailyPageHelper::DAY], | ||
164 | ]; | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * Data provider for testExtractRequestedDateTime() test method. | ||
169 | */ | ||
170 | public function getRequestedDateTimes(): array | ||
171 | { | ||
172 | return [ | ||
173 | [DailyPageHelper::DAY, '20201013', null, new \DateTime('2020-10-13')], | ||
174 | [ | ||
175 | DailyPageHelper::DAY, | ||
176 | '', | ||
177 | (new Bookmark())->setCreated($date = new \DateTime('2020-10-13 12:05:31')), | ||
178 | $date, | ||
179 | ], | ||
180 | [DailyPageHelper::DAY, '', null, new \DateTime()], | ||
181 | [DailyPageHelper::WEEK, '202030', null, new \DateTime('2020-07-20')], | ||
182 | [ | ||
183 | DailyPageHelper::WEEK, | ||
184 | '', | ||
185 | (new Bookmark())->setCreated($date = new \DateTime('2020-10-13 12:05:31')), | ||
186 | new \DateTime('2020-10-13'), | ||
187 | ], | ||
188 | [DailyPageHelper::WEEK, '', null, new \DateTime(), 'Ym'], | ||
189 | [DailyPageHelper::MONTH, '202008', null, new \DateTime('2020-08-01'), 'Ym'], | ||
190 | [ | ||
191 | DailyPageHelper::MONTH, | ||
192 | '', | ||
193 | (new Bookmark())->setCreated($date = new \DateTime('2020-10-13 12:05:31')), | ||
194 | new \DateTime('2020-10-13'), | ||
195 | 'Ym' | ||
196 | ], | ||
197 | [DailyPageHelper::MONTH, '', null, new \DateTime(), 'Ym'], | ||
198 | ]; | ||
199 | } | ||
200 | |||
201 | /** | ||
202 | * Data provider for testGetFormatByType() test method. | ||
203 | */ | ||
204 | public function getFormatsByType(): array | ||
205 | { | ||
206 | return [ | ||
207 | [DailyPageHelper::DAY, 'Ymd'], | ||
208 | [DailyPageHelper::WEEK, 'YW'], | ||
209 | [DailyPageHelper::MONTH, 'Ym'], | ||
210 | ]; | ||
211 | } | ||
212 | |||
213 | /** | ||
214 | * Data provider for testGetStartDatesByType() test method. | ||
215 | */ | ||
216 | public function getStartDatesByType(): array | ||
217 | { | ||
218 | return [ | ||
219 | [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 00:00:00')], | ||
220 | [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-05 00:00:00')], | ||
221 | [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-01 00:00:00')], | ||
222 | ]; | ||
223 | } | ||
224 | |||
225 | /** | ||
226 | * Data provider for testGetEndDatesByType() test method. | ||
227 | */ | ||
228 | public function getEndDatesByType(): array | ||
229 | { | ||
230 | return [ | ||
231 | [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 23:59:59')], | ||
232 | [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-11 23:59:59')], | ||
233 | [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-31 23:59:59')], | ||
234 | ]; | ||
235 | } | ||
236 | |||
237 | /** | ||
238 | * Data provider for testGetDescriptionsByType() test method. | ||
239 | */ | ||
240 | public function getDescriptionsByType(): array | ||
241 | { | ||
242 | return [ | ||
243 | [DailyPageHelper::DAY, $date = new \DateTimeImmutable(), 'Today - ' . $date->format('F j, Y')], | ||
244 | [DailyPageHelper::DAY, $date = new \DateTimeImmutable('-1 day'), 'Yesterday - ' . $date->format('F j, Y')], | ||
245 | [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'], | ||
246 | [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'], | ||
247 | [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'], | ||
248 | ]; | ||
249 | } | ||
250 | |||
251 | /** | ||
252 | * Data provider for testGetDescriptionsByType() test method. | ||
253 | */ | ||
254 | public function getRssLengthsByType(): array | ||
255 | { | ||
256 | return [ | ||
257 | [DailyPageHelper::DAY], | ||
258 | [DailyPageHelper::WEEK], | ||
259 | [DailyPageHelper::MONTH], | ||
260 | ]; | ||
261 | } | ||
262 | } | ||
diff --git a/tests/FileUtilsTest.php b/tests/helper/FileUtilsTest.php index 9163bdf1..8035f79c 100644 --- a/tests/FileUtilsTest.php +++ b/tests/helper/FileUtilsTest.php | |||
@@ -1,27 +1,51 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | namespace Shaarli; | 3 | namespace Shaarli\Helper; |
4 | 4 | ||
5 | use Exception; | 5 | use Exception; |
6 | use Shaarli\Exceptions\IOException; | ||
7 | use Shaarli\TestCase; | ||
6 | 8 | ||
7 | /** | 9 | /** |
8 | * Class FileUtilsTest | 10 | * Class FileUtilsTest |
9 | * | 11 | * |
10 | * Test file utility class. | 12 | * Test file utility class. |
11 | */ | 13 | */ |
12 | class FileUtilsTest extends \Shaarli\TestCase | 14 | class FileUtilsTest extends TestCase |
13 | { | 15 | { |
14 | /** | 16 | /** |
15 | * @var string Test file path. | 17 | * @var string Test file path. |
16 | */ | 18 | */ |
17 | protected static $file = 'sandbox/flat.db'; | 19 | protected static $file = 'sandbox/flat.db'; |
18 | 20 | ||
21 | protected function setUp(): void | ||
22 | { | ||
23 | @mkdir('sandbox'); | ||
24 | mkdir('sandbox/folder2'); | ||
25 | touch('sandbox/file1'); | ||
26 | touch('sandbox/file2'); | ||
27 | mkdir('sandbox/folder1'); | ||
28 | touch('sandbox/folder1/file1'); | ||
29 | touch('sandbox/folder1/file2'); | ||
30 | mkdir('sandbox/folder3'); | ||
31 | mkdir('/tmp/shaarli-to-delete'); | ||
32 | } | ||
33 | |||
19 | /** | 34 | /** |
20 | * Delete test file after every test. | 35 | * Delete test file after every test. |
21 | */ | 36 | */ |
22 | protected function tearDown(): void | 37 | protected function tearDown(): void |
23 | { | 38 | { |
24 | @unlink(self::$file); | 39 | @unlink(self::$file); |
40 | |||
41 | @unlink('sandbox/folder1/file1'); | ||
42 | @unlink('sandbox/folder1/file2'); | ||
43 | @rmdir('sandbox/folder1'); | ||
44 | @unlink('sandbox/file1'); | ||
45 | @unlink('sandbox/file2'); | ||
46 | @rmdir('sandbox/folder2'); | ||
47 | @rmdir('sandbox/folder3'); | ||
48 | @rmdir('/tmp/shaarli-to-delete'); | ||
25 | } | 49 | } |
26 | 50 | ||
27 | /** | 51 | /** |
@@ -107,4 +131,67 @@ class FileUtilsTest extends \Shaarli\TestCase | |||
107 | $this->assertEquals(null, FileUtils::readFlatDB(self::$file)); | 131 | $this->assertEquals(null, FileUtils::readFlatDB(self::$file)); |
108 | $this->assertEquals(['test'], FileUtils::readFlatDB(self::$file, ['test'])); | 132 | $this->assertEquals(['test'], FileUtils::readFlatDB(self::$file, ['test'])); |
109 | } | 133 | } |
134 | |||
135 | /** | ||
136 | * Test clearFolder with self delete and excluded files | ||
137 | */ | ||
138 | public function testClearFolderSelfDeleteWithExclusion(): void | ||
139 | { | ||
140 | FileUtils::clearFolder('sandbox', true, ['file2']); | ||
141 | |||
142 | static::assertFileExists('sandbox/folder1/file2'); | ||
143 | static::assertFileExists('sandbox/folder1'); | ||
144 | static::assertFileExists('sandbox/file2'); | ||
145 | static::assertFileExists('sandbox'); | ||
146 | |||
147 | static::assertFileNotExists('sandbox/folder1/file1'); | ||
148 | static::assertFileNotExists('sandbox/file1'); | ||
149 | static::assertFileNotExists('sandbox/folder3'); | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * Test clearFolder with self delete and excluded files | ||
154 | */ | ||
155 | public function testClearFolderSelfDeleteWithoutExclusion(): void | ||
156 | { | ||
157 | FileUtils::clearFolder('sandbox', true); | ||
158 | |||
159 | static::assertFileNotExists('sandbox'); | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * Test clearFolder with self delete and excluded files | ||
164 | */ | ||
165 | public function testClearFolderNoSelfDeleteWithoutExclusion(): void | ||
166 | { | ||
167 | FileUtils::clearFolder('sandbox', false); | ||
168 | |||
169 | static::assertFileExists('sandbox'); | ||
170 | |||
171 | // 2 because '.' and '..' | ||
172 | static::assertCount(2, new \DirectoryIterator('sandbox')); | ||
173 | } | ||
174 | |||
175 | /** | ||
176 | * Test clearFolder on a file instead of a folder | ||
177 | */ | ||
178 | public function testClearFolderOnANonDirectory(): void | ||
179 | { | ||
180 | $this->expectException(IOException::class); | ||
181 | $this->expectExceptionMessage('Provided path is not a directory.'); | ||
182 | |||
183 | FileUtils::clearFolder('sandbox/file1', false); | ||
184 | } | ||
185 | |||
186 | /** | ||
187 | * Test clearFolder on a file instead of a folder | ||
188 | */ | ||
189 | public function testClearFolderOutsideOfShaarliDirectory(): void | ||
190 | { | ||
191 | $this->expectException(IOException::class); | ||
192 | $this->expectExceptionMessage('Trying to delete a folder outside of Shaarli path.'); | ||
193 | |||
194 | |||
195 | FileUtils::clearFolder('/tmp/shaarli-to-delete', true); | ||
196 | } | ||
110 | } | 197 | } |
diff --git a/tests/security/BanManagerTest.php b/tests/security/BanManagerTest.php index 22aa8666..29d2791b 100644 --- a/tests/security/BanManagerTest.php +++ b/tests/security/BanManagerTest.php | |||
@@ -4,7 +4,7 @@ | |||
4 | namespace Shaarli\Security; | 4 | namespace Shaarli\Security; |
5 | 5 | ||
6 | use Psr\Log\LoggerInterface; | 6 | use Psr\Log\LoggerInterface; |
7 | use Shaarli\FileUtils; | 7 | use Shaarli\Helper\FileUtils; |
8 | use Shaarli\TestCase; | 8 | use Shaarli\TestCase; |
9 | 9 | ||
10 | /** | 10 | /** |
diff --git a/tests/utils/FakeApplicationUtils.php b/tests/utils/FakeApplicationUtils.php index de83d598..d5289ede 100644 --- a/tests/utils/FakeApplicationUtils.php +++ b/tests/utils/FakeApplicationUtils.php | |||
@@ -2,6 +2,8 @@ | |||
2 | 2 | ||
3 | namespace Shaarli; | 3 | namespace Shaarli; |
4 | 4 | ||
5 | use Shaarli\Helper\ApplicationUtils; | ||
6 | |||
5 | /** | 7 | /** |
6 | * Fake ApplicationUtils class to avoid HTTP requests | 8 | * Fake ApplicationUtils class to avoid HTTP requests |
7 | */ | 9 | */ |
diff --git a/tests/utils/ReferenceHistory.php b/tests/utils/ReferenceHistory.php index 516c9f51..aed5d2cf 100644 --- a/tests/utils/ReferenceHistory.php +++ b/tests/utils/ReferenceHistory.php | |||
@@ -1,6 +1,6 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | use Shaarli\FileUtils; | 3 | use Shaarli\Helper\FileUtils; |
4 | use Shaarli\History; | 4 | use Shaarli\History; |
5 | 5 | ||
6 | /** | 6 | /** |
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/daily.html b/tpl/default/daily.html index 3749bffb..5e038c39 100644 --- a/tpl/default/daily.html +++ b/tpl/default/daily.html | |||
@@ -7,11 +7,24 @@ | |||
7 | {include="page.header"} | 7 | {include="page.header"} |
8 | 8 | ||
9 | <div class="pure-g"> | 9 | <div class="pure-g"> |
10 | <div class="pure-u-1 pure-alert pure-alert-success tag-sort"> | ||
11 | <a href="{$base_path}/daily?day">{'Daily'|t}</a> | ||
12 | <a href="{$base_path}/daily?week">{'Weekly'|t}</a> | ||
13 | <a href="{$base_path}/daily?month">{'Monthly'|t}</a> | ||
14 | </div> | ||
15 | </div> | ||
16 | |||
17 | |||
18 | <div class="pure-g"> | ||
10 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> | 19 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> |
11 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor" id="daily"> | 20 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor" id="daily"> |
12 | <h2 class="window-title"> | 21 | <h2 class="window-title"> |
13 | {'The Daily Shaarli'|t} | 22 | {$localizedType} Shaarli |
14 | <a href="{$base_path}/daily-rss" title="{'1 RSS entry per day'|t}"><i class="fa fa-rss"></i></a> | 23 | <a href="{$base_path}/daily-rss?{$type}" |
24 | title="{function="t('1 RSS entry per :type', '', 1, 'shaarli', [':type' => t($type)])"}" | ||
25 | > | ||
26 | <i class="fa fa-rss"></i> | ||
27 | </a> | ||
15 | </h2> | 28 | </h2> |
16 | 29 | ||
17 | <div id="plugin_zone_start_daily" class="plugin_zone"> | 30 | <div id="plugin_zone_start_daily" class="plugin_zone"> |
@@ -25,19 +38,19 @@ | |||
25 | <div class="pure-g"> | 38 | <div class="pure-g"> |
26 | <div class="pure-u-lg-1-3 pure-u-1 center"> | 39 | <div class="pure-u-lg-1-3 pure-u-1 center"> |
27 | {if="$previousday"} | 40 | {if="$previousday"} |
28 | <a href="{$base_path}/daily?day={$previousday}"> | 41 | <a href="{$base_path}/daily?{$type}={$previousday}"> |
29 | <i class="fa fa-arrow-left"></i> | 42 | <i class="fa fa-arrow-left"></i> |
30 | {'Previous day'|t} | 43 | {function="t('Previous :type', '', 1, 'shaarli', [':type' => t($type)], true)"} |
31 | </a> | 44 | </a> |
32 | {/if} | 45 | {/if} |
33 | </div> | 46 | </div> |
34 | <div class="daily-desc pure-u-lg-1-3 pure-u-1 center"> | 47 | <div class="daily-desc pure-u-lg-1-3 pure-u-1 center"> |
35 | {'All links of one day in a single page.'|t} | 48 | {function="t('All links of one :type in a single page.', '', 1, 'shaarli', [':type' => t($type)])"} |
36 | </div> | 49 | </div> |
37 | <div class="pure-u-lg-1-3 pure-u-1 center"> | 50 | <div class="pure-u-lg-1-3 pure-u-1 center"> |
38 | {if="$nextday"} | 51 | {if="$nextday"} |
39 | <a href="{$base_path}/daily?day={$nextday}"> | 52 | <a href="{$base_path}/daily?{$type}={$nextday}"> |
40 | {'Next day'|t} | 53 | {function="t('Next :type', '', 1, 'shaarli', [':type' => t($type)], true)"} |
41 | <i class="fa fa-arrow-right"></i> | 54 | <i class="fa fa-arrow-right"></i> |
42 | </a> | 55 | </a> |
43 | {/if} | 56 | {/if} |
@@ -45,10 +58,7 @@ | |||
45 | </div> | 58 | </div> |
46 | <div> | 59 | <div> |
47 | <h3 class="window-subtitle"> | 60 | <h3 class="window-subtitle"> |
48 | {if="!empty($dayDesc)"} | 61 | {$dayDesc} |
49 | {$dayDesc} - | ||
50 | {/if} | ||
51 | {function="format_date($dayDate, false)"} | ||
52 | </h3> | 62 | </h3> |
53 | 63 | ||
54 | <div id="plugin_zone_about_daily" class="plugin_zone"> | 64 | <div id="plugin_zone_about_daily" class="plugin_zone"> |
diff --git a/tpl/default/dailyrss.html b/tpl/default/dailyrss.html index d40d9496..871a3ba7 100644 --- a/tpl/default/dailyrss.html +++ b/tpl/default/dailyrss.html | |||
@@ -1,9 +1,9 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <rss version="2.0"> | 2 | <rss version="2.0"> |
3 | <channel> | 3 | <channel> |
4 | <title>Daily - {$title}</title> | 4 | <title>{$localizedType} - {$title}</title> |
5 | <link>{$index_url}</link> | 5 | <link>{$index_url}</link> |
6 | <description>Daily shaared bookmarks</description> | 6 | <description>{function="t('All links of one :type in a single page.', '', 1, 'shaarli', [':type' => t($type)])"}</description> |
7 | <language>{$language}</language> | 7 | <language>{$language}</language> |
8 | <copyright>{$index_url}</copyright> | 8 | <copyright>{$index_url}</copyright> |
9 | <generator>Shaarli</generator> | 9 | <generator>Shaarli</generator> |
@@ -18,12 +18,15 @@ | |||
18 | {loop="$value.links"} | 18 | {loop="$value.links"} |
19 | <h3><a href="{$value.url}">{$value.title}</a></h3> | 19 | <h3><a href="{$value.url}">{$value.title}</a></h3> |
20 | <small> | 20 | <small> |
21 | {if="!$hide_timestamps"}{$value.created|format_date} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> | 21 | {if="!$hide_timestamps"}{$value.created|format_date} — {/if} |
22 | <a href="{$index_url}shaare/{$value.shorturl}">{'Permalink'|t}</a> | ||
23 | {if="$value.tags"} — {$value.tags}{/if} | ||
24 | <br> | ||
22 | {$value.url} | 25 | {$value.url} |
23 | </small><br> | 26 | </small><br> |
24 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> | 27 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> |
25 | {if="$value.description"}{$value.description}{/if} | 28 | {if="$value.description"}{$value.description}{/if} |
26 | <br><br><hr> | 29 | <br><hr> |
27 | {/loop} | 30 | {/loop} |
28 | ]]></description> | 31 | ]]></description> |
29 | </item> | 32 | </item> |
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/tpl/default/install.html b/tpl/default/install.html index a506a2eb..4f98d49d 100644 --- a/tpl/default/install.html +++ b/tpl/default/install.html | |||
@@ -163,6 +163,16 @@ | |||
163 | </div> | 163 | </div> |
164 | </div> | 164 | </div> |
165 | </form> | 165 | </form> |
166 | |||
167 | <div class="pure-g"> | ||
168 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> | ||
169 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-form-complete"> | ||
170 | <h2 class="window-title">{'Server requirements'|t}</h2> | ||
171 | |||
172 | {include="server.requirements"} | ||
173 | </div> | ||
174 | </div> | ||
175 | |||
166 | {include="page.footer"} | 176 | {include="page.footer"} |
167 | </body> | 177 | </body> |
168 | </html> | 178 | </html> |
diff --git a/tpl/default/linklist.html b/tpl/default/linklist.html index 48cd9aad..e1115d49 100644 --- a/tpl/default/linklist.html +++ b/tpl/default/linklist.html | |||
@@ -129,6 +129,7 @@ | |||
129 | {$strAddTag=t('Add tag')} | 129 | {$strAddTag=t('Add tag')} |
130 | {$strToggleSticky=t('Toggle sticky')} | 130 | {$strToggleSticky=t('Toggle sticky')} |
131 | {$strSticky=t('Sticky')} | 131 | {$strSticky=t('Sticky')} |
132 | {$strShaarePrivate=t('Share a private link')} | ||
132 | {ignore}End of translations{/ignore} | 133 | {ignore}End of translations{/ignore} |
133 | {loop="links"} | 134 | {loop="links"} |
134 | <div class="anchor" id="{$value.shorturl}"></div> | 135 | <div class="anchor" id="{$value.shorturl}"></div> |
@@ -241,6 +242,12 @@ | |||
241 | {$strPermalinkLc} | 242 | {$strPermalinkLc} |
242 | </a> | 243 | </a> |
243 | 244 | ||
245 | {if="$is_logged_in && $value.private"} | ||
246 | <a href="{$base_path}/admin/shaare/private/{$value.shorturl}?token={$token}" title="{$strShaarePrivate}"> | ||
247 | <i class="fa fa-share-alt"></i> | ||
248 | </a> | ||
249 | {/if} | ||
250 | |||
244 | <div class="pure-u-0 pure-u-lg-visible"> | 251 | <div class="pure-u-0 pure-u-lg-visible"> |
245 | {if="isset($value.link_plugin)"} | 252 | {if="isset($value.link_plugin)"} |
246 | · | 253 | · |
diff --git a/tpl/default/server.html b/tpl/default/server.html new file mode 100644 index 00000000..de1c8b53 --- /dev/null +++ b/tpl/default/server.html | |||
@@ -0,0 +1,129 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html{if="$language !== 'auto'"} lang="{$language}"{/if}> | ||
3 | <head> | ||
4 | {include="includes"} | ||
5 | </head> | ||
6 | <body> | ||
7 | {include="page.header"} | ||
8 | |||
9 | <div class="pure-g"> | ||
10 | <div class="pure-u-lg-1-4 pure-u-1-24"></div> | ||
11 | <div class="pure-u-lg-1-2 pure-u-22-24 page-form server-tables-page"> | ||
12 | <h2 class="window-title">{'Server administration'|t}</h2> | ||
13 | |||
14 | <h3 class="window-subtitle">{'General'|t}</h3> | ||
15 | |||
16 | <div class="pure-g server-row"> | ||
17 | <div class="pure-u-lg-1-2 pure-u-1 server-label"> | ||
18 | <p>{'Index URL'|t}</p> | ||
19 | </div> | ||
20 | <div class="pure-u-lg-1-2 pure-u-1"> | ||
21 | <p><a href="{$index_url}" title="{$pagetitle}">{$index_url}</a></p> | ||
22 | </div> | ||
23 | </div> | ||
24 | <div class="pure-g server-row"> | ||
25 | <div class="pure-u-lg-1-2 pure-u-1 server-label"> | ||
26 | <p>{'Base path'|t}</p> | ||
27 | </div> | ||
28 | <div class="pure-u-lg-1-2 pure-u-1"> | ||
29 | <p>{$base_path}</p> | ||
30 | </div> | ||
31 | </div> | ||
32 | <div class="pure-g server-row"> | ||
33 | <div class="pure-u-lg-1-2 pure-u-1 server-label"> | ||
34 | <p>{'Client IP'|t}</p> | ||
35 | </div> | ||
36 | <div class="pure-u-lg-1-2 pure-u-1"> | ||
37 | <p>{$client_ip}</p> | ||
38 | </div> | ||
39 | </div> | ||
40 | <div class="pure-g server-row"> | ||
41 | <div class="pure-u-lg-1-2 pure-u-1 server-label"> | ||
42 | <p>{'Trusted reverse proxies'|t}</p> | ||
43 | </div> | ||
44 | <div class="pure-u-lg-1-2 pure-u-1"> | ||
45 | {if="count($trusted_proxies) > 0"} | ||
46 | <p> | ||
47 | {loop="$trusted_proxies"} | ||
48 | {$value}<br> | ||
49 | {/loop} | ||
50 | </p> | ||
51 | {else} | ||
52 | <p>{'N/A'|t}</p> | ||
53 | {/if} | ||
54 | </div> | ||
55 | </div> | ||
56 | |||
57 | {include="server.requirements"} | ||
58 | |||
59 | <h3 class="window-subtitle">Version</h3> | ||
60 | |||
61 | <div class="pure-g server-row"> | ||
62 | <div class="pure-u-lg-1-2 pure-u-1 server-label"> | ||
63 | <p>Current version</p> | ||
64 | </div> | ||
65 | <div class="pure-u-lg-1-2 pure-u-1"> | ||
66 | <p>{$current_version}</p> | ||
67 | </div> | ||
68 | </div> | ||
69 | |||
70 | <div class="pure-g server-row"> | ||
71 | <div class="pure-u-lg-1-2 pure-u-1 server-label"> | ||
72 | <p>Latest release</p> | ||
73 | </div> | ||
74 | <div class="pure-u-lg-1-2 pure-u-1"> | ||
75 | <p> | ||
76 | <a href="{$release_url}" title="{'Visit releases page on Github'|t}"> | ||
77 | {$latest_version} | ||
78 | </a> | ||
79 | </p> | ||
80 | </div> | ||
81 | </div> | ||
82 | |||
83 | <h3 class="window-subtitle">Thumbnails</h3> | ||
84 | |||
85 | <div class="pure-g server-row"> | ||
86 | <div class="pure-u-lg-1-2 pure-u-1 server-label"> | ||
87 | <p>Thumbnails status</p> | ||
88 | </div> | ||
89 | <div class="pure-u-lg-1-2 pure-u-1"> | ||
90 | <p> | ||
91 | {if="$thumbnails_mode==='all'"} | ||
92 | {'All'|t} | ||
93 | {elseif="$thumbnails_mode==='common'"} | ||
94 | {'Only common media hosts'|t} | ||
95 | {else} | ||
96 | {'None'|t} | ||
97 | {/if} | ||
98 | </p> | ||
99 | </div> | ||
100 | </div> | ||
101 | |||
102 | {if="$thumbnails_mode!=='none'"} | ||
103 | <div class="center tools-item"> | ||
104 | <a href="{$base_path}/admin/thumbnails" title="{'Synchronize all link thumbnails'|t}"> | ||
105 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Synchronize thumbnails'|t}</span> | ||
106 | </a> | ||
107 | </div> | ||
108 | {/if} | ||
109 | |||
110 | <h3 class="window-subtitle">Cache</h3> | ||
111 | |||
112 | <div class="center tools-item"> | ||
113 | <a href="{$base_path}/admin/clear-cache?type=main"> | ||
114 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Clear main cache</span> | ||
115 | </a> | ||
116 | </div> | ||
117 | |||
118 | <div class="center tools-item"> | ||
119 | <a href="{$base_path}/admin/clear-cache?type=thumbnails"> | ||
120 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Clear thumbnails cache</span> | ||
121 | </a> | ||
122 | </div> | ||
123 | </div> | ||
124 | </div> | ||
125 | |||
126 | {include="page.footer"} | ||
127 | |||
128 | </body> | ||
129 | </html> | ||
diff --git a/tpl/default/server.requirements.html b/tpl/default/server.requirements.html new file mode 100644 index 00000000..85def9b7 --- /dev/null +++ b/tpl/default/server.requirements.html | |||
@@ -0,0 +1,68 @@ | |||
1 | <div class="server-tables"> | ||
2 | <h3 class="window-subtitle">{'Permissions'|t}</h3> | ||
3 | |||
4 | {if="count($permissions) > 0"} | ||
5 | <p class="center"> | ||
6 | <i class="fa fa-close fa-color-red" aria-hidden="true"></i> | ||
7 | {'There are permissions that need to be fixed.'|t} | ||
8 | </p> | ||
9 | |||
10 | <p> | ||
11 | {loop="$permissions"} | ||
12 | <div class="center">{$value}</div> | ||
13 | {/loop} | ||
14 | </p> | ||
15 | {else} | ||
16 | <p class="center"> | ||
17 | <i class="fa fa-check fa-color-green" aria-hidden="true"></i> | ||
18 | {'All read/write permissions are properly set.'|t} | ||
19 | </p> | ||
20 | {/if} | ||
21 | |||
22 | <h3 class="window-subtitle">PHP</h3> | ||
23 | |||
24 | <p class="center"> | ||
25 | <strong>{'Running PHP'|t} {$php_version}</strong> | ||
26 | {if="$php_has_reached_eol"} | ||
27 | <i class="fa fa-circle fa-color-orange" aria-label="hidden"></i><br> | ||
28 | {'End of life: '|t} {$php_eol} | ||
29 | {else} | ||
30 | <i class="fa fa-circle fa-color-green" aria-label="hidden"></i><br> | ||
31 | {/if} | ||
32 | </p> | ||
33 | |||
34 | <table class="center"> | ||
35 | <thead> | ||
36 | <tr> | ||
37 | <th>{'Extension'|t}</th> | ||
38 | <th>{'Usage'|t}</th> | ||
39 | <th>{'Status'|t}</th> | ||
40 | <th>{'Loaded'|t}</th> | ||
41 | </tr> | ||
42 | </thead> | ||
43 | <tbody> | ||
44 | {loop="$php_extensions"} | ||
45 | <tr> | ||
46 | <td>{$value.name}</td> | ||
47 | <td>{$value.desc}</td> | ||
48 | <td>{$value.required ? t('Required') : t('Optional')}</td> | ||
49 | <td> | ||
50 | {if="$value.loaded"} | ||
51 | {$classLoaded="fa-color-green"} | ||
52 | {$strLoaded=t('Loaded')} | ||
53 | {else} | ||
54 | {$strLoaded=t('Not loaded')} | ||
55 | {if="$value.required"} | ||
56 | {$classLoaded="fa-color-red"} | ||
57 | {else} | ||
58 | {$classLoaded="fa-color-orange"} | ||
59 | {/if} | ||
60 | {/if} | ||
61 | |||
62 | <i class="fa fa-circle {$classLoaded}" aria-label="{$strLoaded}" title="{$strLoaded}"></i> | ||
63 | </td> | ||
64 | </tr> | ||
65 | {/loop} | ||
66 | </tbody> | ||
67 | </table> | ||
68 | </div> | ||
diff --git a/tpl/default/tools.html b/tpl/default/tools.html index 2cb08e38..2df73598 100644 --- a/tpl/default/tools.html +++ b/tpl/default/tools.html | |||
@@ -20,6 +20,12 @@ | |||
20 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Plugin administration'|t}</span> | 20 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Plugin administration'|t}</span> |
21 | </a> | 21 | </a> |
22 | </div> | 22 | </div> |
23 | <div class="tools-item"> | ||
24 | <a href="{$base_path}/admin/server" | ||
25 | title="{'Check instance\'s server configuration'|t}"> | ||
26 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Server administration'|t}</span> | ||
27 | </a> | ||
28 | </div> | ||
23 | {if="!$openshaarli"} | 29 | {if="!$openshaarli"} |
24 | <div class="tools-item"> | 30 | <div class="tools-item"> |
25 | <a href="{$base_path}/admin/password" title="{'Change your password'|t}"> | 31 | <a href="{$base_path}/admin/password" title="{'Change your password'|t}"> |
@@ -45,14 +51,6 @@ | |||
45 | </a> | 51 | </a> |
46 | </div> | 52 | </div> |
47 | 53 | ||
48 | {if="$thumbnails_enabled"} | ||
49 | <div class="tools-item"> | ||
50 | <a href="{$base_path}/admin/thumbnails" title="{'Synchronize all link thumbnails'|t}"> | ||
51 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Synchronize thumbnails'|t}</span> | ||
52 | </a> | ||
53 | </div> | ||
54 | {/if} | ||
55 | |||
56 | {loop="$tools_plugin"} | 54 | {loop="$tools_plugin"} |
57 | <div class="tools-item"> | 55 | <div class="tools-item"> |
58 | {$value} | 56 | {$value} |
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', |