aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/front/controller/visitor
diff options
context:
space:
mode:
Diffstat (limited to 'application/front/controller/visitor')
-rw-r--r--application/front/controller/visitor/BookmarkListController.php53
-rw-r--r--application/front/controller/visitor/DailyController.php108
-rw-r--r--application/front/controller/visitor/ErrorController.php12
-rw-r--r--application/front/controller/visitor/FeedController.php2
-rw-r--r--application/front/controller/visitor/InstallController.php44
-rw-r--r--application/front/controller/visitor/LoginController.php9
-rw-r--r--application/front/controller/visitor/PictureWallController.php2
-rw-r--r--application/front/controller/visitor/ShaarliVisitorController.php5
-rw-r--r--application/front/controller/visitor/TagCloudController.php12
-rw-r--r--application/front/controller/visitor/TagController.php18
10 files changed, 160 insertions, 105 deletions
diff --git a/application/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php
index 18368751..fe8231be 100644
--- a/application/front/controller/visitor/BookmarkListController.php
+++ b/application/front/controller/visitor/BookmarkListController.php
@@ -35,7 +35,8 @@ class BookmarkListController extends ShaarliVisitorController
35 $formatter->addContextData('base_path', $this->container->basePath); 35 $formatter->addContextData('base_path', $this->container->basePath);
36 36
37 $searchTags = normalize_spaces($request->getParam('searchtags') ?? ''); 37 $searchTags = normalize_spaces($request->getParam('searchtags') ?? '');
38 $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));; 38 $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));
39 ;
39 40
40 // Filter bookmarks according search parameters. 41 // Filter bookmarks according search parameters.
41 $visibility = $this->container->sessionManager->getSessionParameter('visibility'); 42 $visibility = $this->container->sessionManager->getSessionParameter('visibility');
@@ -95,6 +96,10 @@ class BookmarkListController extends ShaarliVisitorController
95 $next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl; 96 $next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl;
96 } 97 }
97 98
99 $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
100 $searchTagsUrlEncoded = array_map('urlencode', tags_str2array($searchTags, $tagsSeparator));
101 $searchTags = !empty($searchTags) ? trim($searchTags, $tagsSeparator) . $tagsSeparator : '';
102
98 // Fill all template fields. 103 // Fill all template fields.
99 $data = array_merge( 104 $data = array_merge(
100 $this->initializeTemplateVars(), 105 $this->initializeTemplateVars(),
@@ -106,7 +111,7 @@ class BookmarkListController extends ShaarliVisitorController
106 'result_count' => count($linksToDisplay), 111 'result_count' => count($linksToDisplay),
107 'search_term' => escape($searchTerm), 112 'search_term' => escape($searchTerm),
108 'search_tags' => escape($searchTags), 113 'search_tags' => escape($searchTags),
109 'search_tags_url' => array_map('urlencode', explode(' ', $searchTags)), 114 'search_tags_url' => $searchTagsUrlEncoded,
110 'visibility' => $visibility, 115 'visibility' => $visibility,
111 'links' => $linkDisp, 116 'links' => $linkDisp,
112 ] 117 ]
@@ -119,8 +124,9 @@ class BookmarkListController extends ShaarliVisitorController
119 return '[' . $tag . ']'; 124 return '[' . $tag . ']';
120 }; 125 };
121 $data['pagetitle'] .= ! empty($searchTags) 126 $data['pagetitle'] .= ! empty($searchTags)
122 ? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchTags))) . ' ' 127 ? implode(' ', array_map($bracketWrap, tags_str2array($searchTags, $tagsSeparator))) . ' '
123 : ''; 128 : ''
129 ;
124 $data['pagetitle'] .= '- '; 130 $data['pagetitle'] .= '- ';
125 } 131 }
126 132
@@ -137,8 +143,10 @@ class BookmarkListController extends ShaarliVisitorController
137 */ 143 */
138 public function permalink(Request $request, Response $response, array $args): Response 144 public function permalink(Request $request, Response $response, array $args): Response
139 { 145 {
146 $privateKey = $request->getParam('key');
147
140 try { 148 try {
141 $bookmark = $this->container->bookmarkService->findByHash($args['hash']); 149 $bookmark = $this->container->bookmarkService->findByHash($args['hash'], $privateKey);
142 } catch (BookmarkNotFoundException $e) { 150 } catch (BookmarkNotFoundException $e) {
143 $this->assignView('error_message', $e->getMessage()); 151 $this->assignView('error_message', $e->getMessage());
144 152
@@ -153,7 +161,7 @@ class BookmarkListController extends ShaarliVisitorController
153 $data = array_merge( 161 $data = array_merge(
154 $this->initializeTemplateVars(), 162 $this->initializeTemplateVars(),
155 [ 163 [
156 'pagetitle' => $bookmark->getTitle() .' - '. $this->container->conf->get('general.title', 'Shaarli'), 164 'pagetitle' => $bookmark->getTitle() . ' - ' . $this->container->conf->get('general.title', 'Shaarli'),
157 'links' => [$formatter->format($bookmark)], 165 'links' => [$formatter->format($bookmark)],
158 ] 166 ]
159 ); 167 );
@@ -169,19 +177,25 @@ class BookmarkListController extends ShaarliVisitorController
169 */ 177 */
170 protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool 178 protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool
171 { 179 {
172 // Logged in, thumbnails enabled, not a note, is HTTP 180 if (false === $this->container->loginManager->isLoggedIn()) {
173 // and (never retrieved yet or no valid cache file) 181 return false;
174 if ($this->container->loginManager->isLoggedIn() 182 }
175 && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE 183
176 && false !== $bookmark->getThumbnail() 184 // If thumbnail should be updated, we reset it to null
177 && !$bookmark->isNote() 185 if ($bookmark->shouldUpdateThumbnail()) {
178 && (null === $bookmark->getThumbnail() || !is_file($bookmark->getThumbnail())) 186 $bookmark->setThumbnail(null);
179 && startsWith(strtolower($bookmark->getUrl()), 'http') 187
180 ) { 188 // Requires an update, not async retrieval, thumbnails enabled
181 $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); 189 if (
182 $this->container->bookmarkService->set($bookmark, $writeDatastore); 190 $bookmark->shouldUpdateThumbnail()
183 191 && true !== $this->container->conf->get('general.enable_async_metadata', true)
184 return true; 192 && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
193 ) {
194 $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
195 $this->container->bookmarkService->set($bookmark, $writeDatastore);
196
197 return true;
198 }
185 } 199 }
186 200
187 return false; 201 return false;
@@ -198,6 +212,7 @@ class BookmarkListController extends ShaarliVisitorController
198 'page_max' => '', 212 'page_max' => '',
199 'search_tags' => '', 213 'search_tags' => '',
200 'result_count' => '', 214 'result_count' => '',
215 'async_metadata' => $this->container->conf->get('general.enable_async_metadata', true)
201 ]; 216 ];
202 } 217 }
203 218
diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php
index 07617cf1..29492a5f 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));
@@ -96,9 +86,11 @@ class DailyController extends ShaarliVisitorController
96 public function rss(Request $request, Response $response): Response 86 public function rss(Request $request, Response $response): Response
97 { 87 {
98 $response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8'); 88 $response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8');
89 $type = DailyPageHelper::extractRequestedType($request);
90 $cacheDuration = DailyPageHelper::getCacheDatePeriodByType($type);
99 91
100 $pageUrl = page_url($this->container->environment); 92 $pageUrl = page_url($this->container->environment);
101 $cache = $this->container->pageCacheManager->getCachePage($pageUrl); 93 $cache = $this->container->pageCacheManager->getCachePage($pageUrl, $cacheDuration);
102 94
103 $cached = $cache->cachedVersion(); 95 $cached = $cache->cachedVersion();
104 if (!empty($cached)) { 96 if (!empty($cached)) {
@@ -106,11 +98,13 @@ class DailyController extends ShaarliVisitorController
106 } 98 }
107 99
108 $days = []; 100 $days = [];
101 $format = DailyPageHelper::getFormatByType($type);
102 $length = DailyPageHelper::getRssLengthByType($type);
109 foreach ($this->container->bookmarkService->search() as $bookmark) { 103 foreach ($this->container->bookmarkService->search() as $bookmark) {
110 $day = $bookmark->getCreated()->format('Ymd'); 104 $day = $bookmark->getCreated()->format($format);
111 105
112 // Stop iterating after DAILY_RSS_NB_DAYS entries 106 // Stop iterating after DAILY_RSS_NB_DAYS entries
113 if (count($days) === static::$DAILY_RSS_NB_DAYS && !isset($days[$day])) { 107 if (count($days) === $length && !isset($days[$day])) {
114 break; 108 break;
115 } 109 }
116 110
@@ -127,12 +121,19 @@ class DailyController extends ShaarliVisitorController
127 121
128 /** @var Bookmark[] $bookmarks */ 122 /** @var Bookmark[] $bookmarks */
129 foreach ($days as $day => $bookmarks) { 123 foreach ($days as $day => $bookmarks) {
130 $dayDatetime = DateTimeImmutable::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); 124 $dayDateTime = DailyPageHelper::extractRequestedDateTime($type, (string) $day);
125 $endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dayDateTime);
126
127 // We only want the RSS entry to be published when the period is over.
128 if (new DateTime() < $endDateTime) {
129 continue;
130 }
131
131 $dataPerDay[$day] = [ 132 $dataPerDay[$day] = [
132 'date' => $dayDatetime, 133 'date' => $endDateTime,
133 'date_rss' => $dayDatetime->format(DateTime::RSS), 134 'date_rss' => $endDateTime->format(DateTime::RSS),
134 'date_human' => format_date($dayDatetime, false, true), 135 'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime, false),
135 'absolute_url' => $indexUrl . 'daily?day=' . $day, 136 'absolute_url' => $indexUrl . 'daily?' . $type . '=' . $day,
136 'links' => [], 137 'links' => [],
137 ]; 138 ];
138 139
@@ -141,16 +142,20 @@ class DailyController extends ShaarliVisitorController
141 142
142 // Make permalink URL absolute 143 // Make permalink URL absolute
143 if ($bookmark->isNote()) { 144 if ($bookmark->isNote()) {
144 $dataPerDay[$day]['links'][$key]['url'] = $indexUrl . $bookmark->getUrl(); 145 $dataPerDay[$day]['links'][$key]['url'] = rtrim($indexUrl, '/') . $bookmark->getUrl();
145 } 146 }
146 } 147 }
147 } 148 }
148 149
149 $this->assignView('title', $this->container->conf->get('general.title', 'Shaarli')); 150 $this->assignAllView([
150 $this->assignView('index_url', $indexUrl); 151 'title' => $this->container->conf->get('general.title', 'Shaarli'),
151 $this->assignView('page_url', $pageUrl); 152 'index_url' => $indexUrl,
152 $this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false)); 153 'page_url' => $pageUrl,
153 $this->assignView('days', $dataPerDay); 154 'hide_timestamps' => $this->container->conf->get('privacy.hide_timestamps', false),
155 'days' => $dataPerDay,
156 'type' => $type,
157 'localizedType' => $this->translateType($type),
158 ]);
154 159
155 $rssContent = $this->render(TemplatePage::DAILY_RSS); 160 $rssContent = $this->render(TemplatePage::DAILY_RSS);
156 161
@@ -189,4 +194,13 @@ class DailyController extends ShaarliVisitorController
189 194
190 return $columns; 195 return $columns;
191 } 196 }
197
198 protected function translateType($type): string
199 {
200 return [
201 t('day') => t('Daily'),
202 t('week') => t('Weekly'),
203 t('month') => t('Monthly'),
204 ][t($type)] ?? t('Daily');
205 }
192} 206}
diff --git a/application/front/controller/visitor/ErrorController.php b/application/front/controller/visitor/ErrorController.php
index 10aa84c8..428e8254 100644
--- a/application/front/controller/visitor/ErrorController.php
+++ b/application/front/controller/visitor/ErrorController.php
@@ -26,12 +26,15 @@ class ErrorController extends ShaarliVisitorController
26 $response = $response->withStatus($throwable->getCode()); 26 $response = $response->withStatus($throwable->getCode());
27 } else { 27 } else {
28 // Internal error (any other Throwable) 28 // Internal error (any other Throwable)
29 if ($this->container->conf->get('dev.debug', false)) { 29 if ($this->container->conf->get('dev.debug', false) || $this->container->loginManager->isLoggedIn()) {
30 $this->assignView('message', $throwable->getMessage()); 30 $this->assignView('message', t('Error: ') . $throwable->getMessage());
31 $this->assignView( 31 $this->assignView(
32 'stacktrace', 32 'text',
33 nl2br(get_class($throwable) .': '. PHP_EOL . $throwable->getTraceAsString()) 33 '<a href="https://github.com/shaarli/Shaarli/issues/new">'
34 . t('Please report it on Github.')
35 . '</a>'
34 ); 36 );
37 $this->assignView('stacktrace', exception2text($throwable));
35 } else { 38 } else {
36 $this->assignView('message', t('An unexpected error occurred.')); 39 $this->assignView('message', t('An unexpected error occurred.'));
37 } 40 }
@@ -39,7 +42,6 @@ class ErrorController extends ShaarliVisitorController
39 $response = $response->withStatus(500); 42 $response = $response->withStatus(500);
40 } 43 }
41 44
42
43 return $response->write($this->render('error')); 45 return $response->write($this->render('error'));
44 } 46 }
45} 47}
diff --git a/application/front/controller/visitor/FeedController.php b/application/front/controller/visitor/FeedController.php
index 8d8b546a..edc7ef43 100644
--- a/application/front/controller/visitor/FeedController.php
+++ b/application/front/controller/visitor/FeedController.php
@@ -27,7 +27,7 @@ class FeedController extends ShaarliVisitorController
27 27
28 protected function processRequest(string $feedType, Request $request, Response $response): Response 28 protected function processRequest(string $feedType, Request $request, Response $response): Response
29 { 29 {
30 $response = $response->withHeader('Content-Type', 'application/'. $feedType .'+xml; charset=utf-8'); 30 $response = $response->withHeader('Content-Type', 'application/' . $feedType . '+xml; charset=utf-8');
31 31
32 $pageUrl = page_url($this->container->environment); 32 $pageUrl = page_url($this->container->environment);
33 $cache = $this->container->pageCacheManager->getCachePage($pageUrl); 33 $cache = $this->container->pageCacheManager->getCachePage($pageUrl);
diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php
index 7cb32777..418d4a49 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;
@@ -39,7 +39,8 @@ class InstallController extends ShaarliVisitorController
39 // Before installation, we'll make sure that permissions are set properly, and sessions are working. 39 // Before installation, we'll make sure that permissions are set properly, and sessions are working.
40 $this->checkPermissions(); 40 $this->checkPermissions();
41 41
42 if (static::SESSION_TEST_VALUE 42 if (
43 static::SESSION_TEST_VALUE
43 !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY) 44 !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY)
44 ) { 45 ) {
45 $this->container->sessionManager->setSessionParameter(static::SESSION_TEST_KEY, static::SESSION_TEST_VALUE); 46 $this->container->sessionManager->setSessionParameter(static::SESSION_TEST_KEY, static::SESSION_TEST_VALUE);
@@ -53,6 +54,21 @@ class InstallController extends ShaarliVisitorController
53 $this->assignView('cities', $cities); 54 $this->assignView('cities', $cities);
54 $this->assignView('languages', Languages::getAvailableLanguages()); 55 $this->assignView('languages', Languages::getAvailableLanguages());
55 56
57 $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION));
58
59 $permissions = array_merge(
60 ApplicationUtils::checkResourcePermissions($this->container->conf),
61 ApplicationUtils::checkDatastoreMutex()
62 );
63
64 $this->assignView('php_version', PHP_VERSION);
65 $this->assignView('php_eol', format_date($phpEol, false));
66 $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable());
67 $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement());
68 $this->assignView('permissions', $permissions);
69
70 $this->assignView('pagetitle', t('Install Shaarli'));
71
56 return $response->write($this->render('install')); 72 return $response->write($this->render('install'));
57 } 73 }
58 74
@@ -65,17 +81,18 @@ class InstallController extends ShaarliVisitorController
65 // This part makes sure sessions works correctly. 81 // This part makes sure sessions works correctly.
66 // (Because on some hosts, session.save_path may not be set correctly, 82 // (Because on some hosts, session.save_path may not be set correctly,
67 // or we may not have write access to it.) 83 // or we may not have write access to it.)
68 if (static::SESSION_TEST_VALUE 84 if (
85 static::SESSION_TEST_VALUE
69 !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY) 86 !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY)
70 ) { 87 ) {
71 // Step 2: Check if data in session is correct. 88 // Step 2: Check if data in session is correct.
72 $msg = t( 89 $msg = t(
73 '<pre>Sessions do not seem to work correctly on your server.<br>'. 90 '<pre>Sessions do not seem to work correctly on your server.<br>' .
74 'Make sure the variable "session.save_path" is set correctly in your PHP config, '. 91 'Make sure the variable "session.save_path" is set correctly in your PHP config, ' .
75 'and that you have write access to it.<br>'. 92 'and that you have write access to it.<br>' .
76 'It currently points to %s.<br>'. 93 'It currently points to %s.<br>' .
77 'On some browsers, accessing your server via a hostname like \'localhost\' '. 94 'On some browsers, accessing your server via a hostname like \'localhost\' ' .
78 'or any custom hostname without a dot causes cookie storage to fail. '. 95 'or any custom hostname without a dot causes cookie storage to fail. ' .
79 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>' 96 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>'
80 ); 97 );
81 $msg = sprintf($msg, $this->container->sessionManager->getSavePath()); 98 $msg = sprintf($msg, $this->container->sessionManager->getSavePath());
@@ -94,7 +111,8 @@ class InstallController extends ShaarliVisitorController
94 public function save(Request $request, Response $response): Response 111 public function save(Request $request, Response $response): Response
95 { 112 {
96 $timezone = 'UTC'; 113 $timezone = 'UTC';
97 if (!empty($request->getParam('continent')) 114 if (
115 !empty($request->getParam('continent'))
98 && !empty($request->getParam('city')) 116 && !empty($request->getParam('city'))
99 && isTimeZoneValid($request->getParam('continent'), $request->getParam('city')) 117 && isTimeZoneValid($request->getParam('continent'), $request->getParam('city'))
100 ) { 118 ) {
@@ -104,7 +122,7 @@ class InstallController extends ShaarliVisitorController
104 122
105 $login = $request->getParam('setlogin'); 123 $login = $request->getParam('setlogin');
106 $this->container->conf->set('credentials.login', $login); 124 $this->container->conf->set('credentials.login', $login);
107 $salt = sha1(uniqid('', true) .'_'. mt_rand()); 125 $salt = sha1(uniqid('', true) . '_' . mt_rand());
108 $this->container->conf->set('credentials.salt', $salt); 126 $this->container->conf->set('credentials.salt', $salt);
109 $this->container->conf->set('credentials.hash', sha1($request->getParam('setpassword') . $login . $salt)); 127 $this->container->conf->set('credentials.hash', sha1($request->getParam('setpassword') . $login . $salt));
110 128
@@ -113,7 +131,7 @@ class InstallController extends ShaarliVisitorController
113 } else { 131 } else {
114 $this->container->conf->set( 132 $this->container->conf->set(
115 'general.title', 133 'general.title',
116 'Shared bookmarks on '.escape(index_url($this->container->environment)) 134 'Shared bookmarks on ' . escape(index_url($this->container->environment))
117 ); 135 );
118 } 136 }
119 137
@@ -150,7 +168,7 @@ class InstallController extends ShaarliVisitorController
150 protected function checkPermissions(): bool 168 protected function checkPermissions(): bool
151 { 169 {
152 // Ensure Shaarli has proper access to its resources 170 // Ensure Shaarli has proper access to its resources
153 $errors = ApplicationUtils::checkResourcePermissions($this->container->conf); 171 $errors = ApplicationUtils::checkResourcePermissions($this->container->conf, true);
154 if (empty($errors)) { 172 if (empty($errors)) {
155 return true; 173 return true;
156 } 174 }
diff --git a/application/front/controller/visitor/LoginController.php b/application/front/controller/visitor/LoginController.php
index 121ba40b..4b881535 100644
--- a/application/front/controller/visitor/LoginController.php
+++ b/application/front/controller/visitor/LoginController.php
@@ -43,7 +43,7 @@ class LoginController extends ShaarliVisitorController
43 $this 43 $this
44 ->assignView('returnurl', escape($returnUrl)) 44 ->assignView('returnurl', escape($returnUrl))
45 ->assignView('remember_user_default', $this->container->conf->get('privacy.remember_user_default', true)) 45 ->assignView('remember_user_default', $this->container->conf->get('privacy.remember_user_default', true))
46 ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli')) 46 ->assignView('pagetitle', t('Login') . ' - ' . $this->container->conf->get('general.title', 'Shaarli'))
47 ; 47 ;
48 48
49 return $response->write($this->render(TemplatePage::LOGIN)); 49 return $response->write($this->render(TemplatePage::LOGIN));
@@ -64,8 +64,8 @@ class LoginController extends ShaarliVisitorController
64 return $this->redirect($response, '/'); 64 return $this->redirect($response, '/');
65 } 65 }
66 66
67 if (!$this->container->loginManager->checkCredentials( 67 if (
68 $this->container->environment['REMOTE_ADDR'], 68 !$this->container->loginManager->checkCredentials(
69 client_ip_id($this->container->environment), 69 client_ip_id($this->container->environment),
70 $request->getParam('login'), 70 $request->getParam('login'),
71 $request->getParam('password') 71 $request->getParam('password')
@@ -102,7 +102,8 @@ class LoginController extends ShaarliVisitorController
102 */ 102 */
103 protected function checkLoginState(): bool 103 protected function checkLoginState(): bool
104 { 104 {
105 if ($this->container->loginManager->isLoggedIn() 105 if (
106 $this->container->loginManager->isLoggedIn()
106 || $this->container->conf->get('security.open_shaarli', false) 107 || $this->container->conf->get('security.open_shaarli', false)
107 ) { 108 ) {
108 throw new CantLoginException(); 109 throw new CantLoginException();
diff --git a/application/front/controller/visitor/PictureWallController.php b/application/front/controller/visitor/PictureWallController.php
index 3c57f8dd..23553ee6 100644
--- a/application/front/controller/visitor/PictureWallController.php
+++ b/application/front/controller/visitor/PictureWallController.php
@@ -26,7 +26,7 @@ class PictureWallController extends ShaarliVisitorController
26 26
27 $this->assignView( 27 $this->assignView(
28 'pagetitle', 28 'pagetitle',
29 t('Picture wall') .' - '. $this->container->conf->get('general.title', 'Shaarli') 29 t('Picture wall') . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
30 ); 30 );
31 31
32 // Optionally filter the results: 32 // Optionally filter the results:
diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php
index 54f9fe03..ae946c59 100644
--- a/application/front/controller/visitor/ShaarliVisitorController.php
+++ b/application/front/controller/visitor/ShaarliVisitorController.php
@@ -144,7 +144,8 @@ abstract class ShaarliVisitorController
144 if (null !== $referer) { 144 if (null !== $referer) {
145 $currentUrl = parse_url($referer); 145 $currentUrl = parse_url($referer);
146 // If the referer is not related to Shaarli instance, redirect to default 146 // If the referer is not related to Shaarli instance, redirect to default
147 if (isset($currentUrl['host']) 147 if (
148 isset($currentUrl['host'])
148 && strpos(index_url($this->container->environment), $currentUrl['host']) === false 149 && strpos(index_url($this->container->environment), $currentUrl['host']) === false
149 ) { 150 ) {
150 return $response->withRedirect($defaultPath); 151 return $response->withRedirect($defaultPath);
@@ -173,7 +174,7 @@ abstract class ShaarliVisitorController
173 } 174 }
174 } 175 }
175 176
176 $queryString = count($params) > 0 ? '?'. http_build_query($params) : ''; 177 $queryString = count($params) > 0 ? '?' . http_build_query($params) : '';
177 $anchor = $anchor ? '#' . $anchor : ''; 178 $anchor = $anchor ? '#' . $anchor : '';
178 179
179 return $response->withRedirect($path . $queryString . $anchor); 180 return $response->withRedirect($path . $queryString . $anchor);
diff --git a/application/front/controller/visitor/TagCloudController.php b/application/front/controller/visitor/TagCloudController.php
index 76ed7690..46d62779 100644
--- a/application/front/controller/visitor/TagCloudController.php
+++ b/application/front/controller/visitor/TagCloudController.php
@@ -47,13 +47,14 @@ class TagCloudController extends ShaarliVisitorController
47 */ 47 */
48 protected function processRequest(string $type, Request $request, Response $response): Response 48 protected function processRequest(string $type, Request $request, Response $response): Response
49 { 49 {
50 $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
50 if ($this->container->loginManager->isLoggedIn() === true) { 51 if ($this->container->loginManager->isLoggedIn() === true) {
51 $visibility = $this->container->sessionManager->getSessionParameter('visibility'); 52 $visibility = $this->container->sessionManager->getSessionParameter('visibility');
52 } 53 }
53 54
54 $sort = $request->getQueryParam('sort'); 55 $sort = $request->getQueryParam('sort');
55 $searchTags = $request->getQueryParam('searchtags'); 56 $searchTags = $request->getQueryParam('searchtags');
56 $filteringTags = $searchTags !== null ? explode(' ', $searchTags) : []; 57 $filteringTags = $searchTags !== null ? explode($tagsSeparator, $searchTags) : [];
57 58
58 $tags = $this->container->bookmarkService->bookmarksCountPerTag($filteringTags, $visibility ?? null); 59 $tags = $this->container->bookmarkService->bookmarksCountPerTag($filteringTags, $visibility ?? null);
59 60
@@ -71,8 +72,9 @@ class TagCloudController extends ShaarliVisitorController
71 $tagsUrl[escape($tag)] = urlencode((string) $tag); 72 $tagsUrl[escape($tag)] = urlencode((string) $tag);
72 } 73 }
73 74
74 $searchTags = implode(' ', escape($filteringTags)); 75 $searchTags = tags_array2str($filteringTags, $tagsSeparator);
75 $searchTagsUrl = urlencode(implode(' ', $filteringTags)); 76 $searchTags = !empty($searchTags) ? trim($searchTags, $tagsSeparator) . $tagsSeparator : '';
77 $searchTagsUrl = urlencode($searchTags);
76 $data = [ 78 $data = [
77 'search_tags' => escape($searchTags), 79 'search_tags' => escape($searchTags),
78 'search_tags_url' => $searchTagsUrl, 80 'search_tags_url' => $searchTagsUrl,
@@ -82,10 +84,10 @@ class TagCloudController extends ShaarliVisitorController
82 $this->executePageHooks('render_tag' . $type, $data, 'tag.' . $type); 84 $this->executePageHooks('render_tag' . $type, $data, 'tag.' . $type);
83 $this->assignAllView($data); 85 $this->assignAllView($data);
84 86
85 $searchTags = !empty($searchTags) ? $searchTags .' - ' : ''; 87 $searchTags = !empty($searchTags) ? trim(str_replace($tagsSeparator, ' ', $searchTags)) . ' - ' : '';
86 $this->assignView( 88 $this->assignView(
87 'pagetitle', 89 'pagetitle',
88 $searchTags . t('Tag '. $type) .' - '. $this->container->conf->get('general.title', 'Shaarli') 90 $searchTags . t('Tag ' . $type) . ' - ' . $this->container->conf->get('general.title', 'Shaarli')
89 ); 91 );
90 92
91 return $response->write($this->render('tag.' . $type)); 93 return $response->write($this->render('tag.' . $type));
diff --git a/application/front/controller/visitor/TagController.php b/application/front/controller/visitor/TagController.php
index de4e7ea2..3aa58542 100644
--- a/application/front/controller/visitor/TagController.php
+++ b/application/front/controller/visitor/TagController.php
@@ -27,7 +27,7 @@ class TagController extends ShaarliVisitorController
27 // In case browser does not send HTTP_REFERER, we search a single tag 27 // In case browser does not send HTTP_REFERER, we search a single tag
28 if (null === $referer) { 28 if (null === $referer) {
29 if (null !== $newTag) { 29 if (null !== $newTag) {
30 return $this->redirect($response, '/?searchtags='. urlencode($newTag)); 30 return $this->redirect($response, '/?searchtags=' . urlencode($newTag));
31 } 31 }
32 32
33 return $this->redirect($response, '/'); 33 return $this->redirect($response, '/');
@@ -37,7 +37,7 @@ class TagController extends ShaarliVisitorController
37 parse_str($currentUrl['query'] ?? '', $params); 37 parse_str($currentUrl['query'] ?? '', $params);
38 38
39 if (null === $newTag) { 39 if (null === $newTag) {
40 return $response->withRedirect(($currentUrl['path'] ?? './') .'?'. http_build_query($params)); 40 return $response->withRedirect(($currentUrl['path'] ?? './') . '?' . http_build_query($params));
41 } 41 }
42 42
43 // Prevent redirection loop 43 // Prevent redirection loop
@@ -45,9 +45,10 @@ class TagController extends ShaarliVisitorController
45 unset($params['addtag']); 45 unset($params['addtag']);
46 } 46 }
47 47
48 $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
48 // Check if this tag is already in the search query and ignore it if it is. 49 // Check if this tag is already in the search query and ignore it if it is.
49 // Each tag is always separated by a space 50 // Each tag is always separated by a space
50 $currentTags = isset($params['searchtags']) ? explode(' ', $params['searchtags']) : []; 51 $currentTags = tags_str2array($params['searchtags'] ?? '', $tagsSeparator);
51 52
52 $addtag = true; 53 $addtag = true;
53 foreach ($currentTags as $value) { 54 foreach ($currentTags as $value) {
@@ -62,12 +63,12 @@ class TagController extends ShaarliVisitorController
62 $currentTags[] = trim($newTag); 63 $currentTags[] = trim($newTag);
63 } 64 }
64 65
65 $params['searchtags'] = trim(implode(' ', $currentTags)); 66 $params['searchtags'] = tags_array2str($currentTags, $tagsSeparator);
66 67
67 // We also remove page (keeping the same page has no sense, since the results are different) 68 // We also remove page (keeping the same page has no sense, since the results are different)
68 unset($params['page']); 69 unset($params['page']);
69 70
70 return $response->withRedirect(($currentUrl['path'] ?? './') .'?'. http_build_query($params)); 71 return $response->withRedirect(($currentUrl['path'] ?? './') . '?' . http_build_query($params));
71 } 72 }
72 73
73 /** 74 /**
@@ -89,7 +90,7 @@ class TagController extends ShaarliVisitorController
89 parse_str($currentUrl['query'] ?? '', $params); 90 parse_str($currentUrl['query'] ?? '', $params);
90 91
91 if (null === $tagToRemove) { 92 if (null === $tagToRemove) {
92 return $response->withRedirect(($currentUrl['path'] ?? './') .'?'. http_build_query($params)); 93 return $response->withRedirect(($currentUrl['path'] ?? './') . '?' . http_build_query($params));
93 } 94 }
94 95
95 // Prevent redirection loop 96 // Prevent redirection loop
@@ -98,10 +99,11 @@ class TagController extends ShaarliVisitorController
98 } 99 }
99 100
100 if (isset($params['searchtags'])) { 101 if (isset($params['searchtags'])) {
101 $tags = explode(' ', $params['searchtags']); 102 $tagsSeparator = $this->container->conf->get('general.tags_separator', ' ');
103 $tags = tags_str2array($params['searchtags'] ?? '', $tagsSeparator);
102 // Remove value from array $tags. 104 // Remove value from array $tags.
103 $tags = array_diff($tags, [$tagToRemove]); 105 $tags = array_diff($tags, [$tagToRemove]);
104 $params['searchtags'] = implode(' ', $tags); 106 $params['searchtags'] = tags_array2str($tags, $tagsSeparator);
105 107
106 if (empty($params['searchtags'])) { 108 if (empty($params['searchtags'])) {
107 unset($params['searchtags']); 109 unset($params['searchtags']);