aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile68
-rw-r--r--README.md2
-rw-r--r--application/ApplicationUtils.php17
-rw-r--r--application/Base64Url.php7
-rw-r--r--application/FeedBuilder.php4
-rw-r--r--application/HttpUtils.php29
-rw-r--r--application/Languages.php15
-rw-r--r--application/LinkDB.php32
-rw-r--r--application/LinkFilter.php25
-rw-r--r--application/LinkUtils.php5
-rw-r--r--application/NetscapeBookmarkUtils.php16
-rw-r--r--application/PageBuilder.php5
-rw-r--r--application/PluginManager.php3
-rw-r--r--application/Router.php6
-rw-r--r--application/Thumbnailer.php5
-rw-r--r--application/Updater.php22
-rw-r--r--application/Url.php19
-rw-r--r--application/Utils.php16
-rw-r--r--application/api/ApiMiddleware.php5
-rw-r--r--application/api/controllers/ApiController.php2
-rw-r--r--application/api/controllers/History.php3
-rw-r--r--application/api/controllers/Info.php4
-rw-r--r--application/api/exceptions/ApiException.php8
-rw-r--r--application/api/exceptions/ApiLinkNotFoundException.php1
-rw-r--r--application/api/exceptions/ApiTagNotFoundException.php1
-rw-r--r--application/config/ConfigPhp.php12
-rw-r--r--application/config/ConfigPlugin.php3
-rw-r--r--application/security/LoginManager.php1
-rw-r--r--assets/default/js/base.js29
-rw-r--r--assets/default/scss/shaarli.scss16
-rw-r--r--composer.json8
-rw-r--r--composer.lock434
-rw-r--r--doc/custom_theme/main.html23
-rw-r--r--doc/md/Community-&-Related-software.md2
-rw-r--r--doc/md/Server-configuration.md9
-rw-r--r--doc/md/Sharing-content.md17
-rw-r--r--doc/md/images/icon.pngbin0 -> 1266 bytes
-rw-r--r--doc/md/index.md120
-rw-r--r--inc/languages/fr/LC_MESSAGES/shaarli.po486
-rw-r--r--index.php312
-rw-r--r--mkdocs.yml4
-rw-r--r--phpcs.xml17
-rw-r--r--plugins/archiveorg/archiveorg.php2
-rw-r--r--plugins/demo_plugin/demo_plugin.php12
-rw-r--r--plugins/isso/comment.pngbin0 -> 277 bytes
-rw-r--r--plugins/isso/isso.php31
-rw-r--r--plugins/isso/isso_button.html5
-rw-r--r--plugins/markdown/markdown.php26
-rw-r--r--plugins/pubsubhubbub/pubsubhubbub.php5
-rw-r--r--plugins/qrcode/qrcode.php3
-rw-r--r--plugins/wallabag/wallabag.php1
-rw-r--r--tests/ApplicationUtilsTest.php6
-rw-r--r--tests/CacheTest.php2
-rw-r--r--tests/FeedBuilderTest.php18
-rw-r--r--tests/HttpUtils/GetIpAdressFromProxyTest.php3
-rw-r--r--tests/LinkDBTest.php16
-rw-r--r--tests/LinkFilterTest.php18
-rw-r--r--tests/LinkUtilsTest.php24
-rw-r--r--tests/NetscapeBookmarkUtils/BookmarkExportTest.php4
-rw-r--r--tests/RouterTest.php1
-rw-r--r--tests/ThumbnailerTest.php8
-rw-r--r--tests/Updater/DummyUpdater.php8
-rw-r--r--tests/Updater/UpdaterTest.php81
-rw-r--r--tests/Url/CleanupUrlTest.php1
-rw-r--r--tests/Url/GetUrlSchemeTest.php1
-rw-r--r--tests/Url/UnparseUrlTest.php1
-rw-r--r--tests/Url/UrlTest.php6
-rw-r--r--tests/UtilsTest.php9
-rw-r--r--tests/api/ApiUtilsTest.php3
-rw-r--r--tests/api/controllers/history/HistoryTest.php1
-rw-r--r--tests/api/controllers/info/InfoTest.php4
-rw-r--r--tests/api/controllers/links/GetLinksTest.php21
-rw-r--r--tests/api/controllers/links/PostLinkTest.php9
-rw-r--r--tests/api/controllers/links/PutLinkTest.php9
-rw-r--r--tests/api/controllers/tags/PutTagTest.php1
-rw-r--r--tests/languages/de/UtilsDeTest.php2
-rw-r--r--tests/languages/fr/LanguagesFrTest.php1
-rw-r--r--tests/plugins/PluginIssoTest.php16
-rw-r--r--tests/plugins/PluginMarkdownTest.php16
-rw-r--r--tests/plugins/PluginQrcodeTest.php3
-rw-r--r--tests/plugins/resources/hashtags.md10
-rw-r--r--tests/plugins/resources/hashtags.raw10
-rw-r--r--tests/plugins/resources/markdown.html8
-rw-r--r--tests/plugins/resources/markdown.md2
-rw-r--r--tests/security/SessionManagerTest.php1
-rw-r--r--tests/utils/ReferenceLinkDB.php49
-rw-r--r--tests/utils/config/configPhp.php2
-rw-r--r--tpl/default/addlink.html3
-rw-r--r--tpl/default/daily.html2
-rw-r--r--tpl/default/includes.html20
-rw-r--r--tpl/default/linklist.html22
-rw-r--r--tpl/default/linklist.paging.html3
-rw-r--r--tpl/default/page.header.html2
-rw-r--r--tpl/default/picwall.html2
-rw-r--r--tpl/vintage/daily.html2
-rw-r--r--tpl/vintage/includes.html20
-rw-r--r--tpl/vintage/linklist.html2
-rw-r--r--tpl/vintage/picwall.html2
98 files changed, 1167 insertions, 1186 deletions
diff --git a/Makefile b/Makefile
index 56cf09b2..b758d1fd 100644
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,6 @@
2# Makefile for PHP code analysis & testing, documentation and release generation 2# Makefile for PHP code analysis & testing, documentation and release generation
3 3
4BIN = vendor/bin 4BIN = vendor/bin
5PHP_SOURCE = index.php application tests plugins
6PHP_COMMA_SOURCE = index.php,application,tests,plugins
7 5
8all: static_analysis_summary check_permissions test 6all: static_analysis_summary check_permissions test
9 7
@@ -18,84 +16,32 @@ docker_%:
18 cd ~/shaarli && make $* 16 cd ~/shaarli && make $*
19 17
20## 18##
21# Concise status of the project
22# These targets are non-blocking: || exit 0
23##
24
25static_analysis_summary: code_sniffer_source copy_paste mess_detector_summary
26 @echo
27
28##
29# PHP_CodeSniffer 19# PHP_CodeSniffer
30# Detects PHP syntax errors 20# Detects PHP syntax errors
31# Documentation (usage, output formatting): 21# Documentation (usage, output formatting):
32# - http://pear.php.net/manual/en/package.php.php-codesniffer.usage.php 22# - http://pear.php.net/manual/en/package.php.php-codesniffer.usage.php
33# - http://pear.php.net/manual/en/package.php.php-codesniffer.reporting.php 23# - http://pear.php.net/manual/en/package.php.php-codesniffer.reporting.php
34## 24##
25PHPCS := $(BIN)/phpcs
35 26
36code_sniffer: code_sniffer_full 27code_sniffer:
28 @$(PHPCS)
37 29
38### - errors filtered by coding standard: PEAR, PSR1, PSR2, Zend... 30### - errors filtered by coding standard: PEAR, PSR1, PSR2, Zend...
39PHPCS_%: 31PHPCS_%:
40 @$(BIN)/phpcs $(PHP_SOURCE) --report-full --report-width=200 --standard=$* 32 @$(PHPCS) --report-full --report-width=200 --standard=$*
41 33
42### - errors by Git author 34### - errors by Git author
43code_sniffer_blame: 35code_sniffer_blame:
44 @$(BIN)/phpcs $(PHP_SOURCE) --report-gitblame 36 @$(PHPCS) --report-gitblame
45 37
46### - all errors/warnings 38### - all errors/warnings
47code_sniffer_full: 39code_sniffer_full:
48 @$(BIN)/phpcs $(PHP_SOURCE) --report-full --report-width=200 40 @$(PHPCS) --report-full --report-width=200
49 41
50### - errors grouped by kind 42### - errors grouped by kind
51code_sniffer_source: 43code_sniffer_source:
52 @$(BIN)/phpcs $(PHP_SOURCE) --report-source || exit 0 44 @$(PHPCS) --report-source || exit 0
53
54##
55# PHP Copy/Paste Detector
56# Detects code redundancy
57# Documentation: https://github.com/sebastianbergmann/phpcpd
58##
59
60copy_paste:
61 @echo "-----------------------"
62 @echo "PHP COPY/PASTE DETECTOR"
63 @echo "-----------------------"
64 @$(BIN)/phpcpd $(PHP_SOURCE) || exit 0
65 @echo
66
67##
68# PHP Mess Detector
69# Detects PHP syntax errors, sorted by category
70# Rules documentation: http://phpmd.org/rules/index.html
71##
72MESS_DETECTOR_RULES = cleancode,codesize,controversial,design,naming,unusedcode
73
74mess_title:
75 @echo "-----------------"
76 @echo "PHP MESS DETECTOR"
77 @echo "-----------------"
78
79### - all warnings
80mess_detector: mess_title
81 @$(BIN)/phpmd $(PHP_COMMA_SOURCE) text $(MESS_DETECTOR_RULES) | sed 's_.*\/__'
82
83### - all warnings + HTML output contains links to PHPMD's documentation
84mess_detector_html:
85 @$(BIN)/phpmd $(PHP_COMMA_SOURCE) html $(MESS_DETECTOR_RULES) \
86 --reportfile phpmd.html || exit 0
87
88### - warnings grouped by message, sorted by descending frequency order
89mess_detector_grouped: mess_title
90 @$(BIN)/phpmd $(PHP_SOURCE) text $(MESS_DETECTOR_RULES) \
91 | cut -f 2 | sort | uniq -c | sort -nr
92
93### - summary: number of warnings by rule set
94mess_detector_summary: mess_title
95 @for rule in $$(echo $(MESS_DETECTOR_RULES) | tr ',' ' '); do \
96 warnings=$$($(BIN)/phpmd $(PHP_COMMA_SOURCE) text $$rule | wc -l); \
97 printf "$$warnings\t$$rule\n"; \
98 done;
99 45
100## 46##
101# Checks source file & script permissions 47# Checks source file & script permissions
diff --git a/README.md b/README.md
index 2ff54600..0e23e33d 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ _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.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.10.1) 12[![](https://img.shields.io/badge/latest-v0.10.2-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.10.2)
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.10.x-blue.svg)](https://github.com/shaarli/Shaarli)
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
index 911873a0..a3b2dcb1 100644
--- a/application/ApplicationUtils.php
+++ b/application/ApplicationUtils.php
@@ -24,7 +24,7 @@ class ApplicationUtils
24 * 24 *
25 * @return mixed the version code from the repository if available, else 'false' 25 * @return mixed the version code from the repository if available, else 'false'
26 */ 26 */
27 public static function getLatestGitVersionCode($url, $timeout=2) 27 public static function getLatestGitVersionCode($url, $timeout = 2)
28 { 28 {
29 list($headers, $data) = get_http_response($url, $timeout); 29 list($headers, $data) = get_http_response($url, $timeout);
30 30
@@ -86,13 +86,14 @@ class ApplicationUtils
86 * 86 *
87 * @return mixed the new version code if available and greater, else 'false' 87 * @return mixed the new version code if available and greater, else 'false'
88 */ 88 */
89 public static function checkUpdate($currentVersion, 89 public static function checkUpdate(
90 $updateFile, 90 $currentVersion,
91 $checkInterval, 91 $updateFile,
92 $enableCheck, 92 $checkInterval,
93 $isLoggedIn, 93 $enableCheck,
94 $branch='stable') 94 $isLoggedIn,
95 { 95 $branch = 'stable'
96 ) {
96 // Do not check versions for visitors 97 // Do not check versions for visitors
97 // Do not check if the user doesn't want to 98 // Do not check if the user doesn't want to
98 // Do not check with dev version 99 // Do not check with dev version
diff --git a/application/Base64Url.php b/application/Base64Url.php
index 61590e43..54d0fcd5 100644
--- a/application/Base64Url.php
+++ b/application/Base64Url.php
@@ -2,7 +2,6 @@
2 2
3namespace Shaarli; 3namespace Shaarli;
4 4
5
6/** 5/**
7 * URL-safe Base64 operations 6 * URL-safe Base64 operations
8 * 7 *
@@ -17,7 +16,8 @@ class Base64Url
17 * 16 *
18 * @return string Base64Url-encoded data 17 * @return string Base64Url-encoded data
19 */ 18 */
20 public static function encode($data) { 19 public static function encode($data)
20 {
21 return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 21 return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
22 } 22 }
23 23
@@ -28,7 +28,8 @@ class Base64Url
28 * 28 *
29 * @return string Decoded data 29 * @return string Decoded data
30 */ 30 */
31 public static function decode($data) { 31 public static function decode($data)
32 {
32 return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); 33 return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
33 } 34 }
34} 35}
diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php
index ebae18b4..73fafcbe 100644
--- a/application/FeedBuilder.php
+++ b/application/FeedBuilder.php
@@ -163,7 +163,8 @@ class FeedBuilder
163 $upDate = $link['updated']; 163 $upDate = $link['updated'];
164 $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM); 164 $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM);
165 } else { 165 } else {
166 $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);; 166 $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);
167 ;
167 } 168 }
168 169
169 // Save the more recent item. 170 // Save the more recent item.
@@ -261,7 +262,6 @@ class FeedBuilder
261 } 262 }
262 if ($this->feedType == self::$FEED_RSS) { 263 if ($this->feedType == self::$FEED_RSS) {
263 return $date->format(DateTime::RSS); 264 return $date->format(DateTime::RSS);
264
265 } 265 }
266 return $date->format(DateTime::ATOM); 266 return $date->format(DateTime::ATOM);
267 } 267 }
diff --git a/application/HttpUtils.php b/application/HttpUtils.php
index e9282506..9c438160 100644
--- a/application/HttpUtils.php
+++ b/application/HttpUtils.php
@@ -7,7 +7,8 @@
7 * @param int $timeout network timeout (in seconds) 7 * @param int $timeout network timeout (in seconds)
8 * @param int $maxBytes maximum downloaded bytes (default: 4 MiB) 8 * @param int $maxBytes maximum downloaded bytes (default: 4 MiB)
9 * @param callable|string $curlWriteFunction Optional callback called during the download (cURL CURLOPT_WRITEFUNCTION). 9 * @param callable|string $curlWriteFunction Optional callback called during the download (cURL CURLOPT_WRITEFUNCTION).
10 * Can be used to add download conditions on the headers (response code, content type, etc.). 10 * Can be used to add download conditions on the
11 * headers (response code, content type, etc.).
11 * 12 *
12 * @return array HTTP response headers, downloaded content 13 * @return array HTTP response headers, downloaded content
13 * 14 *
@@ -64,29 +65,30 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
64 } 65 }
65 66
66 // General cURL settings 67 // General cURL settings
67 curl_setopt($ch, CURLOPT_AUTOREFERER, true); 68 curl_setopt($ch, CURLOPT_AUTOREFERER, true);
68 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 69 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
69 curl_setopt($ch, CURLOPT_HEADER, true); 70 curl_setopt($ch, CURLOPT_HEADER, true);
70 curl_setopt( 71 curl_setopt(
71 $ch, 72 $ch,
72 CURLOPT_HTTPHEADER, 73 CURLOPT_HTTPHEADER,
73 array('Accept-Language: ' . $acceptLanguage) 74 array('Accept-Language: ' . $acceptLanguage)
74 ); 75 );
75 curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirs); 76 curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirs);
76 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 77 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
77 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); 78 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
78 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent); 79 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
79 80
80 if (is_callable($curlWriteFunction)) { 81 if (is_callable($curlWriteFunction)) {
81 curl_setopt($ch, CURLOPT_WRITEFUNCTION, $curlWriteFunction); 82 curl_setopt($ch, CURLOPT_WRITEFUNCTION, $curlWriteFunction);
82 } 83 }
83 84
84 // Max download size management 85 // Max download size management
85 curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16); 86 curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16);
86 curl_setopt($ch, CURLOPT_NOPROGRESS, false); 87 curl_setopt($ch, CURLOPT_NOPROGRESS, false);
87 curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, 88 curl_setopt(
88 function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes) 89 $ch,
89 { 90 CURLOPT_PROGRESSFUNCTION,
91 function ($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes) {
90 if (version_compare(phpversion(), '5.5', '<')) { 92 if (version_compare(phpversion(), '5.5', '<')) {
91 // PHP version lower than 5.5 93 // PHP version lower than 5.5
92 // Callback has 4 arguments 94 // Callback has 4 arguments
@@ -232,7 +234,6 @@ function get_redirected_headers($url, $redirectionLimit = 3)
232 && !empty($headers) 234 && !empty($headers)
233 && (strpos($headers[0], '301') !== false || strpos($headers[0], '302') !== false) 235 && (strpos($headers[0], '301') !== false || strpos($headers[0], '302') !== false)
234 && !empty($headers['Location'])) { 236 && !empty($headers['Location'])) {
235
236 $redirection = is_array($headers['Location']) ? end($headers['Location']) : $headers['Location']; 237 $redirection = is_array($headers['Location']) ? end($headers['Location']) : $headers['Location'];
237 if ($redirection != $url) { 238 if ($redirection != $url) {
238 $redirection = getAbsoluteUrl($url, $redirection); 239 $redirection = getAbsoluteUrl($url, $redirection);
diff --git a/application/Languages.php b/application/Languages.php
index 4fa32426..b9c5d0e8 100644
--- a/application/Languages.php
+++ b/application/Languages.php
@@ -92,7 +92,7 @@ class Languages
92 /** 92 /**
93 * Initialize the translator using php gettext extension (gettext dependency act as a wrapper). 93 * Initialize the translator using php gettext extension (gettext dependency act as a wrapper).
94 */ 94 */
95 protected function initGettextTranslator () 95 protected function initGettextTranslator()
96 { 96 {
97 $this->translator = new GettextTranslator(); 97 $this->translator = new GettextTranslator();
98 $this->translator->setLanguage($this->language); 98 $this->translator->setLanguage($this->language);
@@ -125,7 +125,8 @@ class Languages
125 $translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po'); 125 $translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po');
126 $translations->setDomain('shaarli'); 126 $translations->setDomain('shaarli');
127 $this->translator->loadTranslations($translations); 127 $this->translator->loadTranslations($translations);
128 } catch (\InvalidArgumentException $e) {} 128 } catch (\InvalidArgumentException $e) {
129 }
129 130
130 // Default extension translation from the current theme 131 // Default extension translation from the current theme
131 $theme = $this->conf->get('theme'); 132 $theme = $this->conf->get('theme');
@@ -137,7 +138,8 @@ class Languages
137 ); 138 );
138 $translations->setDomain($theme); 139 $translations->setDomain($theme);
139 $this->translator->loadTranslations($translations); 140 $this->translator->loadTranslations($translations);
140 } catch (\InvalidArgumentException $e) {} 141 } catch (\InvalidArgumentException $e) {
142 }
141 } 143 }
142 144
143 // Extension translations (plugins, themes, etc.). 145 // Extension translations (plugins, themes, etc.).
@@ -147,10 +149,13 @@ class Languages
147 } 149 }
148 150
149 try { 151 try {
150 $extension = Translations::fromPoFile($translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po'); 152 $extension = Translations::fromPoFile(
153 $translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po'
154 );
151 $extension->setDomain($domain); 155 $extension->setDomain($domain);
152 $this->translator->loadTranslations($extension); 156 $this->translator->loadTranslations($extension);
153 } catch (\InvalidArgumentException $e) {} 157 } catch (\InvalidArgumentException $e) {
158 }
154 } 159 }
155 } 160 }
156 161
diff --git a/application/LinkDB.php b/application/LinkDB.php
index cd0f2967..4bbc2950 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -107,8 +107,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
107 $hidePublicLinks, 107 $hidePublicLinks,
108 $redirector = '', 108 $redirector = '',
109 $redirectorEncode = true 109 $redirectorEncode = true
110 ) 110 ) {
111 {
112 $this->datastore = $datastore; 111 $this->datastore = $datastore;
113 $this->loggedIn = $isLoggedIn; 112 $this->loggedIn = $isLoggedIn;
114 $this->hidePublicLinks = $hidePublicLinks; 113 $this->hidePublicLinks = $hidePublicLinks;
@@ -250,11 +249,14 @@ class LinkDB implements Iterator, Countable, ArrayAccess
250 'id' => 1, 249 'id' => 1,
251 'title'=> t('The personal, minimalist, super-fast, database free, bookmarking service'), 250 'title'=> t('The personal, minimalist, super-fast, database free, bookmarking service'),
252 'url'=>'https://shaarli.readthedocs.io', 251 'url'=>'https://shaarli.readthedocs.io',
253 'description'=>t('Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. 252 'description'=>t(
253 'Welcome to Shaarli! This is your first public bookmark. '
254 .'To edit or delete me, you must first login.
254 255
255To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page. 256To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page.
256 257
257You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'), 258You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
259 ),
258 'private'=>0, 260 'private'=>0,
259 'created'=> new DateTime(), 261 'created'=> new DateTime(),
260 'tags'=>'opensource software' 262 'tags'=>'opensource software'
@@ -317,8 +319,7 @@ You use the community supported version of the original Shaarli project, by Seba
317 } else { 319 } else {
318 $link['real_url'] .= $link['url']; 320 $link['real_url'] .= $link['url'];
319 } 321 }
320 } 322 } else {
321 else {
322 $link['real_url'] = $link['url']; 323 $link['real_url'] = $link['url'];
323 } 324 }
324 325
@@ -403,7 +404,8 @@ You use the community supported version of the original Shaarli project, by Seba
403 * 404 *
404 * @return array list of shaare found. 405 * @return array list of shaare found.
405 */ 406 */
406 public function filterDay($request) { 407 public function filterDay($request)
408 {
407 $linkFilter = new LinkFilter($this->links); 409 $linkFilter = new LinkFilter($this->links);
408 return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request); 410 return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request);
409 } 411 }
@@ -420,8 +422,12 @@ You use the community supported version of the original Shaarli project, by Seba
420 * 422 *
421 * @return array filtered links, all links if no suitable filter was provided. 423 * @return array filtered links, all links if no suitable filter was provided.
422 */ 424 */
423 public function filterSearch($filterRequest = array(), $casesensitive = false, $visibility = 'all', $untaggedonly = false) 425 public function filterSearch(
424 { 426 $filterRequest = array(),
427 $casesensitive = false,
428 $visibility = 'all',
429 $untaggedonly = false
430 ) {
425 // Filter link database according to parameters. 431 // Filter link database according to parameters.
426 $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; 432 $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
427 $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; 433 $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
@@ -492,8 +498,7 @@ You use the community supported version of the original Shaarli project, by Seba
492 $delete = empty($to); 498 $delete = empty($to);
493 // True for case-sensitive tag search. 499 // True for case-sensitive tag search.
494 $linksToAlter = $this->filterSearch(['searchtags' => $from], true); 500 $linksToAlter = $this->filterSearch(['searchtags' => $from], true);
495 foreach($linksToAlter as $key => &$value) 501 foreach ($linksToAlter as $key => &$value) {
496 {
497 $tags = preg_split('/\s+/', trim($value['tags'])); 502 $tags = preg_split('/\s+/', trim($value['tags']));
498 if (($pos = array_search($from, $tags)) !== false) { 503 if (($pos = array_search($from, $tags)) !== false) {
499 if ($delete) { 504 if ($delete) {
@@ -536,7 +541,10 @@ You use the community supported version of the original Shaarli project, by Seba
536 { 541 {
537 $order = $order === 'ASC' ? -1 : 1; 542 $order = $order === 'ASC' ? -1 : 1;
538 // Reorder array by dates. 543 // Reorder array by dates.
539 usort($this->links, function($a, $b) use ($order) { 544 usort($this->links, function ($a, $b) use ($order) {
545 if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) {
546 return $a['sticky'] ? -1 : 1;
547 }
540 return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; 548 return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
541 }); 549 });
542 550
diff --git a/application/LinkFilter.php b/application/LinkFilter.php
index e52239b8..8f147974 100644
--- a/application/LinkFilter.php
+++ b/application/LinkFilter.php
@@ -62,7 +62,7 @@ class LinkFilter
62 $visibility = 'all'; 62 $visibility = 'all';
63 } 63 }
64 64
65 switch($type) { 65 switch ($type) {
66 case self::$FILTER_HASH: 66 case self::$FILTER_HASH:
67 return $this->filterSmallHash($request); 67 return $this->filterSmallHash($request);
68 case self::$FILTER_TAG | self::$FILTER_TEXT: // == "vuotext" 68 case self::$FILTER_TAG | self::$FILTER_TEXT: // == "vuotext"
@@ -205,7 +205,6 @@ class LinkFilter
205 205
206 // Iterate over every stored link. 206 // Iterate over every stored link.
207 foreach ($this->links as $id => $link) { 207 foreach ($this->links as $id => $link) {
208
209 // ignore non private links when 'privatonly' is on. 208 // ignore non private links when 'privatonly' is on.
210 if ($visibility !== 'all') { 209 if ($visibility !== 'all') {
211 if (! $link['private'] && $visibility === 'private') { 210 if (! $link['private'] && $visibility === 'private') {
@@ -257,11 +256,11 @@ class LinkFilter
257 private static function tag2regex($tag) 256 private static function tag2regex($tag)
258 { 257 {
259 $len = strlen($tag); 258 $len = strlen($tag);
260 if(!$len || $tag === "-" || $tag === "*"){ 259 if (!$len || $tag === "-" || $tag === "*") {
261 // nothing to search, return empty regex 260 // nothing to search, return empty regex
262 return ''; 261 return '';
263 } 262 }
264 if($tag[0] === "-") { 263 if ($tag[0] === "-") {
265 // query is negated 264 // query is negated
266 $i = 1; // use offset to start after '-' character 265 $i = 1; // use offset to start after '-' character
267 $regex = '(?!'; // create negative lookahead 266 $regex = '(?!'; // create negative lookahead
@@ -271,14 +270,14 @@ class LinkFilter
271 } 270 }
272 $regex .= '.*(?:^| )'; // before tag may only be a space or the beginning 271 $regex .= '.*(?:^| )'; // before tag may only be a space or the beginning
273 // iterate over string, separating it into placeholder and content 272 // iterate over string, separating it into placeholder and content
274 for(; $i < $len; $i++){ 273 for (; $i < $len; $i++) {
275 if($tag[$i] === '*'){ 274 if ($tag[$i] === '*') {
276 // placeholder found 275 // placeholder found
277 $regex .= '[^ ]*?'; 276 $regex .= '[^ ]*?';
278 } else { 277 } else {
279 // regular characters 278 // regular characters
280 $offset = strpos($tag, '*', $i); 279 $offset = strpos($tag, '*', $i);
281 if($offset === false){ 280 if ($offset === false) {
282 // no placeholder found, set offset to end of string 281 // no placeholder found, set offset to end of string
283 $offset = $len; 282 $offset = $len;
284 } 283 }
@@ -310,19 +309,19 @@ class LinkFilter
310 { 309 {
311 // get single tags (we may get passed an array, even though the docs say different) 310 // get single tags (we may get passed an array, even though the docs say different)
312 $inputTags = $tags; 311 $inputTags = $tags;
313 if(!is_array($tags)) { 312 if (!is_array($tags)) {
314 // we got an input string, split tags 313 // we got an input string, split tags
315 $inputTags = preg_split('/(?:\s+)|,/', $inputTags, -1, PREG_SPLIT_NO_EMPTY); 314 $inputTags = preg_split('/(?:\s+)|,/', $inputTags, -1, PREG_SPLIT_NO_EMPTY);
316 } 315 }
317 316
318 if(!count($inputTags)){ 317 if (!count($inputTags)) {
319 // no input tags 318 // no input tags
320 return $this->noFilter($visibility); 319 return $this->noFilter($visibility);
321 } 320 }
322 321
323 // build regex from all tags 322 // build regex from all tags
324 $re = '/^' . implode(array_map("self::tag2regex", $inputTags)) . '.*$/'; 323 $re = '/^' . implode(array_map("self::tag2regex", $inputTags)) . '.*$/';
325 if(!$casesensitive) { 324 if (!$casesensitive) {
326 // make regex case insensitive 325 // make regex case insensitive
327 $re .= 'i'; 326 $re .= 'i';
328 } 327 }
@@ -342,7 +341,7 @@ class LinkFilter
342 } 341 }
343 } 342 }
344 $search = $link['tags']; // build search string, start with tags of current link 343 $search = $link['tags']; // build search string, start with tags of current link
345 if(strlen(trim($link['description'])) && strpos($link['description'], '#') !== false){ 344 if (strlen(trim($link['description'])) && strpos($link['description'], '#') !== false) {
346 // description given and at least one possible tag found 345 // description given and at least one possible tag found
347 $descTags = array(); 346 $descTags = array();
348 // find all tags in the form of #tag in the description 347 // find all tags in the form of #tag in the description
@@ -351,13 +350,13 @@ class LinkFilter
351 $link['description'], 350 $link['description'],
352 $descTags 351 $descTags
353 ); 352 );
354 if(count($descTags[1])){ 353 if (count($descTags[1])) {
355 // there were some tags in the description, add them to the search string 354 // there were some tags in the description, add them to the search string
356 $search .= ' ' . implode(' ', $descTags[1]); 355 $search .= ' ' . implode(' ', $descTags[1]);
357 } 356 }
358 }; 357 };
359 // match regular expression with search string 358 // match regular expression with search string
360 if(!preg_match($re, $search)){ 359 if (!preg_match($re, $search)) {
361 // this entry does _not_ match our regex 360 // this entry does _not_ match our regex
362 continue; 361 continue;
363 } 362 }
diff --git a/application/LinkUtils.php b/application/LinkUtils.php
index 4df5c0ca..d56e019f 100644
--- a/application/LinkUtils.php
+++ b/application/LinkUtils.php
@@ -23,7 +23,7 @@ function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_get
23 * 23 *
24 * @return int|bool length of $data or false if we need to stop the download 24 * @return int|bool length of $data or false if we need to stop the download
25 */ 25 */
26 return function(&$ch, $data) use ($curlGetInfo, &$charset, &$title, &$isRedirected) { 26 return function (&$ch, $data) use ($curlGetInfo, &$charset, &$title, &$isRedirected) {
27 $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE); 27 $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
28 if (!empty($responseCode) && in_array($responseCode, [301, 302])) { 28 if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
29 $isRedirected = true; 29 $isRedirected = true;
@@ -201,7 +201,8 @@ function space2nbsp($text)
201 201
202 * @return string formatted description. 202 * @return string formatted description.
203 */ 203 */
204function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '') { 204function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '')
205{
205 return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector, $urlEncode), $indexUrl))); 206 return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector, $urlEncode), $indexUrl)));
206} 207}
207 208
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php
index b4d16d00..84dd2b20 100644
--- a/application/NetscapeBookmarkUtils.php
+++ b/application/NetscapeBookmarkUtils.php
@@ -72,18 +72,20 @@ class NetscapeBookmarkUtils
72 private static function importStatus( 72 private static function importStatus(
73 $filename, 73 $filename,
74 $filesize, 74 $filesize,
75 $importCount=0, 75 $importCount = 0,
76 $overwriteCount=0, 76 $overwriteCount = 0,
77 $skipCount=0, 77 $skipCount = 0,
78 $duration=0 78 $duration = 0
79 ) 79 ) {
80 {
81 $status = sprintf(t('File %s (%d bytes) '), $filename, $filesize); 80 $status = sprintf(t('File %s (%d bytes) '), $filename, $filesize);
82 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { 81 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
83 $status .= t('has an unknown file format. Nothing was imported.'); 82 $status .= t('has an unknown file format. Nothing was imported.');
84 } else { 83 } else {
85 $status .= vsprintf( 84 $status .= vsprintf(
86 t('was successfully processed in %d seconds: %d links imported, %d links overwritten, %d links skipped.'), 85 t(
86 'was successfully processed in %d seconds: '
87 .'%d links imported, %d links overwritten, %d links skipped.'
88 ),
87 [$duration, $importCount, $overwriteCount, $skipCount] 89 [$duration, $importCount, $overwriteCount, $skipCount]
88 ); 90 );
89 } 91 }
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index b1abe0d0..2ca95832 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -78,7 +78,6 @@ class PageBuilder
78 ); 78 );
79 $this->tpl->assign('newVersion', escape($version)); 79 $this->tpl->assign('newVersion', escape($version));
80 $this->tpl->assign('versionError', ''); 80 $this->tpl->assign('versionError', '');
81
82 } catch (Exception $exc) { 81 } catch (Exception $exc) {
83 logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage()); 82 logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage());
84 $this->tpl->assign('newVersion', ''); 83 $this->tpl->assign('newVersion', '');
@@ -101,7 +100,7 @@ class PageBuilder
101 'version_hash', 100 'version_hash',
102 ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt')) 101 ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt'))
103 ); 102 );
104 $this->tpl->assign('scripturl', index_url($_SERVER)); 103 $this->tpl->assign('index_url', index_url($_SERVER));
105 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; 104 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '';
106 $this->tpl->assign('visibility', $visibility); 105 $this->tpl->assign('visibility', $visibility);
107 $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly'])); 106 $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly']));
@@ -163,7 +162,7 @@ class PageBuilder
163 $this->initialize(); 162 $this->initialize();
164 } 163 }
165 164
166 if (empty($data) || !is_array($data)){ 165 if (empty($data) || !is_array($data)) {
167 return false; 166 return false;
168 } 167 }
169 168
diff --git a/application/PluginManager.php b/application/PluginManager.php
index cf603845..1ed4db4b 100644
--- a/application/PluginManager.php
+++ b/application/PluginManager.php
@@ -75,8 +75,7 @@ class PluginManager
75 75
76 try { 76 try {
77 $this->loadPlugin($dirs[$index], $plugin); 77 $this->loadPlugin($dirs[$index], $plugin);
78 } 78 } catch (PluginFileNotFoundException $e) {
79 catch (PluginFileNotFoundException $e) {
80 error_log($e->getMessage()); 79 error_log($e->getMessage());
81 } 80 }
82 } 81 }
diff --git a/application/Router.php b/application/Router.php
index bf86b884..beb3165b 100644
--- a/application/Router.php
+++ b/application/Router.php
@@ -37,6 +37,8 @@ class Router
37 37
38 public static $PAGE_DELETELINK = 'delete_link'; 38 public static $PAGE_DELETELINK = 'delete_link';
39 39
40 public static $PAGE_PINLINK = 'pin';
41
40 public static $PAGE_EXPORT = 'export'; 42 public static $PAGE_EXPORT = 'export';
41 43
42 public static $PAGE_IMPORT = 'import'; 44 public static $PAGE_IMPORT = 'import';
@@ -146,6 +148,10 @@ class Router
146 return self::$PAGE_DELETELINK; 148 return self::$PAGE_DELETELINK;
147 } 149 }
148 150
151 if (startsWith($query, 'do='. self::$PAGE_PINLINK)) {
152 return self::$PAGE_PINLINK;
153 }
154
149 if (startsWith($query, 'do='. self::$PAGE_EXPORT)) { 155 if (startsWith($query, 'do='. self::$PAGE_EXPORT)) {
150 return self::$PAGE_EXPORT; 156 return self::$PAGE_EXPORT;
151 } 157 }
diff --git a/application/Thumbnailer.php b/application/Thumbnailer.php
index 7d0d9c33..37ed97a1 100644
--- a/application/Thumbnailer.php
+++ b/application/Thumbnailer.php
@@ -58,7 +58,10 @@ class Thumbnailer
58 $this->conf->set('thumbnails.enabled', false); 58 $this->conf->set('thumbnails.enabled', false);
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('php-gd extension must be loaded to use thumbnails. Thumbnails are now disabled. Please reload the page.')); 61 die(t(
62 'php-gd extension must be loaded to use thumbnails. '
63 .'Thumbnails are now disabled. Please reload the page.'
64 ));
62 } 65 }
63 66
64 $this->wt = new WebThumbnailer(); 67 $this->wt = new WebThumbnailer();
diff --git a/application/Updater.php b/application/Updater.php
index 480bff82..6b94c5e3 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -183,7 +183,7 @@ class Updater
183 } 183 }
184 } 184 }
185 185
186 try{ 186 try {
187 $this->conf->write($this->isLoggedIn); 187 $this->conf->write($this->isLoggedIn);
188 return true; 188 return true;
189 } catch (IOException $e) { 189 } catch (IOException $e) {
@@ -517,6 +517,26 @@ class Updater
517 517
518 return true; 518 return true;
519 } 519 }
520
521 /**
522 * Set sticky = false on all links
523 *
524 * @return bool true if the update is successful, false otherwise.
525 */
526 public function updateMethodSetSticky()
527 {
528 foreach ($this->linkDB as $key => $link) {
529 if (isset($link['sticky'])) {
530 return true;
531 }
532 $link['sticky'] = false;
533 $this->linkDB[$key] = $link;
534 }
535
536 $this->linkDB->save($this->conf->get('resource.page_cache'));
537
538 return true;
539 }
520} 540}
521 541
522/** 542/**
diff --git a/application/Url.php b/application/Url.php
index 6b9870f0..3b7f19c2 100644
--- a/application/Url.php
+++ b/application/Url.php
@@ -34,8 +34,8 @@ function unparse_url($parsedUrl)
34 */ 34 */
35function cleanup_url($url) 35function cleanup_url($url)
36{ 36{
37 $obj_url = new Url($url); 37 $obj_url = new Url($url);
38 return $obj_url->cleanup(); 38 return $obj_url->cleanup();
39} 39}
40 40
41/** 41/**
@@ -47,8 +47,8 @@ function cleanup_url($url)
47 */ 47 */
48function get_url_scheme($url) 48function get_url_scheme($url)
49{ 49{
50 $obj_url = new Url($url); 50 $obj_url = new Url($url);
51 return $obj_url->getScheme(); 51 return $obj_url->getScheme();
52} 52}
53 53
54/** 54/**
@@ -217,7 +217,7 @@ class Url
217 } 217 }
218 218
219 $this->parts['query'] = implode('&', $queryParams); 219 $this->parts['query'] = implode('&', $queryParams);
220 } 220 }
221 221
222 /** 222 /**
223 * Removes undesired fragments 223 * Removes undesired fragments
@@ -269,7 +269,8 @@ class Url
269 * 269 *
270 * @return string the URL scheme or false if none is provided. 270 * @return string the URL scheme or false if none is provided.
271 */ 271 */
272 public function getScheme() { 272 public function getScheme()
273 {
273 if (!isset($this->parts['scheme'])) { 274 if (!isset($this->parts['scheme'])) {
274 return false; 275 return false;
275 } 276 }
@@ -281,7 +282,8 @@ class Url
281 * 282 *
282 * @return string the URL host or false if none is provided. 283 * @return string the URL host or false if none is provided.
283 */ 284 */
284 public function getHost() { 285 public function getHost()
286 {
285 if (empty($this->parts['host'])) { 287 if (empty($this->parts['host'])) {
286 return false; 288 return false;
287 } 289 }
@@ -293,7 +295,8 @@ class Url
293 * 295 *
294 * @return true is HTTP, false otherwise. 296 * @return true is HTTP, false otherwise.
295 */ 297 */
296 public function isHttp() { 298 public function isHttp()
299 {
297 return strpos(strtolower($this->parts['scheme']), 'http') !== false; 300 return strpos(strtolower($this->parts['scheme']), 'http') !== false;
298 } 301 }
299} 302}
diff --git a/application/Utils.php b/application/Utils.php
index 97b12fcf..925e1a22 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -97,7 +97,7 @@ function escape($input)
97 97
98 if (is_array($input)) { 98 if (is_array($input)) {
99 $out = array(); 99 $out = array();
100 foreach($input as $key => $value) { 100 foreach ($input as $key => $value) {
101 $out[$key] = escape($value); 101 $out[$key] = escape($value);
102 } 102 }
103 return $out; 103 return $out;
@@ -355,10 +355,13 @@ function return_bytes($val)
355 $val = trim($val); 355 $val = trim($val);
356 $last = strtolower($val[strlen($val)-1]); 356 $last = strtolower($val[strlen($val)-1]);
357 $val = intval(substr($val, 0, -1)); 357 $val = intval(substr($val, 0, -1));
358 switch($last) { 358 switch ($last) {
359 case 'g': $val *= 1024; 359 case 'g':
360 case 'm': $val *= 1024; 360 $val *= 1024;
361 case 'k': $val *= 1024; 361 case 'm':
362 $val *= 1024;
363 case 'k':
364 $val *= 1024;
362 } 365 }
363 return $val; 366 return $val;
364} 367}
@@ -452,6 +455,7 @@ function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
452 * 455 *
453 * @return string Text translated. 456 * @return string Text translated.
454 */ 457 */
455function t($text, $nText = '', $nb = 1, $domain = 'shaarli') { 458function t($text, $nText = '', $nb = 1, $domain = 'shaarli')
459{
456 return dn__($domain, $text, $nText, $nb); 460 return dn__($domain, $text, $nText, $nb);
457} 461}
diff --git a/application/api/ApiMiddleware.php b/application/api/ApiMiddleware.php
index ff209393..66eac133 100644
--- a/application/api/ApiMiddleware.php
+++ b/application/api/ApiMiddleware.php
@@ -65,7 +65,7 @@ class ApiMiddleware
65 try { 65 try {
66 $this->checkRequest($request); 66 $this->checkRequest($request);
67 $response = $next($request, $response); 67 $response = $next($request, $response);
68 } catch(ApiException $e) { 68 } catch (ApiException $e) {
69 $e->setResponse($response); 69 $e->setResponse($response);
70 $e->setDebug($this->conf->get('dev.debug', false)); 70 $e->setDebug($this->conf->get('dev.debug', false));
71 $response = $e->getApiResponse(); 71 $response = $e->getApiResponse();
@@ -98,7 +98,8 @@ class ApiMiddleware
98 * 98 *
99 * @throws ApiAuthorizationException The token couldn't be validated. 99 * @throws ApiAuthorizationException The token couldn't be validated.
100 */ 100 */
101 protected function checkToken($request) { 101 protected function checkToken($request)
102 {
102 if (! $request->hasHeader('Authorization')) { 103 if (! $request->hasHeader('Authorization')) {
103 throw new ApiAuthorizationException('JWT token not provided'); 104 throw new ApiAuthorizationException('JWT token not provided');
104 } 105 }
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php
index 3be85b98..9edefcf6 100644
--- a/application/api/controllers/ApiController.php
+++ b/application/api/controllers/ApiController.php
@@ -41,7 +41,7 @@ abstract class ApiController
41 41
42 /** 42 /**
43 * ApiController constructor. 43 * ApiController constructor.
44 * 44 *
45 * Note: enabling debug mode displays JSON with readable formatting. 45 * Note: enabling debug mode displays JSON with readable formatting.
46 * 46 *
47 * @param Container $ci Slim container. 47 * @param Container $ci Slim container.
diff --git a/application/api/controllers/History.php b/application/api/controllers/History.php
index 5cc453bf..4582e8b2 100644
--- a/application/api/controllers/History.php
+++ b/application/api/controllers/History.php
@@ -35,8 +35,7 @@ class History extends ApiController
35 $offset = $request->getParam('offset'); 35 $offset = $request->getParam('offset');
36 if (empty($offset)) { 36 if (empty($offset)) {
37 $offset = 0; 37 $offset = 0;
38 } 38 } elseif (ctype_digit($offset)) {
39 elseif (ctype_digit($offset)) {
40 $offset = (int) $offset; 39 $offset = (int) $offset;
41 } else { 40 } else {
42 throw new ApiBadParametersException('Invalid offset'); 41 throw new ApiBadParametersException('Invalid offset');
diff --git a/application/api/controllers/Info.php b/application/api/controllers/Info.php
index 25433f72..f37dcae5 100644
--- a/application/api/controllers/Info.php
+++ b/application/api/controllers/Info.php
@@ -7,7 +7,7 @@ use Slim\Http\Response;
7 7
8/** 8/**
9 * Class Info 9 * Class Info
10 * 10 *
11 * REST API Controller: /info 11 * REST API Controller: /info
12 * 12 *
13 * @package Api\Controllers 13 * @package Api\Controllers
@@ -17,7 +17,7 @@ class Info extends ApiController
17{ 17{
18 /** 18 /**
19 * Service providing various information about Shaarli instance. 19 * Service providing various information about Shaarli instance.
20 * 20 *
21 * @param Request $request Slim request. 21 * @param Request $request Slim request.
22 * @param Response $response Slim response. 22 * @param Response $response Slim response.
23 * 23 *
diff --git a/application/api/exceptions/ApiException.php b/application/api/exceptions/ApiException.php
index c8490e0c..d6b66323 100644
--- a/application/api/exceptions/ApiException.php
+++ b/application/api/exceptions/ApiException.php
@@ -10,7 +10,8 @@ use Slim\Http\Response;
10 * Parent Exception related to the API, able to generate a valid Response (ResponseInterface). 10 * Parent Exception related to the API, able to generate a valid Response (ResponseInterface).
11 * Also can include various information in debug mode. 11 * Also can include various information in debug mode.
12 */ 12 */
13abstract class ApiException extends \Exception { 13abstract class ApiException extends \Exception
14{
14 15
15 /** 16 /**
16 * @var Response instance from Slim. 17 * @var Response instance from Slim.
@@ -27,7 +28,7 @@ abstract class ApiException extends \Exception {
27 * 28 *
28 * @return Response Final response to give. 29 * @return Response Final response to give.
29 */ 30 */
30 public abstract function getApiResponse(); 31 abstract public function getApiResponse();
31 32
32 /** 33 /**
33 * Creates ApiResponse body. 34 * Creates ApiResponse body.
@@ -36,7 +37,8 @@ abstract class ApiException extends \Exception {
36 * 37 *
37 * @return array|string response body 38 * @return array|string response body
38 */ 39 */
39 protected function getApiResponseBody() { 40 protected function getApiResponseBody()
41 {
40 if ($this->debug !== true) { 42 if ($this->debug !== true) {
41 return $this->getMessage(); 43 return $this->getMessage();
42 } 44 }
diff --git a/application/api/exceptions/ApiLinkNotFoundException.php b/application/api/exceptions/ApiLinkNotFoundException.php
index de7e14f5..c727f4f0 100644
--- a/application/api/exceptions/ApiLinkNotFoundException.php
+++ b/application/api/exceptions/ApiLinkNotFoundException.php
@@ -2,7 +2,6 @@
2 2
3namespace Shaarli\Api\Exceptions; 3namespace Shaarli\Api\Exceptions;
4 4
5
6use Slim\Http\Response; 5use Slim\Http\Response;
7 6
8/** 7/**
diff --git a/application/api/exceptions/ApiTagNotFoundException.php b/application/api/exceptions/ApiTagNotFoundException.php
index eed5afa5..eee152fe 100644
--- a/application/api/exceptions/ApiTagNotFoundException.php
+++ b/application/api/exceptions/ApiTagNotFoundException.php
@@ -2,7 +2,6 @@
2 2
3namespace Shaarli\Api\Exceptions; 3namespace Shaarli\Api\Exceptions;
4 4
5
6use Slim\Http\Response; 5use Slim\Http\Response;
7 6
8/** 7/**
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php
index 8add8bcd..9625fe1a 100644
--- a/application/config/ConfigPhp.php
+++ b/application/config/ConfigPhp.php
@@ -104,12 +104,20 @@ class ConfigPhp implements ConfigIO
104 104
105 // Store all $conf['config'] 105 // Store all $conf['config']
106 foreach ($conf['config'] as $key => $value) { 106 foreach ($conf['config'] as $key => $value) {
107 $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL; 107 $configStr .= '$GLOBALS[\'config\'][\''
108 . $key
109 .'\'] = '
110 .var_export($conf['config'][$key], true).';'
111 . PHP_EOL;
108 } 112 }
109 113
110 if (isset($conf['plugins'])) { 114 if (isset($conf['plugins'])) {
111 foreach ($conf['plugins'] as $key => $value) { 115 foreach ($conf['plugins'] as $key => $value) {
112 $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($conf['plugins'][$key], true).';'. PHP_EOL; 116 $configStr .= '$GLOBALS[\'plugins\'][\''
117 . $key
118 .'\'] = '
119 .var_export($conf['plugins'][$key], true).';'
120 . PHP_EOL;
113 } 121 }
114 } 122 }
115 123
diff --git a/application/config/ConfigPlugin.php b/application/config/ConfigPlugin.php
index b3d9752b..dbb24937 100644
--- a/application/config/ConfigPlugin.php
+++ b/application/config/ConfigPlugin.php
@@ -34,8 +34,7 @@ function save_plugin_config($formData)
34 // If there is no order, it means a disabled plugin has been enabled. 34 // If there is no order, it means a disabled plugin has been enabled.
35 if (isset($formData['order_' . $key])) { 35 if (isset($formData['order_' . $key])) {
36 $plugins[(int) $formData['order_' . $key]] = $key; 36 $plugins[(int) $formData['order_' . $key]] = $key;
37 } 37 } else {
38 else {
39 $newEnabledPlugins[] = $key; 38 $newEnabledPlugins[] = $key;
40 } 39 }
41 } 40 }
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php
index d6784d6d..0f315483 100644
--- a/application/security/LoginManager.php
+++ b/application/security/LoginManager.php
@@ -95,7 +95,6 @@ class LoginManager
95 // The user client has a valid stay-signed-in cookie 95 // The user client has a valid stay-signed-in cookie
96 // Session information is updated with the current client information 96 // Session information is updated with the current client information
97 $this->sessionManager->storeLoginInfo($clientIpId); 97 $this->sessionManager->storeLoginInfo($clientIpId);
98
99 } elseif ($this->sessionManager->hasSessionExpired() 98 } elseif ($this->sessionManager->hasSessionExpired()
100 || $this->sessionManager->hasClientIpChanged($clientIpId) 99 || $this->sessionManager->hasClientIpChanged($clientIpId)
101 ) { 100 ) {
diff --git a/assets/default/js/base.js b/assets/default/js/base.js
index 8bf79d3e..99e03370 100644
--- a/assets/default/js/base.js
+++ b/assets/default/js/base.js
@@ -422,12 +422,12 @@ function init(description) {
422 /** 422 /**
423 * Bulk actions 423 * Bulk actions
424 */ 424 */
425 const linkCheckboxes = document.querySelectorAll('.delete-checkbox'); 425 const linkCheckboxes = document.querySelectorAll('.link-checkbox');
426 const bar = document.getElementById('actions'); 426 const bar = document.getElementById('actions');
427 [...linkCheckboxes].forEach((checkbox) => { 427 [...linkCheckboxes].forEach((checkbox) => {
428 checkbox.style.display = 'inline-block'; 428 checkbox.style.display = 'inline-block';
429 checkbox.addEventListener('click', () => { 429 checkbox.addEventListener('change', () => {
430 const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); 430 const linkCheckedCheckboxes = document.querySelectorAll('.link-checkbox:checked');
431 const count = [...linkCheckedCheckboxes].length; 431 const count = [...linkCheckedCheckboxes].length;
432 if (count === 0 && bar.classList.contains('open')) { 432 if (count === 0 && bar.classList.contains('open')) {
433 bar.classList.toggle('open'); 433 bar.classList.toggle('open');
@@ -444,7 +444,7 @@ function init(description) {
444 event.preventDefault(); 444 event.preventDefault();
445 445
446 const links = []; 446 const links = [];
447 const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); 447 const linkCheckedCheckboxes = document.querySelectorAll('.link-checkbox:checked');
448 [...linkCheckedCheckboxes].forEach((checkbox) => { 448 [...linkCheckedCheckboxes].forEach((checkbox) => {
449 links.push({ 449 links.push({
450 id: checkbox.value, 450 id: checkbox.value,
@@ -467,6 +467,25 @@ function init(description) {
467 } 467 }
468 468
469 /** 469 /**
470 * Select all button
471 */
472 const selectAllButtons = document.querySelectorAll('.select-all-button');
473 [...selectAllButtons].forEach((selectAllButton) => {
474 selectAllButton.addEventListener('click', (e) => {
475 e.preventDefault();
476 const checked = selectAllButton.classList.contains('filter-off');
477 [...selectAllButtons].forEach((selectAllButton2) => {
478 selectAllButton2.classList.toggle('filter-off');
479 selectAllButton2.classList.toggle('filter-on');
480 });
481 [...linkCheckboxes].forEach((linkCheckbox) => {
482 linkCheckbox.checked = checked;
483 linkCheckbox.dispatchEvent(new Event('change'));
484 });
485 });
486 });
487
488 /**
470 * Tag list operations 489 * Tag list operations
471 * 490 *
472 * TODO: support error code in the backend for AJAX requests 491 * TODO: support error code in the backend for AJAX requests
@@ -548,7 +567,7 @@ function init(description) {
548 event.preventDefault(); 567 event.preventDefault();
549 const block = findParent(event.target, 'div', { class: 'tag-list-item' }); 568 const block = findParent(event.target, 'div', { class: 'tag-list-item' });
550 const tag = block.getAttribute('data-tag'); 569 const tag = block.getAttribute('data-tag');
551 const refreshedToken = document.getElementById('token'); 570 const refreshedToken = document.getElementById('token').value;
552 571
553 if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) { 572 if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) {
554 const xhr = new XMLHttpRequest(); 573 const xhr = new XMLHttpRequest();
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss
index 6b286f1e..760d8d6a 100644
--- a/assets/default/scss/shaarli.scss
+++ b/assets/default/scss/shaarli.scss
@@ -381,8 +381,6 @@ body,
381 box-shadow: 0 1px 0 $light-shadow, 0 1px 4px $dark-shadow inset; 381 box-shadow: 0 1px 0 $light-shadow, 0 1px 4px $dark-shadow inset;
382 background: $almost-white; 382 background: $almost-white;
383 padding: 5px 5px 3px 15px; 383 padding: 5px 5px 3px 15px;
384 width: 20%;
385 height: 20px;
386 color: $dark-grey; 384 color: $dark-grey;
387} 385}
388 386
@@ -742,7 +740,7 @@ body,
742 font-size: 1em; 740 font-size: 1em;
743 } 741 }
744 742
745 .delete-checkbox { 743 .link-checkbox {
746 display: none; 744 display: none;
747 } 745 }
748} 746}
@@ -757,6 +755,14 @@ body,
757 font-size: 1.3em; 755 font-size: 1.3em;
758} 756}
759 757
758.pin-link {
759 font-size: 1.3em;
760}
761
762.pinned-link {
763 color: $blue !important;
764}
765
760.linklist-item-description { 766.linklist-item-description {
761 position: relative; 767 position: relative;
762 padding: 0 10px; 768 padding: 0 10px;
@@ -850,6 +856,10 @@ body,
850 margin: 0 7px; 856 margin: 0 7px;
851} 857}
852 858
859.ctrl-delete {
860 margin: 0 7px 0 0;
861}
862
853// 64em -> lg 863// 64em -> lg
854@media screen and (max-width: 64em) { 864@media screen and (max-width: 64em) {
855 .linklist-item-infos-url { 865 .linklist-item-infos-url {
diff --git a/composer.json b/composer.json
index 99ef0b5e..dccf83b6 100644
--- a/composer.json
+++ b/composer.json
@@ -16,7 +16,7 @@
16 }, 16 },
17 "require": { 17 "require": {
18 "php": ">=5.6", 18 "php": ">=5.6",
19 "shaarli/netscape-bookmark-parser": "^2.0", 19 "shaarli/netscape-bookmark-parser": "^2.1",
20 "erusev/parsedown": "^1.6", 20 "erusev/parsedown": "^1.6",
21 "slim/slim": "^3.0", 21 "slim/slim": "^3.0",
22 "arthurhoaro/web-thumbnailer": "^1.1", 22 "arthurhoaro/web-thumbnailer": "^1.1",
@@ -24,11 +24,9 @@
24 "gettext/gettext": "^4.4" 24 "gettext/gettext": "^4.4"
25 }, 25 },
26 "require-dev": { 26 "require-dev": {
27 "phpmd/phpmd" : "@stable", 27 "phpunit/phpcov": "*",
28 "phpunit/phpunit": "^5.0", 28 "phpunit/phpunit": "^5.0",
29 "sebastian/phpcpd": "*", 29 "squizlabs/php_codesniffer": "2.*"
30 "squizlabs/php_codesniffer": "2.*",
31 "phpunit/phpcov": "*"
32 }, 30 },
33 "autoload": { 31 "autoload": {
34 "psr-4": { 32 "psr-4": {
diff --git a/composer.lock b/composer.lock
index 22c97fa9..c43dad6f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 "This file is @generated automatically" 5 "This file is @generated automatically"
6 ], 6 ],
7 "content-hash": "da7a0c081b61d949154c5d2e5370cbab", 7 "content-hash": "3876b34296fedb365517b785af8384de",
8 "packages": [ 8 "packages": [
9 { 9 {
10 "name": "arthurhoaro/web-thumbnailer", 10 "name": "arthurhoaro/web-thumbnailer",
@@ -133,16 +133,16 @@
133 }, 133 },
134 { 134 {
135 "name": "gettext/gettext", 135 "name": "gettext/gettext",
136 "version": "v4.6.0", 136 "version": "v4.6.1",
137 "source": { 137 "source": {
138 "type": "git", 138 "type": "git",
139 "url": "https://github.com/oscarotero/Gettext.git", 139 "url": "https://github.com/oscarotero/Gettext.git",
140 "reference": "cae84aff39a87e07bd6e5cddb5adb720a0ffa357" 140 "reference": "854ff5f5aaf92d2af7080ba8fc15718b27b5c89a"
141 }, 141 },
142 "dist": { 142 "dist": {
143 "type": "zip", 143 "type": "zip",
144 "url": "https://api.github.com/repos/oscarotero/Gettext/zipball/cae84aff39a87e07bd6e5cddb5adb720a0ffa357", 144 "url": "https://api.github.com/repos/oscarotero/Gettext/zipball/854ff5f5aaf92d2af7080ba8fc15718b27b5c89a",
145 "reference": "cae84aff39a87e07bd6e5cddb5adb720a0ffa357", 145 "reference": "854ff5f5aaf92d2af7080ba8fc15718b27b5c89a",
146 "shasum": "" 146 "shasum": ""
147 }, 147 },
148 "require": { 148 "require": {
@@ -191,7 +191,7 @@
191 "po", 191 "po",
192 "translation" 192 "translation"
193 ], 193 ],
194 "time": "2018-06-26T16:51:09+00:00" 194 "time": "2018-08-27T15:40:19+00:00"
195 }, 195 },
196 { 196 {
197 "name": "gettext/languages", 197 "name": "gettext/languages",
@@ -593,15 +593,16 @@
593 "source": { 593 "source": {
594 "type": "git", 594 "type": "git",
595 "url": "https://github.com/pubsubhubbub/php-publisher.git", 595 "url": "https://github.com/pubsubhubbub/php-publisher.git",
596 "reference": "5008fc529b057251b48f4d17a10fdb20047ea8f5" 596 "reference": "047b0faf6219071527a45942d6fef4dbc6d1d884"
597 }, 597 },
598 "dist": { 598 "dist": {
599 "type": "zip", 599 "type": "zip",
600 "url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/5008fc529b057251b48f4d17a10fdb20047ea8f5", 600 "url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/047b0faf6219071527a45942d6fef4dbc6d1d884",
601 "reference": "5008fc529b057251b48f4d17a10fdb20047ea8f5", 601 "reference": "047b0faf6219071527a45942d6fef4dbc6d1d884",
602 "shasum": "" 602 "shasum": ""
603 }, 603 },
604 "require": { 604 "require": {
605 "ext-curl": "*",
605 "php": "~5.4 || ~7.0" 606 "php": "~5.4 || ~7.0"
606 }, 607 },
607 "type": "library", 608 "type": "library",
@@ -626,30 +627,31 @@
626 "data", 627 "data",
627 "feeds", 628 "feeds",
628 "publishers", 629 "publishers",
629 "pubsubhubbub" 630 "pubsubhubbub",
631 "websub"
630 ], 632 ],
631 "time": "2018-05-22T11:56:26+00:00" 633 "time": "2018-10-09T05:20:28+00:00"
632 }, 634 },
633 { 635 {
634 "name": "shaarli/netscape-bookmark-parser", 636 "name": "shaarli/netscape-bookmark-parser",
635 "version": "v2.0.5", 637 "version": "v2.1.0",
636 "source": { 638 "source": {
637 "type": "git", 639 "type": "git",
638 "url": "https://github.com/shaarli/netscape-bookmark-parser.git", 640 "url": "https://github.com/shaarli/netscape-bookmark-parser.git",
639 "reference": "ea6911a0ea3dd372fa7002593c5aef9c15a49315" 641 "reference": "819008ee42c4dd7e45d988176a4a22d6ed689577"
640 }, 642 },
641 "dist": { 643 "dist": {
642 "type": "zip", 644 "type": "zip",
643 "url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/ea6911a0ea3dd372fa7002593c5aef9c15a49315", 645 "url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/819008ee42c4dd7e45d988176a4a22d6ed689577",
644 "reference": "ea6911a0ea3dd372fa7002593c5aef9c15a49315", 646 "reference": "819008ee42c4dd7e45d988176a4a22d6ed689577",
645 "shasum": "" 647 "shasum": ""
646 }, 648 },
647 "require": { 649 "require": {
648 "katzgrau/klogger": "~1.0", 650 "katzgrau/klogger": "~1.0",
649 "php": ">=5.3.4" 651 "php": ">=5.6"
650 }, 652 },
651 "require-dev": { 653 "require-dev": {
652 "phpunit/phpunit": "4.8.*" 654 "phpunit/phpunit": "^5.0"
653 }, 655 },
654 "type": "library", 656 "type": "library",
655 "autoload": { 657 "autoload": {
@@ -681,22 +683,22 @@
681 "bookmark", 683 "bookmark",
682 "link", 684 "link",
683 "netscape", 685 "netscape",
684 "parse" 686 "parser"
685 ], 687 ],
686 "time": "2018-01-30T17:34:48+00:00" 688 "time": "2018-10-06T14:43:38+00:00"
687 }, 689 },
688 { 690 {
689 "name": "slim/slim", 691 "name": "slim/slim",
690 "version": "3.10.0", 692 "version": "3.11.0",
691 "source": { 693 "source": {
692 "type": "git", 694 "type": "git",
693 "url": "https://github.com/slimphp/Slim.git", 695 "url": "https://github.com/slimphp/Slim.git",
694 "reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748" 696 "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a"
695 }, 697 },
696 "dist": { 698 "dist": {
697 "type": "zip", 699 "type": "zip",
698 "url": "https://api.github.com/repos/slimphp/Slim/zipball/d8aabeacc3688b25e2f2dd2db91df91ec6fdd748", 700 "url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
699 "reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748", 701 "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
700 "shasum": "" 702 "shasum": ""
701 }, 703 },
702 "require": { 704 "require": {
@@ -754,7 +756,7 @@
754 "micro", 756 "micro",
755 "router" 757 "router"
756 ], 758 ],
757 "time": "2018-04-19T19:29:08+00:00" 759 "time": "2018-09-16T10:54:21+00:00"
758 } 760 }
759 ], 761 ],
760 "packages-dev": [ 762 "packages-dev": [
@@ -858,46 +860,6 @@
858 "time": "2017-10-19T19:58:43+00:00" 860 "time": "2017-10-19T19:58:43+00:00"
859 }, 861 },
860 { 862 {
861 "name": "pdepend/pdepend",
862 "version": "2.5.2",
863 "source": {
864 "type": "git",
865 "url": "https://github.com/pdepend/pdepend.git",
866 "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239"
867 },
868 "dist": {
869 "type": "zip",
870 "url": "https://api.github.com/repos/pdepend/pdepend/zipball/9daf26d0368d4a12bed1cacae1a9f3a6f0adf239",
871 "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239",
872 "shasum": ""
873 },
874 "require": {
875 "php": ">=5.3.7",
876 "symfony/config": "^2.3.0|^3|^4",
877 "symfony/dependency-injection": "^2.3.0|^3|^4",
878 "symfony/filesystem": "^2.3.0|^3|^4"
879 },
880 "require-dev": {
881 "phpunit/phpunit": "^4.8|^5.7",
882 "squizlabs/php_codesniffer": "^2.0.0"
883 },
884 "bin": [
885 "src/bin/pdepend"
886 ],
887 "type": "library",
888 "autoload": {
889 "psr-4": {
890 "PDepend\\": "src/main/php/PDepend"
891 }
892 },
893 "notification-url": "https://packagist.org/downloads/",
894 "license": [
895 "BSD-3-Clause"
896 ],
897 "description": "Official version of pdepend to be handled with Composer",
898 "time": "2017-12-13T13:21:38+00:00"
899 },
900 {
901 "name": "phpdocumentor/reflection-common", 863 "name": "phpdocumentor/reflection-common",
902 "version": "1.0.1", 864 "version": "1.0.1",
903 "source": { 865 "source": {
@@ -1044,72 +1006,6 @@
1044 "time": "2017-07-14T14:27:02+00:00" 1006 "time": "2017-07-14T14:27:02+00:00"
1045 }, 1007 },
1046 { 1008 {
1047 "name": "phpmd/phpmd",
1048 "version": "2.6.0",
1049 "source": {
1050 "type": "git",
1051 "url": "https://github.com/phpmd/phpmd.git",
1052 "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374"
1053 },
1054 "dist": {
1055 "type": "zip",
1056 "url": "https://api.github.com/repos/phpmd/phpmd/zipball/4e9924b2c157a3eb64395460fcf56b31badc8374",
1057 "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374",
1058 "shasum": ""
1059 },
1060 "require": {
1061 "ext-xml": "*",
1062 "pdepend/pdepend": "^2.5",
1063 "php": ">=5.3.9"
1064 },
1065 "require-dev": {
1066 "phpunit/phpunit": "^4.0",
1067 "squizlabs/php_codesniffer": "^2.0"
1068 },
1069 "bin": [
1070 "src/bin/phpmd"
1071 ],
1072 "type": "project",
1073 "autoload": {
1074 "psr-0": {
1075 "PHPMD\\": "src/main/php"
1076 }
1077 },
1078 "notification-url": "https://packagist.org/downloads/",
1079 "license": [
1080 "BSD-3-Clause"
1081 ],
1082 "authors": [
1083 {
1084 "name": "Manuel Pichler",
1085 "email": "github@manuel-pichler.de",
1086 "homepage": "https://github.com/manuelpichler",
1087 "role": "Project Founder"
1088 },
1089 {
1090 "name": "Other contributors",
1091 "homepage": "https://github.com/phpmd/phpmd/graphs/contributors",
1092 "role": "Contributors"
1093 },
1094 {
1095 "name": "Marc Würth",
1096 "email": "ravage@bluewin.ch",
1097 "homepage": "https://github.com/ravage84",
1098 "role": "Project Maintainer"
1099 }
1100 ],
1101 "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.",
1102 "homepage": "http://phpmd.org/",
1103 "keywords": [
1104 "mess detection",
1105 "mess detector",
1106 "pdepend",
1107 "phpmd",
1108 "pmd"
1109 ],
1110 "time": "2017-01-20T14:41:10+00:00"
1111 },
1112 {
1113 "name": "phpspec/prophecy", 1009 "name": "phpspec/prophecy",
1114 "version": "1.8.0", 1010 "version": "1.8.0",
1115 "source": { 1011 "source": {
@@ -1988,56 +1884,6 @@
1988 "time": "2017-02-18T15:18:39+00:00" 1884 "time": "2017-02-18T15:18:39+00:00"
1989 }, 1885 },
1990 { 1886 {
1991 "name": "sebastian/phpcpd",
1992 "version": "3.0.1",
1993 "source": {
1994 "type": "git",
1995 "url": "https://github.com/sebastianbergmann/phpcpd.git",
1996 "reference": "dfed51c1288790fc957c9433e2f49ab152e8a564"
1997 },
1998 "dist": {
1999 "type": "zip",
2000 "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/dfed51c1288790fc957c9433e2f49ab152e8a564",
2001 "reference": "dfed51c1288790fc957c9433e2f49ab152e8a564",
2002 "shasum": ""
2003 },
2004 "require": {
2005 "php": "^5.6|^7.0",
2006 "phpunit/php-timer": "^1.0.6",
2007 "sebastian/finder-facade": "^1.1",
2008 "sebastian/version": "^1.0|^2.0",
2009 "symfony/console": "^2.7|^3.0|^4.0"
2010 },
2011 "bin": [
2012 "phpcpd"
2013 ],
2014 "type": "library",
2015 "extra": {
2016 "branch-alias": {
2017 "dev-master": "3.0-dev"
2018 }
2019 },
2020 "autoload": {
2021 "classmap": [
2022 "src/"
2023 ]
2024 },
2025 "notification-url": "https://packagist.org/downloads/",
2026 "license": [
2027 "BSD-3-Clause"
2028 ],
2029 "authors": [
2030 {
2031 "name": "Sebastian Bergmann",
2032 "email": "sebastian@phpunit.de",
2033 "role": "lead"
2034 }
2035 ],
2036 "description": "Copy/Paste Detector (CPD) for PHP code.",
2037 "homepage": "https://github.com/sebastianbergmann/phpcpd",
2038 "time": "2017-11-16T08:49:28+00:00"
2039 },
2040 {
2041 "name": "sebastian/recursion-context", 1887 "name": "sebastian/recursion-context",
2042 "version": "2.0.0", 1888 "version": "2.0.0",
2043 "source": { 1889 "source": {
@@ -2254,81 +2100,17 @@
2254 "time": "2017-05-22T02:43:20+00:00" 2100 "time": "2017-05-22T02:43:20+00:00"
2255 }, 2101 },
2256 { 2102 {
2257 "name": "symfony/config",
2258 "version": "v3.4.14",
2259 "source": {
2260 "type": "git",
2261 "url": "https://github.com/symfony/config.git",
2262 "reference": "7b08223b7f6abd859651c56bcabf900d1627d085"
2263 },
2264 "dist": {
2265 "type": "zip",
2266 "url": "https://api.github.com/repos/symfony/config/zipball/7b08223b7f6abd859651c56bcabf900d1627d085",
2267 "reference": "7b08223b7f6abd859651c56bcabf900d1627d085",
2268 "shasum": ""
2269 },
2270 "require": {
2271 "php": "^5.5.9|>=7.0.8",
2272 "symfony/filesystem": "~2.8|~3.0|~4.0",
2273 "symfony/polyfill-ctype": "~1.8"
2274 },
2275 "conflict": {
2276 "symfony/dependency-injection": "<3.3",
2277 "symfony/finder": "<3.3"
2278 },
2279 "require-dev": {
2280 "symfony/dependency-injection": "~3.3|~4.0",
2281 "symfony/event-dispatcher": "~3.3|~4.0",
2282 "symfony/finder": "~3.3|~4.0",
2283 "symfony/yaml": "~3.0|~4.0"
2284 },
2285 "suggest": {
2286 "symfony/yaml": "To use the yaml reference dumper"
2287 },
2288 "type": "library",
2289 "extra": {
2290 "branch-alias": {
2291 "dev-master": "3.4-dev"
2292 }
2293 },
2294 "autoload": {
2295 "psr-4": {
2296 "Symfony\\Component\\Config\\": ""
2297 },
2298 "exclude-from-classmap": [
2299 "/Tests/"
2300 ]
2301 },
2302 "notification-url": "https://packagist.org/downloads/",
2303 "license": [
2304 "MIT"
2305 ],
2306 "authors": [
2307 {
2308 "name": "Fabien Potencier",
2309 "email": "fabien@symfony.com"
2310 },
2311 {
2312 "name": "Symfony Community",
2313 "homepage": "https://symfony.com/contributors"
2314 }
2315 ],
2316 "description": "Symfony Config Component",
2317 "homepage": "https://symfony.com",
2318 "time": "2018-07-26T11:19:56+00:00"
2319 },
2320 {
2321 "name": "symfony/console", 2103 "name": "symfony/console",
2322 "version": "v3.4.14", 2104 "version": "v3.4.17",
2323 "source": { 2105 "source": {
2324 "type": "git", 2106 "type": "git",
2325 "url": "https://github.com/symfony/console.git", 2107 "url": "https://github.com/symfony/console.git",
2326 "reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73" 2108 "reference": "3b2b415d4c48fbefca7dc742aa0a0171bfae4e0b"
2327 }, 2109 },
2328 "dist": { 2110 "dist": {
2329 "type": "zip", 2111 "type": "zip",
2330 "url": "https://api.github.com/repos/symfony/console/zipball/6b217594552b9323bcdcfc14f8a0ce126e84cd73", 2112 "url": "https://api.github.com/repos/symfony/console/zipball/3b2b415d4c48fbefca7dc742aa0a0171bfae4e0b",
2331 "reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73", 2113 "reference": "3b2b415d4c48fbefca7dc742aa0a0171bfae4e0b",
2332 "shasum": "" 2114 "shasum": ""
2333 }, 2115 },
2334 "require": { 2116 "require": {
@@ -2384,20 +2166,20 @@
2384 ], 2166 ],
2385 "description": "Symfony Console Component", 2167 "description": "Symfony Console Component",
2386 "homepage": "https://symfony.com", 2168 "homepage": "https://symfony.com",
2387 "time": "2018-07-26T11:19:56+00:00" 2169 "time": "2018-10-02T16:33:53+00:00"
2388 }, 2170 },
2389 { 2171 {
2390 "name": "symfony/debug", 2172 "name": "symfony/debug",
2391 "version": "v3.4.14", 2173 "version": "v3.4.17",
2392 "source": { 2174 "source": {
2393 "type": "git", 2175 "type": "git",
2394 "url": "https://github.com/symfony/debug.git", 2176 "url": "https://github.com/symfony/debug.git",
2395 "reference": "d5a058ff6ecad26b30c1ba452241306ea34c65cc" 2177 "reference": "0a612e9dfbd2ccce03eb174365f31ecdca930ff6"
2396 }, 2178 },
2397 "dist": { 2179 "dist": {
2398 "type": "zip", 2180 "type": "zip",
2399 "url": "https://api.github.com/repos/symfony/debug/zipball/d5a058ff6ecad26b30c1ba452241306ea34c65cc", 2181 "url": "https://api.github.com/repos/symfony/debug/zipball/0a612e9dfbd2ccce03eb174365f31ecdca930ff6",
2400 "reference": "d5a058ff6ecad26b30c1ba452241306ea34c65cc", 2182 "reference": "0a612e9dfbd2ccce03eb174365f31ecdca930ff6",
2401 "shasum": "" 2183 "shasum": ""
2402 }, 2184 },
2403 "require": { 2185 "require": {
@@ -2440,141 +2222,20 @@
2440 ], 2222 ],
2441 "description": "Symfony Debug Component", 2223 "description": "Symfony Debug Component",
2442 "homepage": "https://symfony.com", 2224 "homepage": "https://symfony.com",
2443 "time": "2018-07-26T11:19:56+00:00" 2225 "time": "2018-10-02T16:33:53+00:00"
2444 },
2445 {
2446 "name": "symfony/dependency-injection",
2447 "version": "v3.4.14",
2448 "source": {
2449 "type": "git",
2450 "url": "https://github.com/symfony/dependency-injection.git",
2451 "reference": "1c0e679e522591fd744fdf242fec41a43d62b2b1"
2452 },
2453 "dist": {
2454 "type": "zip",
2455 "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/1c0e679e522591fd744fdf242fec41a43d62b2b1",
2456 "reference": "1c0e679e522591fd744fdf242fec41a43d62b2b1",
2457 "shasum": ""
2458 },
2459 "require": {
2460 "php": "^5.5.9|>=7.0.8",
2461 "psr/container": "^1.0"
2462 },
2463 "conflict": {
2464 "symfony/config": "<3.3.7",
2465 "symfony/finder": "<3.3",
2466 "symfony/proxy-manager-bridge": "<3.4",
2467 "symfony/yaml": "<3.4"
2468 },
2469 "provide": {
2470 "psr/container-implementation": "1.0"
2471 },
2472 "require-dev": {
2473 "symfony/config": "~3.3|~4.0",
2474 "symfony/expression-language": "~2.8|~3.0|~4.0",
2475 "symfony/yaml": "~3.4|~4.0"
2476 },
2477 "suggest": {
2478 "symfony/config": "",
2479 "symfony/expression-language": "For using expressions in service container configuration",
2480 "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
2481 "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
2482 "symfony/yaml": ""
2483 },
2484 "type": "library",
2485 "extra": {
2486 "branch-alias": {
2487 "dev-master": "3.4-dev"
2488 }
2489 },
2490 "autoload": {
2491 "psr-4": {
2492 "Symfony\\Component\\DependencyInjection\\": ""
2493 },
2494 "exclude-from-classmap": [
2495 "/Tests/"
2496 ]
2497 },
2498 "notification-url": "https://packagist.org/downloads/",
2499 "license": [
2500 "MIT"
2501 ],
2502 "authors": [
2503 {
2504 "name": "Fabien Potencier",
2505 "email": "fabien@symfony.com"
2506 },
2507 {
2508 "name": "Symfony Community",
2509 "homepage": "https://symfony.com/contributors"
2510 }
2511 ],
2512 "description": "Symfony DependencyInjection Component",
2513 "homepage": "https://symfony.com",
2514 "time": "2018-07-29T15:19:31+00:00"
2515 },
2516 {
2517 "name": "symfony/filesystem",
2518 "version": "v3.4.14",
2519 "source": {
2520 "type": "git",
2521 "url": "https://github.com/symfony/filesystem.git",
2522 "reference": "a59f917e3c5d82332514cb4538387638f5bde2d6"
2523 },
2524 "dist": {
2525 "type": "zip",
2526 "url": "https://api.github.com/repos/symfony/filesystem/zipball/a59f917e3c5d82332514cb4538387638f5bde2d6",
2527 "reference": "a59f917e3c5d82332514cb4538387638f5bde2d6",
2528 "shasum": ""
2529 },
2530 "require": {
2531 "php": "^5.5.9|>=7.0.8",
2532 "symfony/polyfill-ctype": "~1.8"
2533 },
2534 "type": "library",
2535 "extra": {
2536 "branch-alias": {
2537 "dev-master": "3.4-dev"
2538 }
2539 },
2540 "autoload": {
2541 "psr-4": {
2542 "Symfony\\Component\\Filesystem\\": ""
2543 },
2544 "exclude-from-classmap": [
2545 "/Tests/"
2546 ]
2547 },
2548 "notification-url": "https://packagist.org/downloads/",
2549 "license": [
2550 "MIT"
2551 ],
2552 "authors": [
2553 {
2554 "name": "Fabien Potencier",
2555 "email": "fabien@symfony.com"
2556 },
2557 {
2558 "name": "Symfony Community",
2559 "homepage": "https://symfony.com/contributors"
2560 }
2561 ],
2562 "description": "Symfony Filesystem Component",
2563 "homepage": "https://symfony.com",
2564 "time": "2018-07-26T11:19:56+00:00"
2565 }, 2226 },
2566 { 2227 {
2567 "name": "symfony/finder", 2228 "name": "symfony/finder",
2568 "version": "v3.4.14", 2229 "version": "v3.4.17",
2569 "source": { 2230 "source": {
2570 "type": "git", 2231 "type": "git",
2571 "url": "https://github.com/symfony/finder.git", 2232 "url": "https://github.com/symfony/finder.git",
2572 "reference": "8a84fcb207451df0013b2c74cbbf1b62d47b999a" 2233 "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d"
2573 }, 2234 },
2574 "dist": { 2235 "dist": {
2575 "type": "zip", 2236 "type": "zip",
2576 "url": "https://api.github.com/repos/symfony/finder/zipball/8a84fcb207451df0013b2c74cbbf1b62d47b999a", 2237 "url": "https://api.github.com/repos/symfony/finder/zipball/54ba444dddc5bd5708a34bd095ea67c6eb54644d",
2577 "reference": "8a84fcb207451df0013b2c74cbbf1b62d47b999a", 2238 "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d",
2578 "shasum": "" 2239 "shasum": ""
2579 }, 2240 },
2580 "require": { 2241 "require": {
@@ -2610,7 +2271,7 @@
2610 ], 2271 ],
2611 "description": "Symfony Finder Component", 2272 "description": "Symfony Finder Component",
2612 "homepage": "https://symfony.com", 2273 "homepage": "https://symfony.com",
2613 "time": "2018-07-26T11:19:56+00:00" 2274 "time": "2018-10-03T08:46:40+00:00"
2614 }, 2275 },
2615 { 2276 {
2616 "name": "symfony/polyfill-ctype", 2277 "name": "symfony/polyfill-ctype",
@@ -2731,16 +2392,16 @@
2731 }, 2392 },
2732 { 2393 {
2733 "name": "symfony/yaml", 2394 "name": "symfony/yaml",
2734 "version": "v3.4.14", 2395 "version": "v3.4.17",
2735 "source": { 2396 "source": {
2736 "type": "git", 2397 "type": "git",
2737 "url": "https://github.com/symfony/yaml.git", 2398 "url": "https://github.com/symfony/yaml.git",
2738 "reference": "810af2d35fc72b6cf5c01116806d2b65ccaaf2e2" 2399 "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f"
2739 }, 2400 },
2740 "dist": { 2401 "dist": {
2741 "type": "zip", 2402 "type": "zip",
2742 "url": "https://api.github.com/repos/symfony/yaml/zipball/810af2d35fc72b6cf5c01116806d2b65ccaaf2e2", 2403 "url": "https://api.github.com/repos/symfony/yaml/zipball/640b6c27fed4066d64b64d5903a86043f4a4de7f",
2743 "reference": "810af2d35fc72b6cf5c01116806d2b65ccaaf2e2", 2404 "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f",
2744 "shasum": "" 2405 "shasum": ""
2745 }, 2406 },
2746 "require": { 2407 "require": {
@@ -2786,7 +2447,7 @@
2786 ], 2447 ],
2787 "description": "Symfony Yaml Component", 2448 "description": "Symfony Yaml Component",
2788 "homepage": "https://symfony.com", 2449 "homepage": "https://symfony.com",
2789 "time": "2018-07-26T11:19:56+00:00" 2450 "time": "2018-10-02T16:33:53+00:00"
2790 }, 2451 },
2791 { 2452 {
2792 "name": "theseer/fdomdocument", 2453 "name": "theseer/fdomdocument",
@@ -2882,8 +2543,7 @@
2882 "aliases": [], 2543 "aliases": [],
2883 "minimum-stability": "stable", 2544 "minimum-stability": "stable",
2884 "stability-flags": { 2545 "stability-flags": {
2885 "pubsubhubbub/publisher": 20, 2546 "pubsubhubbub/publisher": 20
2886 "phpmd/phpmd": 0
2887 }, 2547 },
2888 "prefer-stable": false, 2548 "prefer-stable": false,
2889 "prefer-lowest": false, 2549 "prefer-lowest": false,
diff --git a/doc/custom_theme/main.html b/doc/custom_theme/main.html
new file mode 100644
index 00000000..cc2a703e
--- /dev/null
+++ b/doc/custom_theme/main.html
@@ -0,0 +1,23 @@
1{% extends "base.html" %}
2
3{#
4The entry point for the ReadTheDocs Theme.
5
6Any theme customisations should override this file to redefine blocks defined in
7the various templates. The custom theme should only need to define a main.html
8which `{% extends "base.html" %}` and defines various blocks which will replace
9the blocks defined in base.html and its included child templates.
10#}
11
12{%- block site_meta %}
13<meta charset="utf-8">
14<meta http-equiv="X-UA-Compatible" content="IE=edge">
15<meta name="viewport" content="width=device-width, initial-scale=1.0">
16
17{%- if 'media.readthedocs.org' not in config.extra_css[0] %}
18<meta name="robots" content="noindex, nofollow">
19{%- endif %}
20
21{% if page and page.is_homepage %}<meta name="description" content="{{ config.site_description }}">{% endif %}
22{% if config.site_author %}<meta name="author" content="{{ config.site_author }}">{% endif %}
23{%- endblock %}
diff --git a/doc/md/Community-&-Related-software.md b/doc/md/Community-&-Related-software.md
index 49c20c9c..67fdd70f 100644
--- a/doc/md/Community-&-Related-software.md
+++ b/doc/md/Community-&-Related-software.md
@@ -51,7 +51,7 @@ See [Theming](Theming) for a list of community-contributed themes, and an instal
51- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among French shaarliers: [shaarli.fr](http://shaarli.fr/)) 51- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among French shaarliers: [shaarli.fr](http://shaarli.fr/))
52- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis 52- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis
53- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli 53- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli
54- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming). 54- [Self dead link](https://framagit.org/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://framagit.org/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).
55- [Bookmark Archiver](https://github.com/pirate/bookmark-archiver) - Save an archived copy of all websites starred using browser bookmarks/Shaarli/Delicious/Instapaper/Unmark.it/Pocket/Pinboard. Outputs browseable html. 55- [Bookmark Archiver](https://github.com/pirate/bookmark-archiver) - Save an archived copy of all websites starred using browser bookmarks/Shaarli/Delicious/Instapaper/Unmark.it/Pocket/Pinboard. Outputs browseable html.
56 56
57## Alternatives to Shaarli 57## Alternatives to Shaarli
diff --git a/doc/md/Server-configuration.md b/doc/md/Server-configuration.md
index e281dc85..78083a46 100644
--- a/doc/md/Server-configuration.md
+++ b/doc/md/Server-configuration.md
@@ -18,7 +18,7 @@ Version | Status | Shaarli compatibility
187.2 | Supported | Yes 187.2 | Supported | Yes
197.1 | Supported | Yes 197.1 | Supported | Yes
207.0 | Supported | Yes 207.0 | Supported | Yes
215.6 | Supported | Yes 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)
245.3 | EOL: 2014-08-14 | Yes (up to Shaarli 0.8.x) 245.3 | EOL: 2014-08-14 | Yes (up to Shaarli 0.8.x)
@@ -397,6 +397,7 @@ http {
397``` 397```
398 398
399## Proxies 399## Proxies
400
400If Shaarli is served behind a proxy (i.e. there is a proxy server between clients and the web server hosting Shaarli), please refer to the proxy server documentation for proper configuration. In particular, you have to ensure that the following server variables are properly set: 401If Shaarli is served behind a proxy (i.e. there is a proxy server between clients and the web server hosting Shaarli), please refer to the proxy server documentation for proper configuration. In particular, you have to ensure that the following server variables are properly set:
401 402
402- `X-Forwarded-Proto` 403- `X-Forwarded-Proto`
@@ -405,6 +406,12 @@ If Shaarli is served behind a proxy (i.e. there is a proxy server between client
405 406
406See also [proxy-related](https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&q=label%3Aproxy+) issues. 407See also [proxy-related](https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&q=label%3Aproxy+) issues.
407 408
409## Robots and crawlers
410
411Shaarli disallows indexing and crawling of your local documentation pages by search engines, using `<meta name="robots">` HTML tags.
412Your Shaarli instance and other pages you host may still be indexed by various robots on the public Internet.
413You may want to setup a robots.txt file or other crawler control mechanism on your server.
414See [[1]](https://en.wikipedia.org/wiki/Robots_exclusion_standard), [[2]](https://support.google.com/webmasters/answer/6062608?hl=en) and [[3]](https://developers.google.com/search/reference/robots_meta_tag)
408 415
409## See also 416## See also
410 417
diff --git a/doc/md/Sharing-content.md b/doc/md/Sharing-content.md
index 4910ff6c..9a16fc62 100644
--- a/doc/md/Sharing-content.md
+++ b/doc/md/Sharing-content.md
@@ -15,7 +15,6 @@ While logged in to your Shaarli, you can add new Shaares in several ways:
15 15
16 * [+Shaare button](#shaare-button) 16 * [+Shaare button](#shaare-button)
17 * [Bookmarklet](#bookmarklet) 17 * [Bookmarklet](#bookmarklet)
18 * [Firefox Share](#firefox-share)
19 * Third-party [apps and browser addons](Community-&-Related-software.md#mobile-apps) 18 * Third-party [apps and browser addons](Community-&-Related-software.md#mobile-apps)
20 * [REST API](https://shaarli.github.io/api-documentation/) 19 * [REST API](https://shaarli.github.io/api-documentation/)
21 20
@@ -52,22 +51,6 @@ bookmarklet in your browser! The same `New Shaare` dialog as above is displayed.
52![](images/bookmarklet.png) 51![](images/bookmarklet.png)
53 52
54 53
55### Firefox Share
56
57Before using Firefox Share, you must first add Shaarli as a sharing provider:
58
59- Click the `Tools` button in the top bar
60- Click the `✚Add to Firefox social` button and accept the activation.
61
62Once this is done, you can share any URL you are visiting by clicking the Firefox
63_Share_ button ![images/firefoxshare.png](images/firefoxshare.png)
64
65| Note | Firefox Share is no longer available for Firefox 57 and later versions. |
66|---------|---------|
67
68| Note | Your Shaarli instance must be hosted on an HTTPS (SSL/TLS secure connection) enabled server for Firefox Share to work. Firefox Share will not work over plaintext HTTP connections. |
69|---------|---------|
70
71-------------------------------------------------------------------------------- 54--------------------------------------------------------------------------------
72 55
73## Editing Shaares 56## Editing Shaares
diff --git a/doc/md/images/icon.png b/doc/md/images/icon.png
new file mode 100644
index 00000000..530d7469
--- /dev/null
+++ b/doc/md/images/icon.png
Binary files differ
diff --git a/doc/md/index.md b/doc/md/index.md
index c18332b4..220eeec1 100644
--- a/doc/md/index.md
+++ b/doc/md/index.md
@@ -1,25 +1,19 @@
1# [Shaarli](https://github.com/shaarli/Shaarli/) documentation 1# <img src="images/icon.png" width="20px" height="20px"> Shaarli
2 2
3The personal, minimalist, super-fast, database free, bookmarking service. 3The personal, minimalist, super-fast, database free, bookmarking service.
4 4
5Do you want to share the links you discover? 5Do you want to share the links you discover?
6Shaarli is a minimalist link sharing service that you can install on your own server. 6Shaarli is a minimalist bookmark manager and link sharing service that you can install on your own server.
7It is designed to be personal (single-user), fast and handy. 7It is designed to be personal (single-user), fast and handy.
8 8
9<!-- TODO screenshots --> 9<!-- TODO screenshots -->
10 10
11Here you can find some info on how to use, configure, tweak and solve problems with your Shaarli. 11Visit the pages in the sidebar to find information on how to setup, use, configure, tweak and troubleshoot Shaarli.
12For general information, read the [README](https://github.com/shaarli/Shaarli/blob/master/README.md).
13 12
14If you have any questions or ideas, please join the [chat](https://gitter.im/shaarli/Shaarli) (also reachable via [IRC](https://irc.gitter.im/)), post them in our [general discussion](https://github.com/shaarli/Shaarli/issues/308) or read the current [issues](https://github.com/shaarli/Shaarli/issues).
15
16If you've found a bug, please create a [new issue](https://github.com/shaarli/Shaarli/issues/new).
17
18If you would like a feature added to Shaarli, check the issues labeled [`feature`](https://github.com/shaarli/Shaarli/labels/feature), [`enhancement`](https://github.com/shaarli/Shaarli/labels/enhancement), and [`plugin`](https://github.com/shaarli/Shaarli/labels/plugin).
19 13
20* [GitHub project page](https://github.com/shaarli/Shaarli) 14* [GitHub project page](https://github.com/shaarli/Shaarli)
21* [Online documentation](https://shaarli.readthedocs.io/) (this page) 15* [Online documentation](https://shaarli.readthedocs.io/)
22* [Latest Shaarli releases](https://github.com/shaarli/Shaarli/releases) 16* [Latest releases](https://github.com/shaarli/Shaarli/releases)
23* [Changelog](https://github.com/shaarli/Shaarli/blob/master/CHANGELOG.md) 17* [Changelog](https://github.com/shaarli/Shaarli/blob/master/CHANGELOG.md)
24 18
25 19
@@ -30,87 +24,70 @@ It runs the latest development version of Shaarli and is updated/reset daily.
30 24
31Login: `demo`; Password: `demo` 25Login: `demo`; Password: `demo`
32 26
33<!-- TODO review everything below this point -->
34
35
36## Features 27## Features
37 28
38Shaarli can be used: 29Shaarli can be used:
39 30
40- to share, comment and save interesting links and news. 31- to share, comment and save interesting links and news
41- to bookmark useful/frequent personal links (as private links) and share them between computers. 32- to bookmark useful/frequent links and share them between computers
42- as a minimal blog/microblog/writing platform (no character limit). 33- as a minimal blog/microblog/writing platform
43- as a read-it-later list (for example items tagged `readlater`). 34- as a read-it-later list
44- to draft and save articles/posts/ideas. 35- to draft and save articles/posts/ideas
45- to keep code snippets. 36- to keep notes, documentation and code snippets
46- to keep notes and documentation. 37- as a shared clipboard/notepad/pastebin between machines
47- as a shared clipboard/notepad/pastebin between machines. 38- as a todo list
48- as a todo list. 39- to store media playlists
49- to store playlists (e.g. with the `music` or `video` tags).
50- to keep extracts/comments from webpages that may disappear. 40- to keep extracts/comments from webpages that may disappear.
51- to keep track of ongoing discussions (for example items tagged `discussion`). 41- to keep track of ongoing discussions
52- [to feed RSS aggregators](http://shaarli.chassegnouf.net/?9Efeiw) (planets) with specific tags. 42- to feed other blogs, aggregators, social networks... using RSS feeds
53- to feed other social networks, blogs... using RSS feeds and external services (dlvr.it, ifttt.com ...).
54 43
55### Interface 44### Edit, view and search your links
56 45
57- minimalist design (simple is beautiful) 46- Minimalist design
58- FAST 47- FAST
59- ATOM and RSS feeds 48- Customizable link titles and descriptions
60- views: 49- Tags to organize your links (features tag autocompletion, renaming, merging and deletion)
61 - paginated link list (with image and video thumbnails) 50- Search by tag or using the full-text search
62 - tag cloud 51- Public and private links (visible only to logged-in users)
63 - picture wall: image and video thumbnails (with lazy loading) 52- Unique permalinks for easy reference
64 - daily: newspaper-like daily digest 53- Paginated link list (with image and video thumbnails)
65 - daily RSS feed 54- Tag cloud and list views
66- permalinks for easy reference 55- Picture wall: image and video thumbnails view (with lazy loading)
67- links can be public or private 56- ATOM and RSS feeds (can also be filtered using tags or text search)
68- thumbnail generation for images and video services 57- Daily: newspaper-like daily digest (and daily RSS feed)
69- URL cleanup: automatic removal of `?utm_source=...`, `fb=...` 58- URL cleanup: automatic removal of `?utm_source=...`, `fb=...`
70- extensible through [plugins](https://shaarli.readthedocs.io/en/master/Plugins/#plugin-usage) 59- Extensible through [plugins](https://shaarli.readthedocs.io/en/master/Plugins/#plugin-usage)
71
72### Tag, view and search your links
73
74- add a custom title and description to archived links
75- add tags to classify and search links
76 - features tag autocompletion, renaming, merging and deletion
77- full-text and tag search
78 60
79### Easy setup 61### Easy setup
80 62
81- dead-simple installation: drop the files, open the page 63- Dead-simple installation: drop the files, open the page
82- links are stored in a file 64- Links are stored in a file (no database required, easy backup: simply copy the datastore file)
83 - compact storage 65- Import and export links as Netscape bookmarks compatible with most Web browsers
84 - no database required
85 - easy backup: simply copy the datastore file
86- import and export links as Netscape bookmarks
87 66
88### Accessibility 67### Accessibility
89 68
90- bookmarlet to share links in one click 69- Bookmarklet and other tools to share links in one click
91- support for mobile browsers 70- Support for mobile browsers
92- degrades gracefully with Javascript disabled 71- Degrades gracefully with Javascript disabled
93- easy page customization through HTML/CSS/RainTPL 72- Easy page customization through HTML/CSS/RainTPL
94 73
95### Security 74### Security
96 75
97- discreet pop-up notification when a new release is available 76- Discreet pop-up notification when a new release is available
98- bruteforce protection on the login form 77- Bruteforce protection on the login form
99- protected against [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) and session cookie hijacking 78- Protected against [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) and session cookie hijacking
100 79
101<!-- TODO Limitations --> 80<!-- TODO Limitations -->
102 81
103### REST API 82### REST API
104 83
105Easily extensible by any client using the REST API exposed by Shaarli. 84- Easily extensible by any client using the REST API exposed by Shaarli ([API documentation](http://shaarli.github.io/api-documentation/)).
106
107See the [API documentation](http://shaarli.github.io/api-documentation/).
108 85
109## About 86## About
110 87
111### Shaarli community fork 88### Shaarli community fork
112 89
113This friendly fork is maintained by the Shaarli community at https://github.com/shaarli/Shaarli 90This friendly fork is maintained by the Shaarli community at <https://github.com/shaarli/Shaarli>
114 91
115This is a community fork of the original [Shaarli](https://github.com/sebsauvage/Shaarli/) project by [Sébastien Sauvage](http://sebsauvage.net/). 92This is a community fork of the original [Shaarli](https://github.com/sebsauvage/Shaarli/) project by [Sébastien Sauvage](http://sebsauvage.net/).
116 93
@@ -123,16 +100,15 @@ in this repository, and will keep maintaining the project for the foreseeable
123future, while keeping Shaarli simple and efficient. 100future, while keeping Shaarli simple and efficient.
124 101
125 102
126### Contributing 103### Contributing and getting help
127 104
128If you'd like to help, please: 105Feedback is very appreciated!
129 106
130- have a look at the open [issues](https://github.com/shaarli/Shaarli/issues) 107- If you have any questions or ideas, please join the [chat](https://gitter.im/shaarli/Shaarli) (also reachable via [IRC](https://irc.gitter.im/)), post them in our [general discussion](https://github.com/shaarli/Shaarli/issues/308) or read the current [issues](https://github.com/shaarli/Shaarli/issues).
131and [pull requests](https://github.com/shaarli/Shaarli/pulls) 108- Have a look at the open [issues](https://github.com/shaarli/Shaarli/issues) and [pull requests](https://github.com/shaarli/Shaarli/pulls)
132- feel free to report bugs (feedback is much appreciated) 109- If you would like a feature added to Shaarli, check the issues labeled [`feature`](https://github.com/shaarli/Shaarli/labels/feature), [`enhancement`](https://github.com/shaarli/Shaarli/labels/enhancement), and [`plugin`](https://github.com/shaarli/Shaarli/labels/plugin).
133- suggest new features and improvements to both code and [documentation](https://github.com/shaarli/Shaarli/tree/master/doc/md/) 110- If you've found a bug, please create a [new issue](https://github.com/shaarli/Shaarli/issues/new).
134- propose solutions to existing problems 111- Feel free to propose solutions to existing problems, help us improve the documentation and translations, and submit pull requests :-)
135- submit pull requests :-)
136 112
137 113
138### License 114### License
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po
index 155eb52e..102c80da 100644
--- a/inc/languages/fr/LC_MESSAGES/shaarli.po
+++ b/inc/languages/fr/LC_MESSAGES/shaarli.po
@@ -1,15 +1,15 @@
1msgid "" 1msgid ""
2msgstr "" 2msgstr ""
3"Project-Id-Version: Shaarli\n" 3"Project-Id-Version: Shaarli\n"
4"POT-Creation-Date: 2018-07-17 13:04+0200\n" 4"POT-Creation-Date: 2018-10-06 13:08+0200\n"
5"PO-Revision-Date: 2018-07-17 13:07+0200\n" 5"PO-Revision-Date: 2018-10-06 13:08+0200\n"
6"Last-Translator: \n" 6"Last-Translator: \n"
7"Language-Team: Shaarli\n" 7"Language-Team: Shaarli\n"
8"Language: fr_FR\n" 8"Language: fr_FR\n"
9"MIME-Version: 1.0\n" 9"MIME-Version: 1.0\n"
10"Content-Type: text/plain; charset=UTF-8\n" 10"Content-Type: text/plain; charset=UTF-8\n"
11"Content-Transfer-Encoding: 8bit\n" 11"Content-Transfer-Encoding: 8bit\n"
12"X-Generator: Poedit 2.0.9\n" 12"X-Generator: Poedit 2.1.1\n"
13"X-Poedit-Basepath: ../../../..\n" 13"X-Poedit-Basepath: ../../../..\n"
14"Plural-Forms: nplurals=2; plural=(n > 1);\n" 14"Plural-Forms: nplurals=2; plural=(n > 1);\n"
15"X-Poedit-SourceCharset: UTF-8\n" 15"X-Poedit-SourceCharset: UTF-8\n"
@@ -48,7 +48,7 @@ msgstr "le fichier n'est pas accessible en écriture"
48#: application/Cache.php:16 48#: application/Cache.php:16
49#, php-format 49#, php-format
50msgid "Cannot purge %s: no directory" 50msgid "Cannot purge %s: no directory"
51msgstr "Impossible de purger %s: le répertoire n'existe pas" 51msgstr "Impossible de purger %s : le répertoire n'existe pas"
52 52
53#: application/FeedBuilder.php:151 53#: application/FeedBuilder.php:151
54msgid "Direct link" 54msgid "Direct link"
@@ -98,17 +98,15 @@ msgstr "Vous devez utiliser un entier comme clé."
98 98
99#: application/LinkDB.php:145 99#: application/LinkDB.php:145
100msgid "Array offset and link ID must be equal." 100msgid "Array offset and link ID must be equal."
101msgstr "La clé du tableau et l'ID du lien doivent être égaux." 101msgstr "La clé du tableau et l'ID du lien doivent être identiques."
102 102
103#: application/LinkDB.php:251 103#: application/LinkDB.php:251
104#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
105#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
106#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14 104#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14
107#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48 105#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
108msgid "" 106msgid ""
109"The personal, minimalist, super-fast, database free, bookmarking service" 107"The personal, minimalist, super-fast, database free, bookmarking service"
110msgstr "" 108msgstr ""
111"Le gestionnaire de marque-page personnel, minimaliste, et sans base de " 109"Le gestionnaire de marque-pages personnel, minimaliste, et sans base de "
112"données" 110"données"
113 111
114#: application/LinkDB.php:253 112#: application/LinkDB.php:253
@@ -125,11 +123,11 @@ msgstr ""
125"Bienvenue sur Shaarli ! Ceci est votre premier marque-page public. Pour me " 123"Bienvenue sur Shaarli ! Ceci est votre premier marque-page public. Pour me "
126"modifier ou me supprimer, vous devez d'abord vous connecter.\n" 124"modifier ou me supprimer, vous devez d'abord vous connecter.\n"
127"\n" 125"\n"
128"Pour apprendre comment utiliser Shaarli, consultez le lien « Documentation » " 126"Pour apprendre à utiliser Shaarli, consultez le lien « Documentation » en "
129"en bas de page.\n" 127"bas de page.\n"
130"\n" 128"\n"
131"Vous utilisez la version supportée par la communauté du projet original " 129"Vous utilisez la version supportée par la communauté du projet original "
132"Shaarli, de Sébastien Sauvage." 130"Shaarli de Sébastien Sauvage."
133 131
134#: application/LinkDB.php:267 132#: application/LinkDB.php:267
135msgid "My secret stuff... - Pastebin.com" 133msgid "My secret stuff... - Pastebin.com"
@@ -185,14 +183,14 @@ msgid ""
185"php-gd extension must be loaded to use thumbnails. Thumbnails are now " 183"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
186"disabled. Please reload the page." 184"disabled. Please reload the page."
187msgstr "" 185msgstr ""
188"php-gd extension must be loaded to use thumbnails. Thumbnails are now " 186"l'extension php-gd doit être chargée pour utiliser les miniatures. Les "
189"disabled. Please reload the page." 187"miniatures sont désormais désactivées. Rechargez la page."
190 188
191#: application/Updater.php:86 189#: application/Updater.php:86
192msgid "Couldn't retrieve Updater class methods." 190msgid "Couldn't retrieve Updater class methods."
193msgstr "Impossible de récupérer les méthodes de la classe Updater." 191msgstr "Impossible de récupérer les méthodes de la classe Updater."
194 192
195#: application/Updater.php:514 index.php:1023 193#: application/Updater.php:514 index.php:1022
196msgid "" 194msgid ""
197"You have enabled or changed thumbnails mode. <a href=\"?do=thumbs_update" 195"You have enabled or changed thumbnails mode. <a href=\"?do=thumbs_update"
198"\">Please synchronize them</a>." 196"\">Please synchronize them</a>."
@@ -200,17 +198,17 @@ msgstr ""
200"Vous avez activé ou changé le mode de miniatures. <a href=\"?do=thumbs_update" 198"Vous avez activé ou changé le mode de miniatures. <a href=\"?do=thumbs_update"
201"\">Merci de les synchroniser</a>." 199"\">Merci de les synchroniser</a>."
202 200
203#: application/Updater.php:566 201#: application/Updater.php:586
204msgid "An error occurred while running the update " 202msgid "An error occurred while running the update "
205msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " 203msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour "
206 204
207#: application/Updater.php:606 205#: application/Updater.php:626
208msgid "Updates file path is not set, can't write updates." 206msgid "Updates file path is not set, can't write updates."
209msgstr "" 207msgstr ""
210"Le chemin vers le fichier de mise à jour n'est pas défini, impossible " 208"Le chemin vers le fichier de mise à jour n'est pas défini, impossible "
211"d'écrire les mises à jour." 209"d'écrire les mises à jour."
212 210
213#: application/Updater.php:611 211#: application/Updater.php:631
214msgid "Unable to write updates in " 212msgid "Unable to write updates in "
215msgstr "Impossible d'écrire les mises à jour dans " 213msgstr "Impossible d'écrire les mises à jour dans "
216 214
@@ -286,74 +284,66 @@ msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard."
286 284
287#: index.php:273 285#: index.php:273
288msgid "Wrong login/password." 286msgid "Wrong login/password."
289msgstr "Nom d'utilisateur ou mot de passe incorrects." 287msgstr "Nom d'utilisateur ou mot de passe incorrect(s)."
290 288
291#: index.php:483 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 289#: index.php:482 tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:46
292#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:46
293msgid "Daily" 290msgid "Daily"
294msgstr "Quotidien" 291msgstr "Quotidien"
295 292
296#: index.php:589 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 293#: index.php:588 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
297#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 294#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
298#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
299#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99
300#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:75 295#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:75
301#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:99 296#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:99
302msgid "Login" 297msgid "Login"
303msgstr "Connexion" 298msgstr "Connexion"
304 299
305#: index.php:606 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 300#: index.php:605 tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:41
306#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:41
307msgid "Picture wall" 301msgid "Picture wall"
308msgstr "Mur d'images" 302msgstr "Mur d'images"
309 303
310#: index.php:683 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 304#: index.php:682 tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36
311#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36
312#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 305#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
313msgid "Tag cloud" 306msgid "Tag cloud"
314msgstr "Nuage de tags" 307msgstr "Nuage de tags"
315 308
316#: index.php:716 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 309#: index.php:715 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
317msgid "Tag list" 310msgid "Tag list"
318msgstr "Liste des tags" 311msgstr "Liste des tags"
319 312
320#: index.php:941 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 313#: index.php:940 tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31
321#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31
322msgid "Tools" 314msgid "Tools"
323msgstr "Outils" 315msgstr "Outils"
324 316
325#: index.php:950 317#: index.php:949
326msgid "You are not supposed to change a password on an Open Shaarli." 318msgid "You are not supposed to change a password on an Open Shaarli."
327msgstr "" 319msgstr ""
328"Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert." 320"Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert."
329 321
330#: index.php:955 index.php:997 index.php:1085 index.php:1116 index.php:1221 322#: index.php:954 index.php:996 index.php:1084 index.php:1116 index.php:1221
331msgid "Wrong token." 323msgid "Wrong token."
332msgstr "Jeton invalide." 324msgstr "Jeton invalide."
333 325
334#: index.php:960 326#: index.php:959
335msgid "The old password is not correct." 327msgid "The old password is not correct."
336msgstr "L'ancien mot de passe est incorrect." 328msgstr "L'ancien mot de passe est incorrect."
337 329
338#: index.php:980 330#: index.php:979
339msgid "Your password has been changed" 331msgid "Your password has been changed"
340msgstr "Votre mot de passe a été modifié" 332msgstr "Votre mot de passe a été modifié"
341 333
342#: index.php:985 334#: index.php:984 tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
343#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
344#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
345msgid "Change password" 335msgid "Change password"
346msgstr "Modification du mot de passe" 336msgstr "Modifier le mot de passe"
347 337
348#: index.php:1043 338#: index.php:1042
349msgid "Configuration was saved." 339msgid "Configuration was saved."
350msgstr "La configuration a été sauvegardé." 340msgstr "La configuration a été sauvegardée."
351 341
352#: index.php:1068 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 342#: index.php:1067 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
353msgid "Configure" 343msgid "Configure"
354msgstr "Configurer" 344msgstr "Configurer"
355 345
356#: index.php:1079 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 346#: index.php:1078 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
357#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 347#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
358msgid "Manage tags" 348msgid "Manage tags"
359msgstr "Gérer les tags" 349msgstr "Gérer les tags"
@@ -381,7 +371,6 @@ msgid "Edit"
381msgstr "Modifier" 371msgstr "Modifier"
382 372
383#: index.php:1281 index.php:1351 373#: index.php:1281 index.php:1351
384#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
385#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26 374#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26
386msgid "Shaare" 375msgid "Shaare"
387msgstr "Shaare" 376msgstr "Shaare"
@@ -390,15 +379,19 @@ msgstr "Shaare"
390msgid "Note: " 379msgid "Note: "
391msgstr "Note : " 380msgstr "Note : "
392 381
393#: index.php:1360 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 382#: index.php:1359
383msgid "Invalid link ID provided"
384msgstr ""
385
386#: index.php:1379
394msgid "Export" 387msgid "Export"
395msgstr "Exporter" 388msgstr "Exporter"
396 389
397#: index.php:1422 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 390#: index.php:1441
398msgid "Import" 391msgid "Import"
399msgstr "Importer" 392msgstr "Importer"
400 393
401#: index.php:1432 394#: index.php:1451
402#, php-format 395#, php-format
403msgid "" 396msgid ""
404"The file you are trying to upload is probably bigger than what this " 397"The file you are trying to upload is probably bigger than what this "
@@ -408,20 +401,20 @@ msgstr ""
408"le serveur web peut accepter (%s). Merci de l'envoyer en parties plus " 401"le serveur web peut accepter (%s). Merci de l'envoyer en parties plus "
409"légères." 402"légères."
410 403
411#: index.php:1471 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 404#: index.php:1490 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
412#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 405#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
413msgid "Plugin administration" 406msgid "Plugin administration"
414msgstr "Administration des extensions" 407msgstr "Administration des plugins"
415 408
416#: index.php:1523 tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 409#: index.php:1542 tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
417msgid "Thumbnails update" 410msgid "Thumbnails update"
418msgstr "Mise à jour des miniatures" 411msgstr "Mise à jour des miniatures"
419 412
420#: index.php:1695 413#: index.php:1714
421msgid "Search: " 414msgid "Search: "
422msgstr "Recherche : " 415msgstr "Recherche : "
423 416
424#: index.php:1735 417#: index.php:1754
425#, php-format 418#, php-format
426msgid "" 419msgid ""
427"<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " 420"<pre>Sessions do not seem to work correctly on your server.<br>Make sure the "
@@ -431,16 +424,16 @@ msgid ""
431"custom hostname without a dot causes cookie storage to fail. We recommend " 424"custom hostname without a dot causes cookie storage to fail. We recommend "
432"accessing your server via it's IP address or Fully Qualified Domain Name.<br>" 425"accessing your server via it's IP address or Fully Qualified Domain Name.<br>"
433msgstr "" 426msgstr ""
434"<pre>Les sesssions ne semble pas fonctionner sur ce serveur.<br>Assurez vous " 427"<pre>Les sesssions ne semblent pas fonctionner sur ce serveur.<br>Assurez "
435"que la variable « session.save_path » est correctement définie dans votre " 428"vous que la variable « session.save_path » est correctement définie dans "
436"fichier de configuration PHP, et que vous y avez les droits d'écriture." 429"votre fichier de configuration PHP, et que vous avez les droits d'écriture "
437"<br>Ce paramètre pointe actuellement sur %s.<br>Sur certains navigateurs, " 430"dessus.<br>Ce paramètre pointe actuellement sur %s.<br>Sur certains "
438"accéder à votre serveur depuis un nom d'hôte comme « localhost » ou autre " 431"navigateurs, accéder à votre serveur depuis un nom d'hôte comme « localhost "
439"nom personnalisé sans point '.' entraine l'échec de la sauvegarde des " 432"» ou autre nom personnalisé sans point '.' entraine l'échec de la sauvegarde "
440"cookies. Nous vous recommandons d'accéder à votre serveur depuis son adresse " 433"des cookies. Nous vous recommandons d'accéder à votre serveur depuis son "
441"IP ou un <em>Fully Qualified Domain Name</em>.<br>" 434"adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>"
442 435
443#: index.php:1745 436#: index.php:1764
444msgid "Click to try again." 437msgid "Click to try again."
445msgstr "Cliquer ici pour réessayer." 438msgstr "Cliquer ici pour réessayer."
446 439
@@ -455,7 +448,7 @@ msgstr "Shaare"
455 448
456#: plugins/addlink_toolbar/addlink_toolbar.php:50 449#: plugins/addlink_toolbar/addlink_toolbar.php:50
457msgid "Adds the addlink input on the linklist page." 450msgid "Adds the addlink input on the linklist page."
458msgstr "Ajout le formulaire d'ajout de liens sur la page principale." 451msgstr "Ajoute le formulaire d'ajout de liens sur la page principale."
459 452
460#: plugins/archiveorg/archiveorg.php:23 453#: plugins/archiveorg/archiveorg.php:23
461msgid "View on archive.org" 454msgid "View on archive.org"
@@ -471,7 +464,7 @@ msgid ""
471"developers." 464"developers."
472msgstr "" 465msgstr ""
473"Une extension de démonstration couvrant tous les cas d'utilisation pour les " 466"Une extension de démonstration couvrant tous les cas d'utilisation pour les "
474"designers et les développeurs." 467"designers de thèmes et les développeurs d'extensions."
475 468
476#: plugins/isso/isso.php:20 469#: plugins/isso/isso.php:20
477msgid "" 470msgid ""
@@ -481,12 +474,13 @@ msgstr ""
481"Erreur de l'extension Isso : Merci de définir le paramètre « ISSO_SERVER » " 474"Erreur de l'extension Isso : Merci de définir le paramètre « ISSO_SERVER » "
482"dans la page d'administration des extensions." 475"dans la page d'administration des extensions."
483 476
484#: plugins/isso/isso.php:63 477#: plugins/isso/isso.php:90
485msgid "Let visitor comment your shaares on permalinks with Isso." 478msgid "Let visitor comment your shaares on permalinks with Isso."
486msgstr "" 479msgstr ""
487"Permet aux visiteurs de commenter vos shaares sur les permaliens avec Isso." 480"Permettre aux visiteurs de commenter vos shaares sur les permaliens avec "
481"Isso."
488 482
489#: plugins/isso/isso.php:64 483#: plugins/isso/isso.php:91
490msgid "Isso server URL (without 'http://')" 484msgid "Isso server URL (without 'http://')"
491msgstr "URL du serveur Isso (sans 'http://')" 485msgstr "URL du serveur Isso (sans 'http://')"
492 486
@@ -578,7 +572,7 @@ msgstr "Active la publication de flux vers PubSubHubbub."
578 572
579#: plugins/qrcode/qrcode.php:69 plugins/wallabag/wallabag.php:68 573#: plugins/qrcode/qrcode.php:69 plugins/wallabag/wallabag.php:68
580msgid "For each link, add a QRCode icon." 574msgid "For each link, add a QRCode icon."
581msgstr "Pour chaque liens, ajouter une icône de QRCode." 575msgstr "Pour chaque lien, ajouter une icône de QRCode."
582 576
583#: plugins/wallabag/wallabag.php:21 577#: plugins/wallabag/wallabag.php:21
584msgid "" 578msgid ""
@@ -603,35 +597,17 @@ msgstr "Version de l'API Wallabag (1 ou 2)"
603#: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227 597#: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227
604#: tests/languages/fr/LanguagesFrTest.php:160 598#: tests/languages/fr/LanguagesFrTest.php:160
605#: tests/languages/fr/LanguagesFrTest.php:173 599#: tests/languages/fr/LanguagesFrTest.php:173
606#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85
607#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:85 600#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:85
608msgid "Search" 601msgid "Search"
609msgid_plural "Search" 602msgid_plural "Search"
610msgstr[0] "Rechercher" 603msgstr[0] "Rechercher"
611msgstr[1] "Rechercher" 604msgstr[1] "Rechercher"
612 605
613#: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
614msgid "Sorry, nothing to see here."
615msgstr "Désolé, il y a rien à voir ici."
616
617#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 606#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
618msgid "URL or leave empty to post a note" 607msgid "URL or leave empty to post a note"
619msgstr "URL ou laisser vide pour créer une note" 608msgstr "URL ou laisser vide pour créer une note"
620 609
621#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
622msgid "Current password"
623msgstr "Mot de passe actuel"
624
625#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
626msgid "New password"
627msgstr "Nouveau mot de passe"
628
629#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
630msgid "Change"
631msgstr "Changer"
632
633#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 610#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
634#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
635msgid "Tag" 611msgid "Tag"
636msgstr "Tag" 612msgstr "Tag"
637 613
@@ -661,6 +637,34 @@ msgstr "Vous pouvez aussi modifier les tags dans la"
661msgid "tag list" 637msgid "tag list"
662msgstr "liste des tags" 638msgstr "liste des tags"
663 639
640#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:143
641#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:296
642msgid "All"
643msgstr "Tous"
644
645#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:147
646#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:300
647msgid "Only common media hosts"
648msgstr "Seulement les hébergeurs de média connus"
649
650#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:151
651#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:304
652msgid "None"
653msgstr "Aucune"
654
655#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:158
656#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:281
657msgid "You need to enable the extension <code>php-gd</code> to use thumbnails."
658msgstr ""
659"Vous devez activer l'extension <code>php-gd</code> pour utiliser les "
660"miniatures."
661
662#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:162
663#, fuzzy
664#| msgid "Enable thumbnails"
665msgid "Synchonize thumbnails"
666msgstr "Activer les miniatures"
667
664#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 668#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
665msgid "title" 669msgid "title"
666msgstr "titre" 670msgstr "titre"
@@ -678,22 +682,18 @@ msgid "Theme"
678msgstr "Thème" 682msgstr "Thème"
679 683
680#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87 684#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
681#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
682msgid "Language" 685msgid "Language"
683msgstr "Langue" 686msgstr "Langue"
684 687
685#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116 688#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116
686#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
687msgid "Timezone" 689msgid "Timezone"
688msgstr "Fuseau horaire" 690msgstr "Fuseau horaire"
689 691
690#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 692#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
691#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
692msgid "Continent" 693msgid "Continent"
693msgstr "Continent" 694msgstr "Continent"
694 695
695#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 696#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
696#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
697msgid "City" 697msgid "City"
698msgstr "Ville" 698msgstr "Ville"
699 699
@@ -734,25 +734,21 @@ msgid "Do not show any links if the user is not logged in"
734msgstr "N'afficher aucun lien sans être connecté" 734msgstr "N'afficher aucun lien sans être connecté"
735 735
736#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231 736#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231
737#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
738msgid "Check updates" 737msgid "Check updates"
739msgstr "Vérifier les mises à jour" 738msgstr "Vérifier les mises à jour"
740 739
741#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232 740#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232
742#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152
743msgid "Notify me when a new release is ready" 741msgid "Notify me when a new release is ready"
744msgstr "Me notifier lorsqu'une nouvelle version est disponible" 742msgstr "Me notifier lorsqu'une nouvelle version est disponible"
745 743
746#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247 744#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247
747#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
748msgid "Enable REST API" 745msgid "Enable REST API"
749msgstr "Activer l'API REST" 746msgstr "Activer l'API REST"
750 747
751#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248 748#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248
752#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
753msgid "Allow third party software to use Shaarli such as mobile application" 749msgid "Allow third party software to use Shaarli such as mobile application"
754msgstr "" 750msgstr ""
755"Permets aux applications tierces d'utiliser Shaarli, par exemple les " 751"Permet aux applications tierces d'utiliser Shaarli, par exemple les "
756"applications mobiles" 752"applications mobiles"
757 753
758#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263 754#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263
@@ -763,30 +759,11 @@ msgstr "Clé d'API secrète"
763msgid "Enable thumbnails" 759msgid "Enable thumbnails"
764msgstr "Activer les miniatures" 760msgstr "Activer les miniatures"
765 761
766#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:281
767msgid "You need to enable the extension <code>php-gd</code> to use thumbnails."
768msgstr ""
769"Vous devez activer l'extension <code>php-gd</code> pour utiliser les "
770"miniatures."
771
772#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:285 762#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:285
773#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 763#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
774msgid "Synchronize thumbnails" 764msgid "Synchronize thumbnails"
775msgstr "Synchroniser les miniatures" 765msgstr "Synchroniser les miniatures"
776 766
777#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:296
778#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
779msgid "All"
780msgstr "Tous"
781
782#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:300
783msgid "Only common media hosts"
784msgstr "Seulement les hébergeurs de média connus"
785
786#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:304
787msgid "None"
788msgstr "Aucune"
789
790#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:312 767#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:312
791#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 768#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
792#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 769#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
@@ -847,107 +824,13 @@ msgid "Tags"
847msgstr "Tags" 824msgstr "Tags"
848 825
849#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57 826#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57
850#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
851#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167 827#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167
852msgid "Private" 828msgid "Private"
853msgstr "Privé" 829msgstr "Privé"
854 830
855#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 831#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
856msgid "Apply Changes" 832msgid "Apply Changes"
857msgstr "Appliquer" 833msgstr "Appliquer les changements"
858
859#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
860msgid "Export Database"
861msgstr "Exporter les données"
862
863#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
864msgid "Selection"
865msgstr "Choisir"
866
867#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
868msgid "Public"
869msgstr "Publics"
870
871#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52
872msgid "Prepend note permalinks with this Shaarli instance's URL"
873msgstr "Préfixer les liens de notes avec l'URL de l'instance de Shaarli"
874
875#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
876msgid "Useful to import bookmarks in a web browser"
877msgstr "Utile pour importer les marques-pages dans un navigateur"
878
879#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
880msgid "Import Database"
881msgstr "Importer des données"
882
883#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
884msgid "Maximum size allowed:"
885msgstr "Taille maximum autorisée :"
886
887#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
888msgid "Visibility"
889msgstr "Visibilité"
890
891#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
892msgid "Use values from the imported file, default to public"
893msgstr ""
894"Utiliser les valeurs présentes dans le fichier d'import, public par défaut"
895
896#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
897msgid "Import all bookmarks as private"
898msgstr "Importer tous les liens comme privés"
899
900#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
901msgid "Import all bookmarks as public"
902msgstr "Importer tous les liens comme publics"
903
904#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57
905msgid "Overwrite existing bookmarks"
906msgstr "Remplacer les liens existants"
907
908#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
909msgid "Duplicates based on URL"
910msgstr "Les doublons s'appuient sur les URL"
911
912#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
913msgid "Add default tags"
914msgstr "Ajouter des tags par défaut"
915
916#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
917msgid "Install Shaarli"
918msgstr "Installation de Shaarli"
919
920#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
921msgid "It looks like it's the first time you run Shaarli. Please configure it."
922msgstr ""
923"Il semblerait que ça soit la première fois que vous lancez Shaarli. Merci de "
924"le configurer."
925
926#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33
927#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
928#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151
929#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:151
930msgid "Username"
931msgstr "Nom d'utilisateur"
932
933#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
934#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
935#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152
936#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:152
937msgid "Password"
938msgstr "Mot de passe"
939
940#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
941msgid "Shaarli title"
942msgstr "Titre du Shaarli"
943
944#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69
945msgid "My links"
946msgstr "Mes liens"
947
948#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182
949msgid "Install"
950msgstr "Installer"
951 834
952#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 835#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
953#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79 836#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79
@@ -964,13 +847,11 @@ msgstr[0] "lien privé"
964msgstr[1] "liens privés" 847msgstr[1] "liens privés"
965 848
966#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 849#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
967#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
968#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:121 850#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:121
969msgid "Search text" 851msgid "Search text"
970msgstr "Recherche texte" 852msgstr "Recherche texte"
971 853
972#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37 854#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37
973#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:128
974#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:128 855#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:128
975#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 856#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
976#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64 857#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64
@@ -1011,7 +892,6 @@ msgid "without any tag"
1011msgstr "sans tag" 892msgstr "sans tag"
1012 893
1013#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 894#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173
1014#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
1015#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42 895#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42
1016msgid "Fold" 896msgid "Fold"
1017msgstr "Replier" 897msgstr "Replier"
@@ -1028,36 +908,36 @@ msgstr "permalien"
1028msgid "Add tag" 908msgid "Add tag"
1029msgstr "Ajouter un tag" 909msgstr "Ajouter un tag"
1030 910
1031#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7 911#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
912msgid "Toggle sticky"
913msgstr "Changer statut épinglé"
914
915#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:185
916msgid "Sticky"
917msgstr "Épinglé"
918
1032#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:7 919#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:7
1033msgid "Filters" 920msgid "Filters"
1034msgstr "Filtres" 921msgstr "Filtres"
1035 922
1036#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
1037#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:12 923#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:12
1038msgid "Only display private links" 924msgid "Only display private links"
1039msgstr "Afficher uniquement les liens privés" 925msgstr "Afficher uniquement les liens privés"
1040 926
1041#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
1042#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:15 927#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:15
1043msgid "Only display public links" 928msgid "Only display public links"
1044msgstr "Afficher uniquement les liens publics" 929msgstr "Afficher uniquement les liens publics"
1045 930
1046#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
1047#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:20 931#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:20
1048msgid "Filter untagged links" 932msgid "Filter untagged links"
1049msgstr "Filtrer par liens privés" 933msgstr "Filtrer par liens privés"
1050 934
1051#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
1052#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
1053#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:24 935#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:24
1054#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:76 936#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:76
1055#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
1056#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43 937#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43
1057msgid "Fold all" 938msgid "Fold all"
1058msgstr "Replier tout" 939msgstr "Replier tout"
1059 940
1060#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69
1061#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:69 941#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:69
1062msgid "Links per page" 942msgid "Links per page"
1063msgstr "Liens par page" 943msgstr "Liens par page"
@@ -1066,62 +946,59 @@ msgstr "Liens par page"
1066msgid "" 946msgid ""
1067"You have been banned after too many failed login attempts. Try again later." 947"You have been banned after too many failed login attempts. Try again later."
1068msgstr "" 948msgstr ""
1069"Vous avez été banni après trop d'échec d'authentification. Merci de " 949"Vous avez été banni après trop d'échecs d'authentification. Merci de "
1070"réessayer plus tard." 950"réessayer plus tard."
1071 951
952#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
953#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:151
954msgid "Username"
955msgstr "Nom d'utilisateur"
956
957#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
958#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:152
959msgid "Password"
960msgstr "Mot de passe"
961
1072#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 962#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
1073#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
1074#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:155 963#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:155
1075msgid "Remember me" 964msgid "Remember me"
1076msgstr "Rester connecté" 965msgstr "Rester connecté"
1077 966
1078#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
1079#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
1080#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14 967#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14
1081#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48 968#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
1082msgid "by the Shaarli community" 969msgid "by the Shaarli community"
1083msgstr "par la communauté Shaarli" 970msgstr "par la communauté Shaarli"
1084 971
1085#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
1086#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15 972#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
1087msgid "Documentation" 973msgid "Documentation"
1088msgstr "Documentation" 974msgstr "Documentation"
1089 975
1090#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
1091#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44 976#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44
1092msgid "Expand" 977msgid "Expand"
1093msgstr "Déplier" 978msgstr "Déplier"
1094 979
1095#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45
1096#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45 980#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45
1097msgid "Expand all" 981msgid "Expand all"
1098msgstr "Déplier tout" 982msgstr "Déplier tout"
1099 983
1100#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
1101#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46 984#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46
1102msgid "Are you sure you want to delete this link?" 985msgid "Are you sure you want to delete this link?"
1103msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?" 986msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?"
1104 987
1105#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
1106#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:90
1107#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:65 988#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:65
1108#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:90 989#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:90
1109msgid "RSS Feed" 990msgid "RSS Feed"
1110msgstr "Flux RSS" 991msgstr "Flux RSS"
1111 992
1112#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:70
1113#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
1114#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:70 993#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:70
1115#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:106 994#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:106
1116msgid "Logout" 995msgid "Logout"
1117msgstr "Déconnexion" 996msgstr "Déconnexion"
1118 997
1119#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173
1120#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:173 998#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:173
1121msgid "is available" 999msgid "is available"
1122msgstr "est disponible" 1000msgstr "est disponible"
1123 1001
1124#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:180
1125#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:180 1002#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:180
1126msgid "Error" 1003msgid "Error"
1127msgstr "Erreur" 1004msgstr "Erreur"
@@ -1221,22 +1098,18 @@ msgstr "tags"
1221msgid "List all links with those tags" 1098msgid "List all links with those tags"
1222msgstr "Lister tous les liens avec ces tags" 1099msgstr "Lister tous les liens avec ces tags"
1223 1100
1224#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3
1225#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3 1101#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3
1226msgid "Sort by:" 1102msgid "Sort by:"
1227msgstr "Trier par :" 1103msgstr "Trier par :"
1228 1104
1229#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5
1230#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:5 1105#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:5
1231msgid "Cloud" 1106msgid "Cloud"
1232msgstr "Nuage" 1107msgstr "Nuage"
1233 1108
1234#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:6
1235#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:6 1109#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:6
1236msgid "Most used" 1110msgid "Most used"
1237msgstr "Plus utilisés" 1111msgstr "Plus utilisés"
1238 1112
1239#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
1240#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:7 1113#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:7
1241msgid "Alphabetical" 1114msgid "Alphabetical"
1242msgstr "Alphabétique" 1115msgstr "Alphabétique"
@@ -1251,7 +1124,7 @@ msgstr "Changer les paramètres de Shaarli : titre, fuseau horaire, etc."
1251 1124
1252#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17 1125#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
1253msgid "Configure your Shaarli" 1126msgid "Configure your Shaarli"
1254msgstr "Conguration de Shaarli" 1127msgstr "Configurer Shaarli"
1255 1128
1256#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21 1129#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21
1257msgid "Enable, disable and configure plugins" 1130msgid "Enable, disable and configure plugins"
@@ -1259,31 +1132,39 @@ msgstr "Activer, désactiver et configurer les extensions"
1259 1132
1260#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 1133#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
1261msgid "Change your password" 1134msgid "Change your password"
1262msgstr "Modification du mot de passe" 1135msgstr "Modifier le mot de passe"
1263 1136
1264#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 1137#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
1265msgid "Rename or delete a tag in all links" 1138msgid "Rename or delete a tag in all links"
1266msgstr "Rename or delete a tag in all links" 1139msgstr "Renommer ou supprimer un tag dans tous les liens"
1267 1140
1268#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 1141#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
1142#, fuzzy
1143#| msgid ""
1144#| "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, "
1145#| "delicious…)"
1269msgid "" 1146msgid ""
1270"Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, " 1147"Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, "
1271"delicious...)" 1148"delicious...)"
1272msgstr "" 1149msgstr ""
1273"Importer des marques pages au format Netscape HTML (comme exportés depuis " 1150"Importer des marques pages au format Netscape HTML (comme exportés depuis "
1274"Firefox, Chrome, Opera, delicious...)" 1151"Firefox, Chrome, Opera, delicious…)"
1275 1152
1276#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 1153#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
1277msgid "Import links" 1154msgid "Import links"
1278msgstr "Importer des liens" 1155msgstr "Importer des liens"
1279 1156
1280#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 1157#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
1158#, fuzzy
1159#| msgid ""
1160#| "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, "
1161#| "Opera, delicious…)"
1281msgid "" 1162msgid ""
1282"Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, " 1163"Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, "
1283"Opera, delicious...)" 1164"Opera, delicious...)"
1284msgstr "" 1165msgstr ""
1285"Exporter les marques pages au format Netscape HTML (comme exportés depuis " 1166"Exporter les marques pages au format Netscape HTML (comme exportés depuis "
1286"Firefox, Chrome, Opera, delicious...)" 1167"Firefox, Chrome, Opera, delicious…)"
1287 1168
1288#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 1169#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
1289msgid "Export database" 1170msgid "Export database"
@@ -1298,13 +1179,13 @@ msgid ""
1298"Drag one of these button to your bookmarks toolbar or right-click it and " 1179"Drag one of these button to your bookmarks toolbar or right-click it and "
1299"\"Bookmark This Link\"" 1180"\"Bookmark This Link\""
1300msgstr "" 1181msgstr ""
1301"Glisser un de ces bouttons dans votre barre de favoris ou cliquer droit " 1182"Glisser un de ces boutons dans votre barre de favoris ou cliquer droit "
1302"dessus et « Ajouter aux favoris »" 1183"dessus et « Ajouter aux favoris »"
1303 1184
1304#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82 1185#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82
1305msgid "then click on the bookmarklet in any page you want to share." 1186msgid "then click on the bookmarklet in any page you want to share."
1306msgstr "" 1187msgstr ""
1307"puis cliquer sur le marque page depuis un site que vous souhaitez partager." 1188"puis cliquer sur le marque-page depuis un site que vous souhaitez partager."
1308 1189
1309#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 1190#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
1310#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110 1191#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
@@ -1339,33 +1220,16 @@ msgstr ""
1339msgid "Add Note" 1220msgid "Add Note"
1340msgstr "Ajouter une Note" 1221msgstr "Ajouter une Note"
1341 1222
1342#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 1223#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:136
1343msgid ""
1344"You need to browse your Shaarli over <strong>HTTPS</strong> to use this "
1345"functionality."
1346msgstr ""
1347"Vous devez utiliser Shaarli en <strong>HTTPS</strong> pour utiliser cette "
1348"fonctionalité."
1349
1350#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
1351msgid "Add to"
1352msgstr "Ajouter à"
1353
1354#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
1355msgid "3rd party" 1224msgid "3rd party"
1356msgstr "Applications tierces" 1225msgstr "Applications tierces"
1357 1226
1358#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:157 1227#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
1359#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:163 1228#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
1360msgid "Plugin"
1361msgstr "Extension"
1362
1363#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:158
1364#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164
1365msgid "plugin" 1229msgid "plugin"
1366msgstr "extension" 1230msgstr "extension"
1367 1231
1368#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:191 1232#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
1369msgid "" 1233msgid ""
1370"Drag this link to your bookmarks toolbar, or right-click it and choose " 1234"Drag this link to your bookmarks toolbar, or right-click it and choose "
1371"Bookmark This Link" 1235"Bookmark This Link"
@@ -1373,10 +1237,91 @@ msgstr ""
1373"Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " 1237"Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « "
1374"Ajouter aux favoris »" 1238"Ajouter aux favoris »"
1375 1239
1376#, fuzzy 1240#~ msgid "Sorry, nothing to see here."
1377#~| msgid "Enable thumbnails" 1241#~ msgstr "Désolé, il y a rien à voir ici."
1378#~ msgid "Synchonize thumbnails" 1242
1379#~ msgstr "Activer les miniatures" 1243#~ msgid "Current password"
1244#~ msgstr "Mot de passe actuel"
1245
1246#~ msgid "New password"
1247#~ msgstr "Nouveau mot de passe"
1248
1249#~ msgid "Change"
1250#~ msgstr "Changer"
1251
1252#~ msgid "Export Database"
1253#~ msgstr "Exporter les données"
1254
1255#~ msgid "Selection"
1256#~ msgstr "Choisir"
1257
1258#~ msgid "Public"
1259#~ msgstr "Publics"
1260
1261#~ msgid "Prepend note permalinks with this Shaarli instance's URL"
1262#~ msgstr "Préfixer les liens de note avec l'URL de l'instance de Shaarli"
1263
1264#~ msgid "Useful to import bookmarks in a web browser"
1265#~ msgstr "Utile pour importer les marques-pages dans un navigateur"
1266
1267#~ msgid "Import Database"
1268#~ msgstr "Importer des données"
1269
1270#~ msgid "Maximum size allowed:"
1271#~ msgstr "Taille maximum autorisée :"
1272
1273#~ msgid "Visibility"
1274#~ msgstr "Visibilité"
1275
1276#~ msgid "Use values from the imported file, default to public"
1277#~ msgstr ""
1278#~ "Utiliser les valeurs présentes dans le fichier d'import, public par défaut"
1279
1280#~ msgid "Import all bookmarks as private"
1281#~ msgstr "Importer tous les liens comme privés"
1282
1283#~ msgid "Import all bookmarks as public"
1284#~ msgstr "Importer tous les liens comme publics"
1285
1286#~ msgid "Overwrite existing bookmarks"
1287#~ msgstr "Remplacer les liens existants"
1288
1289#~ msgid "Duplicates based on URL"
1290#~ msgstr "Les doublons s'appuient sur les URL"
1291
1292#~ msgid "Add default tags"
1293#~ msgstr "Ajouter des tags par défaut"
1294
1295#~ msgid "Install Shaarli"
1296#~ msgstr "Installation de Shaarli"
1297
1298#~ msgid ""
1299#~ "It looks like it's the first time you run Shaarli. Please configure it."
1300#~ msgstr ""
1301#~ "Il semblerait que ça soit la première fois que vous lancez Shaarli. Merci "
1302#~ "de le configurer."
1303
1304#~ msgid "Shaarli title"
1305#~ msgstr "Titre du Shaarli"
1306
1307#~ msgid "My links"
1308#~ msgstr "Mes liens"
1309
1310#~ msgid "Install"
1311#~ msgstr "Installer"
1312
1313#~ msgid ""
1314#~ "You need to browse your Shaarli over <strong>HTTPS</strong> to use this "
1315#~ "functionality."
1316#~ msgstr ""
1317#~ "Vous devez utiliser Shaarli en <strong>HTTPS</strong> pour utiliser cette "
1318#~ "fonctionalité."
1319
1320#~ msgid "Add to"
1321#~ msgstr "Ajouter à"
1322
1323#~ msgid "Plugin"
1324#~ msgstr "Extension"
1380 1325
1381#~ msgid "Warning: " 1326#~ msgid "Warning: "
1382#~ msgstr "Attention : " 1327#~ msgstr "Attention : "
@@ -1450,7 +1395,8 @@ msgstr ""
1450#~ "\n" 1395#~ "\n"
1451 1396
1452#~ msgid "Sessions do not seem to work correctly on your server." 1397#~ msgid "Sessions do not seem to work correctly on your server."
1453#~ msgstr "Les sessions ne semblent " 1398#~ msgstr ""
1399#~ "Les sessions ne semblent pas fonctionner correctement sur votre serveur."
1454 1400
1455#~ msgid "Tag was renamed in " 1401#~ msgid "Tag was renamed in "
1456#~ msgstr "Le tag a été renommé dans " 1402#~ msgstr "Le tag a été renommé dans "
diff --git a/index.php b/index.php
index 4b86a3e2..acfcc660 100644
--- a/index.php
+++ b/index.php
@@ -28,7 +28,7 @@ if (date_default_timezone_get() == '') {
28define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0))); 28define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
29 29
30// High execution time in case of problematic imports/exports. 30// High execution time in case of problematic imports/exports.
31ini_set('max_input_time','60'); 31ini_set('max_input_time', '60');
32 32
33// Try to set max upload file size and read 33// Try to set max upload file size and read
34ini_set('memory_limit', '128M'); 34ini_set('memory_limit', '128M');
@@ -85,7 +85,7 @@ use \Shaarli\Thumbnailer;
85// Ensure the PHP version is supported 85// Ensure the PHP version is supported
86try { 86try {
87 ApplicationUtils::checkPHPVersion('5.5', PHP_VERSION); 87 ApplicationUtils::checkPHPVersion('5.5', PHP_VERSION);
88} catch(Exception $exc) { 88} catch (Exception $exc) {
89 header('Content-Type: text/plain; charset=utf-8'); 89 header('Content-Type: text/plain; charset=utf-8');
90 echo $exc->getMessage(); 90 echo $exc->getMessage();
91 exit; 91 exit;
@@ -111,7 +111,7 @@ ini_set('session.use_trans_sid', false);
111 111
112session_name('shaarli'); 112session_name('shaarli');
113// Start session if needed (Some server auto-start sessions). 113// Start session if needed (Some server auto-start sessions).
114if (session_id() == '') { 114if (session_status() == PHP_SESSION_NONE) {
115 session_start(); 115 session_start();
116} 116}
117 117
@@ -223,7 +223,6 @@ if (isset($_POST['login'])) {
223 $expirationTime, 223 $expirationTime,
224 WEB_PATH 224 WEB_PATH
225 ); 225 );
226
227 } else { 226 } else {
228 // Standard session expiration (=when browser closes) 227 // Standard session expiration (=when browser closes)
229 $expirationTime = 0; 228 $expirationTime = 0;
@@ -257,7 +256,8 @@ if (isset($_POST['login'])) {
257 exit; 256 exit;
258 } 257 }
259 } 258 }
260 header('Location: ?'); exit; 259 header('Location: ?');
260 exit;
261 } else { 261 } else {
262 $loginManager->handleFailedLogin($_SERVER); 262 $loginManager->handleFailedLogin($_SERVER);
263 $redir = '&username='. urlencode($_POST['login']); 263 $redir = '&username='. urlencode($_POST['login']);
@@ -278,7 +278,9 @@ if (isset($_POST['login'])) {
278// ------------------------------------------------------------------------------------------ 278// ------------------------------------------------------------------------------------------
279// Token management for XSRF protection 279// Token management for XSRF protection
280// Token should be used in any form which acts on data (create,update,delete,import...). 280// Token should be used in any form which acts on data (create,update,delete,import...).
281if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. 281if (!isset($_SESSION['tokens'])) {
282 $_SESSION['tokens']=array(); // Token are attached to the session.
283}
282 284
283/** 285/**
284 * Daily RSS feed: 1 RSS entry per day giving all the links on that day. 286 * Daily RSS feed: 1 RSS entry per day giving all the links on that day.
@@ -288,13 +290,14 @@ if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are atta
288 * @param ConfigManager $conf Configuration Manager instance 290 * @param ConfigManager $conf Configuration Manager instance
289 * @param LoginManager $loginManager LoginManager instance 291 * @param LoginManager $loginManager LoginManager instance
290 */ 292 */
291function showDailyRSS($conf, $loginManager) { 293function showDailyRSS($conf, $loginManager)
294{
292 // Cache system 295 // Cache system
293 $query = $_SERVER['QUERY_STRING']; 296 $query = $_SERVER['QUERY_STRING'];
294 $cache = new CachedPage( 297 $cache = new CachedPage(
295 $conf->get('config.PAGE_CACHE'), 298 $conf->get('config.PAGE_CACHE'),
296 page_url($_SERVER), 299 page_url($_SERVER),
297 startsWith($query,'do=dailyrss') && !$loginManager->isLoggedIn() 300 startsWith($query, 'do=dailyrss') && !$loginManager->isLoggedIn()
298 ); 301 );
299 $cached = $cache->cachedVersion(); 302 $cached = $cache->cachedVersion();
300 if (!empty($cached)) { 303 if (!empty($cached)) {
@@ -395,7 +398,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
395{ 398{
396 $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD. 399 $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
397 if (isset($_GET['day'])) { 400 if (isset($_GET['day'])) {
398 $day = $_GET['day']; 401 $day = $_GET['day'];
399 } 402 }
400 403
401 $days = $LINKSDB->days(); 404 $days = $LINKSDB->days();
@@ -413,7 +416,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
413 $previousday=$days[$i - 1]; 416 $previousday=$days[$i - 1];
414 } 417 }
415 if ($i < count($days) - 1) { 418 if ($i < count($days) - 1) {
416 $nextday = $days[$i + 1]; 419 $nextday = $days[$i + 1];
417 } 420 }
418 } 421 }
419 try { 422 try {
@@ -424,8 +427,8 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
424 } 427 }
425 428
426 // We pre-format some fields for proper output. 429 // We pre-format some fields for proper output.
427 foreach($linksToDisplay as $key => $link) { 430 foreach ($linksToDisplay as $key => $link) {
428 $taglist = explode(' ',$link['tags']); 431 $taglist = explode(' ', $link['tags']);
429 uasort($taglist, 'strcasecmp'); 432 uasort($taglist, 'strcasecmp');
430 $linksToDisplay[$key]['taglist']=$taglist; 433 $linksToDisplay[$key]['taglist']=$taglist;
431 $linksToDisplay[$key]['formatedDescription'] = format_description( 434 $linksToDisplay[$key]['formatedDescription'] = format_description(
@@ -457,14 +460,14 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
457 */ 460 */
458 $columns = array(array(), array(), array()); // Entries to display, for each column. 461 $columns = array(array(), array(), array()); // Entries to display, for each column.
459 $fill = array(0, 0, 0); // Rough estimate of columns fill. 462 $fill = array(0, 0, 0); // Rough estimate of columns fill.
460 foreach($data['linksToDisplay'] as $key => $link) { 463 foreach ($data['linksToDisplay'] as $key => $link) {
461 // Roughly estimate length of entry (by counting characters) 464 // Roughly estimate length of entry (by counting characters)
462 // Title: 30 chars = 1 line. 1 line is 30 pixels height. 465 // Title: 30 chars = 1 line. 1 line is 30 pixels height.
463 // Description: 836 characters gives roughly 342 pixel height. 466 // Description: 836 characters gives roughly 342 pixel height.
464 // This is not perfect, but it's usually OK. 467 // This is not perfect, but it's usually OK.
465 $length = strlen($link['title']) + (342 * strlen($link['description'])) / 836; 468 $length = strlen($link['title']) + (342 * strlen($link['description'])) / 836;
466 if ($link['thumbnail']) { 469 if ($link['thumbnail']) {
467 $length += 100; // 1 thumbnails roughly takes 100 pixels height. 470 $length += 100; // 1 thumbnails roughly takes 100 pixels height.
468 } 471 }
469 // Then put in column which is the less filled: 472 // Then put in column which is the less filled:
470 $smallest = min($fill); // find smallest value in array. 473 $smallest = min($fill); // find smallest value in array.
@@ -492,8 +495,9 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
492 * @param ConfigManager $conf Configuration Manager instance. 495 * @param ConfigManager $conf Configuration Manager instance.
493 * @param PluginManager $pluginManager Plugin Manager instance. 496 * @param PluginManager $pluginManager Plugin Manager instance.
494 */ 497 */
495function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager) { 498function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
496 buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager, $loginManager); 499{
500 buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
497 $PAGE->renderPage('linklist'); 501 $PAGE->renderPage('linklist');
498} 502}
499 503
@@ -524,8 +528,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
524 $updater->getDoneUpdates() 528 $updater->getDoneUpdates()
525 ); 529 );
526 } 530 }
527 } 531 } catch (Exception $e) {
528 catch(Exception $e) {
529 die($e->getMessage()); 532 die($e->getMessage());
530 } 533 }
531 534
@@ -538,8 +541,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
538 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; 541 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
539 $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn()); 542 $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn());
540 543
541 if ( 544 if (// if the user isn't logged in
542 // if the user isn't logged in
543 !$loginManager->isLoggedIn() && 545 !$loginManager->isLoggedIn() &&
544 // and Shaarli doesn't have public content... 546 // and Shaarli doesn't have public content...
545 $conf->get('privacy.hide_public_links') && 547 $conf->get('privacy.hide_public_links') &&
@@ -563,9 +565,11 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
563 'footer', 565 'footer',
564 ); 566 );
565 567
566 foreach($common_hooks as $name) { 568 foreach ($common_hooks as $name) {
567 $plugin_data = array(); 569 $plugin_data = array();
568 $pluginManager->executeHooks('render_' . $name, $plugin_data, 570 $pluginManager->executeHooks(
571 'render_' . $name,
572 $plugin_data,
569 array( 573 array(
570 'target' => $targetPage, 574 'target' => $targetPage,
571 'loggedin' => $loginManager->isLoggedIn() 575 'loggedin' => $loginManager->isLoggedIn()
@@ -575,13 +579,15 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
575 } 579 }
576 580
577 // -------- Display login form. 581 // -------- Display login form.
578 if ($targetPage == Router::$PAGE_LOGIN) 582 if ($targetPage == Router::$PAGE_LOGIN) {
579 { 583 if ($conf->get('security.open_shaarli')) {
580 if ($conf->get('security.open_shaarli')) { header('Location: ?'); exit; } // No need to login for open Shaarli 584 header('Location: ?');
585 exit;
586 } // No need to login for open Shaarli
581 if (isset($_GET['username'])) { 587 if (isset($_GET['username'])) {
582 $PAGE->assign('username', escape($_GET['username'])); 588 $PAGE->assign('username', escape($_GET['username']));
583 } 589 }
584 $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):'')); 590 $PAGE->assign('returnurl', (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):''));
585 // add default state of the 'remember me' checkbox 591 // add default state of the 'remember me' checkbox
586 $PAGE->assign('remember_user_default', $conf->get('privacy.remember_user_default')); 592 $PAGE->assign('remember_user_default', $conf->get('privacy.remember_user_default'));
587 $PAGE->assign('user_can_login', $loginManager->canLogin($_SERVER)); 593 $PAGE->assign('user_can_login', $loginManager->canLogin($_SERVER));
@@ -590,8 +596,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
590 exit; 596 exit;
591 } 597 }
592 // -------- User wants to logout. 598 // -------- User wants to logout.
593 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) 599 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) {
594 {
595 invalidateCaches($conf->get('resource.page_cache')); 600 invalidateCaches($conf->get('resource.page_cache'));
596 $sessionManager->logout(); 601 $sessionManager->logout();
597 setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH); 602 setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH);
@@ -600,8 +605,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
600 } 605 }
601 606
602 // -------- Picture wall 607 // -------- Picture wall
603 if ($targetPage == Router::$PAGE_PICWALL) 608 if ($targetPage == Router::$PAGE_PICWALL) {
604 {
605 $PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli')); 609 $PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli'));
606 if (! $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) { 610 if (! $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) {
607 $PAGE->assign('linksToDisplay', []); 611 $PAGE->assign('linksToDisplay', []);
@@ -615,8 +619,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
615 619
616 // Get only links which have a thumbnail. 620 // Get only links which have a thumbnail.
617 // Note: we do not retrieve thumbnails here, the request is too heavy. 621 // Note: we do not retrieve thumbnails here, the request is too heavy.
618 foreach($links as $key => $link) 622 foreach ($links as $key => $link) {
619 {
620 if (isset($link['thumbnail']) && $link['thumbnail'] !== false) { 623 if (isset($link['thumbnail']) && $link['thumbnail'] !== false) {
621 $linksToDisplay[] = $link; // Add to array. 624 $linksToDisplay[] = $link; // Add to array.
622 } 625 }
@@ -637,8 +640,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
637 } 640 }
638 641
639 // -------- Tag cloud 642 // -------- Tag cloud
640 if ($targetPage == Router::$PAGE_TAGCLOUD) 643 if ($targetPage == Router::$PAGE_TAGCLOUD) {
641 {
642 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; 644 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '';
643 $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; 645 $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : [];
644 $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); 646 $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility);
@@ -653,7 +655,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
653 alphabetical_sort($tags, false, true); 655 alphabetical_sort($tags, false, true);
654 656
655 $tagList = array(); 657 $tagList = array();
656 foreach($tags as $key => $value) { 658 foreach ($tags as $key => $value) {
657 if (in_array($key, $filteringTags)) { 659 if (in_array($key, $filteringTags)) {
658 continue; 660 continue;
659 } 661 }
@@ -685,8 +687,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
685 } 687 }
686 688
687 // -------- Tag list 689 // -------- Tag list
688 if ($targetPage == Router::$PAGE_TAGLIST) 690 if ($targetPage == Router::$PAGE_TAGLIST) {
689 {
690 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; 691 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '';
691 $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; 692 $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : [];
692 $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); 693 $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility);
@@ -732,7 +733,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
732 $cache = new CachedPage( 733 $cache = new CachedPage(
733 $conf->get('resource.page_cache'), 734 $conf->get('resource.page_cache'),
734 page_url($_SERVER), 735 page_url($_SERVER),
735 startsWith($query,'do='. $targetPage) && !$loginManager->isLoggedIn() 736 startsWith($query, 'do='. $targetPage) && !$loginManager->isLoggedIn()
736 ); 737 );
737 $cached = $cache->cachedVersion(); 738 $cached = $cache->cachedVersion();
738 if (!empty($cached)) { 739 if (!empty($cached)) {
@@ -770,11 +771,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
770 } 771 }
771 772
772 // -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...) 773 // -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...)
773 if (isset($_GET['addtag'])) 774 if (isset($_GET['addtag'])) {
774 {
775 // Get previous URL (http_referer) and add the tag to the searchtags parameters in query. 775 // Get previous URL (http_referer) and add the tag to the searchtags parameters in query.
776 if (empty($_SERVER['HTTP_REFERER'])) { header('Location: ?searchtags='.urlencode($_GET['addtag'])); exit; } // In case browser does not send HTTP_REFERER 776 if (empty($_SERVER['HTTP_REFERER'])) {
777 parse_str(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_QUERY), $params); 777 // In case browser does not send HTTP_REFERER
778 header('Location: ?searchtags='.urlencode($_GET['addtag']));
779 exit;
780 }
781 parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $params);
778 782
779 // Prevent redirection loop 783 // Prevent redirection loop
780 if (isset($params['addtag'])) { 784 if (isset($params['addtag'])) {
@@ -798,12 +802,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
798 // Append the tag if necessary 802 // Append the tag if necessary
799 if (empty($params['searchtags'])) { 803 if (empty($params['searchtags'])) {
800 $params['searchtags'] = trim($_GET['addtag']); 804 $params['searchtags'] = trim($_GET['addtag']);
801 } 805 } elseif ($addtag) {
802 elseif ($addtag) {
803 $params['searchtags'] = trim($params['searchtags']).' '.trim($_GET['addtag']); 806 $params['searchtags'] = trim($params['searchtags']).' '.trim($_GET['addtag']);
804 } 807 }
805 808
806 unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different) 809 // We also remove page (keeping the same page has no sense, since the
810 // results are different)
811 unset($params['page']);
812
807 header('Location: ?'.http_build_query($params)); 813 header('Location: ?'.http_build_query($params));
808 exit; 814 exit;
809 } 815 }
@@ -828,13 +834,15 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
828 $tags = explode(' ', $params['searchtags']); 834 $tags = explode(' ', $params['searchtags']);
829 // Remove value from array $tags. 835 // Remove value from array $tags.
830 $tags = array_diff($tags, array($_GET['removetag'])); 836 $tags = array_diff($tags, array($_GET['removetag']));
831 $params['searchtags'] = implode(' ',$tags); 837 $params['searchtags'] = implode(' ', $tags);
832 838
833 if (empty($params['searchtags'])) { 839 if (empty($params['searchtags'])) {
834 unset($params['searchtags']); 840 unset($params['searchtags']);
835 } 841 }
836 842
837 unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different) 843 // We also remove page (keeping the same page has no sense, since
844 // the results are different)
845 unset($params['page']);
838 } 846 }
839 header('Location: ?'.http_build_query($params)); 847 header('Location: ?'.http_build_query($params));
840 exit; 848 exit;
@@ -897,12 +905,10 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
897 } 905 }
898 906
899 // -------- Handle other actions allowed for non-logged in users: 907 // -------- Handle other actions allowed for non-logged in users:
900 if (!$loginManager->isLoggedIn()) 908 if (!$loginManager->isLoggedIn()) {
901 {
902 // User tries to post new link but is not logged in: 909 // User tries to post new link but is not logged in:
903 // Show login screen, then redirect to ?post=... 910 // Show login screen, then redirect to ?post=...
904 if (isset($_GET['post'])) 911 if (isset($_GET['post'])) {
905 {
906 header( // Redirect to login page, then back to post link. 912 header( // Redirect to login page, then back to post link.
907 'Location: ?do=login&post='.urlencode($_GET['post']). 913 'Location: ?do=login&post='.urlencode($_GET['post']).
908 (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):''). 914 (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').
@@ -925,8 +931,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
925 // -------- All other functions are reserved for the registered user: 931 // -------- All other functions are reserved for the registered user:
926 932
927 // -------- Display the Tools menu if requested (import/export/bookmarklet...) 933 // -------- Display the Tools menu if requested (import/export/bookmarklet...)
928 if ($targetPage == Router::$PAGE_TOOLS) 934 if ($targetPage == Router::$PAGE_TOOLS) {
929 {
930 $data = [ 935 $data = [
931 'pageabsaddr' => index_url($_SERVER), 936 'pageabsaddr' => index_url($_SERVER),
932 'sslenabled' => is_https($_SERVER), 937 'sslenabled' => is_https($_SERVER),
@@ -943,30 +948,40 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
943 } 948 }
944 949
945 // -------- User wants to change his/her password. 950 // -------- User wants to change his/her password.
946 if ($targetPage == Router::$PAGE_CHANGEPASSWORD) 951 if ($targetPage == Router::$PAGE_CHANGEPASSWORD) {
947 {
948 if ($conf->get('security.open_shaarli')) { 952 if ($conf->get('security.open_shaarli')) {
949 die(t('You are not supposed to change a password on an Open Shaarli.')); 953 die(t('You are not supposed to change a password on an Open Shaarli.'));
950 } 954 }
951 955
952 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) 956 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) {
953 { 957 if (!$sessionManager->checkToken($_POST['token'])) {
954 if (!$sessionManager->checkToken($_POST['token'])) die(t('Wrong token.')); // Go away! 958 die(t('Wrong token.')); // Go away!
959 }
955 960
956 // Make sure old password is correct. 961 // Make sure old password is correct.
957 $oldhash = sha1($_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt')); 962 $oldhash = sha1(
958 if ($oldhash!= $conf->get('credentials.hash')) { 963 $_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt')
959 echo '<script>alert("'. t('The old password is not correct.') .'");document.location=\'?do=changepasswd\';</script>'; 964 );
965 if ($oldhash != $conf->get('credentials.hash')) {
966 echo '<script>alert("'
967 . t('The old password is not correct.')
968 .'");document.location=\'?do=changepasswd\';</script>';
960 exit; 969 exit;
961 } 970 }
962 // Save new password 971 // Save new password
963 // Salt renders rainbow-tables attacks useless. 972 // Salt renders rainbow-tables attacks useless.
964 $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); 973 $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
965 $conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt'))); 974 $conf->set(
975 'credentials.hash',
976 sha1(
977 $_POST['setpassword']
978 . $conf->get('credentials.login')
979 . $conf->get('credentials.salt')
980 )
981 );
966 try { 982 try {
967 $conf->write($loginManager->isLoggedIn()); 983 $conf->write($loginManager->isLoggedIn());
968 } 984 } catch (Exception $e) {
969 catch(Exception $e) {
970 error_log( 985 error_log(
971 'ERROR while writing config file after changing password.' . PHP_EOL . 986 'ERROR while writing config file after changing password.' . PHP_EOL .
972 $e->getMessage() 987 $e->getMessage()
@@ -978,9 +993,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
978 } 993 }
979 echo '<script>alert("'. t('Your password has been changed') .'");document.location=\'?do=tools\';</script>'; 994 echo '<script>alert("'. t('Your password has been changed') .'");document.location=\'?do=tools\';</script>';
980 exit; 995 exit;
981 } 996 } else {
982 else // show the change password form. 997 // show the change password form.
983 {
984 $PAGE->assign('pagetitle', t('Change password') .' - '. $conf->get('general.title', 'Shaarli')); 998 $PAGE->assign('pagetitle', t('Change password') .' - '. $conf->get('general.title', 'Shaarli'));
985 $PAGE->renderPage('changepassword'); 999 $PAGE->renderPage('changepassword');
986 exit; 1000 exit;
@@ -988,10 +1002,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
988 } 1002 }
989 1003
990 // -------- User wants to change configuration 1004 // -------- User wants to change configuration
991 if ($targetPage == Router::$PAGE_CONFIGURE) 1005 if ($targetPage == Router::$PAGE_CONFIGURE) {
992 { 1006 if (!empty($_POST['title'])) {
993 if (!empty($_POST['title']) )
994 {
995 if (!$sessionManager->checkToken($_POST['token'])) { 1007 if (!$sessionManager->checkToken($_POST['token'])) {
996 die(t('Wrong token.')); // Go away! 1008 die(t('Wrong token.')); // Go away!
997 } 1009 }
@@ -1019,7 +1031,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1019 && $thumbnailsMode !== $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) 1031 && $thumbnailsMode !== $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)
1020 ) { 1032 ) {
1021 $_SESSION['warnings'][] = t( 1033 $_SESSION['warnings'][] = t(
1022 'You have enabled or changed thumbnails mode. <a href="?do=thumbs_update">Please synchronize them</a>.' 1034 'You have enabled or changed thumbnails mode. '
1035 .'<a href="?do=thumbs_update">Please synchronize them</a>.'
1023 ); 1036 );
1024 } 1037 }
1025 $conf->set('thumbnails.mode', $thumbnailsMode); 1038 $conf->set('thumbnails.mode', $thumbnailsMode);
@@ -1028,8 +1041,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1028 $conf->write($loginManager->isLoggedIn()); 1041 $conf->write($loginManager->isLoggedIn());
1029 $history->updateSettings(); 1042 $history->updateSettings();
1030 invalidateCaches($conf->get('resource.page_cache')); 1043 invalidateCaches($conf->get('resource.page_cache'));
1031 } 1044 } catch (Exception $e) {
1032 catch(Exception $e) {
1033 error_log( 1045 error_log(
1034 'ERROR while writing config file after configuration update.' . PHP_EOL . 1046 'ERROR while writing config file after configuration update.' . PHP_EOL .
1035 $e->getMessage() 1047 $e->getMessage()
@@ -1041,9 +1053,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1041 } 1053 }
1042 echo '<script>alert("'. t('Configuration was saved.') .'");document.location=\'?do=configure\';</script>'; 1054 echo '<script>alert("'. t('Configuration was saved.') .'");document.location=\'?do=configure\';</script>';
1043 exit; 1055 exit;
1044 } 1056 } else {
1045 else // Show the configuration form. 1057 // Show the configuration form.
1046 {
1047 $PAGE->assign('title', $conf->get('general.title')); 1058 $PAGE->assign('title', $conf->get('general.title'));
1048 $PAGE->assign('theme', $conf->get('resource.theme')); 1059 $PAGE->assign('theme', $conf->get('resource.theme'));
1049 $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); 1060 $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl')));
@@ -1071,8 +1082,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1071 } 1082 }
1072 1083
1073 // -------- User wants to rename a tag or delete it 1084 // -------- User wants to rename a tag or delete it
1074 if ($targetPage == Router::$PAGE_CHANGETAG) 1085 if ($targetPage == Router::$PAGE_CHANGETAG) {
1075 {
1076 if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { 1086 if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) {
1077 $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : ''); 1087 $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : '');
1078 $PAGE->assign('pagetitle', t('Manage tags') .' - '. $conf->get('general.title', 'Shaarli')); 1088 $PAGE->assign('pagetitle', t('Manage tags') .' - '. $conf->get('general.title', 'Shaarli'));
@@ -1084,7 +1094,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1084 die(t('Wrong token.')); 1094 die(t('Wrong token.'));
1085 } 1095 }
1086 1096
1087 $alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), escape($_POST['totag'])); 1097 $toTag = isset($_POST['totag']) ? escape($_POST['totag']) : null;
1098 $alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), $toTag);
1088 $LINKSDB->save($conf->get('resource.page_cache')); 1099 $LINKSDB->save($conf->get('resource.page_cache'));
1089 foreach ($alteredLinks as $link) { 1100 foreach ($alteredLinks as $link) {
1090 $history->updateLink($link); 1101 $history->updateLink($link);
@@ -1100,16 +1111,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1100 } 1111 }
1101 1112
1102 // -------- User wants to add a link without using the bookmarklet: Show form. 1113 // -------- User wants to add a link without using the bookmarklet: Show form.
1103 if ($targetPage == Router::$PAGE_ADDLINK) 1114 if ($targetPage == Router::$PAGE_ADDLINK) {
1104 {
1105 $PAGE->assign('pagetitle', t('Shaare a new link') .' - '. $conf->get('general.title', 'Shaarli')); 1115 $PAGE->assign('pagetitle', t('Shaare a new link') .' - '. $conf->get('general.title', 'Shaarli'));
1106 $PAGE->renderPage('addlink'); 1116 $PAGE->renderPage('addlink');
1107 exit; 1117 exit;
1108 } 1118 }
1109 1119
1110 // -------- User clicked the "Save" button when editing a link: Save link to database. 1120 // -------- User clicked the "Save" button when editing a link: Save link to database.
1111 if (isset($_POST['save_edit'])) 1121 if (isset($_POST['save_edit'])) {
1112 {
1113 // Go away! 1122 // Go away!
1114 if (! $sessionManager->checkToken($_POST['token'])) { 1123 if (! $sessionManager->checkToken($_POST['token'])) {
1115 die(t('Wrong token.')); 1124 die(t('Wrong token.'));
@@ -1196,14 +1205,16 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1196 } 1205 }
1197 1206
1198 // -------- User clicked the "Cancel" button when editing a link. 1207 // -------- User clicked the "Cancel" button when editing a link.
1199 if (isset($_POST['cancel_edit'])) 1208 if (isset($_POST['cancel_edit'])) {
1200 {
1201 $id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : false; 1209 $id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : false;
1202 if (! isset($LINKSDB[$id])) { 1210 if (! isset($LINKSDB[$id])) {
1203 header('Location: ?'); 1211 header('Location: ?');
1204 } 1212 }
1205 // If we are called from the bookmarklet, we must close the popup: 1213 // If we are called from the bookmarklet, we must close the popup:
1206 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1214 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
1215 echo '<script>self.close();</script>';
1216 exit;
1217 }
1207 $link = $LINKSDB[$id]; 1218 $link = $LINKSDB[$id];
1208 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); 1219 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
1209 // Scroll to the link which has been edited. 1220 // Scroll to the link which has been edited.
@@ -1214,8 +1225,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1214 } 1225 }
1215 1226
1216 // -------- User clicked the "Delete" button when editing a link: Delete link from database. 1227 // -------- User clicked the "Delete" button when editing a link: Delete link from database.
1217 if ($targetPage == Router::$PAGE_DELETELINK) 1228 if ($targetPage == Router::$PAGE_DELETELINK) {
1218 {
1219 if (! $sessionManager->checkToken($_GET['token'])) { 1229 if (! $sessionManager->checkToken($_GET['token'])) {
1220 die(t('Wrong token.')); 1230 die(t('Wrong token.'));
1221 } 1231 }
@@ -1229,28 +1239,31 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1229 $ids = [$ids]; 1239 $ids = [$ids];
1230 } 1240 }
1231 // assert at least one id is given 1241 // assert at least one id is given
1232 if(!count($ids)){ 1242 if (!count($ids)) {
1233 die('no id provided'); 1243 die('no id provided');
1234 } 1244 }
1235 foreach ($ids as $id) { 1245 foreach ($ids as $id) {
1236 $id = (int) escape($id); 1246 $id = (int) escape($id);
1237 $link = $LINKSDB[$id]; 1247 $link = $LINKSDB[$id];
1238 $pluginManager->executeHooks('delete_link', $link); 1248 $pluginManager->executeHooks('delete_link', $link);
1249 $history->deleteLink($link);
1239 unset($LINKSDB[$id]); 1250 unset($LINKSDB[$id]);
1240 } 1251 }
1241 $LINKSDB->save($conf->get('resource.page_cache')); // save to disk 1252 $LINKSDB->save($conf->get('resource.page_cache')); // save to disk
1242 $history->deleteLink($link);
1243 1253
1244 // If we are called from the bookmarklet, we must close the popup: 1254 // If we are called from the bookmarklet, we must close the popup:
1245 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1255 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
1256 echo '<script>self.close();</script>';
1257 exit;
1258 }
1246 1259
1247 $location = '?'; 1260 $location = '?';
1248 if (isset($_SERVER['HTTP_REFERER'])) { 1261 if (isset($_SERVER['HTTP_REFERER'])) {
1249 // Don't redirect to where we were previously if it was a permalink or an edit_link, because it would 404. 1262 // Don't redirect to where we were previously if it was a permalink or an edit_link, because it would 404.
1250 $location = generateLocation( 1263 $location = generateLocation(
1251 $_SERVER['HTTP_REFERER'], 1264 $_SERVER['HTTP_REFERER'],
1252 $_SERVER['HTTP_HOST'], 1265 $_SERVER['HTTP_HOST'],
1253 ['delete_link', 'edit_link', $link['shorturl']] 1266 ['delete_link', 'edit_link', $link['shorturl']]
1254 ); 1267 );
1255 } 1268 }
1256 1269
@@ -1259,11 +1272,13 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1259 } 1272 }
1260 1273
1261 // -------- User clicked the "EDIT" button on a link: Display link edit form. 1274 // -------- User clicked the "EDIT" button on a link: Display link edit form.
1262 if (isset($_GET['edit_link'])) 1275 if (isset($_GET['edit_link'])) {
1263 {
1264 $id = (int) escape($_GET['edit_link']); 1276 $id = (int) escape($_GET['edit_link']);
1265 $link = $LINKSDB[$id]; // Read database 1277 $link = $LINKSDB[$id]; // Read database
1266 if (!$link) { header('Location: ?'); exit; } // Link not found in database. 1278 if (!$link) {
1279 header('Location: ?');
1280 exit;
1281 } // Link not found in database.
1267 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT); 1282 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
1268 $data = array( 1283 $data = array(
1269 'link' => $link, 1284 'link' => $link,
@@ -1289,8 +1304,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1289 $link_is_new = false; 1304 $link_is_new = false;
1290 // Check if URL is not already in database (in this case, we will edit the existing link) 1305 // Check if URL is not already in database (in this case, we will edit the existing link)
1291 $link = $LINKSDB->getLinkFromUrl($url); 1306 $link = $LINKSDB->getLinkFromUrl($url);
1292 if (! $link) 1307 if (! $link) {
1293 {
1294 $link_is_new = true; 1308 $link_is_new = true;
1295 $linkdate = strval(date(LinkDB::LINK_DATE_FORMAT)); 1309 $linkdate = strval(date(LinkDB::LINK_DATE_FORMAT));
1296 // Get title if it was provided in URL (by the bookmarklet). 1310 // Get title if it was provided in URL (by the bookmarklet).
@@ -1299,7 +1313,9 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1299 $description = empty($_GET['description']) ? '' : escape($_GET['description']); 1313 $description = empty($_GET['description']) ? '' : escape($_GET['description']);
1300 $tags = empty($_GET['tags']) ? '' : escape($_GET['tags']); 1314 $tags = empty($_GET['tags']) ? '' : escape($_GET['tags']);
1301 $private = !empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0; 1315 $private = !empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0;
1302 // If this is an HTTP(S) link, we try go get the page to extract the title (otherwise we will to straight to the edit form.) 1316
1317 // If this is an HTTP(S) link, we try go get the page to extract
1318 // the title (otherwise we will to straight to the edit form.)
1303 if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { 1319 if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) {
1304 // Short timeout to keep the application responsive 1320 // Short timeout to keep the application responsive
1305 // The callback will fill $charset and $title with data from the downloaded page. 1321 // The callback will fill $charset and $title with data from the downloaded page.
@@ -1352,6 +1368,25 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1352 exit; 1368 exit;
1353 } 1369 }
1354 1370
1371 if ($targetPage == Router::$PAGE_PINLINK) {
1372 if (! isset($_GET['id']) || empty($LINKSDB[$_GET['id']])) {
1373 // FIXME! Use a proper error system.
1374 $msg = t('Invalid link ID provided');
1375 echo '<script>alert("'. $msg .'");document.location=\''. index_url($_SERVER) .'\';</script>';
1376 exit;
1377 }
1378 if (! $sessionManager->checkToken($_GET['token'])) {
1379 die('Wrong token.');
1380 }
1381
1382 $link = $LINKSDB[$_GET['id']];
1383 $link['sticky'] = ! $link['sticky'];
1384 $LINKSDB[(int) $_GET['id']] = $link;
1385 $LINKSDB->save($conf->get('resource.page_cache'));
1386 header('Location: '.index_url($_SERVER));
1387 exit;
1388 }
1389
1355 if ($targetPage == Router::$PAGE_EXPORT) { 1390 if ($targetPage == Router::$PAGE_EXPORT) {
1356 // Export links as a Netscape Bookmarks file 1391 // Export links as a Netscape Bookmarks file
1357 1392
@@ -1388,7 +1423,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1388 header('Content-Type: text/html; charset=utf-8'); 1423 header('Content-Type: text/html; charset=utf-8');
1389 header( 1424 header(
1390 'Content-disposition: attachment; filename=bookmarks_' 1425 'Content-disposition: attachment; filename=bookmarks_'
1391 .$selection.'_'.$now->format(LinkDB::LINK_DATE_FORMAT).'.html' 1426 .$selection.'_'.$now->format(LinkDB::LINK_DATE_FORMAT).'.html'
1392 ); 1427 );
1393 $PAGE->assign('date', $now->format(DateTime::RFC822)); 1428 $PAGE->assign('date', $now->format(DateTime::RFC822));
1394 $PAGE->assign('eol', PHP_EOL); 1429 $PAGE->assign('eol', PHP_EOL);
@@ -1456,14 +1491,20 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1456 $pluginMeta = $pluginManager->getPluginsMeta(); 1491 $pluginMeta = $pluginManager->getPluginsMeta();
1457 1492
1458 // Split plugins into 2 arrays: ordered enabled plugins and disabled. 1493 // Split plugins into 2 arrays: ordered enabled plugins and disabled.
1459 $enabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] !== false; }); 1494 $enabledPlugins = array_filter($pluginMeta, function ($v) {
1495 return $v['order'] !== false;
1496 });
1460 // Load parameters. 1497 // Load parameters.
1461 $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array())); 1498 $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array()));
1462 uasort( 1499 uasort(
1463 $enabledPlugins, 1500 $enabledPlugins,
1464 function($a, $b) { return $a['order'] - $b['order']; } 1501 function ($a, $b) {
1502 return $a['order'] - $b['order'];
1503 }
1465 ); 1504 );
1466 $disabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] === false; }); 1505 $disabledPlugins = array_filter($pluginMeta, function ($v) {
1506 return $v['order'] === false;
1507 });
1467 1508
1468 $PAGE->assign('enabledPlugins', $enabledPlugins); 1509 $PAGE->assign('enabledPlugins', $enabledPlugins);
1469 $PAGE->assign('disabledPlugins', $disabledPlugins); 1510 $PAGE->assign('disabledPlugins', $disabledPlugins);
@@ -1480,21 +1521,23 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1480 foreach ($_POST as $param => $value) { 1521 foreach ($_POST as $param => $value) {
1481 $conf->set('plugins.'. $param, escape($value)); 1522 $conf->set('plugins.'. $param, escape($value));
1482 } 1523 }
1483 } 1524 } else {
1484 else {
1485 $conf->set('general.enabled_plugins', save_plugin_config($_POST)); 1525 $conf->set('general.enabled_plugins', save_plugin_config($_POST));
1486 } 1526 }
1487 $conf->write($loginManager->isLoggedIn()); 1527 $conf->write($loginManager->isLoggedIn());
1488 $history->updateSettings(); 1528 $history->updateSettings();
1489 } 1529 } catch (Exception $e) {
1490 catch (Exception $e) {
1491 error_log( 1530 error_log(
1492 'ERROR while saving plugin configuration:.' . PHP_EOL . 1531 'ERROR while saving plugin configuration:.' . PHP_EOL .
1493 $e->getMessage() 1532 $e->getMessage()
1494 ); 1533 );
1495 1534
1496 // TODO: do not handle exceptions/errors in JS. 1535 // TODO: do not handle exceptions/errors in JS.
1497 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do='. Router::$PAGE_PLUGINSADMIN .'\';</script>'; 1536 echo '<script>alert("'
1537 . $e->getMessage()
1538 .'");document.location=\'?do='
1539 . Router::$PAGE_PLUGINSADMIN
1540 .'\';</script>';
1498 exit; 1541 exit;
1499 } 1542 }
1500 header('Location: ?do='. Router::$PAGE_PLUGINSADMIN); 1543 header('Location: ?do='. Router::$PAGE_PLUGINSADMIN);
@@ -1615,8 +1658,7 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
1615 } 1658 }
1616 1659
1617 $linkDisp = array(); 1660 $linkDisp = array();
1618 while ($i<$end && $i<count($keys)) 1661 while ($i<$end && $i<count($keys)) {
1619 {
1620 $link = $linksToDisplay[$keys[$i]]; 1662 $link = $linksToDisplay[$keys[$i]];
1621 $link['description'] = format_description( 1663 $link['description'] = format_description(
1622 $link['description'], 1664 $link['description'],
@@ -1719,16 +1761,19 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
1719 * @param SessionManager $sessionManager SessionManager instance 1761 * @param SessionManager $sessionManager SessionManager instance
1720 * @param LoginManager $loginManager LoginManager instance 1762 * @param LoginManager $loginManager LoginManager instance
1721 */ 1763 */
1722function install($conf, $sessionManager, $loginManager) { 1764function install($conf, $sessionManager, $loginManager)
1765{
1723 // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. 1766 // On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
1724 if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705); 1767 if (endsWith($_SERVER['HTTP_HOST'], '.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) {
1768 mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions', 0705);
1769 }
1725 1770
1726 1771
1727 // This part makes sure sessions works correctly. 1772 // This part makes sure sessions works correctly.
1728 // (Because on some hosts, session.save_path may not be set correctly, 1773 // (Because on some hosts, session.save_path may not be set correctly,
1729 // or we may not have write access to it.) 1774 // or we may not have write access to it.)
1730 if (isset($_GET['test_session']) && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working')) 1775 if (isset($_GET['test_session'])
1731 { 1776 && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working')) {
1732 // Step 2: Check if data in session is correct. 1777 // Step 2: Check if data in session is correct.
1733 $msg = t( 1778 $msg = t(
1734 '<pre>Sessions do not seem to work correctly on your server.<br>'. 1779 '<pre>Sessions do not seem to work correctly on your server.<br>'.
@@ -1744,19 +1789,18 @@ function install($conf, $sessionManager, $loginManager) {
1744 echo '<br><a href="?">'. t('Click to try again.') .'</a></pre>'; 1789 echo '<br><a href="?">'. t('Click to try again.') .'</a></pre>';
1745 die; 1790 die;
1746 } 1791 }
1747 if (!isset($_SESSION['session_tested'])) 1792 if (!isset($_SESSION['session_tested'])) {
1748 { // Step 1 : Try to store data in session and reload page. 1793 // Step 1 : Try to store data in session and reload page.
1749 $_SESSION['session_tested'] = 'Working'; // Try to set a variable in session. 1794 $_SESSION['session_tested'] = 'Working'; // Try to set a variable in session.
1750 header('Location: '.index_url($_SERVER).'?test_session'); // Redirect to check stored data. 1795 header('Location: '.index_url($_SERVER).'?test_session'); // Redirect to check stored data.
1751 } 1796 }
1752 if (isset($_GET['test_session'])) 1797 if (isset($_GET['test_session'])) {
1753 { // Step 3: Sessions are OK. Remove test parameter from URL. 1798 // Step 3: Sessions are OK. Remove test parameter from URL.
1754 header('Location: '.index_url($_SERVER)); 1799 header('Location: '.index_url($_SERVER));
1755 } 1800 }
1756 1801
1757 1802
1758 if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) 1803 if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) {
1759 {
1760 $tz = 'UTC'; 1804 $tz = 'UTC';
1761 if (!empty($_POST['continent']) && !empty($_POST['city']) 1805 if (!empty($_POST['continent']) && !empty($_POST['city'])
1762 && isTimeZoneValid($_POST['continent'], $_POST['city']) 1806 && isTimeZoneValid($_POST['continent'], $_POST['city'])
@@ -1787,18 +1831,20 @@ function install($conf, $sessionManager, $loginManager) {
1787 try { 1831 try {
1788 // Everything is ok, let's create config file. 1832 // Everything is ok, let's create config file.
1789 $conf->write($loginManager->isLoggedIn()); 1833 $conf->write($loginManager->isLoggedIn());
1790 } 1834 } catch (Exception $e) {
1791 catch(Exception $e) {
1792 error_log( 1835 error_log(
1793 'ERROR while writing config file after installation.' . PHP_EOL . 1836 'ERROR while writing config file after installation.' . PHP_EOL .
1794 $e->getMessage() 1837 $e->getMessage()
1795 ); 1838 );
1796 1839
1797 // TODO: do not handle exceptions/errors in JS. 1840 // TODO: do not handle exceptions/errors in JS.
1798 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>'; 1841 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>';
1799 exit; 1842 exit;
1800 } 1843 }
1801 echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>'; 1844 echo '<script>alert('
1845 .'"Shaarli is now configured. '
1846 .'Please enter your login/password and start shaaring your links!"'
1847 .');document.location=\'?do=login\';</script>';
1802 exit; 1848 exit;
1803 } 1849 }
1804 1850
@@ -1822,7 +1868,7 @@ if (!isset($_SESSION['LINKS_PER_PAGE'])) {
1822 1868
1823try { 1869try {
1824 $history = new History($conf->get('resource.history')); 1870 $history = new History($conf->get('resource.history'));
1825} catch(Exception $e) { 1871} catch (Exception $e) {
1826 die($e->getMessage()); 1872 die($e->getMessage());
1827} 1873}
1828 1874
@@ -1841,7 +1887,7 @@ $container['history'] = $history;
1841$app = new \Slim\App($container); 1887$app = new \Slim\App($container);
1842 1888
1843// REST API routes 1889// REST API routes
1844$app->group('/api/v1', function() { 1890$app->group('/api/v1', function () {
1845 $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo'); 1891 $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo');
1846 $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks'); 1892 $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks');
1847 $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink'); 1893 $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink');
@@ -1858,6 +1904,7 @@ $app->group('/api/v1', function() {
1858})->add('\Shaarli\Api\ApiMiddleware'); 1904})->add('\Shaarli\Api\ApiMiddleware');
1859 1905
1860$response = $app->run(true); 1906$response = $app->run(true);
1907
1861// Hack to make Slim and Shaarli router work together: 1908// Hack to make Slim and Shaarli router work together:
1862// If a Slim route isn't found and NOT API call, we call renderPage(). 1909// If a Slim route isn't found and NOT API call, we call renderPage().
1863if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) { 1910if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) {
@@ -1865,5 +1912,12 @@ if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v
1865 header('Content-Type: text/html; charset=utf-8'); 1912 header('Content-Type: text/html; charset=utf-8');
1866 renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager); 1913 renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager);
1867} else { 1914} else {
1915 $response = $response
1916 ->withHeader('Access-Control-Allow-Origin', '*')
1917 ->withHeader(
1918 'Access-Control-Allow-Headers',
1919 'X-Requested-With, Content-Type, Accept, Origin, Authorization'
1920 )
1921 ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
1868 $app->respond($response); 1922 $app->respond($response);
1869} 1923}
diff --git a/mkdocs.yml b/mkdocs.yml
index 941fce3a..248fdbfe 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -2,7 +2,9 @@ site_name: Shaarli Documentation
2repo_url: https://github.com/shaarli/Shaarli 2repo_url: https://github.com/shaarli/Shaarli
3edit_uri: edit/master/doc/md 3edit_uri: edit/master/doc/md
4site_description: The personal, minimalist, super-fast, database free, bookmarking service 4site_description: The personal, minimalist, super-fast, database free, bookmarking service
5theme: readthedocs 5theme:
6 name: readthedocs
7 custom_dir: doc/custom_theme/
6docs_dir: doc/md 8docs_dir: doc/md
7site_dir: doc/html 9site_dir: doc/html
8# Disable strict mode until ReadTheDocs provides up-to-date MkDocs settings: 10# Disable strict mode until ReadTheDocs provides up-to-date MkDocs settings:
diff --git a/phpcs.xml b/phpcs.xml
new file mode 100644
index 00000000..29b95d56
--- /dev/null
+++ b/phpcs.xml
@@ -0,0 +1,17 @@
1<?xml version="1.0"?>
2<ruleset name="Shaarli">
3 <description>The Shaarli coding standards</description>
4
5 <file>index.php</file>
6 <file>application</file>
7 <file>plugins</file>
8 <file>tests</file>
9
10 <exclude-pattern>*/*.css</exclude-pattern>
11 <exclude-pattern>*/*.js</exclude-pattern>
12
13 <arg name="colors"/>
14
15 <rule ref="PSR1"/>
16 <rule ref="PSR2"/>
17</ruleset>
diff --git a/plugins/archiveorg/archiveorg.php b/plugins/archiveorg/archiveorg.php
index cda35751..5dcea5a6 100644
--- a/plugins/archiveorg/archiveorg.php
+++ b/plugins/archiveorg/archiveorg.php
@@ -17,7 +17,7 @@ function hook_archiveorg_render_linklist($data)
17 $archive_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/archiveorg/archiveorg.html'); 17 $archive_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/archiveorg/archiveorg.html');
18 18
19 foreach ($data['links'] as &$value) { 19 foreach ($data['links'] as &$value) {
20 if($value['private'] && preg_match('/^\?[a-zA-Z0-9-_@]{6}($|&|#)/', $value['real_url'])) { 20 if ($value['private'] && preg_match('/^\?[a-zA-Z0-9-_@]{6}($|&|#)/', $value['real_url'])) {
21 continue; 21 continue;
22 } 22 }
23 $archive = sprintf($archive_html, $value['url'], t('View on archive.org')); 23 $archive = sprintf($archive_html, $value['url'], t('View on archive.org'));
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php
index f3a63b6a..ca520d15 100644
--- a/plugins/demo_plugin/demo_plugin.php
+++ b/plugins/demo_plugin/demo_plugin.php
@@ -73,7 +73,6 @@ function hook_demo_plugin_render_header($data)
73{ 73{
74 // Only execute when linklist is rendered. 74 // Only execute when linklist is rendered.
75 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { 75 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
76
77 // If loggedin 76 // If loggedin
78 if ($data['_LOGGEDIN_'] === true) { 77 if ($data['_LOGGEDIN_'] === true) {
79 /* 78 /*
@@ -109,10 +108,10 @@ function hook_demo_plugin_render_header($data)
109 * ], 108 * ],
110 * ] 109 * ]
111 * This example renders as: 110 * This example renders as:
112 * <form form-attribute-1="form attribute 1 value" form-attribute-2="form attribute 2 value"> 111 * <form form-attribute-1="form attribute 1 value" form-attribute-2="form attribute 2 value">
113 * <input input-1-attribute-1="input 1 attribute 1 value" input-1-attribute-2="input 1 attribute 2 value"> 112 * <input input-1-attribute-1="input 1 attribute 1 value" input-1-attribute-2="input 1 attribute 2 value">
114 * <input input-2-attribute-1="input 2 attribute 1 value"> 113 * <input input-2-attribute-1="input 2 attribute 1 value">
115 * </form> 114 * </form>
116 */ 115 */
117 $form = array( 116 $form = array(
118 'attr' => array( 117 'attr' => array(
@@ -448,8 +447,7 @@ function hook_demo_plugin_render_feed($data)
448 foreach ($data['links'] as &$link) { 447 foreach ($data['links'] as &$link) {
449 if ($data['_PAGE_'] == Router::$PAGE_FEED_ATOM) { 448 if ($data['_PAGE_'] == Router::$PAGE_FEED_ATOM) {
450 $link['description'] .= ' - ATOM Feed' ; 449 $link['description'] .= ' - ATOM Feed' ;
451 } 450 } elseif ($data['_PAGE_'] == Router::$PAGE_FEED_RSS) {
452 elseif ($data['_PAGE_'] == Router::$PAGE_FEED_RSS) {
453 $link['description'] .= ' - RSS Feed'; 451 $link['description'] .= ' - RSS Feed';
454 } 452 }
455 } 453 }
diff --git a/plugins/isso/comment.png b/plugins/isso/comment.png
new file mode 100644
index 00000000..0158c03b
--- /dev/null
+++ b/plugins/isso/comment.png
Binary files differ
diff --git a/plugins/isso/isso.php b/plugins/isso/isso.php
index 5bc1cce2..378c11af 100644
--- a/plugins/isso/isso.php
+++ b/plugins/isso/isso.php
@@ -46,9 +46,36 @@ function hook_isso_render_linklist($data, $conf)
46 46
47 $isso = sprintf($issoHtml, $issoUrl, $issoUrl, $link['id'], $link['id']); 47 $isso = sprintf($issoHtml, $issoUrl, $issoUrl, $link['id'], $link['id']);
48 $data['plugin_end_zone'][] = $isso; 48 $data['plugin_end_zone'][] = $isso;
49 } else {
50 $button = '<span><a href="?%s#isso-thread">';
51 // For the default theme we use a FontAwesome icon which is better than an image
52 if ($conf->get('resource.theme') === 'default') {
53 $button .= '<i class="linklist-plugin-icon fa fa-comment"></i>';
54 } else {
55 $button .= '<img class="linklist-plugin-icon" src="plugins/isso/comment.png" ';
56 $button .= 'title="Comment on this shaare" alt="Comments" />';
57 }
58 $button .= '</a></span>';
59 foreach ($data['links'] as &$value) {
60 $commentLink = sprintf($button, $value['shorturl']);
61 $value['link_plugin'][] = $commentLink;
62 }
63 }
49 64
50 // Hackish way to include this CSS file only when necessary. 65 return $data;
51 $data['plugins_includes']['css_files'][] = PluginManager::$PLUGINS_PATH . '/isso/isso.css'; 66}
67
68/**
69 * When linklist is displayed, include isso CSS file.
70 *
71 * @param array $data - header data.
72 *
73 * @return mixed - header data with isso CSS file added.
74 */
75function hook_isso_render_includes($data)
76{
77 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
78 $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/isso/isso.css';
52 } 79 }
53 80
54 return $data; 81 return $data;
diff --git a/plugins/isso/isso_button.html b/plugins/isso/isso_button.html
new file mode 100644
index 00000000..3f828480
--- /dev/null
+++ b/plugins/isso/isso_button.html
@@ -0,0 +1,5 @@
1<span>
2 <a href="?%s#isso-thread">
3 <img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="%s" alt="archive.org" />
4 </a>
5</span>
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php
index 821bb125..8823af91 100644
--- a/plugins/markdown/markdown.php
+++ b/plugins/markdown/markdown.php
@@ -28,6 +28,7 @@ function hook_markdown_render_linklist($data, $conf)
28 $value = stripNoMarkdownTag($value); 28 $value = stripNoMarkdownTag($value);
29 continue; 29 continue;
30 } 30 }
31 $value['description_src'] = $value['description'];
31 $value['description'] = process_markdown( 32 $value['description'] = process_markdown(
32 $value['description'], 33 $value['description'],
33 $conf->get('security.markdown_escape', true), 34 $conf->get('security.markdown_escape', true),
@@ -138,7 +139,6 @@ function hook_markdown_render_includes($data)
138 || $data['_PAGE_'] == Router::$PAGE_DAILY 139 || $data['_PAGE_'] == Router::$PAGE_DAILY
139 || $data['_PAGE_'] == Router::$PAGE_EDITLINK 140 || $data['_PAGE_'] == Router::$PAGE_EDITLINK
140 ) { 141 ) {
141
142 $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/markdown/markdown.css'; 142 $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/markdown/markdown.css';
143 } 143 }
144 144
@@ -194,8 +194,7 @@ function reverse_text2clickable($description)
194 // Detect and toggle block of code 194 // Detect and toggle block of code
195 if (!$codeBlockOn) { 195 if (!$codeBlockOn) {
196 $codeBlockOn = preg_match('/^```/', $descriptionLine) > 0; 196 $codeBlockOn = preg_match('/^```/', $descriptionLine) > 0;
197 } 197 } elseif (preg_match('/^```/', $descriptionLine) > 0) {
198 elseif (preg_match('/^```/', $descriptionLine) > 0) {
199 $codeBlockOn = false; 198 $codeBlockOn = false;
200 } 199 }
201 200
@@ -215,6 +214,15 @@ function reverse_text2clickable($description)
215 $descriptionLine 214 $descriptionLine
216 ); 215 );
217 216
217 // Make hashtag links markdown ready, otherwise the links will be ignored with escape set to true
218 if (!$codeBlockOn && !$codeLineOn) {
219 $descriptionLine = preg_replace(
220 '#<a href="([^ ]*)"'. $hashtagTitle .'>([^<]+)</a>#m',
221 '[$2]($1)',
222 $descriptionLine
223 );
224 }
225
218 $descriptionOut .= $descriptionLine; 226 $descriptionOut .= $descriptionLine;
219 if ($lineCount++ < count($descriptionLines) - 1) { 227 if ($lineCount++ < count($descriptionLines) - 1) {
220 $descriptionOut .= PHP_EOL; 228 $descriptionOut .= PHP_EOL;
@@ -292,13 +300,17 @@ function sanitize_html($description)
292 foreach ($escapeTags as $tag) { 300 foreach ($escapeTags as $tag) {
293 $description = preg_replace_callback( 301 $description = preg_replace_callback(
294 '#<\s*'. $tag .'[^>]*>(.*</\s*'. $tag .'[^>]*>)?#is', 302 '#<\s*'. $tag .'[^>]*>(.*</\s*'. $tag .'[^>]*>)?#is',
295 function ($match) { return escape($match[0]); }, 303 function ($match) {
296 $description); 304 return escape($match[0]);
305 },
306 $description
307 );
297 } 308 }
298 $description = preg_replace( 309 $description = preg_replace(
299 '#(<[^>]+\s)on[a-z]*="?[^ "]*"?#is', 310 '#(<[^>]+\s)on[a-z]*="?[^ "]*"?#is',
300 '$1', 311 '$1',
301 $description); 312 $description
313 );
302 return $description; 314 return $description;
303} 315}
304 316
@@ -331,7 +343,7 @@ function process_markdown($description, $escape = true, $allowedProtocols = [])
331 ->text($processedDescription); 343 ->text($processedDescription);
332 $processedDescription = sanitize_html($processedDescription); 344 $processedDescription = sanitize_html($processedDescription);
333 345
334 if(!empty($processedDescription)){ 346 if (!empty($processedDescription)) {
335 $processedDescription = '<div class="markdown">'. $processedDescription . '</div>'; 347 $processedDescription = '<div class="markdown">'. $processedDescription . '</div>';
336 } 348 }
337 349
diff --git a/plugins/pubsubhubbub/pubsubhubbub.php b/plugins/pubsubhubbub/pubsubhubbub.php
index 184b588b..9f0342a3 100644
--- a/plugins/pubsubhubbub/pubsubhubbub.php
+++ b/plugins/pubsubhubbub/pubsubhubbub.php
@@ -6,7 +6,7 @@
6 * PubSub is a protocol which fasten up RSS fetching: 6 * PubSub is a protocol which fasten up RSS fetching:
7 * - Every time a new link is posted, Shaarli notify the hub. 7 * - Every time a new link is posted, Shaarli notify the hub.
8 * - The hub notify all feed subscribers that a new link has been posted. 8 * - The hub notify all feed subscribers that a new link has been posted.
9 * - Subscribers retrieve the new link. 9 * - Subscribers retrieve the new link.
10 */ 10 */
11 11
12use pubsubhubbub\publisher\Publisher; 12use pubsubhubbub\publisher\Publisher;
@@ -82,7 +82,8 @@ function hook_pubsubhubbub_save_link($data, $conf)
82 * 82 *
83 * @throws Exception An error occurred. 83 * @throws Exception An error occurred.
84 */ 84 */
85function nocurl_http_post($url, $postString) { 85function nocurl_http_post($url, $postString)
86{
86 $params = array('http' => array( 87 $params = array('http' => array(
87 'method' => 'POST', 88 'method' => 'POST',
88 'content' => $postString, 89 'content' => $postString,
diff --git a/plugins/qrcode/qrcode.php b/plugins/qrcode/qrcode.php
index 0f96a106..4b59caa0 100644
--- a/plugins/qrcode/qrcode.php
+++ b/plugins/qrcode/qrcode.php
@@ -17,7 +17,8 @@ function hook_qrcode_render_linklist($data)
17 $qrcode_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.html'); 17 $qrcode_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.html');
18 18
19 foreach ($data['links'] as &$value) { 19 foreach ($data['links'] as &$value) {
20 $qrcode = sprintf($qrcode_html, 20 $qrcode = sprintf(
21 $qrcode_html,
21 urlencode($value['url']), 22 urlencode($value['url']),
22 $value['url'], 23 $value['url'],
23 PluginManager::$PLUGINS_PATH 24 PluginManager::$PLUGINS_PATH
diff --git a/plugins/wallabag/wallabag.php b/plugins/wallabag/wallabag.php
index 9dfd079e..a6476c71 100644
--- a/plugins/wallabag/wallabag.php
+++ b/plugins/wallabag/wallabag.php
@@ -69,4 +69,3 @@ function wallabag_dummy_translation()
69 t('Wallabag API URL'); 69 t('Wallabag API URL');
70 t('Wallabag API version (1 or 2)'); 70 t('Wallabag API version (1 or 2)');
71} 71}
72
diff --git a/tests/ApplicationUtilsTest.php b/tests/ApplicationUtilsTest.php
index ff4c9e17..fe5f84ce 100644
--- a/tests/ApplicationUtilsTest.php
+++ b/tests/ApplicationUtilsTest.php
@@ -17,7 +17,7 @@ class FakeApplicationUtils extends ApplicationUtils
17 /** 17 /**
18 * Toggle HTTP requests, allow overriding the version code 18 * Toggle HTTP requests, allow overriding the version code
19 */ 19 */
20 public static function getVersion($url, $timeout=0) 20 public static function getVersion($url, $timeout = 0)
21 { 21 {
22 return self::$VERSION_CODE; 22 return self::$VERSION_CODE;
23 } 23 }
@@ -67,7 +67,7 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
67 '0.5.4', 67 '0.5.4',
68 ApplicationUtils::getVersion( 68 ApplicationUtils::getVersion(
69 'https://raw.githubusercontent.com/shaarli/Shaarli/' 69 'https://raw.githubusercontent.com/shaarli/Shaarli/'
70 .'v0.5.4/shaarli_version.php', 70 .'v0.5.4/shaarli_version.php',
71 $testTimeout 71 $testTimeout
72 ) 72 )
73 ); 73 );
@@ -75,7 +75,7 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
75 self::$versionPattern, 75 self::$versionPattern,
76 ApplicationUtils::getVersion( 76 ApplicationUtils::getVersion(
77 'https://raw.githubusercontent.com/shaarli/Shaarli/' 77 'https://raw.githubusercontent.com/shaarli/Shaarli/'
78 .'latest/shaarli_version.php', 78 .'latest/shaarli_version.php',
79 $testTimeout 79 $testTimeout
80 ) 80 )
81 ); 81 );
diff --git a/tests/CacheTest.php b/tests/CacheTest.php
index 992e26a5..f60fad91 100644
--- a/tests/CacheTest.php
+++ b/tests/CacheTest.php
@@ -84,7 +84,7 @@ class CacheTest extends PHPUnit_Framework_TestCase
84 invalidateCaches(self::$testCacheDir); 84 invalidateCaches(self::$testCacheDir);
85 foreach (self::$pages as $page) { 85 foreach (self::$pages as $page) {
86 $this->assertFileNotExists(self::$testCacheDir.'/'.$page.'.cache'); 86 $this->assertFileNotExists(self::$testCacheDir.'/'.$page.'.cache');
87 } 87 }
88 88
89 $this->assertArrayNotHasKey('tags', $_SESSION); 89 $this->assertArrayNotHasKey('tags', $_SESSION);
90 } 90 }
diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php
index a590306d..4ca58e5a 100644
--- a/tests/FeedBuilderTest.php
+++ b/tests/FeedBuilderTest.php
@@ -82,8 +82,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
82 $this->assertFalse($data['usepermalinks']); 82 $this->assertFalse($data['usepermalinks']);
83 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 83 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
84 84
85 // Test first link (note link) 85 // Test first not pinned link (note link)
86 $link = reset($data['links']); 86 $link = $data['links'][array_keys($data['links'])[2]];
87 $this->assertEquals(41, $link['id']); 87 $this->assertEquals(41, $link['id']);
88 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); 88 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
89 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); 89 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
@@ -119,7 +119,7 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
119 $data = $feedBuilder->buildData(); 119 $data = $feedBuilder->buildData();
120 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 120 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
121 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']); 121 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']);
122 $link = reset($data['links']); 122 $link = $data['links'][array_keys($data['links'])[2]];
123 $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']); 123 $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']);
124 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']); 124 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
125 } 125 }
@@ -148,13 +148,13 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
148 public function testBuildDataCount() 148 public function testBuildDataCount()
149 { 149 {
150 $criteria = array( 150 $criteria = array(
151 'nb' => '1', 151 'nb' => '3',
152 ); 152 );
153 $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false); 153 $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false);
154 $feedBuilder->setLocale(self::$LOCALE); 154 $feedBuilder->setLocale(self::$LOCALE);
155 $data = $feedBuilder->buildData(); 155 $data = $feedBuilder->buildData();
156 $this->assertEquals(1, count($data['links'])); 156 $this->assertEquals(3, count($data['links']));
157 $link = array_shift($data['links']); 157 $link = $data['links'][array_keys($data['links'])[2]];
158 $this->assertEquals(41, $link['id']); 158 $this->assertEquals(41, $link['id']);
159 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); 159 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
160 } 160 }
@@ -171,7 +171,7 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
171 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 171 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
172 $this->assertTrue($data['usepermalinks']); 172 $this->assertTrue($data['usepermalinks']);
173 // First link is a permalink 173 // First link is a permalink
174 $link = array_shift($data['links']); 174 $link = $data['links'][array_keys($data['links'])[2]];
175 $this->assertEquals(41, $link['id']); 175 $this->assertEquals(41, $link['id']);
176 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); 176 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
177 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); 177 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
@@ -179,7 +179,7 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
179 $this->assertContains('Direct link', $link['description']); 179 $this->assertContains('Direct link', $link['description']);
180 $this->assertContains('http://host.tld/?WDWyig', $link['description']); 180 $this->assertContains('http://host.tld/?WDWyig', $link['description']);
181 // Second link is a direct link 181 // Second link is a direct link
182 $link = array_shift($data['links']); 182 $link = $data['links'][array_keys($data['links'])[3]];
183 $this->assertEquals(8, $link['id']); 183 $this->assertEquals(8, $link['id']);
184 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'), $link['created']); 184 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'), $link['created']);
185 $this->assertEquals('http://host.tld/?RttfEw', $link['guid']); 185 $this->assertEquals('http://host.tld/?RttfEw', $link['guid']);
@@ -237,7 +237,7 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
237 ); 237 );
238 238
239 // Test first link (note link) 239 // Test first link (note link)
240 $link = array_shift($data['links']); 240 $link = $data['links'][array_keys($data['links'])[2]];
241 $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['guid']); 241 $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['guid']);
242 $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['url']); 242 $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['url']);
243 $this->assertContains('http://host.tld:8080/~user/shaarli/?addtag=hashtag', $link['description']); 243 $this->assertContains('http://host.tld:8080/~user/shaarli/?addtag=hashtag', $link['description']);
diff --git a/tests/HttpUtils/GetIpAdressFromProxyTest.php b/tests/HttpUtils/GetIpAdressFromProxyTest.php
index 6a74a45a..7af5bd9d 100644
--- a/tests/HttpUtils/GetIpAdressFromProxyTest.php
+++ b/tests/HttpUtils/GetIpAdressFromProxyTest.php
@@ -5,7 +5,8 @@ require_once 'application/HttpUtils.php';
5/** 5/**
6 * Unitary tests for getIpAddressFromProxy() 6 * Unitary tests for getIpAddressFromProxy()
7 */ 7 */
8class GetIpAdressFromProxyTest extends PHPUnit_Framework_TestCase { 8class GetIpAdressFromProxyTest extends PHPUnit_Framework_TestCase
9{
9 10
10 /** 11 /**
11 * Test without proxy 12 * Test without proxy
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 3b980878..c763c0cb 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -239,12 +239,12 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
239 public function testDays() 239 public function testDays()
240 { 240 {
241 $this->assertEquals( 241 $this->assertEquals(
242 array('20100310', '20121206', '20130614', '20150310'), 242 array('20100309', '20100310', '20121206', '20121207', '20130614', '20150310'),
243 self::$publicLinkDB->days() 243 self::$publicLinkDB->days()
244 ); 244 );
245 245
246 $this->assertEquals( 246 $this->assertEquals(
247 array('20100310', '20121206', '20130614', '20141125', '20150310'), 247 array('20100309', '20100310', '20121206', '20121207', '20130614', '20141125', '20150310'),
248 self::$privateLinkDB->days() 248 self::$privateLinkDB->days()
249 ); 249 );
250 } 250 }
@@ -362,7 +362,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
362 public function testLinkRealUrlWithoutRedirector() 362 public function testLinkRealUrlWithoutRedirector()
363 { 363 {
364 $db = new LinkDB(self::$testDatastore, false, false); 364 $db = new LinkDB(self::$testDatastore, false, false);
365 foreach($db as $link) { 365 foreach ($db as $link) {
366 $this->assertEquals($link['url'], $link['real_url']); 366 $this->assertEquals($link['url'], $link['real_url']);
367 } 367 }
368 } 368 }
@@ -374,13 +374,13 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
374 { 374 {
375 $redirector = 'http://redirector.to?'; 375 $redirector = 'http://redirector.to?';
376 $db = new LinkDB(self::$testDatastore, false, false, $redirector); 376 $db = new LinkDB(self::$testDatastore, false, false, $redirector);
377 foreach($db as $link) { 377 foreach ($db as $link) {
378 $this->assertStringStartsWith($redirector, $link['real_url']); 378 $this->assertStringStartsWith($redirector, $link['real_url']);
379 $this->assertNotFalse(strpos($link['real_url'], urlencode('://'))); 379 $this->assertNotFalse(strpos($link['real_url'], urlencode('://')));
380 } 380 }
381 381
382 $db = new LinkDB(self::$testDatastore, false, false, $redirector, false); 382 $db = new LinkDB(self::$testDatastore, false, false, $redirector, false);
383 foreach($db as $link) { 383 foreach ($db as $link) {
384 $this->assertStringStartsWith($redirector, $link['real_url']); 384 $this->assertStringStartsWith($redirector, $link['real_url']);
385 $this->assertFalse(strpos($link['real_url'], urlencode('://'))); 385 $this->assertFalse(strpos($link['real_url'], urlencode('://')));
386 } 386 }
@@ -475,13 +475,15 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
475 public function testReorderLinksDesc() 475 public function testReorderLinksDesc()
476 { 476 {
477 self::$privateLinkDB->reorder('ASC'); 477 self::$privateLinkDB->reorder('ASC');
478 $linkIds = array(42, 4, 9, 1, 0, 7, 6, 8, 41); 478 $stickyIds = [11, 10];
479 $standardIds = [42, 4, 9, 1, 0, 7, 6, 8, 41];
480 $linkIds = array_merge($stickyIds, $standardIds);
479 $cpt = 0; 481 $cpt = 0;
480 foreach (self::$privateLinkDB as $key => $value) { 482 foreach (self::$privateLinkDB as $key => $value) {
481 $this->assertEquals($linkIds[$cpt++], $key); 483 $this->assertEquals($linkIds[$cpt++], $key);
482 } 484 }
483 self::$privateLinkDB->reorder('DESC'); 485 self::$privateLinkDB->reorder('DESC');
484 $linkIds = array_reverse($linkIds); 486 $linkIds = array_merge(array_reverse($stickyIds), array_reverse($standardIds));
485 $cpt = 0; 487 $cpt = 0;
486 foreach (self::$privateLinkDB as $key => $value) { 488 foreach (self::$privateLinkDB as $key => $value) {
487 $this->assertEquals($linkIds[$cpt++], $key); 489 $this->assertEquals($linkIds[$cpt++], $key);
diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php
index 9cd6dbd4..eb54c359 100644
--- a/tests/LinkFilterTest.php
+++ b/tests/LinkFilterTest.php
@@ -76,7 +76,15 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
76 76
77 $this->assertEquals( 77 $this->assertEquals(
78 self::$refDB->countUntaggedLinks(), 78 self::$refDB->countUntaggedLinks(),
79 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, /*$request=*/'', /*$casesensitive=*/false, /*$visibility=*/'all', /*$untaggedonly=*/true)) 79 count(
80 self::$linkFilter->filter(
81 LinkFilter::$FILTER_TAG,
82 /*$request=*/'',
83 /*$casesensitive=*/false,
84 /*$visibility=*/'all',
85 /*$untaggedonly=*/true
86 )
87 )
80 ); 88 );
81 89
82 $this->assertEquals( 90 $this->assertEquals(
@@ -246,7 +254,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
246 2, 254 2,
247 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'ars.userfriendly.org')) 255 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'ars.userfriendly.org'))
248 ); 256 );
249 257
250 $this->assertEquals( 258 $this->assertEquals(
251 2, 259 2,
252 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'ars org')) 260 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'ars org'))
@@ -288,16 +296,16 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
288 1, 296 1,
289 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'publishing media')) 297 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'publishing media'))
290 ); 298 );
291 299
292 $this->assertEquals( 300 $this->assertEquals(
293 1, 301 1,
294 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'mercurial w3c')) 302 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'mercurial w3c'))
295 ); 303 );
296 304
297 $this->assertEquals( 305 $this->assertEquals(
298 3, 306 3,
299 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '"free software"')) 307 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '"free software"'))
300 ); 308 );
301 } 309 }
302 310
303 /** 311 /**
diff --git a/tests/LinkUtilsTest.php b/tests/LinkUtilsTest.php
index 7fbd59b0..5407159a 100644
--- a/tests/LinkUtilsTest.php
+++ b/tests/LinkUtilsTest.php
@@ -83,7 +83,9 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase
83 'Date: Sat, 28 Oct 2017 12:01:33 GMT', 83 'Date: Sat, 28 Oct 2017 12:01:33 GMT',
84 'Content-Type: text/html; charset=utf-8', 84 'Content-Type: text/html; charset=utf-8',
85 'Status: 200 OK', 85 'Status: 200 OK',
86 'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea', 86 'end' => 'th=device-width">'
87 .'<title>Refactoring · GitHub</title>'
88 .'<link rel="search" type="application/opensea',
87 '<title>ignored</title>', 89 '<title>ignored</title>',
88 ]; 90 ];
89 foreach ($data as $key => $line) { 91 foreach ($data as $key => $line) {
@@ -106,7 +108,9 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase
106 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset'); 108 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset');
107 $data = [ 109 $data = [
108 'HTTP/1.1 200 OK', 110 'HTTP/1.1 200 OK',
109 'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea', 111 'end' => 'th=device-width">'
112 .'<title>Refactoring · GitHub</title>'
113 .'<link rel="search" type="application/opensea',
110 '<title>ignored</title>', 114 '<title>ignored</title>',
111 ]; 115 ];
112 foreach ($data as $key => $line) { 116 foreach ($data as $key => $line) {
@@ -126,7 +130,9 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase
126 $data = [ 130 $data = [
127 'HTTP/1.1 200 OK', 131 'HTTP/1.1 200 OK',
128 '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />', 132 '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />',
129 'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea', 133 'end' => 'th=device-width">'
134 .'<title>Refactoring · GitHub</title>'
135 .'<link rel="search" type="application/opensea',
130 '<title>ignored</title>', 136 '<title>ignored</title>',
131 ]; 137 ];
132 foreach ($data as $key => $line) { 138 foreach ($data as $key => $line) {
@@ -211,23 +217,26 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase
211 public function testText2clickableWithoutRedirector() 217 public function testText2clickableWithoutRedirector()
212 { 218 {
213 $text = 'stuff http://hello.there/is=someone#here otherstuff'; 219 $text = 'stuff http://hello.there/is=someone#here otherstuff';
214 $expectedText = 'stuff <a href="http://hello.there/is=someone#here">http://hello.there/is=someone#here</a> otherstuff'; 220 $expectedText = 'stuff <a href="http://hello.there/is=someone#here">'
221 .'http://hello.there/is=someone#here</a> otherstuff';
215 $processedText = text2clickable($text, ''); 222 $processedText = text2clickable($text, '');
216 $this->assertEquals($expectedText, $processedText); 223 $this->assertEquals($expectedText, $processedText);
217 224
218 $text = 'stuff http://hello.there/is=someone#here(please) otherstuff'; 225 $text = 'stuff http://hello.there/is=someone#here(please) otherstuff';
219 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)">http://hello.there/is=someone#here(please)</a> otherstuff'; 226 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)">'
227 .'http://hello.there/is=someone#here(please)</a> otherstuff';
220 $processedText = text2clickable($text, ''); 228 $processedText = text2clickable($text, '');
221 $this->assertEquals($expectedText, $processedText); 229 $this->assertEquals($expectedText, $processedText);
222 230
223 $text = 'stuff http://hello.there/is=someone#here(please)&no otherstuff'; 231 $text = 'stuff http://hello.there/is=someone#here(please)&no otherstuff';
224 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)&no">http://hello.there/is=someone#here(please)&no</a> otherstuff'; 232 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)&no">'
233 .'http://hello.there/is=someone#here(please)&no</a> otherstuff';
225 $processedText = text2clickable($text, ''); 234 $processedText = text2clickable($text, '');
226 $this->assertEquals($expectedText, $processedText); 235 $this->assertEquals($expectedText, $processedText);
227 } 236 }
228 237
229 /** 238 /**
230 * Test text2clickable a redirector set. 239 * Test text2clickable with a redirector set.
231 */ 240 */
232 public function testText2clickableWithRedirector() 241 public function testText2clickableWithRedirector()
233 { 242 {
@@ -410,4 +419,3 @@ function ut_curl_getinfo_rs_ct_ko($ch, $type)
410 return 'text/plain'; 419 return 'text/plain';
411 } 420 }
412} 421}
413
diff --git a/tests/NetscapeBookmarkUtils/BookmarkExportTest.php b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
index 6a47bbb9..77fbd5f3 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
@@ -110,7 +110,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
110 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, ''); 110 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, '');
111 $this->assertEquals( 111 $this->assertEquals(
112 '?WDWyig', 112 '?WDWyig',
113 $links[0]['url'] 113 $links[2]['url']
114 ); 114 );
115 } 115 }
116 116
@@ -128,7 +128,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
128 ); 128 );
129 $this->assertEquals( 129 $this->assertEquals(
130 $indexUrl . '?WDWyig', 130 $indexUrl . '?WDWyig',
131 $links[0]['url'] 131 $links[2]['url']
132 ); 132 );
133 } 133 }
134} 134}
diff --git a/tests/RouterTest.php b/tests/RouterTest.php
index 544bcf9c..abf1bd5f 100644
--- a/tests/RouterTest.php
+++ b/tests/RouterTest.php
@@ -218,7 +218,6 @@ class RouterTest extends PHPUnit_Framework_TestCase
218 Router::$PAGE_CHANGEPASSWORD, 218 Router::$PAGE_CHANGEPASSWORD,
219 Router::findPage('do=changepasswd&stuff', array(), true) 219 Router::findPage('do=changepasswd&stuff', array(), true)
220 ); 220 );
221
222 } 221 }
223 222
224 /** 223 /**
diff --git a/tests/ThumbnailerTest.php b/tests/ThumbnailerTest.php
index 08311545..c01849f7 100644
--- a/tests/ThumbnailerTest.php
+++ b/tests/ThumbnailerTest.php
@@ -98,15 +98,17 @@ class ThumbnailerTest extends TestCase
98 ini_set('error_log', $oldlog); 98 ini_set('error_log', $oldlog);
99 } 99 }
100 100
101 protected function rrmdirContent($dir) { 101 protected function rrmdirContent($dir)
102 {
102 if (is_dir($dir)) { 103 if (is_dir($dir)) {
103 $objects = scandir($dir); 104 $objects = scandir($dir);
104 foreach ($objects as $object) { 105 foreach ($objects as $object) {
105 if ($object != "." && $object != "..") { 106 if ($object != "." && $object != "..") {
106 if (is_dir($dir."/".$object)) 107 if (is_dir($dir."/".$object)) {
107 $this->rrmdirContent($dir."/".$object); 108 $this->rrmdirContent($dir."/".$object);
108 else 109 } else {
109 unlink($dir."/".$object); 110 unlink($dir."/".$object);
111 }
110 } 112 }
111 } 113 }
112 } 114 }
diff --git a/tests/Updater/DummyUpdater.php b/tests/Updater/DummyUpdater.php
index a0be4413..a805ab5e 100644
--- a/tests/Updater/DummyUpdater.php
+++ b/tests/Updater/DummyUpdater.php
@@ -31,7 +31,7 @@ class DummyUpdater extends Updater
31 * 31 *
32 * @return bool true. 32 * @return bool true.
33 */ 33 */
34 private final function updateMethodDummy1() 34 final private function updateMethodDummy1()
35 { 35 {
36 return true; 36 return true;
37 } 37 }
@@ -41,7 +41,7 @@ class DummyUpdater extends Updater
41 * 41 *
42 * @return bool true. 42 * @return bool true.
43 */ 43 */
44 private final function updateMethodDummy2() 44 final private function updateMethodDummy2()
45 { 45 {
46 return true; 46 return true;
47 } 47 }
@@ -51,7 +51,7 @@ class DummyUpdater extends Updater
51 * 51 *
52 * @return bool true. 52 * @return bool true.
53 */ 53 */
54 private final function updateMethodDummy3() 54 final private function updateMethodDummy3()
55 { 55 {
56 return true; 56 return true;
57 } 57 }
@@ -61,7 +61,7 @@ class DummyUpdater extends Updater
61 * 61 *
62 * @throws Exception error. 62 * @throws Exception error.
63 */ 63 */
64 private final function updateMethodException() 64 final private function updateMethodException()
65 { 65 {
66 throw new Exception('whatever'); 66 throw new Exception('whatever');
67 } 67 }
diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php
index cacee2d2..c4a6e7ef 100644
--- a/tests/Updater/UpdaterTest.php
+++ b/tests/Updater/UpdaterTest.php
@@ -393,20 +393,32 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
393 $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']); 393 $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']);
394 $this->assertEquals('samba cartoon web', $linkDB[0]['tags']); 394 $this->assertEquals('samba cartoon web', $linkDB[0]['tags']);
395 $this->assertTrue($linkDB[0]['private']); 395 $this->assertTrue($linkDB[0]['private']);
396 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), $linkDB[0]['created']); 396 $this->assertEquals(
397 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'),
398 $linkDB[0]['created']
399 );
397 400
398 $this->assertTrue(isset($linkDB[1])); 401 $this->assertTrue(isset($linkDB[1]));
399 $this->assertFalse(isset($linkDB[1]['linkdate'])); 402 $this->assertFalse(isset($linkDB[1]['linkdate']));
400 $this->assertEquals(1, $linkDB[1]['id']); 403 $this->assertEquals(1, $linkDB[1]['id']);
401 $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']); 404 $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']);
402 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), $linkDB[1]['created']); 405 $this->assertEquals(
406 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'),
407 $linkDB[1]['created']
408 );
403 409
404 $this->assertTrue(isset($linkDB[2])); 410 $this->assertTrue(isset($linkDB[2]));
405 $this->assertFalse(isset($linkDB[2]['linkdate'])); 411 $this->assertFalse(isset($linkDB[2]['linkdate']));
406 $this->assertEquals(2, $linkDB[2]['id']); 412 $this->assertEquals(2, $linkDB[2]['id']);
407 $this->assertEquals('Geek and Poke', $linkDB[2]['title']); 413 $this->assertEquals('Geek and Poke', $linkDB[2]['title']);
408 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), $linkDB[2]['created']); 414 $this->assertEquals(
409 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'), $linkDB[2]['updated']); 415 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'),
416 $linkDB[2]['created']
417 );
418 $this->assertEquals(
419 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'),
420 $linkDB[2]['updated']
421 );
410 } 422 }
411 423
412 /** 424 /**
@@ -688,6 +700,7 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
688 } 700 }
689 701
690 /** 702 /**
703<<<<<<< HEAD
691 * Test updateMethodWebThumbnailer with thumbnails enabled. 704 * Test updateMethodWebThumbnailer with thumbnails enabled.
692 */ 705 */
693 public function testUpdateMethodWebThumbnailerEnabled() 706 public function testUpdateMethodWebThumbnailerEnabled()
@@ -732,4 +745,64 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
732 $this->assertEquals(53, $this->conf->get('thumbnails.height')); 745 $this->assertEquals(53, $this->conf->get('thumbnails.height'));
733 $this->assertTrue(empty($_SESSION['warnings'])); 746 $this->assertTrue(empty($_SESSION['warnings']));
734 } 747 }
748
749 /**
750 * Test updateMethodSetSticky().
751 */
752 public function testUpdateStickyValid()
753 {
754 $blank = [
755 'id' => 1,
756 'url' => 'z',
757 'title' => '',
758 'description' => '',
759 'tags' => '',
760 'created' => new DateTime(),
761 ];
762 $links = [
763 1 => ['id' => 1] + $blank,
764 2 => ['id' => 2] + $blank,
765 ];
766 $refDB = new ReferenceLinkDB();
767 $refDB->setLinks($links);
768 $refDB->write(self::$testDatastore);
769 $linkDB = new LinkDB(self::$testDatastore, true, false);
770
771 $updater = new Updater(array(), $linkDB, $this->conf, true);
772 $this->assertTrue($updater->updateMethodSetSticky());
773
774 $linkDB = new LinkDB(self::$testDatastore, true, false);
775 foreach ($linkDB as $link) {
776 $this->assertFalse($link['sticky']);
777 }
778 }
779
780 /**
781 * Test updateMethodSetSticky().
782 */
783 public function testUpdateStickyNothingToDo()
784 {
785 $blank = [
786 'id' => 1,
787 'url' => 'z',
788 'title' => '',
789 'description' => '',
790 'tags' => '',
791 'created' => new DateTime(),
792 ];
793 $links = [
794 1 => ['id' => 1, 'sticky' => true] + $blank,
795 2 => ['id' => 2] + $blank,
796 ];
797 $refDB = new ReferenceLinkDB();
798 $refDB->setLinks($links);
799 $refDB->write(self::$testDatastore);
800 $linkDB = new LinkDB(self::$testDatastore, true, false);
801
802 $updater = new Updater(array(), $linkDB, $this->conf, true);
803 $this->assertTrue($updater->updateMethodSetSticky());
804
805 $linkDB = new LinkDB(self::$testDatastore, true, false);
806 $this->assertTrue($linkDB[1]['sticky']);
807 }
735} 808}
diff --git a/tests/Url/CleanupUrlTest.php b/tests/Url/CleanupUrlTest.php
index 1407d7d2..24791948 100644
--- a/tests/Url/CleanupUrlTest.php
+++ b/tests/Url/CleanupUrlTest.php
@@ -107,4 +107,3 @@ class CleanupUrlTest extends PHPUnit_Framework_TestCase
107 ); 107 );
108 } 108 }
109} 109}
110
diff --git a/tests/Url/GetUrlSchemeTest.php b/tests/Url/GetUrlSchemeTest.php
index 72d80b30..18b932d6 100644
--- a/tests/Url/GetUrlSchemeTest.php
+++ b/tests/Url/GetUrlSchemeTest.php
@@ -28,4 +28,3 @@ class GetUrlSchemeTest extends PHPUnit_Framework_TestCase
28 $this->assertEquals('git', get_url_scheme('git://domain.tld/push?pull=clone#checkout')); 28 $this->assertEquals('git', get_url_scheme('git://domain.tld/push?pull=clone#checkout'));
29 } 29 }
30} 30}
31
diff --git a/tests/Url/UnparseUrlTest.php b/tests/Url/UnparseUrlTest.php
index edde73e4..e314b484 100644
--- a/tests/Url/UnparseUrlTest.php
+++ b/tests/Url/UnparseUrlTest.php
@@ -28,4 +28,3 @@ class UnparseUrlTest extends PHPUnit_Framework_TestCase
28 $this->assertEquals($ref, unparse_url(parse_url($ref))); 28 $this->assertEquals($ref, unparse_url(parse_url($ref)));
29 } 29 }
30} 30}
31
diff --git a/tests/Url/UrlTest.php b/tests/Url/UrlTest.php
index aa2f2234..db229ce0 100644
--- a/tests/Url/UrlTest.php
+++ b/tests/Url/UrlTest.php
@@ -16,7 +16,7 @@ class UrlTest extends PHPUnit_Framework_TestCase
16 /** 16 /**
17 * Helper method 17 * Helper method
18 */ 18 */
19 private function assertUrlIsCleaned($query='', $fragment='') 19 private function assertUrlIsCleaned($query = '', $fragment = '')
20 { 20 {
21 $url = new Url(self::$baseUrl.$query.$fragment); 21 $url = new Url(self::$baseUrl.$query.$fragment);
22 $url->cleanup(); 22 $url->cleanup();
@@ -135,13 +135,13 @@ class UrlTest extends PHPUnit_Framework_TestCase
135 'about://reader?url=' . urlencode(self::$baseUrl .'?my=stuff&is=kept') 135 'about://reader?url=' . urlencode(self::$baseUrl .'?my=stuff&is=kept')
136 ); 136 );
137 $this->assertEquals(self::$baseUrl.'?my=stuff&is=kept', $url->cleanup()); 137 $this->assertEquals(self::$baseUrl.'?my=stuff&is=kept', $url->cleanup());
138
139 } 138 }
140 139
141 /** 140 /**
142 * Test default http scheme. 141 * Test default http scheme.
143 */ 142 */
144 public function testDefaultScheme() { 143 public function testDefaultScheme()
144 {
145 $url = new Url(self::$baseUrl); 145 $url = new Url(self::$baseUrl);
146 $this->assertEquals('http', $url->getScheme()); 146 $this->assertEquals('http', $url->getScheme());
147 $url = new Url('domain.tld'); 147 $url = new Url('domain.tld');
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php
index 6cd37a7a..d0abd996 100644
--- a/tests/UtilsTest.php
+++ b/tests/UtilsTest.php
@@ -187,7 +187,8 @@ class UtilsTest extends PHPUnit_Framework_TestCase
187 /** 187 /**
188 * Test generate location with valid data. 188 * Test generate location with valid data.
189 */ 189 */
190 public function testGenerateLocation() { 190 public function testGenerateLocation()
191 {
191 $ref = 'http://localhost/?test'; 192 $ref = 'http://localhost/?test';
192 $this->assertEquals($ref, generateLocation($ref, 'localhost')); 193 $this->assertEquals($ref, generateLocation($ref, 'localhost'));
193 $ref = 'http://localhost:8080/?test'; 194 $ref = 'http://localhost:8080/?test';
@@ -199,7 +200,8 @@ class UtilsTest extends PHPUnit_Framework_TestCase
199 /** 200 /**
200 * Test generate location - anti loop. 201 * Test generate location - anti loop.
201 */ 202 */
202 public function testGenerateLocationLoop() { 203 public function testGenerateLocationLoop()
204 {
203 $ref = 'http://localhost/?test'; 205 $ref = 'http://localhost/?test';
204 $this->assertEquals('?', generateLocation($ref, 'localhost', array('test'))); 206 $this->assertEquals('?', generateLocation($ref, 'localhost', array('test')));
205 } 207 }
@@ -207,7 +209,8 @@ class UtilsTest extends PHPUnit_Framework_TestCase
207 /** 209 /**
208 * Test generate location - from other domain. 210 * Test generate location - from other domain.
209 */ 211 */
210 public function testGenerateLocationOut() { 212 public function testGenerateLocationOut()
213 {
211 $ref = 'http://somewebsite.com/?test'; 214 $ref = 'http://somewebsite.com/?test';
212 $this->assertEquals('?', generateLocation($ref, 'localhost')); 215 $this->assertEquals('?', generateLocation($ref, 'localhost'));
213 } 216 }
diff --git a/tests/api/ApiUtilsTest.php b/tests/api/ApiUtilsTest.php
index 62baf4c5..df4e189a 100644
--- a/tests/api/ApiUtilsTest.php
+++ b/tests/api/ApiUtilsTest.php
@@ -4,7 +4,6 @@ namespace Shaarli\Api;
4 4
5use Shaarli\Base64Url; 5use Shaarli\Base64Url;
6 6
7
8/** 7/**
9 * Class ApiUtilsTest 8 * Class ApiUtilsTest
10 */ 9 */
@@ -34,7 +33,7 @@ class ApiUtilsTest extends \PHPUnit_Framework_TestCase
34 $payload = Base64Url::encode('{ 33 $payload = Base64Url::encode('{
35 "iat": '. time() .' 34 "iat": '. time() .'
36 }'); 35 }');
37 $signature = Base64Url::encode(hash_hmac('sha512', $header .'.'. $payload , $secret, true)); 36 $signature = Base64Url::encode(hash_hmac('sha512', $header .'.'. $payload, $secret, true));
38 return $header .'.'. $payload .'.'. $signature; 37 return $header .'.'. $payload .'.'. $signature;
39 } 38 }
40 39
diff --git a/tests/api/controllers/history/HistoryTest.php b/tests/api/controllers/history/HistoryTest.php
index 61046d97..ff34e16d 100644
--- a/tests/api/controllers/history/HistoryTest.php
+++ b/tests/api/controllers/history/HistoryTest.php
@@ -3,7 +3,6 @@
3 3
4namespace Shaarli\Api\Controllers; 4namespace Shaarli\Api\Controllers;
5 5
6
7use Shaarli\Config\ConfigManager; 6use Shaarli\Config\ConfigManager;
8use Slim\Container; 7use Slim\Container;
9use Slim\Http\Environment; 8use Slim\Http\Environment;
diff --git a/tests/api/controllers/info/InfoTest.php b/tests/api/controllers/info/InfoTest.php
index f7e63bfa..e437082a 100644
--- a/tests/api/controllers/info/InfoTest.php
+++ b/tests/api/controllers/info/InfoTest.php
@@ -10,9 +10,9 @@ use Slim\Http\Response;
10 10
11/** 11/**
12 * Class InfoTest 12 * Class InfoTest
13 * 13 *
14 * Test REST API controller Info. 14 * Test REST API controller Info.
15 * 15 *
16 * @package Api\Controllers 16 * @package Api\Controllers
17 */ 17 */
18class InfoTest extends \PHPUnit_Framework_TestCase 18class InfoTest extends \PHPUnit_Framework_TestCase
diff --git a/tests/api/controllers/links/GetLinksTest.php b/tests/api/controllers/links/GetLinksTest.php
index d22ed3bf..64f02774 100644
--- a/tests/api/controllers/links/GetLinksTest.php
+++ b/tests/api/controllers/links/GetLinksTest.php
@@ -95,7 +95,7 @@ class GetLinksTest extends \PHPUnit_Framework_TestCase
95 $this->assertEquals($this->refDB->countLinks(), count($data)); 95 $this->assertEquals($this->refDB->countLinks(), count($data));
96 96
97 // Check order 97 // Check order
98 $order = [41, 8, 6, 7, 0, 1, 9, 4, 42]; 98 $order = [10, 11, 41, 8, 6, 7, 0, 1, 9, 4, 42];
99 $cpt = 0; 99 $cpt = 0;
100 foreach ($data as $link) { 100 foreach ($data as $link) {
101 $this->assertEquals(self::NB_FIELDS_LINK, count($link)); 101 $this->assertEquals(self::NB_FIELDS_LINK, count($link));
@@ -103,7 +103,7 @@ class GetLinksTest extends \PHPUnit_Framework_TestCase
103 } 103 }
104 104
105 // Check first element fields 105 // Check first element fields
106 $first = $data[0]; 106 $first = $data[2];
107 $this->assertEquals('http://domain.tld/?WDWyig', $first['url']); 107 $this->assertEquals('http://domain.tld/?WDWyig', $first['url']);
108 $this->assertEquals('WDWyig', $first['shorturl']); 108 $this->assertEquals('WDWyig', $first['shorturl']);
109 $this->assertEquals('Link title: @website', $first['title']); 109 $this->assertEquals('Link title: @website', $first['title']);
@@ -120,7 +120,7 @@ class GetLinksTest extends \PHPUnit_Framework_TestCase
120 $this->assertEmpty($first['updated']); 120 $this->assertEmpty($first['updated']);
121 121
122 // Multi tags 122 // Multi tags
123 $link = $data[1]; 123 $link = $data[3];
124 $this->assertEquals(7, count($link['tags'])); 124 $this->assertEquals(7, count($link['tags']));
125 125
126 // Update date 126 // Update date
@@ -138,7 +138,7 @@ class GetLinksTest extends \PHPUnit_Framework_TestCase
138 { 138 {
139 $env = Environment::mock([ 139 $env = Environment::mock([
140 'REQUEST_METHOD' => 'GET', 140 'REQUEST_METHOD' => 'GET',
141 'QUERY_STRING' => 'offset=1&limit=1' 141 'QUERY_STRING' => 'offset=3&limit=1'
142 ]); 142 ]);
143 $request = Request::createFromEnvironment($env); 143 $request = Request::createFromEnvironment($env);
144 $response = $this->controller->getLinks($request, new Response()); 144 $response = $this->controller->getLinks($request, new Response());
@@ -164,7 +164,7 @@ class GetLinksTest extends \PHPUnit_Framework_TestCase
164 $data = json_decode((string) $response->getBody(), true); 164 $data = json_decode((string) $response->getBody(), true);
165 $this->assertEquals($this->refDB->countLinks(), count($data)); 165 $this->assertEquals($this->refDB->countLinks(), count($data));
166 // Check order 166 // Check order
167 $order = [41, 8, 6, 7, 0, 1, 9, 4, 42]; 167 $order = [10, 11, 41, 8, 6, 7, 0, 1, 9, 4, 42];
168 $cpt = 0; 168 $cpt = 0;
169 foreach ($data as $link) { 169 foreach ($data as $link) {
170 $this->assertEquals(self::NB_FIELDS_LINK, count($link)); 170 $this->assertEquals(self::NB_FIELDS_LINK, count($link));
@@ -205,7 +205,8 @@ class GetLinksTest extends \PHPUnit_Framework_TestCase
205 $this->assertEquals(200, $response->getStatusCode()); 205 $this->assertEquals(200, $response->getStatusCode());
206 $data = json_decode((string)$response->getBody(), true); 206 $data = json_decode((string)$response->getBody(), true);
207 $this->assertEquals($this->refDB->countLinks(), count($data)); 207 $this->assertEquals($this->refDB->countLinks(), count($data));
208 $this->assertEquals(41, $data[0]['id']); 208 $this->assertEquals(10, $data[0]['id']);
209 $this->assertEquals(41, $data[2]['id']);
209 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0])); 210 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
210 } 211 }
211 212
@@ -243,7 +244,8 @@ class GetLinksTest extends \PHPUnit_Framework_TestCase
243 $this->assertEquals(200, $response->getStatusCode()); 244 $this->assertEquals(200, $response->getStatusCode());
244 $data = json_decode((string)$response->getBody(), true); 245 $data = json_decode((string)$response->getBody(), true);
245 $this->assertEquals($this->refDB->countPublicLinks(), count($data)); 246 $this->assertEquals($this->refDB->countPublicLinks(), count($data));
246 $this->assertEquals(41, $data[0]['id']); 247 $this->assertEquals(10, $data[0]['id']);
248 $this->assertEquals(41, $data[2]['id']);
247 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0])); 249 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
248 } 250 }
249 251
@@ -413,8 +415,9 @@ class GetLinksTest extends \PHPUnit_Framework_TestCase
413 $response = $this->controller->getLinks($request, new Response()); 415 $response = $this->controller->getLinks($request, new Response());
414 $this->assertEquals(200, $response->getStatusCode()); 416 $this->assertEquals(200, $response->getStatusCode());
415 $data = json_decode((string) $response->getBody(), true); 417 $data = json_decode((string) $response->getBody(), true);
416 $this->assertEquals(9, count($data)); 418 $this->assertEquals(\ReferenceLinkDB::$NB_LINKS_TOTAL, count($data));
417 $this->assertEquals(41, $data[0]['id']); 419 $this->assertEquals(10, $data[0]['id']);
420 $this->assertEquals(41, $data[2]['id']);
418 421
419 // wildcard: optional ('*' does not need to expand) 422 // wildcard: optional ('*' does not need to expand)
420 $env = Environment::mock([ 423 $env = Environment::mock([
diff --git a/tests/api/controllers/links/PostLinkTest.php b/tests/api/controllers/links/PostLinkTest.php
index 100a9170..5c2b5623 100644
--- a/tests/api/controllers/links/PostLinkTest.php
+++ b/tests/api/controllers/links/PostLinkTest.php
@@ -2,7 +2,6 @@
2 2
3namespace Shaarli\Api\Controllers; 3namespace Shaarli\Api\Controllers;
4 4
5
6use PHPUnit\Framework\TestCase; 5use PHPUnit\Framework\TestCase;
7use Shaarli\Config\ConfigManager; 6use Shaarli\Config\ConfigManager;
8use Slim\Container; 7use Slim\Container;
@@ -128,7 +127,9 @@ class PostLinkTest extends TestCase
128 $this->assertEquals('', $data['description']); 127 $this->assertEquals('', $data['description']);
129 $this->assertEquals([], $data['tags']); 128 $this->assertEquals([], $data['tags']);
130 $this->assertEquals(false, $data['private']); 129 $this->assertEquals(false, $data['private']);
131 $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])); 130 $this->assertTrue(
131 new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
132 );
132 $this->assertEquals('', $data['updated']); 133 $this->assertEquals('', $data['updated']);
133 134
134 $historyEntry = $this->history->getHistory()[0]; 135 $historyEntry = $this->history->getHistory()[0];
@@ -171,7 +172,9 @@ class PostLinkTest extends TestCase
171 $this->assertEquals($link['description'], $data['description']); 172 $this->assertEquals($link['description'], $data['description']);
172 $this->assertEquals($link['tags'], $data['tags']); 173 $this->assertEquals($link['tags'], $data['tags']);
173 $this->assertEquals(true, $data['private']); 174 $this->assertEquals(true, $data['private']);
174 $this->assertTrue(new \DateTime('2 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])); 175 $this->assertTrue(
176 new \DateTime('2 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
177 );
175 $this->assertEquals('', $data['updated']); 178 $this->assertEquals('', $data['updated']);
176 } 179 }
177 180
diff --git a/tests/api/controllers/links/PutLinkTest.php b/tests/api/controllers/links/PutLinkTest.php
index 8a562571..f276b4c1 100644
--- a/tests/api/controllers/links/PutLinkTest.php
+++ b/tests/api/controllers/links/PutLinkTest.php
@@ -3,7 +3,6 @@
3 3
4namespace Shaarli\Api\Controllers; 4namespace Shaarli\Api\Controllers;
5 5
6
7use Shaarli\Config\ConfigManager; 6use Shaarli\Config\ConfigManager;
8use Slim\Container; 7use Slim\Container;
9use Slim\Http\Environment; 8use Slim\Http\Environment;
@@ -115,7 +114,9 @@ class PutLinkTest extends \PHPUnit_Framework_TestCase
115 \DateTime::createFromFormat('Ymd_His', '20150310_114651'), 114 \DateTime::createFromFormat('Ymd_His', '20150310_114651'),
116 \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) 115 \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
117 ); 116 );
118 $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])); 117 $this->assertTrue(
118 new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
119 );
119 120
120 $historyEntry = $this->history->getHistory()[0]; 121 $historyEntry = $this->history->getHistory()[0];
121 $this->assertEquals(\History::UPDATED, $historyEntry['event']); 122 $this->assertEquals(\History::UPDATED, $historyEntry['event']);
@@ -160,7 +161,9 @@ class PutLinkTest extends \PHPUnit_Framework_TestCase
160 \DateTime::createFromFormat('Ymd_His', '20150310_114651'), 161 \DateTime::createFromFormat('Ymd_His', '20150310_114651'),
161 \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) 162 \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
162 ); 163 );
163 $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])); 164 $this->assertTrue(
165 new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
166 );
164 } 167 }
165 168
166 /** 169 /**
diff --git a/tests/api/controllers/tags/PutTagTest.php b/tests/api/controllers/tags/PutTagTest.php
index 6f7dec22..38017243 100644
--- a/tests/api/controllers/tags/PutTagTest.php
+++ b/tests/api/controllers/tags/PutTagTest.php
@@ -3,7 +3,6 @@
3 3
4namespace Shaarli\Api\Controllers; 4namespace Shaarli\Api\Controllers;
5 5
6
7use Shaarli\Api\Exceptions\ApiBadParametersException; 6use Shaarli\Api\Exceptions\ApiBadParametersException;
8use Shaarli\Config\ConfigManager; 7use Shaarli\Config\ConfigManager;
9use Slim\Container; 8use Slim\Container;
diff --git a/tests/languages/de/UtilsDeTest.php b/tests/languages/de/UtilsDeTest.php
index 4569c923..588c9fd6 100644
--- a/tests/languages/de/UtilsDeTest.php
+++ b/tests/languages/de/UtilsDeTest.php
@@ -20,7 +20,7 @@ class UtilsDeTest extends UtilsTest
20 public function testDateFormatNoTime() 20 public function testDateFormatNoTime()
21 { 21 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112'); 22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertRegExp('/1\. Januar 2017/', format_date($date, false,true)); 23 $this->assertRegExp('/1\. Januar 2017/', format_date($date, false, true));
24 } 24 }
25 25
26 /** 26 /**
diff --git a/tests/languages/fr/LanguagesFrTest.php b/tests/languages/fr/LanguagesFrTest.php
index 0cf74891..38347de1 100644
--- a/tests/languages/fr/LanguagesFrTest.php
+++ b/tests/languages/fr/LanguagesFrTest.php
@@ -3,7 +3,6 @@
3 3
4namespace Shaarli; 4namespace Shaarli;
5 5
6
7use Shaarli\Config\ConfigManager; 6use Shaarli\Config\ConfigManager;
8 7
9/** 8/**
diff --git a/tests/plugins/PluginIssoTest.php b/tests/plugins/PluginIssoTest.php
index 0ae73183..2c9efbcd 100644
--- a/tests/plugins/PluginIssoTest.php
+++ b/tests/plugins/PluginIssoTest.php
@@ -21,7 +21,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
21 /** 21 /**
22 * Test Isso init without errors. 22 * Test Isso init without errors.
23 */ 23 */
24 public function testWallabagInitNoError() 24 public function testIssoInitNoError()
25 { 25 {
26 $conf = new ConfigManager(''); 26 $conf = new ConfigManager('');
27 $conf->set('plugins.ISSO_SERVER', 'value'); 27 $conf->set('plugins.ISSO_SERVER', 'value');
@@ -32,7 +32,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
32 /** 32 /**
33 * Test Isso init with errors. 33 * Test Isso init with errors.
34 */ 34 */
35 public function testWallabagInitError() 35 public function testIssoInitError()
36 { 36 {
37 $conf = new ConfigManager(''); 37 $conf = new ConfigManager('');
38 $errors = isso_init($conf); 38 $errors = isso_init($conf);
@@ -96,19 +96,22 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
96 array( 96 array(
97 'id' => 12, 97 'id' => 12,
98 'url' => $str, 98 'url' => $str,
99 'shorturl' => $short1 = 'abcd',
99 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date1), 100 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date1),
100 ), 101 ),
101 array( 102 array(
102 'id' => 13, 103 'id' => 13,
103 'url' => $str . '2', 104 'url' => $str . '2',
105 'shorturl' => $short2 = 'efgh',
104 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date2), 106 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date2),
105 ), 107 ),
106 ) 108 )
107 ); 109 );
108 110
109 $processed = hook_isso_render_linklist($data, $conf); 111 $processed = hook_isso_render_linklist($data, $conf);
110 // data shouldn't be altered 112 // link_plugin should be added for the icon
111 $this->assertEquals($data, $processed); 113 $this->assertContains('<a href="?'. $short1 .'#isso-thread">', $processed['links'][0]['link_plugin'][0]);
114 $this->assertContains('<a href="?'. $short2 .'#isso-thread">', $processed['links'][1]['link_plugin'][0]);
112 } 115 }
113 116
114 /** 117 /**
@@ -127,6 +130,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
127 array( 130 array(
128 'id' => 12, 131 'id' => 12,
129 'url' => $str, 132 'url' => $str,
133 'shorturl' => $short1 = 'abcd',
130 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date), 134 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
131 ) 135 )
132 ), 136 ),
@@ -135,8 +139,8 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
135 139
136 $processed = hook_isso_render_linklist($data, $conf); 140 $processed = hook_isso_render_linklist($data, $conf);
137 141
138 // data shouldn't be altered 142 // link_plugin should be added for the icon
139 $this->assertEquals($data, $processed); 143 $this->assertContains('<a href="?'. $short1 .'#isso-thread">', $processed['links'][0]['link_plugin'][0]);
140 } 144 }
141 145
142 /** 146 /**
diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php
index b31e817f..44364b05 100644
--- a/tests/plugins/PluginMarkdownTest.php
+++ b/tests/plugins/PluginMarkdownTest.php
@@ -47,6 +47,8 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
47 $data = hook_markdown_render_linklist($data, $this->conf); 47 $data = hook_markdown_render_linklist($data, $this->conf);
48 $this->assertNotFalse(strpos($data['links'][0]['description'], '<h1>')); 48 $this->assertNotFalse(strpos($data['links'][0]['description'], '<h1>'));
49 $this->assertNotFalse(strpos($data['links'][0]['description'], '<p>')); 49 $this->assertNotFalse(strpos($data['links'][0]['description'], '<p>'));
50
51 $this->assertEquals($markdown, $data['links'][0]['description_src']);
50 } 52 }
51 53
52 /** 54 /**
@@ -107,6 +109,18 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
107 } 109 }
108 110
109 /** 111 /**
112 * Test reverse_text2clickable().
113 */
114 public function testReverseText2clickableHashtags()
115 {
116 $text = file_get_contents('tests/plugins/resources/hashtags.raw');
117 $md = file_get_contents('tests/plugins/resources/hashtags.md');
118 $clickableText = hashtag_autolink($text);
119 $reversedText = reverse_text2clickable($clickableText);
120 $this->assertEquals($md, $reversedText);
121 }
122
123 /**
110 * Test reverse_nl2br(). 124 * Test reverse_nl2br().
111 */ 125 */
112 public function testReverseNl2br() 126 public function testReverseNl2br()
@@ -246,7 +260,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
246 $this->conf->get('security.markdown_escape', true), 260 $this->conf->get('security.markdown_escape', true),
247 $this->conf->get('security.allowed_protocols') 261 $this->conf->get('security.allowed_protocols')
248 ); 262 );
249 $this->assertEquals($html, $data); 263 $this->assertEquals($html, $data . PHP_EOL);
250 } 264 }
251 265
252 /** 266 /**
diff --git a/tests/plugins/PluginQrcodeTest.php b/tests/plugins/PluginQrcodeTest.php
index ebfadddf..dd632eee 100644
--- a/tests/plugins/PluginQrcodeTest.php
+++ b/tests/plugins/PluginQrcodeTest.php
@@ -15,7 +15,8 @@ class PluginQrcodeTest extends PHPUnit_Framework_TestCase
15 /** 15 /**
16 * Reset plugin path 16 * Reset plugin path
17 */ 17 */
18 public function setUp() { 18 public function setUp()
19 {
19 PluginManager::$PLUGINS_PATH = 'plugins'; 20 PluginManager::$PLUGINS_PATH = 'plugins';
20 } 21 }
21 22
diff --git a/tests/plugins/resources/hashtags.md b/tests/plugins/resources/hashtags.md
new file mode 100644
index 00000000..46326de3
--- /dev/null
+++ b/tests/plugins/resources/hashtags.md
@@ -0,0 +1,10 @@
1[#lol](?addtag=lol)
2
3 #test
4
5`#test2`
6
7```
8bla #bli blo
9#bla
10```
diff --git a/tests/plugins/resources/hashtags.raw b/tests/plugins/resources/hashtags.raw
new file mode 100644
index 00000000..9d2dc98a
--- /dev/null
+++ b/tests/plugins/resources/hashtags.raw
@@ -0,0 +1,10 @@
1#lol
2
3 #test
4
5`#test2`
6
7```
8bla #bli blo
9#bla
10```
diff --git a/tests/plugins/resources/markdown.html b/tests/plugins/resources/markdown.html
index f1df4e7e..c3460bf7 100644
--- a/tests/plugins/resources/markdown.html
+++ b/tests/plugins/resources/markdown.html
@@ -12,11 +12,11 @@
12<li><a href="http://link.tld">two</a></li> 12<li><a href="http://link.tld">two</a></li>
13<li><a href="http://link.tld">three</a></li> 13<li><a href="http://link.tld">three</a></li>
14<li><a href="http://link.tld">four</a></li> 14<li><a href="http://link.tld">four</a></li>
15<li>foo &lt;a href=&quot;?addtag=foobar&quot; title=&quot;Hashtag foobar&quot;&gt;#foobar&lt;/a&gt;</li> 15<li>foo <a href="?addtag=foobar">#foobar</a></li>
16</ol></li> 16</ol></li>
17</ol> 17</ol>
18<p>&lt;a href=&quot;?addtag=foobar&quot; title=&quot;Hashtag foobar&quot;&gt;#foobar&lt;/a&gt; foo <code>lol #foo</code> &lt;a href=&quot;?addtag=bar&quot; title=&quot;Hashtag bar&quot;&gt;#bar&lt;/a&gt;</p> 18<p><a href="?addtag=foobar">#foobar</a> foo <code>lol #foo</code> <a href="?addtag=bar">#bar</a></p>
19<p>fsdfs <a href="http://link.tld">http://link.tld</a> &lt;a href=&quot;?addtag=foobar&quot; title=&quot;Hashtag foobar&quot;&gt;#foobar&lt;/a&gt; <code>http://link.tld</code></p> 19<p>fsdfs <a href="http://link.tld">http://link.tld</a> <a href="?addtag=foobar">#foobar</a> <code>http://link.tld</code></p>
20<pre><code>http://link.tld #foobar 20<pre><code>http://link.tld #foobar
21next #foo</code></pre> 21next #foo</code></pre>
22<p>Block:</p> 22<p>Block:</p>
@@ -30,4 +30,4 @@ next #foo</code></pre>
30<a href="ftp://test.tld/path/?query=value#hash">link</a><br /> 30<a href="ftp://test.tld/path/?query=value#hash">link</a><br />
31<a href="magnet:test.tld/path/?query=value#hash">link</a><br /> 31<a href="magnet:test.tld/path/?query=value#hash">link</a><br />
32<a href="http://alert(&#039;xss&#039;)">link</a><br /> 32<a href="http://alert(&#039;xss&#039;)">link</a><br />
33<a href="http://test.tld/path/?query=value#hash">link</a></p></div> \ No newline at end of file 33<a href="http://test.tld/path/?query=value#hash">link</a></p></div>
diff --git a/tests/plugins/resources/markdown.md b/tests/plugins/resources/markdown.md
index b8ebd934..9350a8c7 100644
--- a/tests/plugins/resources/markdown.md
+++ b/tests/plugins/resources/markdown.md
@@ -31,4 +31,4 @@ lorem ipsum #foobar http://link.tld
31[link](ftp://test.tld/path/?query=value#hash) 31[link](ftp://test.tld/path/?query=value#hash)
32[link](magnet:test.tld/path/?query=value#hash) 32[link](magnet:test.tld/path/?query=value#hash)
33[link](javascript:alert('xss')) 33[link](javascript:alert('xss'))
34[link](other://test.tld/path/?query=value#hash) \ No newline at end of file 34[link](other://test.tld/path/?query=value#hash)
diff --git a/tests/security/SessionManagerTest.php b/tests/security/SessionManagerTest.php
index 9bd868f8..7961e771 100644
--- a/tests/security/SessionManagerTest.php
+++ b/tests/security/SessionManagerTest.php
@@ -8,7 +8,6 @@ ReferenceSessionIdHashes::genAllHashes();
8use \Shaarli\Security\SessionManager; 8use \Shaarli\Security\SessionManager;
9use \PHPUnit\Framework\TestCase; 9use \PHPUnit\Framework\TestCase;
10 10
11
12/** 11/**
13 * Test coverage for SessionManager 12 * Test coverage for SessionManager
14 */ 13 */
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php
index e887aa78..59679e38 100644
--- a/tests/utils/ReferenceLinkDB.php
+++ b/tests/utils/ReferenceLinkDB.php
@@ -4,7 +4,7 @@
4 */ 4 */
5class ReferenceLinkDB 5class ReferenceLinkDB
6{ 6{
7 public static $NB_LINKS_TOTAL = 9; 7 public static $NB_LINKS_TOTAL = 11;
8 8
9 private $_links = array(); 9 private $_links = array();
10 private $_publicCount = 0; 10 private $_publicCount = 0;
@@ -16,6 +16,32 @@ class ReferenceLinkDB
16 public function __construct() 16 public function __construct()
17 { 17 {
18 $this->addLink( 18 $this->addLink(
19 11,
20 'Pined older',
21 '?PCRizQ',
22 'This is an older pinned link',
23 0,
24 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20100309_101010'),
25 '',
26 null,
27 'PCRizQ',
28 true
29 );
30
31 $this->addLink(
32 10,
33 'Pined',
34 '?0gCTjQ',
35 'This is a pinned link',
36 0,
37 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121207_152312'),
38 '',
39 null,
40 '0gCTjQ',
41 true
42 );
43
44 $this->addLink(
19 41, 45 41,
20 'Link title: @website', 46 'Link title: @website',
21 '?WDWyig', 47 '?WDWyig',
@@ -114,8 +140,18 @@ class ReferenceLinkDB
114 /** 140 /**
115 * Adds a new link 141 * Adds a new link
116 */ 142 */
117 protected function addLink($id, $title, $url, $description, $private, $date, $tags, $updated = '', $shorturl = '') 143 protected function addLink(
118 { 144 $id,
145 $title,
146 $url,
147 $description,
148 $private,
149 $date,
150 $tags,
151 $updated = '',
152 $shorturl = '',
153 $pinned = false
154 ) {
119 $link = array( 155 $link = array(
120 'id' => $id, 156 'id' => $id,
121 'title' => $title, 157 'title' => $title,
@@ -126,6 +162,7 @@ class ReferenceLinkDB
126 'created' => $date, 162 'created' => $date,
127 'updated' => $updated, 163 'updated' => $updated,
128 'shorturl' => $shorturl ? $shorturl : smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id), 164 'shorturl' => $shorturl ? $shorturl : smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id),
165 'sticky' => $pinned
129 ); 166 );
130 $this->_links[$id] = $link; 167 $this->_links[$id] = $link;
131 168
@@ -164,7 +201,11 @@ class ReferenceLinkDB
164 201
165 $order = $order === 'ASC' ? -1 : 1; 202 $order = $order === 'ASC' ? -1 : 1;
166 // Reorder array by dates. 203 // Reorder array by dates.
167 usort($this->_links, function($a, $b) use ($order) { 204 usort($this->_links, function ($a, $b) use ($order) {
205 if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) {
206 return $a['sticky'] ? -1 : 1;
207 }
208
168 return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; 209 return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
169 }); 210 });
170 } 211 }
diff --git a/tests/utils/config/configPhp.php b/tests/utils/config/configPhp.php
index 0e034175..34b11fcd 100644
--- a/tests/utils/config/configPhp.php
+++ b/tests/utils/config/configPhp.php
@@ -1,4 +1,4 @@
1<?php 1<?php
2$GLOBALS['login'] = 'root'; 2$GLOBALS['login'] = 'root';
3$GLOBALS['hash'] = 'hash'; 3$GLOBALS['hash'] = 'hash';
4$GLOBALS['salt'] = 'salt'; 4$GLOBALS['salt'] = 'salt';
diff --git a/tpl/default/addlink.html b/tpl/default/addlink.html
index 2f956e06..55864a02 100644
--- a/tpl/default/addlink.html
+++ b/tpl/default/addlink.html
@@ -11,7 +11,8 @@
11 <h2 class="window-title">{"Shaare a new link"|t}</h2> 11 <h2 class="window-title">{"Shaare a new link"|t}</h2>
12 <form method="GET" action="#" name="addform" class="addform"> 12 <form method="GET" action="#" name="addform" class="addform">
13 <div> 13 <div>
14 <input type="text" name="post" placeholder="{'URL or leave empty to post a note'|t}" class="autofocus"> 14 <label for="shaare">{'URL or leave empty to post a note'|t}</label>
15 <input type="text" name="post" id="shaare" class="autofocus">
15 </div> 16 </div>
16 <div> 17 <div>
17 <input type="submit" value="{'Add link'|t}"> 18 <input type="submit" value="{'Add link'|t}">
diff --git a/tpl/default/daily.html b/tpl/default/daily.html
index 816e5d0a..2c409478 100644
--- a/tpl/default/daily.html
+++ b/tpl/default/daily.html
@@ -72,7 +72,7 @@
72 {if="$thumbnails_enabled && !empty($link.thumbnail)"} 72 {if="$thumbnails_enabled && !empty($link.thumbnail)"}
73 <div class="daily-entry-thumbnail"> 73 <div class="daily-entry-thumbnail">
74 <img data-src="{$link.thumbnail}#" class="b-lazy" 74 <img data-src="{$link.thumbnail}#" class="b-lazy"
75 src="#" 75 src=""
76 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" /> 76 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
77 </div> 77 </div>
78 {/if} 78 {/if}
diff --git a/tpl/default/includes.html b/tpl/default/includes.html
index 5ccacaaf..0427e224 100644
--- a/tpl/default/includes.html
+++ b/tpl/default/includes.html
@@ -15,3 +15,23 @@
15 <link type="text/css" rel="stylesheet" href="data/user.css#" /> 15 <link type="text/css" rel="stylesheet" href="data/user.css#" />
16{/if} 16{/if}
17<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/> 17<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/>
18{if="! empty($links) && count($links) === 1"}
19 {$link=reset($links)}
20 <meta property="og:title" content="{$link.title}" />
21 <meta property="og:type" content="article" />
22 <meta property="og:url" content="{$index_url}?{$link.shorturl}" />
23 {$ogDescription=isset($link.description_src) ? $link.description_src : $link.description}
24 <meta property="og:description" content="{function="substr($ogDescription, 0, 300)"}" />
25 {if="$link.thumbnail"}
26 <meta property="og:image" content="{$index_url}{$link.thumbnail}" />
27 {/if}
28 {if="!$hide_timestamps || $is_logged_in"}
29 <meta property="article:published_time" content="{$link.created->format(DateTime::ATOM)}" />
30 {if="$link.updated"}
31 <meta property="article:modified_time" content="{$link.updated->format(DateTime::ATOM)}" />
32 {/if}
33 {/if}
34 {loop="link.taglist"}
35 <meta property="article:tag" content="{$value}" />
36 {/loop}
37{/if}
diff --git a/tpl/default/linklist.html b/tpl/default/linklist.html
index 8ea2ce66..ed78f40a 100644
--- a/tpl/default/linklist.html
+++ b/tpl/default/linklist.html
@@ -125,6 +125,8 @@
125 {$strPermalink=t('Permalink')} 125 {$strPermalink=t('Permalink')}
126 {$strPermalinkLc=t('permalink')} 126 {$strPermalinkLc=t('permalink')}
127 {$strAddTag=t('Add tag')} 127 {$strAddTag=t('Add tag')}
128 {$strToggleSticky=t('Toggle sticky')}
129 {$strSticky=t('Sticky')}
128 {ignore}End of translations{/ignore} 130 {ignore}End of translations{/ignore}
129 {loop="links"} 131 {loop="links"}
130 <div class="anchor" id="{$value.shorturl}"></div> 132 <div class="anchor" id="{$value.shorturl}"></div>
@@ -137,7 +139,7 @@
137 <a href="{$value.real_url}"> 139 <a href="{$value.real_url}">
138 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} 140 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
139 <img data-src="{$value.thumbnail}#" class="b-lazy" 141 <img data-src="{$value.thumbnail}#" class="b-lazy"
140 src="#" 142 src=""
141 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" /> 143 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
142 </a> 144 </a>
143 </div> 145 </div>
@@ -190,7 +192,7 @@
190 {if="$is_logged_in"} 192 {if="$is_logged_in"}
191 <div class="linklist-item-infos-controls-group pure-u-0 pure-u-lg-visible"> 193 <div class="linklist-item-infos-controls-group pure-u-0 pure-u-lg-visible">
192 <span class="linklist-item-infos-controls-item ctrl-checkbox"> 194 <span class="linklist-item-infos-controls-item ctrl-checkbox">
193 <input type="checkbox" class="delete-checkbox" value="{$value.id}"> 195 <input type="checkbox" class="link-checkbox" value="{$value.id}">
194 </span> 196 </span>
195 <span class="linklist-item-infos-controls-item ctrl-edit"> 197 <span class="linklist-item-infos-controls-item ctrl-edit">
196 <a href="?edit_link={$value.id}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link"></i></a> 198 <a href="?edit_link={$value.id}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link"></i></a>
@@ -201,7 +203,23 @@
201 <i class="fa fa-trash"></i> 203 <i class="fa fa-trash"></i>
202 </a> 204 </a>
203 </span> 205 </span>
206 <span class="linklist-item-infos-controls-item ctrl-pin">
207 <a href="?do=pin&amp;id={$value.id}&amp;token={$token}"
208 title="{$strToggleSticky}" class="pin-link {if="$value.sticky"}pinned-link{/if} pure-u-0 pure-u-lg-visible">
209 <i class="fa fa-thumb-tack"></i>
210 </a>
211 </span>
204 </div> 212 </div>
213 {else}
214 {if="$value.sticky"}
215 <div class="linklist-item-infos-controls-group pure-u-0 pure-u-lg-visible">
216 <span class="linklist-item-infos-controls-item ctrl-pin">
217 <span title="{$strSticky}" class="pin-link pinned-link pure-u-0 pure-u-lg-visible">
218 <i class="fa fa-thumb-tack"></i>
219 </span>
220 </span>
221 </div>
222 {/if}
205 {/if} 223 {/if}
206 <a href="?{$value.shorturl}" title="{$strPermalink}"> 224 <a href="?{$value.shorturl}" title="{$strPermalink}">
207 {if="!$hide_timestamps || $is_logged_in"} 225 {if="!$hide_timestamps || $is_logged_in"}
diff --git a/tpl/default/linklist.paging.html b/tpl/default/linklist.paging.html
index 5309e348..fe665a84 100644
--- a/tpl/default/linklist.paging.html
+++ b/tpl/default/linklist.paging.html
@@ -16,6 +16,9 @@
16 <a href="?untaggedonly" title="{'Filter untagged links'|t}" 16 <a href="?untaggedonly" title="{'Filter untagged links'|t}"
17 class={if="$untaggedonly"}"filter-on"{else}"filter-off"{/if} 17 class={if="$untaggedonly"}"filter-on"{else}"filter-off"{/if}
18 ><i class="fa fa-tag"></i></a> 18 ><i class="fa fa-tag"></i></a>
19 <a href="#" title="{'Select all'|t}"
20 class="filter-off select-all-button"
21 ><i class="fa fa-check-square-o"></i></a>
19 <a href="#" class="filter-off fold-all pure-u-lg-0" title="{'Fold all'|t}"> 22 <a href="#" class="filter-off fold-all pure-u-lg-0" title="{'Fold all'|t}">
20 <i class="fa fa-chevron-up"></i> 23 <i class="fa fa-chevron-up"></i>
21 </a> 24 </a>
diff --git a/tpl/default/page.header.html b/tpl/default/page.header.html
index 3b43a611..4f6dd4d8 100644
--- a/tpl/default/page.header.html
+++ b/tpl/default/page.header.html
@@ -118,7 +118,7 @@
118 <div id="actions" class="subheader-form"> 118 <div id="actions" class="subheader-form">
119 <div class="pure-g"> 119 <div class="pure-g">
120 <div class="pure-u-1"> 120 <div class="pure-u-1">
121 <a href="" id="actions-delete" class="button">Delete</a> 121 <a href="" id="actions-delete" class="button">{'Delete'|t}</a>
122 </div> 122 </div>
123 </div> 123 </div>
124 </div> 124 </div>
diff --git a/tpl/default/picwall.html b/tpl/default/picwall.html
index 9a0b10dc..4c325487 100644
--- a/tpl/default/picwall.html
+++ b/tpl/default/picwall.html
@@ -37,7 +37,7 @@
37 <div class="picwall-pictureframe"> 37 <div class="picwall-pictureframe">
38 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} 38 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
39 <img data-src="{$value.thumbnail}#" class="b-lazy" 39 <img data-src="{$value.thumbnail}#" class="b-lazy"
40 src="#" 40 src=""
41 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" /> 41 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
42 <a href="{$value.real_url}"><span class="info">{$value.title}</span></a> 42 <a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
43 {loop="$value.picwall_plugin"} 43 {loop="$value.picwall_plugin"}
diff --git a/tpl/vintage/daily.html b/tpl/vintage/daily.html
index 00148a58..71d84475 100644
--- a/tpl/vintage/daily.html
+++ b/tpl/vintage/daily.html
@@ -71,7 +71,7 @@
71 {if="$thumbnails_enabled && !empty($link.thumbnail)"} 71 {if="$thumbnails_enabled && !empty($link.thumbnail)"}
72 <div class="dailyEntryThumbnail"> 72 <div class="dailyEntryThumbnail">
73 <img data-src="{$link.thumbnail}#" class="b-lazy" 73 <img data-src="{$link.thumbnail}#" class="b-lazy"
74 src="#" 74 src=""
75 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" /> 75 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
76 </div> 76 </div>
77 {/if} 77 {/if}
diff --git a/tpl/vintage/includes.html b/tpl/vintage/includes.html
index 410b466b..df093495 100644
--- a/tpl/vintage/includes.html
+++ b/tpl/vintage/includes.html
@@ -12,3 +12,23 @@
12{/loop} 12{/loop}
13{if="is_file('data/user.css')"}<link type="text/css" rel="stylesheet" href="data/user.css#" />{/if} 13{if="is_file('data/user.css')"}<link type="text/css" rel="stylesheet" href="data/user.css#" />{/if}
14<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle|htmlspecialchars}"/> 14<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle|htmlspecialchars}"/>
15{if="! empty($links) && count($links) === 1"}
16 {$link=reset($links)}
17 <meta property="og:title" content="{$link.title}" />
18 <meta property="og:type" content="article" />
19 <meta property="og:url" content="{$index_url}?{$link.shorturl}" />
20 {$ogDescription=isset($link.description_src) ? $link.description_src : $link.description}
21 <meta property="og:description" content="{function="mb_substr($ogDescription, 0, 300)"}" />
22 {if="$link.thumbnail"}
23 <meta property="og:image" content="{$index_url}{$link.thumbnail}" />
24 {/if}
25 {if="!$hide_timestamps || $is_logged_in"}
26 <meta property="article:published_time" content="{$link.created->format(DateTime::ATOM)}" />
27 {if="$link.updated"}
28 <meta property="article:modified_time" content="{$link.updated->format(DateTime::ATOM)}" />
29 {/if}
30 {/if}
31 {loop="link.taglist"}
32 <meta property="article:tag" content="{$value}" />
33 {/loop}
34{/if}
diff --git a/tpl/vintage/linklist.html b/tpl/vintage/linklist.html
index 3f202849..dcb14e90 100644
--- a/tpl/vintage/linklist.html
+++ b/tpl/vintage/linklist.html
@@ -85,7 +85,7 @@
85 <a href="{$value.real_url}"> 85 <a href="{$value.real_url}">
86 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} 86 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
87 <img data-src="{$value.thumbnail}#" class="b-lazy" 87 <img data-src="{$value.thumbnail}#" class="b-lazy"
88 src="#" 88 src=""
89 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" /> 89 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
90 </a> 90 </a>
91 </div> 91 </div>
diff --git a/tpl/vintage/picwall.html b/tpl/vintage/picwall.html
index 5f1d266e..b3a16791 100644
--- a/tpl/vintage/picwall.html
+++ b/tpl/vintage/picwall.html
@@ -17,7 +17,7 @@
17 <div class="picwall_pictureframe"> 17 <div class="picwall_pictureframe">
18 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} 18 {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
19 <img data-src="{$value.thumbnail}#" class="b-lazy" 19 <img data-src="{$value.thumbnail}#" class="b-lazy"
20 src="#" 20 src=""
21 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" /> 21 alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
22 <a href="{$value.real_url}"><span class="info">{$value.title}</span></a> 22 <a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
23 {loop="$value.picwall_plugin"} 23 {loop="$value.picwall_plugin"}