aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/front/controller
diff options
context:
space:
mode:
Diffstat (limited to 'application/front/controller')
-rw-r--r--application/front/controller/admin/ManageShaareController.php360
-rw-r--r--application/front/controller/admin/ServerController.php87
-rw-r--r--application/front/controller/admin/ShaareAddController.php34
-rw-r--r--application/front/controller/admin/ShaareManageController.php202
-rw-r--r--application/front/controller/admin/ShaarePublishController.php263
-rw-r--r--application/front/controller/visitor/BookmarkListController.php32
-rw-r--r--application/front/controller/visitor/DailyController.php105
-rw-r--r--application/front/controller/visitor/InstallController.php14
8 files changed, 678 insertions, 419 deletions
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
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin;
6
7use Shaarli\Bookmark\Bookmark;
8use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
9use Shaarli\Formatter\BookmarkMarkdownFormatter;
10use Shaarli\Render\TemplatePage;
11use Shaarli\Thumbnailer;
12use Slim\Http\Request;
13use Slim\Http\Response;
14
15/**
16 * Class PostBookmarkController
17 *
18 * Slim controller used to handle Shaarli create or edit bookmarks.
19 */
20class 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
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin;
6
7use Shaarli\Helper\ApplicationUtils;
8use Shaarli\Helper\FileUtils;
9use Slim\Http\Request;
10use Slim\Http\Response;
11
12/**
13 * Slim controller used to handle Server administration page, and actions.
14 */
15class 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
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin;
6
7use Shaarli\Formatter\BookmarkMarkdownFormatter;
8use Shaarli\Render\TemplatePage;
9use Slim\Http\Request;
10use Slim\Http\Response;
11
12class 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
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin;
6
7use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
8use Slim\Http\Request;
9use Slim\Http\Response;
10
11/**
12 * Class PostBookmarkController
13 *
14 * Slim controller used to handle Shaarli create or edit bookmarks.
15 */
16class 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
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin;
6
7use Shaarli\Bookmark\Bookmark;
8use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
9use Shaarli\Formatter\BookmarkFormatter;
10use Shaarli\Formatter\BookmarkMarkdownFormatter;
11use Shaarli\Render\TemplatePage;
12use Shaarli\Thumbnailer;
13use Slim\Http\Request;
14use Slim\Http\Response;
15
16class 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);
5namespace Shaarli\Front\Controller\Visitor; 5namespace Shaarli\Front\Controller\Visitor;
6 6
7use DateTime; 7use DateTime;
8use DateTimeImmutable;
9use Shaarli\Bookmark\Bookmark; 8use Shaarli\Bookmark\Bookmark;
9use Shaarli\Helper\DailyPageHelper;
10use Shaarli\Render\TemplatePage; 10use Shaarli\Render\TemplatePage;
11use Slim\Http\Request; 11use Slim\Http\Request;
12use Slim\Http\Response; 12use 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
5namespace Shaarli\Front\Controller\Visitor; 5namespace Shaarli\Front\Controller\Visitor;
6 6
7use Shaarli\ApplicationUtils;
8use Shaarli\Container\ShaarliContainer; 7use Shaarli\Container\ShaarliContainer;
9use Shaarli\Front\Exception\AlreadyInstalledException; 8use Shaarli\Front\Exception\AlreadyInstalledException;
10use Shaarli\Front\Exception\ResourcePermissionException; 9use Shaarli\Front\Exception\ResourcePermissionException;
10use Shaarli\Helper\ApplicationUtils;
11use Shaarli\Languages; 11use Shaarli\Languages;
12use Shaarli\Security\SessionManager; 12use Shaarli\Security\SessionManager;
13use Slim\Http\Request; 13use 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 }