aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md34
-rw-r--r--README.md4
-rw-r--r--application/Thumbnailer.php2
-rw-r--r--application/api/ApiMiddleware.php4
-rw-r--r--application/api/ApiUtils.php2
-rw-r--r--application/bookmark/LinkDB.php47
-rw-r--r--application/bookmark/LinkUtils.php37
-rw-r--r--application/config/ConfigManager.php4
-rw-r--r--application/feed/FeedBuilder.php6
-rw-r--r--application/netscape/NetscapeBookmarkUtils.php2
-rw-r--r--application/updater/Updater.php11
-rw-r--r--assets/default/scss/shaarli.scss3
-rw-r--r--doc/md/Download-and-Installation.md6
-rw-r--r--doc/md/Server-configuration.md2
-rw-r--r--doc/md/Shaarli-configuration.md17
-rw-r--r--index.php37
-rw-r--r--tests/bookmark/LinkDBTest.php32
-rw-r--r--tests/bookmark/LinkUtilsTest.php61
-rw-r--r--tests/plugins/PluginMarkdownTest.php2
-rw-r--r--tests/updater/UpdaterTest.php19
20 files changed, 144 insertions, 188 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa1f0d8a..ada0f109 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,36 @@ All notable changes to this project will be documented in this file.
4The format is based on [Keep a Changelog](http://keepachangelog.com/) 4The format is based on [Keep a Changelog](http://keepachangelog.com/)
5and this project adheres to [Semantic Versioning](http://semver.org/). 5and this project adheres to [Semantic Versioning](http://semver.org/).
6 6
7
8## [v0.10.3](https://github.com/shaarli/Shaarli/releases/tag/v0.10.3) - 2019-02-23
9### Added
10- Add OpenGraph metadata tags on permalink page
11- Add CORS headers to REST API reponses
12- Add a button to toggle checkboxes of displayed links
13- Add an icon to the link list when the Isso plugin is enabled
14- Add noindex, nofollow to documentation pages
15- Document usage of robots.txt
16- Add a button to set links as sticky
17
18### Changed
19- Update French translation
20- Refactor the documentation homepage
21- Bump netscape-bookmark-parser
22- Update session_start condition
23- Improve accessibility
24- Cleanup and refactor lint tooling
25
26### Fixed
27- Fix input size for dropdown search form
28- Fix history for bulk link deletion
29- Fix thumbnail requests
30- Fix hashtag rendering when markdown escaping is enabled
31- Fix AJAX tag deletion
32- Fix lint errors and improve PSR-1 and PSR-2 compliance
33
34### Removed
35- Remove Firefox Share documentation
36
7## [v0.10.2](https://github.com/shaarli/Shaarli/releases/tag/v0.10.2) - 2018-08-11 37## [v0.10.2](https://github.com/shaarli/Shaarli/releases/tag/v0.10.2) - 2018-08-11
8 38
9### Fixed 39### Fixed
@@ -12,7 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
12 42
13## [v0.10.1](https://github.com/shaarli/Shaarli/releases/tag/v0.10.1) - 2018-08-11 43## [v0.10.1](https://github.com/shaarli/Shaarli/releases/tag/v0.10.1) - 2018-08-11
14 44
15### Changed 45### Changed
16 46
17- Accessibility: 47- Accessibility:
18 - Remove alt text on the logo 48 - Remove alt text on the logo
@@ -46,7 +76,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
46- Use Travis matrix and stages to run Javascript tests in a dedicated environment 76- Use Travis matrix and stages to run Javascript tests in a dedicated environment
47- Add tag endpoint in the REST API 77- Add tag endpoint in the REST API
48- Build the documentation in Travis builds 78- Build the documentation in Travis builds
49- Provide a Docker Compose example 79- Provide a Docker Compose example
50 80
51### Changed 81### Changed
52- Use web-thumbnailer to retrieve thumbnails (see #687) 82- Use web-thumbnailer to retrieve thumbnails (see #687)
diff --git a/README.md b/README.md
index 0e23e33d..ea2c8176 100644
--- a/README.md
+++ b/README.md
@@ -9,10 +9,10 @@ _It is designed to be personal (single-user), fast and handy._
9[![](https://img.shields.io/badge/stable-v0.9.7-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.7) 9[![](https://img.shields.io/badge/stable-v0.9.7-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.7)
10[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli) 10[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
11• 11•
12[![](https://img.shields.io/badge/latest-v0.10.2-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.10.2) 12[![](https://img.shields.io/badge/latest-v0.10.3-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.10.3)
13[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) 13[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
14• 14•
15[![](https://img.shields.io/badge/master-v0.10.x-blue.svg)](https://github.com/shaarli/Shaarli) 15[![](https://img.shields.io/badge/master-v0.11.x-blue.svg)](https://github.com/shaarli/Shaarli)
16[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli) 16[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli)
17 17
18[![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli) 18[![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli)
diff --git a/application/Thumbnailer.php b/application/Thumbnailer.php
index a23f98e9..d5f5ac28 100644
--- a/application/Thumbnailer.php
+++ b/application/Thumbnailer.php
@@ -55,7 +55,7 @@ class Thumbnailer
55 $this->conf = $conf; 55 $this->conf = $conf;
56 56
57 if (! $this->checkRequirements()) { 57 if (! $this->checkRequirements()) {
58 $this->conf->set('thumbnails.enabled', false); 58 $this->conf->set('thumbnails.mode', Thumbnailer::MODE_NONE);
59 $this->conf->write(true); 59 $this->conf->write(true);
60 // TODO: create a proper error handling system able to catch exceptions... 60 // TODO: create a proper error handling system able to catch exceptions...
61 die(t( 61 die(t(
diff --git a/application/api/ApiMiddleware.php b/application/api/ApiMiddleware.php
index 5ffb8c6d..2d55bda6 100644
--- a/application/api/ApiMiddleware.php
+++ b/application/api/ApiMiddleware.php
@@ -129,9 +129,7 @@ class ApiMiddleware
129 $linkDb = new \Shaarli\Bookmark\LinkDB( 129 $linkDb = new \Shaarli\Bookmark\LinkDB(
130 $conf->get('resource.datastore'), 130 $conf->get('resource.datastore'),
131 true, 131 true,
132 $conf->get('privacy.hide_public_links'), 132 $conf->get('privacy.hide_public_links')
133 $conf->get('redirector.url'),
134 $conf->get('redirector.encode_url')
135 ); 133 );
136 $this->container['db'] = $linkDb; 134 $this->container['db'] = $linkDb;
137 } 135 }
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php
index 1824b5d0..1e3ac02e 100644
--- a/application/api/ApiUtils.php
+++ b/application/api/ApiUtils.php
@@ -59,7 +59,7 @@ class ApiUtils
59 { 59 {
60 $out['id'] = $link['id']; 60 $out['id'] = $link['id'];
61 // Not an internal link 61 // Not an internal link
62 if ($link['url'][0] != '?') { 62 if (! is_note($link['url'])) {
63 $out['url'] = $link['url']; 63 $out['url'] = $link['url'];
64 } else { 64 } else {
65 $out['url'] = $indexUrl . $link['url']; 65 $out['url'] = $indexUrl . $link['url'];
diff --git a/application/bookmark/LinkDB.php b/application/bookmark/LinkDB.php
index c13a1141..efde8468 100644
--- a/application/bookmark/LinkDB.php
+++ b/application/bookmark/LinkDB.php
@@ -29,10 +29,10 @@ use Shaarli\FileUtils;
29 * - private: Is this link private? 0=no, other value=yes 29 * - private: Is this link private? 0=no, other value=yes
30 * - tags: tags attached to this entry (separated by spaces) 30 * - tags: tags attached to this entry (separated by spaces)
31 * - title Title of the link 31 * - title Title of the link
32 * - url URL of the link. Used for displayable links (no redirector, relative, etc.). 32 * - url URL of the link. Used for displayable links.
33 * Can be absolute or relative. 33 * Can be absolute or relative in the database but the relative links
34 * Relative URLs are permalinks (e.g.'?m-ukcw') 34 * will be converted to absolute ones in templates.
35 * - real_url Absolute processed URL. 35 * - real_url Raw URL in stored in the DB (absolute or relative).
36 * - shorturl Permalink smallhash 36 * - shorturl Permalink smallhash
37 * 37 *
38 * Implements 3 interfaces: 38 * Implements 3 interfaces:
@@ -88,19 +88,6 @@ class LinkDB implements Iterator, Countable, ArrayAccess
88 // Hide public links 88 // Hide public links
89 private $hidePublicLinks; 89 private $hidePublicLinks;
90 90
91 // link redirector set in user settings.
92 private $redirector;
93
94 /**
95 * Set this to `true` to urlencode link behind redirector link, `false` to leave it untouched.
96 *
97 * Example:
98 * anonym.to needs clean URL while dereferer.org needs urlencoded URL.
99 *
100 * @var boolean $redirectorEncode parameter: true or false
101 */
102 private $redirectorEncode;
103
104 /** 91 /**
105 * Creates a new LinkDB 92 * Creates a new LinkDB
106 * 93 *
@@ -109,22 +96,16 @@ class LinkDB implements Iterator, Countable, ArrayAccess
109 * @param string $datastore datastore file path. 96 * @param string $datastore datastore file path.
110 * @param boolean $isLoggedIn is the user logged in? 97 * @param boolean $isLoggedIn is the user logged in?
111 * @param boolean $hidePublicLinks if true all links are private. 98 * @param boolean $hidePublicLinks if true all links are private.
112 * @param string $redirector link redirector set in user settings.
113 * @param boolean $redirectorEncode Enable urlencode on redirected urls (default: true).
114 */ 99 */
115 public function __construct( 100 public function __construct(
116 $datastore, 101 $datastore,
117 $isLoggedIn, 102 $isLoggedIn,
118 $hidePublicLinks, 103 $hidePublicLinks
119 $redirector = '',
120 $redirectorEncode = true
121 ) { 104 ) {
122 105
123 $this->datastore = $datastore; 106 $this->datastore = $datastore;
124 $this->loggedIn = $isLoggedIn; 107 $this->loggedIn = $isLoggedIn;
125 $this->hidePublicLinks = $hidePublicLinks; 108 $this->hidePublicLinks = $hidePublicLinks;
126 $this->redirector = $redirector;
127 $this->redirectorEncode = $redirectorEncode === true;
128 $this->check(); 109 $this->check();
129 $this->read(); 110 $this->read();
130 } 111 }
@@ -271,7 +252,8 @@ You use the community supported version of the original Shaarli project, by Seba
271 ), 252 ),
272 'private' => 0, 253 'private' => 0,
273 'created' => new DateTime(), 254 'created' => new DateTime(),
274 'tags' => 'opensource software' 255 'tags' => 'opensource software',
256 'sticky' => false,
275 ); 257 );
276 $link['shorturl'] = link_small_hash($link['created'], $link['id']); 258 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
277 $this->links[1] = $link; 259 $this->links[1] = $link;
@@ -284,6 +266,7 @@ You use the community supported version of the original Shaarli project, by Seba
284 'private' => 1, 266 'private' => 1,
285 'created' => new DateTime('1 minute ago'), 267 'created' => new DateTime('1 minute ago'),
286 'tags' => 'secretstuff', 268 'tags' => 'secretstuff',
269 'sticky' => false,
287 ); 270 );
288 $link['shorturl'] = link_small_hash($link['created'], $link['id']); 271 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
289 $this->links[0] = $link; 272 $this->links[0] = $link;
@@ -323,17 +306,9 @@ You use the community supported version of the original Shaarli project, by Seba
323 $link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']); 306 $link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']);
324 } 307 }
325 308
326 // Do not use the redirector for internal links (Shaarli note URL starting with a '?'). 309 $link['real_url'] = $link['url'];
327 if (!empty($this->redirector) && !startsWith($link['url'], '?')) { 310
328 $link['real_url'] = $this->redirector; 311 $link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false;
329 if ($this->redirectorEncode) {
330 $link['real_url'] .= urlencode(unescape($link['url']));
331 } else {
332 $link['real_url'] .= $link['url'];
333 }
334 } else {
335 $link['real_url'] = $link['url'];
336 }
337 312
338 // To be able to load links before running the update, and prepare the update 313 // To be able to load links before running the update, and prepare the update
339 if (!isset($link['created'])) { 314 if (!isset($link['created'])) {
diff --git a/application/bookmark/LinkUtils.php b/application/bookmark/LinkUtils.php
index de5b61cb..35a5b290 100644
--- a/application/bookmark/LinkUtils.php
+++ b/application/bookmark/LinkUtils.php
@@ -133,29 +133,15 @@ function count_private($links)
133 * In a string, converts URLs to clickable links. 133 * In a string, converts URLs to clickable links.
134 * 134 *
135 * @param string $text input string. 135 * @param string $text input string.
136 * @param string $redirector if a redirector is set, use it to gerenate links.
137 * @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not.
138 * 136 *
139 * @return string returns $text with all links converted to HTML links. 137 * @return string returns $text with all links converted to HTML links.
140 * 138 *
141 * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 139 * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
142 */ 140 */
143function text2clickable($text, $redirector = '', $urlEncode = true) 141function text2clickable($text)
144{ 142{
145 $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si'; 143 $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si';
146 144 return preg_replace($regex, '<a href="$1">$1</a>', $text);
147 if (empty($redirector)) {
148 return preg_replace($regex, '<a href="$1">$1</a>', $text);
149 }
150 // Redirector is set, urlencode the final URL.
151 return preg_replace_callback(
152 $regex,
153 function ($matches) use ($redirector, $urlEncode) {
154 $url = $urlEncode ? urlencode($matches[1]) : $matches[1];
155 return '<a href="' . $redirector . $url .'">'. $matches[1] .'</a>';
156 },
157 $text
158 );
159} 145}
160 146
161/** 147/**
@@ -197,15 +183,13 @@ function space2nbsp($text)
197 * Format Shaarli's description 183 * Format Shaarli's description
198 * 184 *
199 * @param string $description shaare's description. 185 * @param string $description shaare's description.
200 * @param string $redirector if a redirector is set, use it to gerenate links.
201 * @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not.
202 * @param string $indexUrl URL to Shaarli's index. 186 * @param string $indexUrl URL to Shaarli's index.
203 187
204 * @return string formatted description. 188 * @return string formatted description.
205 */ 189 */
206function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '') 190function format_description($description, $indexUrl = '')
207{ 191{
208 return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector, $urlEncode), $indexUrl))); 192 return nl2br(space2nbsp(hashtag_autolink(text2clickable($description), $indexUrl)));
209} 193}
210 194
211/** 195/**
@@ -220,3 +204,16 @@ function link_small_hash($date, $id)
220{ 204{
221 return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id); 205 return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id);
222} 206}
207
208/**
209 * Returns whether or not the link is an internal note.
210 * Its URL starts by `?` because it's actually a permalink.
211 *
212 * @param string $linkUrl
213 *
214 * @return bool true if internal note, false otherwise.
215 */
216function is_note($linkUrl)
217{
218 return isset($linkUrl[0]) && $linkUrl[0] === '?';
219}
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index e6c35073..30993928 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -221,7 +221,6 @@ class ConfigManager
221 'general.title', 221 'general.title',
222 'general.header_link', 222 'general.header_link',
223 'privacy.default_private_links', 223 'privacy.default_private_links',
224 'redirector.url',
225 ); 224 );
226 225
227 // Only logged in user can alter config. 226 // Only logged in user can alter config.
@@ -381,9 +380,6 @@ class ConfigManager
381 // default state of the 'remember me' checkbox of the login form 380 // default state of the 'remember me' checkbox of the login form
382 $this->setEmpty('privacy.remember_user_default', true); 381 $this->setEmpty('privacy.remember_user_default', true);
383 382
384 $this->setEmpty('redirector.url', '');
385 $this->setEmpty('redirector.encode_url', true);
386
387 $this->setEmpty('thumbnails.width', '125'); 383 $this->setEmpty('thumbnails.width', '125');
388 $this->setEmpty('thumbnails.height', '90'); 384 $this->setEmpty('thumbnails.height', '90');
389 385
diff --git a/application/feed/FeedBuilder.php b/application/feed/FeedBuilder.php
index b66f2f91..7c859474 100644
--- a/application/feed/FeedBuilder.php
+++ b/application/feed/FeedBuilder.php
@@ -147,8 +147,8 @@ class FeedBuilder
147 protected function buildItem($link, $pageaddr) 147 protected function buildItem($link, $pageaddr)
148 { 148 {
149 $link['guid'] = $pageaddr . '?' . $link['shorturl']; 149 $link['guid'] = $pageaddr . '?' . $link['shorturl'];
150 // Check for both signs of a note: starting with ? and 7 chars long. 150 // Prepend the root URL for notes
151 if ($link['url'][0] === '?' && strlen($link['url']) === 7) { 151 if (is_note($link['url'])) {
152 $link['url'] = $pageaddr . $link['url']; 152 $link['url'] = $pageaddr . $link['url'];
153 } 153 }
154 if ($this->usePermalinks === true) { 154 if ($this->usePermalinks === true) {
@@ -156,7 +156,7 @@ class FeedBuilder
156 } else { 156 } else {
157 $permalink = '<a href="' . $link['guid'] . '" title="' . t('Permalink') . '">' . t('Permalink') . '</a>'; 157 $permalink = '<a href="' . $link['guid'] . '" title="' . t('Permalink') . '">' . t('Permalink') . '</a>';
158 } 158 }
159 $link['description'] = format_description($link['description'], '', false, $pageaddr); 159 $link['description'] = format_description($link['description'], $pageaddr);
160 $link['description'] .= PHP_EOL . '<br>&#8212; ' . $permalink; 160 $link['description'] .= PHP_EOL . '<br>&#8212; ' . $permalink;
161 161
162 $pubDate = $link['created']; 162 $pubDate = $link['created'];
diff --git a/application/netscape/NetscapeBookmarkUtils.php b/application/netscape/NetscapeBookmarkUtils.php
index 2fb1a4a6..28665941 100644
--- a/application/netscape/NetscapeBookmarkUtils.php
+++ b/application/netscape/NetscapeBookmarkUtils.php
@@ -54,7 +54,7 @@ class NetscapeBookmarkUtils
54 $link['timestamp'] = $date->getTimestamp(); 54 $link['timestamp'] = $date->getTimestamp();
55 $link['taglist'] = str_replace(' ', ',', $link['tags']); 55 $link['taglist'] = str_replace(' ', ',', $link['tags']);
56 56
57 if (startsWith($link['url'], '?') && $prependNoteUrl) { 57 if (is_note($link['url']) && $prependNoteUrl) {
58 $link['url'] = $indexUrl . $link['url']; 58 $link['url'] = $indexUrl . $link['url'];
59 } 59 }
60 60
diff --git a/application/updater/Updater.php b/application/updater/Updater.php
index f12e3516..beb9ea9b 100644
--- a/application/updater/Updater.php
+++ b/application/updater/Updater.php
@@ -218,7 +218,6 @@ class Updater
218 try { 218 try {
219 $this->conf->set('general.title', escape($this->conf->get('general.title'))); 219 $this->conf->set('general.title', escape($this->conf->get('general.title')));
220 $this->conf->set('general.header_link', escape($this->conf->get('general.header_link'))); 220 $this->conf->set('general.header_link', escape($this->conf->get('general.header_link')));
221 $this->conf->set('redirector.url', escape($this->conf->get('redirector.url')));
222 $this->conf->write($this->isLoggedIn); 221 $this->conf->write($this->isLoggedIn);
223 } catch (Exception $e) { 222 } catch (Exception $e) {
224 error_log($e->getMessage()); 223 error_log($e->getMessage());
@@ -550,4 +549,14 @@ class Updater
550 549
551 return true; 550 return true;
552 } 551 }
552
553 /**
554 * Remove redirector settings.
555 */
556 public function updateMethodRemoveRedirector()
557 {
558 $this->conf->remove('redirector');
559 $this->conf->write(true);
560 return true;
561 }
553} 562}
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss
index 760d8d6a..691b2b37 100644
--- a/assets/default/scss/shaarli.scss
+++ b/assets/default/scss/shaarli.scss
@@ -544,7 +544,10 @@ body,
544 color: $dark-grey; 544 color: $dark-grey;
545 font-size: .9em; 545 font-size: .9em;
546 546
547
547 a { 548 a {
549 display: inline-block;
550 margin: 3px 0;
548 padding: 5px 8px; 551 padding: 5px 8px;
549 text-decoration: none; 552 text-decoration: none;
550 } 553 }
diff --git a/doc/md/Download-and-Installation.md b/doc/md/Download-and-Installation.md
index 14649e06..1c4ad947 100644
--- a/doc/md/Download-and-Installation.md
+++ b/doc/md/Download-and-Installation.md
@@ -24,11 +24,11 @@ Using one of the following methods:
24 24
25In most cases, you should download the latest Shaarli release from the [releases](https://github.com/shaarli/Shaarli/releases) page. Download our **shaarli-full** archive to include dependencies. 25In most cases, you should download the latest Shaarli release from the [releases](https://github.com/shaarli/Shaarli/releases) page. Download our **shaarli-full** archive to include dependencies.
26 26
27The current latest released version is `v0.9.7` 27The current latest released version is `v0.10.3`
28 28
29```bash 29```bash
30$ wget https://github.com/shaarli/Shaarli/releases/download/v0.9.7/shaarli-v0.9.7-full.zip 30$ wget https://github.com/shaarli/Shaarli/releases/download/v0.10.3/shaarli-v0.10.3-full.zip
31$ unzip shaarli-v0.9.7-full.zip 31$ unzip shaarli-v0.10.3-full.zip
32$ mv Shaarli /path/to/shaarli/ 32$ mv Shaarli /path/to/shaarli/
33``` 33```
34 34
diff --git a/doc/md/Server-configuration.md b/doc/md/Server-configuration.md
index 78083a46..4753df1d 100644
--- a/doc/md/Server-configuration.md
+++ b/doc/md/Server-configuration.md
@@ -17,7 +17,7 @@ Version | Status | Shaarli compatibility
17:---:|:---:|:---: 17:---:|:---:|:---:
187.2 | Supported | Yes 187.2 | Supported | Yes
197.1 | Supported | Yes 197.1 | Supported | Yes
207.0 | Supported | Yes 207.0 | EOL: 2018-12-03 | Yes (up to Shaarli 0.10.x)
215.6 | EOL: 2018-12-31 | Yes (up to Shaarli 0.10.x) 215.6 | EOL: 2018-12-31 | Yes (up to Shaarli 0.10.x)
225.5 | EOL: 2016-07-10 | Yes 225.5 | EOL: 2016-07-10 | Yes
235.4 | EOL: 2015-09-14 | Yes (up to Shaarli 0.8.x) 235.4 | EOL: 2015-09-14 | Yes (up to Shaarli 0.8.x)
diff --git a/doc/md/Shaarli-configuration.md b/doc/md/Shaarli-configuration.md
index 920c7e27..a931ab1e 100644
--- a/doc/md/Shaarli-configuration.md
+++ b/doc/md/Shaarli-configuration.md
@@ -4,7 +4,7 @@
4 4
5Once your Shaarli instance is installed, the file `data/config.json.php` is generated: 5Once your Shaarli instance is installed, the file `data/config.json.php` is generated:
6* it contains all settings in JSON format, and can be edited to customize values 6* it contains all settings in JSON format, and can be edited to customize values
7* it defines which [plugins](Plugin-System) are enabled[](.html) 7* it defines which [plugins](Plugin-System) are enabled
8* its values override those defined in `index.php` 8* its values override those defined in `index.php`
9* it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration 9* it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration
10 10
@@ -32,13 +32,13 @@ On a Linux distribution:
32- to give it access to Shaarli, either: 32- to give it access to Shaarli, either:
33 - unzip Shaarli in the default web server location (usually `/var/www/`) and set the web server user as the owner 33 - unzip Shaarli in the default web server location (usually `/var/www/`) and set the web server user as the owner
34 - put users in the same group as the web server, and set the appropriate access rights 34 - put users in the same group as the web server, and set the appropriate access rights
35- if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly[](.html) 35- if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly
36 36
37## Configuration 37## Configuration
38 38
39In `data/config.json.php`. 39In `data/config.json.php`.
40 40
41See also [Plugin System](Plugin-System.html). 41See also [Plugin System](Plugin-System).
42 42
43### Credentials 43### Credentials
44 44
@@ -120,11 +120,6 @@ Must be an associative array: `translation domain => translation path`.
120- **enable_thumbnails**: Enable or disable thumbnail display. 120- **enable_thumbnails**: Enable or disable thumbnail display.
121- **enable_localcache**: Enable or disable local cache. 121- **enable_localcache**: Enable or disable local cache.
122 122
123### Redirector
124
125- **url**: Redirector URL, such as `anonym.to`.
126- **encode_url**: Enable this if the redirector needs encoded URL to work properly.
127
128## Configuration file example 123## Configuration file example
129 124
130```json 125```json
@@ -185,8 +180,6 @@ Must be an associative array: `translation domain => translation path`.
185 "hide_public_links": false, 180 "hide_public_links": false,
186 "hide_timestamps": false, 181 "hide_timestamps": false,
187 "open_shaarli": false, 182 "open_shaarli": false,
188 "redirector": "http://anonym.to/?",
189 "redirector_encode_url": false
190 }, 183 },
191 "general": { 184 "general": {
192 "header_link": "?", 185 "header_link": "?",
@@ -218,10 +211,6 @@ Must be an associative array: `translation domain => translation path`.
218 "enable_thumbnails": true, 211 "enable_thumbnails": true,
219 "enable_localcache": true 212 "enable_localcache": true
220 }, 213 },
221 "redirector": {
222 "url": "http://anonym.to/?",
223 "encode_url": false
224 },
225 "plugins": { 214 "plugins": {
226 "WALLABAG_URL": "http://demo.wallabag.org", 215 "WALLABAG_URL": "http://demo.wallabag.org",
227 "WALLABAG_VERSION": "1" 216 "WALLABAG_VERSION": "1"
diff --git a/index.php b/index.php
index 50a871e0..a96c9cfd 100644
--- a/index.php
+++ b/index.php
@@ -312,9 +312,7 @@ function showDailyRSS($conf, $loginManager)
312 $LINKSDB = new LinkDB( 312 $LINKSDB = new LinkDB(
313 $conf->get('resource.datastore'), 313 $conf->get('resource.datastore'),
314 $loginManager->isLoggedIn(), 314 $loginManager->isLoggedIn(),
315 $conf->get('privacy.hide_public_links'), 315 $conf->get('privacy.hide_public_links')
316 $conf->get('redirector.url'),
317 $conf->get('redirector.encode_url')
318 ); 316 );
319 317
320 /* Some Shaarlies may have very few links, so we need to look 318 /* Some Shaarlies may have very few links, so we need to look
@@ -356,13 +354,9 @@ function showDailyRSS($conf, $loginManager)
356 354
357 // We pre-format some fields for proper output. 355 // We pre-format some fields for proper output.
358 foreach ($links as &$link) { 356 foreach ($links as &$link) {
359 $link['formatedDescription'] = format_description( 357 $link['formatedDescription'] = format_description($link['description']);
360 $link['description'],
361 $conf->get('redirector.url'),
362 $conf->get('redirector.encode_url')
363 );
364 $link['timestamp'] = $link['created']->getTimestamp(); 358 $link['timestamp'] = $link['created']->getTimestamp();
365 if (startsWith($link['url'], '?')) { 359 if (is_note($link['url'])) {
366 $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute 360 $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute
367 } 361 }
368 } 362 }
@@ -433,11 +427,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
433 $taglist = explode(' ', $link['tags']); 427 $taglist = explode(' ', $link['tags']);
434 uasort($taglist, 'strcasecmp'); 428 uasort($taglist, 'strcasecmp');
435 $linksToDisplay[$key]['taglist']=$taglist; 429 $linksToDisplay[$key]['taglist']=$taglist;
436 $linksToDisplay[$key]['formatedDescription'] = format_description( 430 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description']);
437 $link['description'],
438 $conf->get('redirector.url'),
439 $conf->get('redirector.encode_url')
440 );
441 $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp(); 431 $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
442 } 432 }
443 433
@@ -1175,11 +1165,15 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1175 $link['title'] = $link['url']; 1165 $link['title'] = $link['url'];
1176 } 1166 }
1177 1167
1178 if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE) { 1168 if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
1169 && ! is_note($link['url'])
1170 ) {
1179 $thumbnailer = new Thumbnailer($conf); 1171 $thumbnailer = new Thumbnailer($conf);
1180 $link['thumbnail'] = $thumbnailer->get($url); 1172 $link['thumbnail'] = $thumbnailer->get($url);
1181 } 1173 }
1182 1174
1175 $link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false;
1176
1183 $pluginManager->executeHooks('save_link', $link); 1177 $pluginManager->executeHooks('save_link', $link);
1184 1178
1185 $LINKSDB[$id] = $link; 1179 $LINKSDB[$id] = $link;
@@ -1557,7 +1551,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1557 $ids = []; 1551 $ids = [];
1558 foreach ($LINKSDB as $link) { 1552 foreach ($LINKSDB as $link) {
1559 // A note or not HTTP(S) 1553 // A note or not HTTP(S)
1560 if ($link['url'][0] === '?' || ! startsWith(strtolower($link['url']), 'http')) { 1554 if (is_note($link['url']) || ! startsWith(strtolower($link['url']), 'http')) {
1561 continue; 1555 continue;
1562 } 1556 }
1563 $ids[] = $link['id']; 1557 $ids[] = $link['id'];
@@ -1661,11 +1655,7 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
1661 $linkDisp = array(); 1655 $linkDisp = array();
1662 while ($i<$end && $i<count($keys)) { 1656 while ($i<$end && $i<count($keys)) {
1663 $link = $linksToDisplay[$keys[$i]]; 1657 $link = $linksToDisplay[$keys[$i]];
1664 $link['description'] = format_description( 1658 $link['description'] = format_description($link['description']);
1665 $link['description'],
1666 $conf->get('redirector.url'),
1667 $conf->get('redirector.encode_url')
1668 );
1669 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; 1659 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
1670 $link['class'] = $link['private'] == 0 ? $classLi : 'private'; 1660 $link['class'] = $link['private'] == 0 ? $classLi : 'private';
1671 $link['timestamp'] = $link['created']->getTimestamp(); 1661 $link['timestamp'] = $link['created']->getTimestamp();
@@ -1726,7 +1716,6 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
1726 'search_term' => $searchterm, 1716 'search_term' => $searchterm,
1727 'search_tags' => $searchtags, 1717 'search_tags' => $searchtags,
1728 'visibility' => ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '', 1718 'visibility' => ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '',
1729 'redirector' => $conf->get('redirector.url'), // Optional redirector URL.
1730 'links' => $linkDisp, 1719 'links' => $linkDisp,
1731 ); 1720 );
1732 1721
@@ -1876,9 +1865,7 @@ try {
1876$linkDb = new LinkDB( 1865$linkDb = new LinkDB(
1877 $conf->get('resource.datastore'), 1866 $conf->get('resource.datastore'),
1878 $loginManager->isLoggedIn(), 1867 $loginManager->isLoggedIn(),
1879 $conf->get('privacy.hide_public_links'), 1868 $conf->get('privacy.hide_public_links')
1880 $conf->get('redirector.url'),
1881 $conf->get('redirector.encode_url')
1882); 1869);
1883 1870
1884$container = new \Slim\Container(); 1871$container = new \Slim\Container();
diff --git a/tests/bookmark/LinkDBTest.php b/tests/bookmark/LinkDBTest.php
index ff5c0b97..2990a6b5 100644
--- a/tests/bookmark/LinkDBTest.php
+++ b/tests/bookmark/LinkDBTest.php
@@ -362,36 +362,6 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase
362 } 362 }
363 363
364 /** 364 /**
365 * Test real_url without redirector.
366 */
367 public function testLinkRealUrlWithoutRedirector()
368 {
369 $db = new LinkDB(self::$testDatastore, false, false);
370 foreach ($db as $link) {
371 $this->assertEquals($link['url'], $link['real_url']);
372 }
373 }
374
375 /**
376 * Test real_url with redirector.
377 */
378 public function testLinkRealUrlWithRedirector()
379 {
380 $redirector = 'http://redirector.to?';
381 $db = new LinkDB(self::$testDatastore, false, false, $redirector);
382 foreach ($db as $link) {
383 $this->assertStringStartsWith($redirector, $link['real_url']);
384 $this->assertNotFalse(strpos($link['real_url'], urlencode('://')));
385 }
386
387 $db = new LinkDB(self::$testDatastore, false, false, $redirector, false);
388 foreach ($db as $link) {
389 $this->assertStringStartsWith($redirector, $link['real_url']);
390 $this->assertFalse(strpos($link['real_url'], urlencode('://')));
391 }
392 }
393
394 /**
395 * Test filter with string. 365 * Test filter with string.
396 */ 366 */
397 public function testFilterString() 367 public function testFilterString()
@@ -516,7 +486,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase
516 public function testRenameTagCaseSensitive() 486 public function testRenameTagCaseSensitive()
517 { 487 {
518 self::$refDB->write(self::$testDatastore); 488 self::$refDB->write(self::$testDatastore);
519 $linkDB = new LinkDB(self::$testDatastore, true, false, ''); 489 $linkDB = new LinkDB(self::$testDatastore, true, false);
520 490
521 $res = $linkDB->renameTag('sTuff', 'Taz'); 491 $res = $linkDB->renameTag('sTuff', 'Taz');
522 $this->assertEquals(1, count($res)); 492 $this->assertEquals(1, count($res));
diff --git a/tests/bookmark/LinkUtilsTest.php b/tests/bookmark/LinkUtilsTest.php
index 1b8688e6..25fb3043 100644
--- a/tests/bookmark/LinkUtilsTest.php
+++ b/tests/bookmark/LinkUtilsTest.php
@@ -216,56 +216,27 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
216 } 216 }
217 217
218 /** 218 /**
219 * Test text2clickable without a redirector being set. 219 * Test text2clickable.
220 */ 220 */
221 public function testText2clickableWithoutRedirector() 221 public function testText2clickable()
222 { 222 {
223 $text = 'stuff http://hello.there/is=someone#here otherstuff'; 223 $text = 'stuff http://hello.there/is=someone#here otherstuff';
224 $expectedText = 'stuff <a href="http://hello.there/is=someone#here">' 224 $expectedText = 'stuff <a href="http://hello.there/is=someone#here">'
225 . 'http://hello.there/is=someone#here</a> otherstuff'; 225 . 'http://hello.there/is=someone#here</a> otherstuff';
226 $processedText = text2clickable($text, ''); 226 $processedText = text2clickable($text);
227 $this->assertEquals($expectedText, $processedText); 227 $this->assertEquals($expectedText, $processedText);
228 228
229 $text = 'stuff http://hello.there/is=someone#here(please) otherstuff'; 229 $text = 'stuff http://hello.there/is=someone#here(please) otherstuff';
230 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)">' 230 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)">'
231 . 'http://hello.there/is=someone#here(please)</a> otherstuff'; 231 . 'http://hello.there/is=someone#here(please)</a> otherstuff';
232 $processedText = text2clickable($text, ''); 232 $processedText = text2clickable($text);
233 $this->assertEquals($expectedText, $processedText); 233 $this->assertEquals($expectedText, $processedText);
234 234
235 $text = 'stuff http://hello.there/is=someone#here(please)&no otherstuff'; 235 $text = 'stuff http://hello.there/is=someone#here(please)&no otherstuff';
236 $text = 'stuff http://hello.there/is=someone#here(please)&no otherstuff';
236 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)&no">' 237 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)&no">'
237 . 'http://hello.there/is=someone#here(please)&no</a> otherstuff'; 238 . 'http://hello.there/is=someone#here(please)&no</a> otherstuff';
238 $processedText = text2clickable($text, ''); 239 $processedText = text2clickable($text);
239 $this->assertEquals($expectedText, $processedText);
240 }
241
242 /**
243 * Test text2clickable with a redirector set.
244 */
245 public function testText2clickableWithRedirector()
246 {
247 $text = 'stuff http://hello.there/is=someone#here otherstuff';
248 $redirector = 'http://redirector.to';
249 $expectedText = 'stuff <a href="' .
250 $redirector .
251 urlencode('http://hello.there/is=someone#here') .
252 '">http://hello.there/is=someone#here</a> otherstuff';
253 $processedText = text2clickable($text, $redirector);
254 $this->assertEquals($expectedText, $processedText);
255 }
256
257 /**
258 * Test text2clickable a redirector set and without URL encode.
259 */
260 public function testText2clickableWithRedirectorDontEncode()
261 {
262 $text = 'stuff http://hello.there/?is=someone&or=something#here otherstuff';
263 $redirector = 'http://redirector.to';
264 $expectedText = 'stuff <a href="' .
265 $redirector .
266 'http://hello.there/?is=someone&or=something#here' .
267 '">http://hello.there/?is=someone&or=something#here</a> otherstuff';
268 $processedText = text2clickable($text, $redirector, false);
269 $this->assertEquals($expectedText, $processedText); 240 $this->assertEquals($expectedText, $processedText);
270 } 241 }
271 242
@@ -318,6 +289,26 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
318 } 289 }
319 290
320 /** 291 /**
292 * Test is_note with note URLs.
293 */
294 public function testIsNote()
295 {
296 $this->assertTrue(is_note('?'));
297 $this->assertTrue(is_note('?abcDEf'));
298 $this->assertTrue(is_note('?_abcDEf#123'));
299 }
300
301 /**
302 * Test is_note with non note URLs.
303 */
304 public function testIsNotNote()
305 {
306 $this->assertFalse(is_note(''));
307 $this->assertFalse(is_note('nope'));
308 $this->assertFalse(is_note('https://github.com/shaarli/Shaarli/?hi'));
309 }
310
311 /**
321 * Util function to build an hashtag link. 312 * Util function to build an hashtag link.
322 * 313 *
323 * @param string $hashtag Hashtag name. 314 * @param string $hashtag Hashtag name.
diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php
index 5e7c02b0..9ddbc558 100644
--- a/tests/plugins/PluginMarkdownTest.php
+++ b/tests/plugins/PluginMarkdownTest.php
@@ -107,7 +107,7 @@ class PluginMarkdownTest extends \PHPUnit\Framework\TestCase
107 public function testReverseText2clickable() 107 public function testReverseText2clickable()
108 { 108 {
109 $text = 'stuff http://hello.there/is=someone#here otherstuff'; 109 $text = 'stuff http://hello.there/is=someone#here otherstuff';
110 $clickableText = text2clickable($text, ''); 110 $clickableText = text2clickable($text);
111 $reversedText = reverse_text2clickable($clickableText); 111 $reversedText = reverse_text2clickable($clickableText);
112 $this->assertEquals($text, $reversedText); 112 $this->assertEquals($text, $reversedText);
113 } 113 }
diff --git a/tests/updater/UpdaterTest.php b/tests/updater/UpdaterTest.php
index d7df5963..93bc86c1 100644
--- a/tests/updater/UpdaterTest.php
+++ b/tests/updater/UpdaterTest.php
@@ -287,17 +287,14 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
287 $this->conf = new ConfigManager($sandbox); 287 $this->conf = new ConfigManager($sandbox);
288 $title = '<script>alert("title");</script>'; 288 $title = '<script>alert("title");</script>';
289 $headerLink = '<script>alert("header_link");</script>'; 289 $headerLink = '<script>alert("header_link");</script>';
290 $redirectorUrl = '<script>alert("redirector");</script>';
291 $this->conf->set('general.title', $title); 290 $this->conf->set('general.title', $title);
292 $this->conf->set('general.header_link', $headerLink); 291 $this->conf->set('general.header_link', $headerLink);
293 $this->conf->set('redirector.url', $redirectorUrl);
294 $updater = new Updater(array(), array(), $this->conf, true); 292 $updater = new Updater(array(), array(), $this->conf, true);
295 $done = $updater->updateMethodEscapeUnescapedConfig(); 293 $done = $updater->updateMethodEscapeUnescapedConfig();
296 $this->assertTrue($done); 294 $this->assertTrue($done);
297 $this->conf->reload(); 295 $this->conf->reload();
298 $this->assertEquals(escape($title), $this->conf->get('general.title')); 296 $this->assertEquals(escape($title), $this->conf->get('general.title'));
299 $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link')); 297 $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link'));
300 $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url'));
301 unlink($sandbox . '.json.php'); 298 unlink($sandbox . '.json.php');
302 } 299 }
303 300
@@ -707,7 +704,6 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
707 } 704 }
708 705
709 /** 706 /**
710<<<<<<< HEAD
711 * Test updateMethodWebThumbnailer with thumbnails enabled. 707 * Test updateMethodWebThumbnailer with thumbnails enabled.
712 */ 708 */
713 public function testUpdateMethodWebThumbnailerEnabled() 709 public function testUpdateMethodWebThumbnailerEnabled()
@@ -812,4 +808,19 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
812 $linkDB = new LinkDB(self::$testDatastore, true, false); 808 $linkDB = new LinkDB(self::$testDatastore, true, false);
813 $this->assertTrue($linkDB[1]['sticky']); 809 $this->assertTrue($linkDB[1]['sticky']);
814 } 810 }
811
812 /**
813 * Test updateMethodRemoveRedirector().
814 */
815 public function testUpdateRemoveRedirector()
816 {
817 $sandboxConf = 'sandbox/config';
818 copy(self::$configFile . '.json.php', $sandboxConf . '.json.php');
819 $this->conf = new ConfigManager($sandboxConf);
820 $updater = new Updater([], null, $this->conf, true);
821 $this->assertTrue($updater->updateMethodRemoveRedirector());
822 $this->assertFalse($this->conf->exists('redirector'));
823 $this->conf = new ConfigManager($sandboxConf);
824 $this->assertFalse($this->conf->exists('redirector'));
825 }
815} 826}