aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMark Schmitz <kramred@gmail.com>2018-06-07 14:23:53 +0100
committerMark Schmitz <kramred@gmail.com>2018-06-07 14:23:53 +0100
commit0deaedeeaef088040fb015f5be3e270e3bae508e (patch)
treebbdab87adf45592a6a881f808e667ba5c21312a9
parentf6b3295d28352cbeb46dff04799cf8f4d39087bb (diff)
parent17e45b2e9c33c736751e059276fadb480f98e621 (diff)
downloadShaarli-0deaedeeaef088040fb015f5be3e270e3bae508e.tar.gz
Shaarli-0deaedeeaef088040fb015f5be3e270e3bae508e.tar.zst
Shaarli-0deaedeeaef088040fb015f5be3e270e3bae508e.zip
Merge remote-tracking branch 'upstream/master'
-rw-r--r--.dev/.eslintrc.js (renamed from .eslintrc.js)0
-rw-r--r--.dev/.sasslintrc15
-rw-r--r--.editorconfig2
-rw-r--r--.gitattributes1
-rw-r--r--.travis.yml42
-rw-r--r--Makefile8
-rw-r--r--application/HttpUtils.php33
-rw-r--r--application/Languages.php1
-rw-r--r--application/LinkDB.php29
-rw-r--r--application/LinkUtils.php17
-rw-r--r--application/LoginManager.php134
-rw-r--r--application/PageBuilder.php9
-rw-r--r--application/SessionManager.php83
-rw-r--r--application/security/LoginManager.php265
-rw-r--r--application/security/SessionManager.php199
-rw-r--r--assets/default/scss/shaarli.scss2046
-rw-r--r--composer.json3
-rw-r--r--doc/md/Bookmarklet.md29
-rw-r--r--doc/md/Community-&-Related-software.md4
-rw-r--r--doc/md/Firefox-share.md20
-rw-r--r--doc/md/Sharing-content.md88
-rw-r--r--doc/md/images/doc-logo.pngbin19543 -> 19520 bytes
-rw-r--r--doc/md/images/firefoxshare.pngbin757 -> 715 bytes
-rw-r--r--doc/md/images/install-shaarli.pngbin44376 -> 33827 bytes
-rw-r--r--doc/md/images/rss-filter-1.pngbin18682 -> 18534 bytes
-rw-r--r--doc/md/images/rss-filter-2.pngbin15604 -> 15440 bytes
-rw-r--r--doc/md/index.md7
-rw-r--r--inc/languages/de/LC_MESSAGES/shaarli.po1313
-rw-r--r--inc/languages/fr/LC_MESSAGES/shaarli.po11
-rw-r--r--index.php242
-rw-r--r--mkdocs.yml3
-rw-r--r--package.json1
-rw-r--r--plugins/markdown/markdown.php8
-rw-r--r--tests/HttpUtils/ClientIpIdTest.php52
-rw-r--r--tests/LinkDBTest.php100
-rw-r--r--tests/SessionManagerTest.php149
-rw-r--r--tests/plugins/PluginMarkdownTest.php55
-rw-r--r--tests/security/LoginManagerTest.php (renamed from tests/LoginManagerTest.php)183
-rw-r--r--tests/security/SessionManagerTest.php273
-rw-r--r--tests/utils/FakeConfigManager.php12
-rw-r--r--tpl/default/404.html2
-rw-r--r--tpl/default/editlink.html5
-rw-r--r--tpl/default/import.html2
-rw-r--r--tpl/default/linklist.html10
-rw-r--r--tpl/default/linklist.paging.html4
-rw-r--r--tpl/default/loginform.html2
-rw-r--r--tpl/default/page.footer.html4
-rw-r--r--tpl/default/page.header.html16
-rw-r--r--tpl/default/picwall.html4
-rw-r--r--tpl/default/pluginsadmin.html2
-rw-r--r--tpl/default/tag.cloud.html2
-rw-r--r--tpl/default/tag.list.html10
-rw-r--r--tpl/default/tools.html10
-rw-r--r--tpl/vintage/daily.html2
-rw-r--r--tpl/vintage/linklist.html4
-rw-r--r--tpl/vintage/linklist.paging.html2
-rw-r--r--tpl/vintage/page.footer.html2
-rw-r--r--tpl/vintage/page.header.html4
-rw-r--r--yarn.lock298
59 files changed, 4212 insertions, 1610 deletions
diff --git a/.eslintrc.js b/.dev/.eslintrc.js
index 151b785b..151b785b 100644
--- a/.eslintrc.js
+++ b/.dev/.eslintrc.js
diff --git a/.dev/.sasslintrc b/.dev/.sasslintrc
new file mode 100644
index 00000000..ac406d7b
--- /dev/null
+++ b/.dev/.sasslintrc
@@ -0,0 +1,15 @@
1options:
2 max-warnings: 0
3rules:
4 property-sort-order:
5 - 1
6 -
7 order: 'concentric'
8 no-important:
9 - 0
10 no-vendor-prefixes:
11 - 0 # this will be fixed with v2: see https://github.com/sasstools/sass-lint/pull/1137
12 nesting-depth:
13 - 1
14 -
15 max-depth: 4
diff --git a/.editorconfig b/.editorconfig
index 8783e4cb..34bd7994 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -10,7 +10,7 @@ trim_trailing_whitespace = true
10indent_style = space 10indent_style = space
11indent_size = 4 11indent_size = 4
12 12
13[*.{htaccess,html,js,json,xml}] 13[*.{htaccess,html,scss,js,json,xml,yml}]
14indent_size = 2 14indent_size = 2
15 15
16[*.php] 16[*.php]
diff --git a/.gitattributes b/.gitattributes
index 549777ef..6b6ffbd5 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -26,6 +26,7 @@ Dockerfile text
26 26
27# Exclude from Git archives 27# Exclude from Git archives
28.editorconfig export-ignore 28.editorconfig export-ignore
29.dev export-ignore
29.gitattributes export-ignore 30.gitattributes export-ignore
30.github export-ignore 31.github export-ignore
31.gitignore export-ignore 32.gitignore export-ignore
diff --git a/.travis.yml b/.travis.yml
index 1b2bf97b..eee1ca74 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,23 +1,45 @@
1sudo: false 1sudo: false
2dist: trusty 2dist: trusty
3language: php 3
4matrix:
5 include:
6 - language: php
7 php: 7.2
8 - language: php
9 php: 7.1
10 - language: php
11 php: 7.0
12 - language: php
13 php: 5.6
14 - language: node_js
15 node_js: 8
16 cache:
17 yarn: true
18 directories:
19 - $HOME/.cache/yarn
20
21 install:
22 - yarn install
23
24 before_script:
25 - PATH=${PATH//:\.\/node_modules\/\.bin/}
26
27 script:
28 - yarn run build # Just to be sure that the build isn't broken
29 - make eslint
30 - make sasslint
31
4cache: 32cache:
5 yarn: true
6 directories: 33 directories:
7 - $HOME/.composer/cache 34 - $HOME/.composer/cache
8 - $HOME/.cache/yarn 35
9php:
10 - 7.2
11 - 7.1
12 - 7.0
13 - 5.6
14install: 36install:
15 - yarn install
16 - composer install --prefer-dist 37 - composer install --prefer-dist
38
17before_script: 39before_script:
18 - PATH=${PATH//:\.\/node_modules\/\.bin/} 40 - PATH=${PATH//:\.\/node_modules\/\.bin/}
41
19script: 42script:
20 - make clean 43 - make clean
21 - make check_permissions 44 - make check_permissions
22 - make eslint
23 - make all_tests 45 - make all_tests
diff --git a/Makefile b/Makefile
index d1216569..4adbdd68 100644
--- a/Makefile
+++ b/Makefile
@@ -218,5 +218,9 @@ translate:
218 218
219### Run ESLint check against Shaarli's JS files 219### Run ESLint check against Shaarli's JS files
220eslint: 220eslint:
221 @yarn run eslint assets/vintage/js/ 221 @yarn run eslint -c .dev/.eslintrc.js assets/vintage/js/
222 @yarn run eslint assets/default/js/ 222 @yarn run eslint -c .dev/.eslintrc.js assets/default/js/
223
224### Run CSSLint check against Shaarli's SCSS files
225sasslint:
226 @yarn run sass-lint -c .dev/.sasslintrc 'assets/default/scss/*.scss' -v -q
diff --git a/application/HttpUtils.php b/application/HttpUtils.php
index 83a4c5e2..e9282506 100644
--- a/application/HttpUtils.php
+++ b/application/HttpUtils.php
@@ -1,7 +1,7 @@
1<?php 1<?php
2/** 2/**
3 * GET an HTTP URL to retrieve its content 3 * GET an HTTP URL to retrieve its content
4 * Uses the cURL library or a fallback method 4 * Uses the cURL library or a fallback method
5 * 5 *
6 * @param string $url URL to get (http://...) 6 * @param string $url URL to get (http://...)
7 * @param int $timeout network timeout (in seconds) 7 * @param int $timeout network timeout (in seconds)
@@ -415,6 +415,37 @@ function getIpAddressFromProxy($server, $trustedIps)
415 return array_pop($ips); 415 return array_pop($ips);
416} 416}
417 417
418
419/**
420 * Return an identifier based on the advertised client IP address(es)
421 *
422 * This aims at preventing session hijacking from users behind the same proxy
423 * by relying on HTTP headers.
424 *
425 * See:
426 * - https://secure.php.net/manual/en/reserved.variables.server.php
427 * - https://stackoverflow.com/questions/3003145/how-to-get-the-client-ip-address-in-php
428 * - https://stackoverflow.com/questions/12233406/preventing-session-hijacking
429 * - https://stackoverflow.com/questions/21354859/trusting-x-forwarded-for-to-identify-a-visitor
430 *
431 * @param array $server The $_SERVER array
432 *
433 * @return string An identifier based on client IP address information
434 */
435function client_ip_id($server)
436{
437 $ip = $server['REMOTE_ADDR'];
438
439 if (isset($server['HTTP_X_FORWARDED_FOR'])) {
440 $ip = $ip . '_' . $server['HTTP_X_FORWARDED_FOR'];
441 }
442 if (isset($server['HTTP_CLIENT_IP'])) {
443 $ip = $ip . '_' . $server['HTTP_CLIENT_IP'];
444 }
445 return $ip;
446}
447
448
418/** 449/**
419 * Returns true if Shaarli's currently browsed in HTTPS. 450 * Returns true if Shaarli's currently browsed in HTTPS.
420 * Supports reverse proxies (if the headers are correctly set). 451 * Supports reverse proxies (if the headers are correctly set).
diff --git a/application/Languages.php b/application/Languages.php
index db4b84ae..4fa32426 100644
--- a/application/Languages.php
+++ b/application/Languages.php
@@ -177,6 +177,7 @@ class Languages
177 'auto' => t('Automatic'), 177 'auto' => t('Automatic'),
178 'en' => t('English'), 178 'en' => t('English'),
179 'fr' => t('French'), 179 'fr' => t('French'),
180 'de' => t('German'),
180 ]; 181 ];
181 } 182 }
182} 183}
diff --git a/application/LinkDB.php b/application/LinkDB.php
index c1661d52..cd0f2967 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -436,15 +436,17 @@ You use the community supported version of the original Shaarli project, by Seba
436 436
437 /** 437 /**
438 * Returns the list tags appearing in the links with the given tags 438 * Returns the list tags appearing in the links with the given tags
439 * @param $filteringTags: tags selecting the links to consider 439 *
440 * @param $visibility: process only all/private/public links 440 * @param array $filteringTags tags selecting the links to consider
441 * @return: a tag=>linksCount array 441 * @param string $visibility process only all/private/public links
442 *
443 * @return array tag => linksCount
442 */ 444 */
443 public function linksCountPerTag($filteringTags = [], $visibility = 'all') 445 public function linksCountPerTag($filteringTags = [], $visibility = 'all')
444 { 446 {
445 $links = empty($filteringTags) ? $this->links : $this->filterSearch(['searchtags' => $filteringTags], false, $visibility); 447 $links = $this->filterSearch(['searchtags' => $filteringTags], false, $visibility);
446 $tags = array(); 448 $tags = [];
447 $caseMapping = array(); 449 $caseMapping = [];
448 foreach ($links as $link) { 450 foreach ($links as $link) {
449 foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) { 451 foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) {
450 if (empty($tag)) { 452 if (empty($tag)) {
@@ -458,8 +460,19 @@ You use the community supported version of the original Shaarli project, by Seba
458 $tags[$caseMapping[strtolower($tag)]]++; 460 $tags[$caseMapping[strtolower($tag)]]++;
459 } 461 }
460 } 462 }
461 // Sort tags by usage (most used tag first) 463
462 arsort($tags); 464 /*
465 * Formerly used arsort(), which doesn't define the sort behaviour for equal values.
466 * Also, this function doesn't produce the same result between PHP 5.6 and 7.
467 *
468 * So we now use array_multisort() to sort tags by DESC occurrences,
469 * then ASC alphabetically for equal values.
470 *
471 * @see https://github.com/shaarli/Shaarli/issues/1142
472 */
473 $keys = array_keys($tags);
474 $tmpTags = array_combine($keys, $keys);
475 array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
463 return $tags; 476 return $tags;
464 } 477 }
465 478
diff --git a/application/LinkUtils.php b/application/LinkUtils.php
index 3705f7e9..4df5c0ca 100644
--- a/application/LinkUtils.php
+++ b/application/LinkUtils.php
@@ -11,6 +11,7 @@
11 */ 11 */
12function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_getinfo') 12function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_getinfo')
13{ 13{
14 $isRedirected = false;
14 /** 15 /**
15 * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download). 16 * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
16 * 17 *
@@ -22,16 +23,24 @@ function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_get
22 * 23 *
23 * @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
24 */ 25 */
25 return function(&$ch, $data) use ($curlGetInfo, &$charset, &$title) { 26 return function(&$ch, $data) use ($curlGetInfo, &$charset, &$title, &$isRedirected) {
26 $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE); 27 $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
27 if (!empty($responseCode) && $responseCode != 200) { 28 if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
29 $isRedirected = true;
30 return strlen($data);
31 }
32 if (!empty($responseCode) && $responseCode !== 200) {
28 return false; 33 return false;
29 } 34 }
30 $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE); 35 // After a redirection, the content type will keep the previous request value
36 // until it finds the next content-type header.
37 if (! $isRedirected || strpos(strtolower($data), 'content-type') !== false) {
38 $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE);
39 }
31 if (!empty($contentType) && strpos($contentType, 'text/html') === false) { 40 if (!empty($contentType) && strpos($contentType, 'text/html') === false) {
32 return false; 41 return false;
33 } 42 }
34 if (empty($charset)) { 43 if (!empty($contentType) && empty($charset)) {
35 $charset = header_extract_charset($contentType); 44 $charset = header_extract_charset($contentType);
36 } 45 }
37 if (empty($charset)) { 46 if (empty($charset)) {
diff --git a/application/LoginManager.php b/application/LoginManager.php
deleted file mode 100644
index 397bc6e3..00000000
--- a/application/LoginManager.php
+++ /dev/null
@@ -1,134 +0,0 @@
1<?php
2namespace Shaarli;
3
4/**
5 * User login management
6 */
7class LoginManager
8{
9 protected $globals = [];
10 protected $configManager = null;
11 protected $banFile = '';
12
13 /**
14 * Constructor
15 *
16 * @param array $globals The $GLOBALS array (reference)
17 * @param ConfigManager $configManager Configuration Manager instance.
18 */
19 public function __construct(& $globals, $configManager)
20 {
21 $this->globals = &$globals;
22 $this->configManager = $configManager;
23 $this->banFile = $this->configManager->get('resource.ban_file', 'data/ipbans.php');
24 $this->readBanFile();
25 }
26
27 /**
28 * Read a file containing banned IPs
29 */
30 protected function readBanFile()
31 {
32 if (! file_exists($this->banFile)) {
33 return;
34 }
35 include $this->banFile;
36 }
37
38 /**
39 * Write the banned IPs to a file
40 */
41 protected function writeBanFile()
42 {
43 if (! array_key_exists('IPBANS', $this->globals)) {
44 return;
45 }
46 file_put_contents(
47 $this->banFile,
48 "<?php\n\$GLOBALS['IPBANS']=" . var_export($this->globals['IPBANS'], true) . ";\n?>"
49 );
50 }
51
52 /**
53 * Handle a failed login and ban the IP after too many failed attempts
54 *
55 * @param array $server The $_SERVER array
56 */
57 public function handleFailedLogin($server)
58 {
59 $ip = $server['REMOTE_ADDR'];
60 $trusted = $this->configManager->get('security.trusted_proxies', []);
61
62 if (in_array($ip, $trusted)) {
63 $ip = getIpAddressFromProxy($server, $trusted);
64 if (! $ip) {
65 // the IP is behind a trusted forward proxy, but is not forwarded
66 // in the HTTP headers, so we do nothing
67 return;
68 }
69 }
70
71 // increment the fail count for this IP
72 if (isset($this->globals['IPBANS']['FAILURES'][$ip])) {
73 $this->globals['IPBANS']['FAILURES'][$ip]++;
74 } else {
75 $this->globals['IPBANS']['FAILURES'][$ip] = 1;
76 }
77
78 if ($this->globals['IPBANS']['FAILURES'][$ip] >= $this->configManager->get('security.ban_after')) {
79 $this->globals['IPBANS']['BANS'][$ip] = time() + $this->configManager->get('security.ban_duration', 1800);
80 logm(
81 $this->configManager->get('resource.log'),
82 $server['REMOTE_ADDR'],
83 'IP address banned from login'
84 );
85 }
86 $this->writeBanFile();
87 }
88
89 /**
90 * Handle a successful login
91 *
92 * @param array $server The $_SERVER array
93 */
94 public function handleSuccessfulLogin($server)
95 {
96 $ip = $server['REMOTE_ADDR'];
97 // FIXME unban when behind a trusted proxy?
98
99 unset($this->globals['IPBANS']['FAILURES'][$ip]);
100 unset($this->globals['IPBANS']['BANS'][$ip]);
101
102 $this->writeBanFile();
103 }
104
105 /**
106 * Check if the user can login from this IP
107 *
108 * @param array $server The $_SERVER array
109 *
110 * @return bool true if the user is allowed to login
111 */
112 public function canLogin($server)
113 {
114 $ip = $server['REMOTE_ADDR'];
115
116 if (! isset($this->globals['IPBANS']['BANS'][$ip])) {
117 // the user is not banned
118 return true;
119 }
120
121 if ($this->globals['IPBANS']['BANS'][$ip] > time()) {
122 // the user is still banned
123 return false;
124 }
125
126 // the ban has expired, the user can attempt to log in again
127 logm($this->configManager->get('resource.log'), $server['REMOTE_ADDR'], 'Ban lifted.');
128 unset($this->globals['IPBANS']['FAILURES'][$ip]);
129 unset($this->globals['IPBANS']['BANS'][$ip]);
130
131 $this->writeBanFile();
132 return true;
133 }
134}
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index 3233d6b6..a4483870 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -25,6 +25,9 @@ class PageBuilder
25 * @var LinkDB $linkDB instance. 25 * @var LinkDB $linkDB instance.
26 */ 26 */
27 protected $linkDB; 27 protected $linkDB;
28
29 /** @var bool $isLoggedIn Whether the user is logged in **/
30 protected $isLoggedIn = false;
28 31
29 /** 32 /**
30 * PageBuilder constructor. 33 * PageBuilder constructor.
@@ -34,12 +37,13 @@ class PageBuilder
34 * @param LinkDB $linkDB instance. 37 * @param LinkDB $linkDB instance.
35 * @param string $token Session token 38 * @param string $token Session token
36 */ 39 */
37 public function __construct(&$conf, $linkDB = null, $token = null) 40 public function __construct(&$conf, $linkDB = null, $token = null, $isLoggedIn = false)
38 { 41 {
39 $this->tpl = false; 42 $this->tpl = false;
40 $this->conf = $conf; 43 $this->conf = $conf;
41 $this->linkDB = $linkDB; 44 $this->linkDB = $linkDB;
42 $this->token = $token; 45 $this->token = $token;
46 $this->isLoggedIn = $isLoggedIn;
43 } 47 }
44 48
45 /** 49 /**
@@ -55,7 +59,7 @@ class PageBuilder
55 $this->conf->get('resource.update_check'), 59 $this->conf->get('resource.update_check'),
56 $this->conf->get('updates.check_updates_interval'), 60 $this->conf->get('updates.check_updates_interval'),
57 $this->conf->get('updates.check_updates'), 61 $this->conf->get('updates.check_updates'),
58 isLoggedIn(), 62 $this->isLoggedIn,
59 $this->conf->get('updates.check_updates_branch') 63 $this->conf->get('updates.check_updates_branch')
60 ); 64 );
61 $this->tpl->assign('newVersion', escape($version)); 65 $this->tpl->assign('newVersion', escape($version));
@@ -67,6 +71,7 @@ class PageBuilder
67 $this->tpl->assign('versionError', escape($exc->getMessage())); 71 $this->tpl->assign('versionError', escape($exc->getMessage()));
68 } 72 }
69 73
74 $this->tpl->assign('is_logged_in', $this->isLoggedIn);
70 $this->tpl->assign('feedurl', escape(index_url($_SERVER))); 75 $this->tpl->assign('feedurl', escape(index_url($_SERVER)));
71 $searchcrits = ''; // Search criteria 76 $searchcrits = ''; // Search criteria
72 if (!empty($_GET['searchtags'])) { 77 if (!empty($_GET['searchtags'])) {
diff --git a/application/SessionManager.php b/application/SessionManager.php
deleted file mode 100644
index 71f0b38d..00000000
--- a/application/SessionManager.php
+++ /dev/null
@@ -1,83 +0,0 @@
1<?php
2namespace Shaarli;
3
4/**
5 * Manages the server-side session
6 */
7class SessionManager
8{
9 protected $session = [];
10
11 /**
12 * Constructor
13 *
14 * @param array $session The $_SESSION array (reference)
15 * @param ConfigManager $conf ConfigManager instance
16 */
17 public function __construct(& $session, $conf)
18 {
19 $this->session = &$session;
20 $this->conf = $conf;
21 }
22
23 /**
24 * Generates a session token
25 *
26 * @return string token
27 */
28 public function generateToken()
29 {
30 $token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt'));
31 $this->session['tokens'][$token] = 1;
32 return $token;
33 }
34
35 /**
36 * Checks the validity of a session token, and destroys it afterwards
37 *
38 * @param string $token The token to check
39 *
40 * @return bool true if the token is valid, else false
41 */
42 public function checkToken($token)
43 {
44 if (! isset($this->session['tokens'][$token])) {
45 // the token is wrong, or has already been used
46 return false;
47 }
48
49 // destroy the token to prevent future use
50 unset($this->session['tokens'][$token]);
51 return true;
52 }
53
54 /**
55 * Validate session ID to prevent Full Path Disclosure.
56 *
57 * See #298.
58 * The session ID's format depends on the hash algorithm set in PHP settings
59 *
60 * @param string $sessionId Session ID
61 *
62 * @return true if valid, false otherwise.
63 *
64 * @see http://php.net/manual/en/function.hash-algos.php
65 * @see http://php.net/manual/en/session.configuration.php
66 */
67 public static function checkId($sessionId)
68 {
69 if (empty($sessionId)) {
70 return false;
71 }
72
73 if (!$sessionId) {
74 return false;
75 }
76
77 if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
78 return false;
79 }
80
81 return true;
82 }
83}
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php
new file mode 100644
index 00000000..d6784d6d
--- /dev/null
+++ b/application/security/LoginManager.php
@@ -0,0 +1,265 @@
1<?php
2namespace Shaarli\Security;
3
4use Shaarli\Config\ConfigManager;
5
6/**
7 * User login management
8 */
9class LoginManager
10{
11 /** @var string Name of the cookie set after logging in **/
12 public static $STAY_SIGNED_IN_COOKIE = 'shaarli_staySignedIn';
13
14 /** @var array A reference to the $_GLOBALS array */
15 protected $globals = [];
16
17 /** @var ConfigManager Configuration Manager instance **/
18 protected $configManager = null;
19
20 /** @var SessionManager Session Manager instance **/
21 protected $sessionManager = null;
22
23 /** @var string Path to the file containing IP bans */
24 protected $banFile = '';
25
26 /** @var bool Whether the user is logged in **/
27 protected $isLoggedIn = false;
28
29 /** @var bool Whether the Shaarli instance is open to public edition **/
30 protected $openShaarli = false;
31
32 /** @var string User sign-in token depending on remote IP and credentials */
33 protected $staySignedInToken = '';
34
35 /**
36 * Constructor
37 *
38 * @param array $globals The $GLOBALS array (reference)
39 * @param ConfigManager $configManager Configuration Manager instance
40 * @param SessionManager $sessionManager SessionManager instance
41 */
42 public function __construct(& $globals, $configManager, $sessionManager)
43 {
44 $this->globals = &$globals;
45 $this->configManager = $configManager;
46 $this->sessionManager = $sessionManager;
47 $this->banFile = $this->configManager->get('resource.ban_file', 'data/ipbans.php');
48 $this->readBanFile();
49 if ($this->configManager->get('security.open_shaarli') === true) {
50 $this->openShaarli = true;
51 }
52 }
53
54 /**
55 * Generate a token depending on deployment salt, user password and client IP
56 *
57 * @param string $clientIpAddress The remote client IP address
58 */
59 public function generateStaySignedInToken($clientIpAddress)
60 {
61 $this->staySignedInToken = sha1(
62 $this->configManager->get('credentials.hash')
63 . $clientIpAddress
64 . $this->configManager->get('credentials.salt')
65 );
66 }
67
68 /**
69 * Return the user's client stay-signed-in token
70 *
71 * @return string User's client stay-signed-in token
72 */
73 public function getStaySignedInToken()
74 {
75 return $this->staySignedInToken;
76 }
77
78 /**
79 * Check user session state and validity (expiration)
80 *
81 * @param array $cookie The $_COOKIE array
82 * @param string $clientIpId Client IP address identifier
83 */
84 public function checkLoginState($cookie, $clientIpId)
85 {
86 if (! $this->configManager->exists('credentials.login')) {
87 // Shaarli is not configured yet
88 $this->isLoggedIn = false;
89 return;
90 }
91
92 if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE])
93 && $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken
94 ) {
95 // The user client has a valid stay-signed-in cookie
96 // Session information is updated with the current client information
97 $this->sessionManager->storeLoginInfo($clientIpId);
98
99 } elseif ($this->sessionManager->hasSessionExpired()
100 || $this->sessionManager->hasClientIpChanged($clientIpId)
101 ) {
102 $this->sessionManager->logout();
103 $this->isLoggedIn = false;
104 return;
105 }
106
107 $this->isLoggedIn = true;
108 $this->sessionManager->extendSession();
109 }
110
111 /**
112 * Return whether the user is currently logged in
113 *
114 * @return true when the user is logged in, false otherwise
115 */
116 public function isLoggedIn()
117 {
118 if ($this->openShaarli) {
119 return true;
120 }
121 return $this->isLoggedIn;
122 }
123
124 /**
125 * Check user credentials are valid
126 *
127 * @param string $remoteIp Remote client IP address
128 * @param string $clientIpId Client IP address identifier
129 * @param string $login Username
130 * @param string $password Password
131 *
132 * @return bool true if the provided credentials are valid, false otherwise
133 */
134 public function checkCredentials($remoteIp, $clientIpId, $login, $password)
135 {
136 $hash = sha1($password . $login . $this->configManager->get('credentials.salt'));
137
138 if ($login != $this->configManager->get('credentials.login')
139 || $hash != $this->configManager->get('credentials.hash')
140 ) {
141 logm(
142 $this->configManager->get('resource.log'),
143 $remoteIp,
144 'Login failed for user ' . $login
145 );
146 return false;
147 }
148
149 $this->sessionManager->storeLoginInfo($clientIpId);
150 logm(
151 $this->configManager->get('resource.log'),
152 $remoteIp,
153 'Login successful'
154 );
155 return true;
156 }
157
158 /**
159 * Read a file containing banned IPs
160 */
161 protected function readBanFile()
162 {
163 if (! file_exists($this->banFile)) {
164 return;
165 }
166 include $this->banFile;
167 }
168
169 /**
170 * Write the banned IPs to a file
171 */
172 protected function writeBanFile()
173 {
174 if (! array_key_exists('IPBANS', $this->globals)) {
175 return;
176 }
177 file_put_contents(
178 $this->banFile,
179 "<?php\n\$GLOBALS['IPBANS']=" . var_export($this->globals['IPBANS'], true) . ";\n?>"
180 );
181 }
182
183 /**
184 * Handle a failed login and ban the IP after too many failed attempts
185 *
186 * @param array $server The $_SERVER array
187 */
188 public function handleFailedLogin($server)
189 {
190 $ip = $server['REMOTE_ADDR'];
191 $trusted = $this->configManager->get('security.trusted_proxies', []);
192
193 if (in_array($ip, $trusted)) {
194 $ip = getIpAddressFromProxy($server, $trusted);
195 if (! $ip) {
196 // the IP is behind a trusted forward proxy, but is not forwarded
197 // in the HTTP headers, so we do nothing
198 return;
199 }
200 }
201
202 // increment the fail count for this IP
203 if (isset($this->globals['IPBANS']['FAILURES'][$ip])) {
204 $this->globals['IPBANS']['FAILURES'][$ip]++;
205 } else {
206 $this->globals['IPBANS']['FAILURES'][$ip] = 1;
207 }
208
209 if ($this->globals['IPBANS']['FAILURES'][$ip] >= $this->configManager->get('security.ban_after')) {
210 $this->globals['IPBANS']['BANS'][$ip] = time() + $this->configManager->get('security.ban_duration', 1800);
211 logm(
212 $this->configManager->get('resource.log'),
213 $server['REMOTE_ADDR'],
214 'IP address banned from login'
215 );
216 }
217 $this->writeBanFile();
218 }
219
220 /**
221 * Handle a successful login
222 *
223 * @param array $server The $_SERVER array
224 */
225 public function handleSuccessfulLogin($server)
226 {
227 $ip = $server['REMOTE_ADDR'];
228 // FIXME unban when behind a trusted proxy?
229
230 unset($this->globals['IPBANS']['FAILURES'][$ip]);
231 unset($this->globals['IPBANS']['BANS'][$ip]);
232
233 $this->writeBanFile();
234 }
235
236 /**
237 * Check if the user can login from this IP
238 *
239 * @param array $server The $_SERVER array
240 *
241 * @return bool true if the user is allowed to login
242 */
243 public function canLogin($server)
244 {
245 $ip = $server['REMOTE_ADDR'];
246
247 if (! isset($this->globals['IPBANS']['BANS'][$ip])) {
248 // the user is not banned
249 return true;
250 }
251
252 if ($this->globals['IPBANS']['BANS'][$ip] > time()) {
253 // the user is still banned
254 return false;
255 }
256
257 // the ban has expired, the user can attempt to log in again
258 logm($this->configManager->get('resource.log'), $server['REMOTE_ADDR'], 'Ban lifted.');
259 unset($this->globals['IPBANS']['FAILURES'][$ip]);
260 unset($this->globals['IPBANS']['BANS'][$ip]);
261
262 $this->writeBanFile();
263 return true;
264 }
265}
diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php
new file mode 100644
index 00000000..b8b8ab8d
--- /dev/null
+++ b/application/security/SessionManager.php
@@ -0,0 +1,199 @@
1<?php
2namespace Shaarli\Security;
3
4use Shaarli\Config\ConfigManager;
5
6/**
7 * Manages the server-side session
8 */
9class SessionManager
10{
11 /** @var int Session expiration timeout, in seconds */
12 public static $SHORT_TIMEOUT = 3600; // 1 hour
13
14 /** @var int Session expiration timeout, in seconds */
15 public static $LONG_TIMEOUT = 31536000; // 1 year
16
17 /** @var array Local reference to the global $_SESSION array */
18 protected $session = [];
19
20 /** @var ConfigManager Configuration Manager instance **/
21 protected $conf = null;
22
23 /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */
24 protected $staySignedIn = false;
25
26 /**
27 * Constructor
28 *
29 * @param array $session The $_SESSION array (reference)
30 * @param ConfigManager $conf ConfigManager instance
31 */
32 public function __construct(& $session, $conf)
33 {
34 $this->session = &$session;
35 $this->conf = $conf;
36 }
37
38 /**
39 * Define whether the user should stay signed in across browser sessions
40 *
41 * @param bool $staySignedIn Keep the user signed in
42 */
43 public function setStaySignedIn($staySignedIn)
44 {
45 $this->staySignedIn = $staySignedIn;
46 }
47
48 /**
49 * Generates a session token
50 *
51 * @return string token
52 */
53 public function generateToken()
54 {
55 $token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt'));
56 $this->session['tokens'][$token] = 1;
57 return $token;
58 }
59
60 /**
61 * Checks the validity of a session token, and destroys it afterwards
62 *
63 * @param string $token The token to check
64 *
65 * @return bool true if the token is valid, else false
66 */
67 public function checkToken($token)
68 {
69 if (! isset($this->session['tokens'][$token])) {
70 // the token is wrong, or has already been used
71 return false;
72 }
73
74 // destroy the token to prevent future use
75 unset($this->session['tokens'][$token]);
76 return true;
77 }
78
79 /**
80 * Validate session ID to prevent Full Path Disclosure.
81 *
82 * See #298.
83 * The session ID's format depends on the hash algorithm set in PHP settings
84 *
85 * @param string $sessionId Session ID
86 *
87 * @return true if valid, false otherwise.
88 *
89 * @see http://php.net/manual/en/function.hash-algos.php
90 * @see http://php.net/manual/en/session.configuration.php
91 */
92 public static function checkId($sessionId)
93 {
94 if (empty($sessionId)) {
95 return false;
96 }
97
98 if (!$sessionId) {
99 return false;
100 }
101
102 if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
103 return false;
104 }
105
106 return true;
107 }
108
109 /**
110 * Store user login information after a successful login
111 *
112 * @param string $clientIpId Client IP address identifier
113 */
114 public function storeLoginInfo($clientIpId)
115 {
116 $this->session['ip'] = $clientIpId;
117 $this->session['username'] = $this->conf->get('credentials.login');
118 $this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
119 }
120
121 /**
122 * Extend session validity
123 */
124 public function extendSession()
125 {
126 if ($this->staySignedIn) {
127 return $this->extendTimeValidityBy(self::$LONG_TIMEOUT);
128 }
129 return $this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
130 }
131
132 /**
133 * Extend expiration time
134 *
135 * @param int $duration Expiration time extension (seconds)
136 *
137 * @return int New session expiration time
138 */
139 protected function extendTimeValidityBy($duration)
140 {
141 $expirationTime = time() + $duration;
142 $this->session['expires_on'] = $expirationTime;
143 return $expirationTime;
144 }
145
146 /**
147 * Logout a user by unsetting all login information
148 *
149 * See:
150 * - https://secure.php.net/manual/en/function.setcookie.php
151 */
152 public function logout()
153 {
154 if (isset($this->session)) {
155 unset($this->session['ip']);
156 unset($this->session['expires_on']);
157 unset($this->session['username']);
158 unset($this->session['visibility']);
159 unset($this->session['untaggedonly']);
160 }
161 }
162
163 /**
164 * Check whether the session has expired
165 *
166 * @param string $clientIpId Client IP address identifier
167 *
168 * @return bool true if the session has expired, false otherwise
169 */
170 public function hasSessionExpired()
171 {
172 if (empty($this->session['expires_on'])) {
173 return true;
174 }
175 if (time() >= $this->session['expires_on']) {
176 return true;
177 }
178 return false;
179 }
180
181 /**
182 * Check whether the client IP address has changed
183 *
184 * @param string $clientIpId Client IP address identifier
185 *
186 * @return bool true if the IP has changed, false if it has not, or
187 * if session protection has been disabled
188 */
189 public function hasClientIpChanged($clientIpId)
190 {
191 if ($this->conf->get('security.session_protection_disabled') === true) {
192 return false;
193 }
194 if (isset($this->session['ip']) && $this->session['ip'] === $clientIpId) {
195 return false;
196 }
197 return true;
198 }
199}
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss
index 25440de1..09d5efbe 100644
--- a/assets/default/scss/shaarli.scss
+++ b/assets/default/scss/shaarli.scss
@@ -1,1357 +1,1545 @@
1$fa-font-path: "~font-awesome/fonts"; 1$fa-font-path: '~font-awesome/fonts';
2 2
3@import "~font-awesome/scss/font-awesome.scss"; 3@import '~font-awesome/scss/font-awesome';
4@import '~purecss/build/pure.css'; 4@import '~purecss/build/pure.css';
5@import '~purecss/build/grids-responsive.css'; 5@import '~purecss/build/grids-responsive.css';
6@import '~pure-extras/css/pure-extras.css'; 6@import '~pure-extras/css/pure-extras.css';
7@import '~awesomplete/awesomplete.css'; 7@import '~awesomplete/awesomplete.css';
8 8
9/** 9$white: #fff;
10 * General 10$black: #000;
11 */ 11$almost-white: #f5f5f5;
12$dark-grey: #252525;
13$light-grey: #797979;
14$main-green: #1b926c;
15$light-green: #b0ddce;
16$dark-green: #2a4c41;
17$red: #ac2925;
18$orange: #f89406;
19$blue: #0b5ea6;
20$background-color: #d0d0d0;
21$background-linklist-info: #ddd;
22$light-shadow: rgba(255, 255, 255, .078);
23$dark-shadow: rgba(0, 0, 0, .298);
24$warning-text: #97600d;
25$form-input-border: #d8d8d8;
26$form-input-background: #eee;
27
28// General
12body { 29body {
13 background: #d0d0d0; 30 background: $background-color;
14} 31}
15 32
16.strong { 33.strong {
17 font-weight: bold; 34 font-weight: bold;
18} 35}
19 36
20.clear { 37.clear {
21 clear: both; 38 clear: both;
22} 39}
23 40
24.center { 41.center {
25 text-align: center; 42 margin: auto;
26 margin: auto; 43 text-align: center;
27} 44}
28 45
29.label { 46.label {
30 display: inline-block; 47 display: inline-block;
31 padding: .25em .4em; 48 border-radius: .25rem;
32 font-size: 75%; 49 padding: .25em .4em;
33 font-weight: 700; 50 vertical-align: baseline;
34 line-height: 1; 51 text-align: center;
35 text-align: center; 52 line-height: 1;
36 white-space: nowrap; 53 white-space: nowrap;
37 vertical-align: baseline; 54 font-size: 75%;
38 border-radius: .25rem; 55 font-weight: 700;
39} 56}
40 57
41pre { 58pre {
42 max-width: 100%; 59 max-width: 100%;
43} 60}
44 61
45@font-face { 62@font-face {
46 font-family: 'Roboto'; 63 font-family: 'Roboto';
47 font-weight: 400; 64 font-weight: 400;
48 font-style: normal; 65 font-style: normal;
49 src: 66 src: local('Roboto'),
50 local('Roboto'), 67 local('Roboto-Regular'),
51 local('Roboto-Regular'), 68 url('../fonts/Roboto-Regular.woff2') format('woff2'),
52 url('../fonts/Roboto-Regular.woff2') format('woff2'), 69 url('../fonts/Roboto-Regular.woff') format('woff');
53 url('../fonts/Roboto-Regular.woff') format('woff');
54} 70}
55 71
56@font-face { 72@font-face {
57 font-family: 'Roboto'; 73 font-family: 'Roboto';
58 font-weight: 700; 74 font-weight: 700;
59 font-style: normal; 75 font-style: normal;
60 src: 76 src: local('Roboto'),
61 local('Roboto'), 77 local('Roboto-Bold'),
62 local('Roboto-Bold'), 78 url('../fonts/Roboto-Bold.woff2') format('woff2'),
63 url('../fonts/Roboto-Bold.woff2') format('woff2'), 79 url('../fonts/Roboto-Bold.woff') format('woff');
64 url('../fonts/Roboto-Bold.woff') format('woff'); 80}
65} 81
66 82body,
67body, .pure-g [class*="pure-u"] { 83.pure-g [class*='pure-u'] {
68 font-family: Roboto, Arial, sans-serif; 84 font-family: Roboto, Arial, sans-serif;
69} 85}
70 86
71/** 87// Extends Pure grids responsive to hide items.
72 * Extends Pure grids responsive to hide items. 88// Use xx-0 to hide an item on xx screen.
73 * Use xx-0 to hide an item on xx screen. 89// Display it at any level with xx-visible.
74 * Display it at any level with xx-visible. 90.pure-u-0 {
75 */ 91 display: none !important;
76.pure-u-0 { display: none !important; } 92}
93
77@media screen and (min-width: 35.5em) { 94@media screen and (min-width: 35.5em) {
78 .pure-u-sm-0 { display: none !important; } 95 .pure-u-sm-0 {
79 .pure-u-sm-visible { display: inline-block !important; } 96 display: none !important;
97 }
98
99 .pure-u-sm-visible {
100 display: inline-block !important;
101 }
80} 102}
103
81@media screen and (min-width: 48em) { 104@media screen and (min-width: 48em) {
82 .pure-u-md-0 { display: none !important; } 105 .pure-u-md-0 {
83 .pure-u-md-visible { display: inline-block !important; } 106 display: none !important;
107 }
108
109 .pure-u-md-visible {
110 display: inline-block !important;
111 }
84} 112}
113
85@media screen and (min-width: 64em) { 114@media screen and (min-width: 64em) {
86 .pure-u-lg-0 { display: none !important; } 115 .pure-u-lg-0 {
87 .pure-u-lg-visible { display: inline-block !important; } 116 display: none !important;
117 }
118
119 .pure-u-lg-visible {
120 display: inline-block !important;
121 }
88} 122}
123
89@media screen and (min-width: 80em) { 124@media screen and (min-width: 80em) {
90 .pure-u-xl-0 { display: none !important; } 125 .pure-u-xl-0 {
91 .pure-u-xl-visible { display: inline-block !important; } 126 display: none !important;
127 }
128
129 .pure-u-xl-visible {
130 display: inline-block !important;
131 }
92} 132}
93 133
94/** 134// Make pure-extras alert closable.
95 * Make pure-extras alert closable. 135.pure-alert-closable {
96 */ 136 .fa-times {
97.pure-alert-closable .fa-times {
98 float: right; 137 float: right;
138 }
99} 139}
140
100.pure-alert-close { 141.pure-alert-close {
101 cursor: pointer; 142 cursor: pointer;
102} 143}
103 144
104.pure-alert-success { 145.pure-alert-success {
105 background-color: #1b926c; 146 background-color: $main-green;
106} 147}
107 148
108.anchor:target { 149.anchor {
150 &:target {
109 padding-top: 40px; 151 padding-top: 40px;
152 }
110} 153}
111/** 154
112 * MENU 155// MENU
113 **/
114.shaarli-menu { 156.shaarli-menu {
115 position: fixed; 157 position: fixed;
116 top: 0; 158 top: 0;
117 width: 100%; 159 transition: max-height .5s;
118 --height: 50px; 160 z-index: 999;
119 background: #1b926c; 161 background: $main-green;
120 -webkit-font-smoothing: antialiased; 162 width: 100%;
121 /* Hack to transition with auto height: http://stackoverflow.com/a/8331169/1484919 */ 163 // Hack to transition with auto height: http://stackoverflow.com/a/8331169/1484919
122 max-height: 45px; 164 max-height: 45px;
123 transition: max-height 0.5s; 165 overflow: hidden;
124 overflow: hidden; 166 -webkit-font-smoothing: antialiased;
125 z-index: 999; 167
168 &.open {
169 transition: max-height .75s;
170 max-height: 500px;
171 }
126} 172}
127 173
128/* Chrome bugfix: with 100% height, it only displays the first element. */
129.pure-menu-item { 174.pure-menu-item {
130 height: 45px; 175 // Chrome bugfix: with 100% height, it only displays the first element.
131} 176 height: 45px;
132 177
133.shaarli-menu.open { 178 &:hover {
134 max-height: 500px; 179 &::after {
135 transition: max-height 0.75s; 180 display: block;
181 margin: -4px auto 0;
182 background: $white;
183 width: 100%;
184 height: 4px;
185 content: '';
186 }
187 }
136} 188}
137 189
138.head-logo { 190.head-logo {
139 float: left; 191 float: left;
140 margin: 0 5px 0 0; 192 margin: 0 5px 0 0;
141} 193}
142 194
143.pure-menu-link, 195%menu-link {
144.pure-menu-link:visited, 196 padding: .8em 1em;
145.pure-menu-selected .pure-menu-link, 197 color: $almost-white;
146.pure-menu-selected .pure-menu-link:visited {
147 padding: 0.8em 1em;
148 color: #f5f5f5;
149} 198}
150 199
151.pure-menu-link:hover, .pure-menu-link:focus, 200%menu-link-hover {
152.pure-menu-selected .pure-menu-link:hover, 201 background: transparent;
153.pure-menu-selected .pure-menu-link:focus { 202 color: $white;
154 color: #fff;
155 background: transparent;
156} 203}
157 204
158.pure-menu-item:hover::after { 205.pure-menu-link {
159 margin: -4px auto 0 auto; 206 @extend %menu-link;
160 display: block; 207
161 content:""; 208 &:visited {
162 background: #fff; 209 @extend %menu-link;
163 height: 4px; 210 }
164 width: 100%; 211
212 &:hover,
213 &:focus {
214 @extend %menu-link-hover;
215 }
165} 216}
166 217
167.menu-toggle { 218.pure-menu-selected {
168 width: 34px; 219 .pure-menu-link {
169 height: 45px; 220 @extend %menu-link;
170 position: absolute; 221
171 top: 5px; 222 &:visited {
172 right: 0; 223 @extend %menu-link;
173 display: none; 224 }
225
226 &:hover,
227 &:focus {
228 @extend %menu-link-hover;
229 }
230 }
174} 231}
175 232
176.menu-toggle .bar { 233.menu-toggle {
177 background-color: #b0ddce; 234 display: none;
235 position: absolute;
236 top: 5px;
237 right: 0;
238 width: 34px;
239 height: 45px;
240
241 .bar {
178 display: block; 242 display: block;
179 width: 20px;
180 height: 2px;
181 border-radius: 100px;
182 position: absolute; 243 position: absolute;
183 top: 18px; 244 top: 18px;
184 right: 7px; 245 right: 7px;
185 transition: all 0.5s; 246 border-radius: 100px;
186} 247 background-color: $light-green;
248 width: 20px;
249 height: 2px;
250 transition-duration: .5s;
187 251
188.menu-toggle .bar:first-child { 252 &:first-child {
189 transform: translateY(-6px); 253 transform: translateY(-6px);
190} 254 }
255 }
191 256
192.menu-toggle.x .bar { 257 &.x {
193 transform: rotate(45deg); 258 .bar {
194} 259 transform: rotate(45deg);
195 260
196.menu-toggle.x .bar:first-child { 261 &:first-child {
197 transform: rotate(-45deg); 262 transform: rotate(-45deg);
263 }
264 }
265 }
198} 266}
199 267
200@media screen and (max-width: 64em) { 268@media screen and (max-width: 64em) {
201 .menu-toggle { 269 .menu-toggle {
202 display: block; 270 display: block;
203 } 271 }
204} 272}
205 273
206.header-buttons { 274.header-buttons {
207 text-align: right; 275 text-align: right;
208} 276}
209 277
210.linkcount { 278.linkcount {
211 color: #252525; 279 color: $dark-grey;
212 font-size: 0.8em; 280 font-size: .8em;
213} 281}
214 282
215@media screen and (min-width: 64em) { 283@media screen and (min-width: 64em) {
216 .linkcount { 284 .linkcount {
217 position: absolute; 285 position: absolute;
218 right: 5px; 286 right: 5px;
287 }
288}
289
290.searchform-block {
291 width: 100%;
292 text-align: center;
293
294 input {
295 &[type='text'] {
296 border: medium none currentColor;
297 border-radius: 2px;
298 box-shadow: 0 1px 0 $light-shadow, 0 1px 1px $dark-shadow inset;
299 background: $almost-white;
300 padding: 0 5px;
301 width: 260px;
302 height: 30px;
303 color: $dark-grey;
304
305 &::-webkit-input-placeholder {
306 color: $light-grey;
307 }
219 } 308 }
220} 309 }
221
222#search, #search-linklist, #search-tagcloud {
223 text-align: center;
224 width: 100%;
225}
226 310
227#search input[type="text"], #search-linklist input[type="text"] { 311 button {
228 padding: 0 5px; 312 border: 0;
229 height: 30px;
230 width: 260px;
231 background: #f5f5f5;
232 border: medium none currentColor;
233 box-shadow: 0 1px 0 rgba(255, 255, 255, 0.078), 0 1px 1px rgba(0, 0, 0, 0.298) inset;
234 border-radius: 2px; 313 border-radius: 2px;
235 color: #252525; 314 background-color: $main-green;
236} 315 padding: 4px 8px 6px;
237@media screen and (max-width: 64em) { 316 color: $almost-white;
238 .searchform { 317 }
239 max-width: 260px;
240 margin: 0 auto;
241 }
242}
243
244/* because chrome */
245#search input[type="text"]::-webkit-input-placeholder,
246#search-linklist input[type="text"]::-webkit-input-placeholder {
247 color: #777777;
248} 318}
249 319
250#search button, 320@media screen and (max-width: 64em) {
251#search-tagcloud button, 321 .searchform {
252#search-linklist button { 322 margin: 0 auto;
253 padding: 4px 8px 6px 8px; 323 max-width: 260px;
254 background-color: #1B926C; 324 }
255 color: #f5f5f5;
256 border: none;
257 border-radius: 2px;
258} 325}
259 326
260#search-tagcloud button { 327.search-tagcloud {
328 button {
261 width: 90%; 329 width: 90%;
330 }
262} 331}
263 332
264@media screen and (max-width: 64em) { 333@media screen and (max-width: 64em) {
265 #search-linklist button { 334 .search-linklist {
266 width: 100%; 335 button {
336 width: 100%;
267 } 337 }
268 #search-linklist .awesomplete {
269 margin: 5px 0;
270 }
271}
272 338
273#search button:hover, 339 .awesomplete {
274#search-linklist button:hover, 340 margin: 5px 0;
275#search-tagcloud button:hover {
276 color: #d0d0d0;
277}
278
279#search,
280#search-linklist {
281 padding: 6px 0;
282}
283
284@media screen and (max-width: 64em) {
285 #search, #search * {
286 visibility: hidden;
287 } 341 }
342 }
288} 343}
289 344
290.subheader-form a.button { 345.header-search,
291 color: #f5f5f5; 346.search-linklist,
292 font-weight: bold; 347.search-tagcloud {
293 text-decoration: none; 348 button {
294 border: 2px solid #f5f5f5; 349 &:hover {
295 border-radius: 5px; 350 color: $background-color;
296 padding: 3px 10px; 351 }
352 }
297} 353}
298 354
299.linklist-item-editbuttons .delete-checkbox { 355.header-search,
300 display: none; 356.search-linklist {
357 padding: 6px 0;
301} 358}
302 359
303#header-login-form input[type="text"], #header-login-form input[type="password"] { 360@media screen and (max-width: 64em) {
304 width: 200px; 361 .header-search ,
362 .header-search * {
363 visibility: hidden;
364 }
305} 365}
306 366
307/* because chrome */ 367%subheader-form-input {
308#header-login-form input[type="text"]::-webkit-input-placeholder, 368 border: medium none currentColor;
309#header-login-form input[type="password"]::-webkit-input-placeholder { 369 border-radius: 2px;
310 color: #777777; 370 box-shadow: 0 1px 0 $light-shadow, 0 1px 4px $dark-shadow inset;
371 background: $almost-white;
372 padding: 5px 5px 3px 15px;
373 width: 20%;
374 height: 20px;
375 color: $dark-grey;
311} 376}
312 377
313.subheader-form { 378.subheader-form {
314 visibility: hidden; 379 display: block;
315 position: fixed; 380 position: fixed;
316 width: 100%; 381 visibility: hidden;
317 text-align: center; 382 z-index: 999;
318 background: #1b926c; 383 background: $main-green;
319 display: block; 384 padding: 5px 0;
320 z-index: 999; 385 width: 100%;
321 height: 30px; 386 height: 30px;
322 padding: 5px 0; 387 text-align: center;
323} 388
324 389 input {
325@media screen and (min-width: 64em) { 390 &[type='text'],
326 .subheader-form.open, .subheader-form.open * { 391 &[type='password'] {
327 visibility: visible; 392 @extend %subheader-form-input;
393
394 &::-webkit-input-placeholder {
395 color: $dark-grey;
396 }
328 } 397 }
329} 398 }
330 399
331.subheader-form input[type="text"], .subheader-form input[type="password"], .subheader-form .remember-me { 400 &[type='submit'] {
332 padding: 5px 5px 3px 15px; 401 display: inline-block;
333 height: 20px; 402 margin: 0 0 5px;
334 width: 20%; 403 border: 1px solid $almost-white;
335 background: #f5f5f5;
336 border: medium none currentColor;
337 border-radius: 2px; 404 border-radius: 2px;
338 box-shadow: 0 1px 0 rgba(255, 255, 255, 0.078), 0 1px 4px rgba(0, 0, 0, 0.298) inset; 405 background: $main-green;
339 color: #252525; 406 padding: 4px 0;
340} 407 width: 100px;
408 height: 28px;
409 color: $almost-white;
341 410
342/* because chrome */ 411 &:hover {
343.subheader-form input[type="text"]::-webkit-input-placeholder, 412 background: $almost-white;
344.subheader-form input[type="password"]::-webkit-input-placeholder 413 color: $main-green;
345{ 414 }
346 color: #252525; 415 }
347} 416
417 .remember-me {
418 @extend %subheader-form-input;
348 419
349.subheader-form .remember-me {
350 display: inline-block; 420 display: inline-block;
351 width: auto;
352 padding: 5px 20px 3px 20px;
353 cursor: pointer; 421 cursor: pointer;
354} 422 padding: 5px 20px 3px;
423 width: auto;
355 424
356.subheader-form .remember-me label, .subheader-form .remember-me input { 425 label,
357 cursor: pointer; 426 input {
427 cursor: pointer;
428 }
429 }
430
431 a {
432 &.button {
433 border: 2px solid $almost-white;
434 border-radius: 5px;
435 padding: 3px 10px;
436 text-decoration: none;
437 color: $almost-white;
438 font-weight: bold;
439 }
440 }
358} 441}
359 442
360.subheader-form input[type="submit"] { 443.header-login-form {
361 display: inline-block; 444 input {
362 margin: 0 0 5px 0; 445 &[type='text'],
363 padding: 4px 0 4px 0; 446 &[type='password'] {
364 height: 28px; 447 width: 200px;
365 width: 100px; 448
366 background: #1b926c; 449 // because chrome
367 border: 1px solid #f5f5f5; 450 &::-webkit-input-placeholder {
368 color: #f5f5f5; 451 color: $light-grey;
369 border-radius: 2px; 452 }
453 }
454 }
370} 455}
371 456
372.subheader-form input[type="submit"]:hover { 457@media screen and (min-width: 64em) {
373 background: #f5f5f5; 458 .subheader-form {
374 color: #1b926c; 459 &.open {
460 visibility: visible;
461
462 * {
463 visibility: visible;
464 }
465 }
466 }
375} 467}
376 468
377.new-version-message { 469.new-version-message {
378 text-align: center; 470 text-align: center;
379}
380 471
381.new-version-message a { 472 a {
382 color: rgb(151, 96, 13); 473 color: $warning-text;
383 font-weight: bold; 474 font-weight: bold;
475 }
384} 476}
385 477
386/** 478// CONTENT - GENERAL
387 * CONTENT - GENERAL 479.container {
388 */ 480 position: relative;
389#content { 481 z-index: 2;
390 position: relative; 482 margin-top: 45px;
391 z-index: 2;
392 margin-top: 45px;
393} 483}
394 484
395/** 485// Plugins additional forms
396 * Plugins additional forms
397 */
398.toolbar-plugin { 486.toolbar-plugin {
399 margin: 5px 0; 487 margin: 5px 0;
400 text-align: center; 488 text-align: center;
401} 489
402 490 input {
403.toolbar-plugin input[type="text"] { 491 &[type='text'] {
404 padding: 0 5px; 492 border: medium none currentColor;
405 height: 30px; 493 border-radius: 2px;
406 width: 300px; 494 box-shadow: 0 1px 0 $light-shadow, 0 1px 1px $dark-shadow inset;
407 background: #f5f5f5; 495 background: $almost-white;
408 border: medium none currentColor; 496 padding: 0 5px;
409 box-shadow: 0 1px 0 rgba(255, 255, 255, 0.078), 0 1px 1px rgba(0, 0, 0, 0.298) inset; 497 width: 300px;
410 border-radius: 2px; 498 height: 30px;
411 color: #252525; 499 color: $dark-grey;
412} 500
413 501 &::-webkit-input-placeholder {
414/* because chrome */ 502 color: $light-grey;
415.toolbar-plugin input[type="text"]::-webkit-input-placeholder { 503 }
416 color: #777777; 504 }
417}
418
419.toolbar-plugin input[type="submit"] {
420 padding: 0 10px;
421 height: 30px;
422 background: #f5f5f5;
423 border: medium none currentColor;
424 border-radius: 2px;
425 color: #252525;
426}
427 505
428.toolbar-plugin input[type="submit"]:hover { 506 &[type='submit'] {
429 background: #fff; 507 border: medium none currentColor;
508 border-radius: 2px;
509 background: $almost-white;
510 padding: 0 10px;
511 height: 30px;
512 color: $dark-grey;
513
514 &:hover {
515 background: $white;
516 }
517 }
518 }
430} 519}
431 520
432@media screen and (max-width: 64em) { 521@media screen and (max-width: 64em) {
433 .toolbar-plugin input[type="text"] { 522 .toolbar-plugin {
523 input {
524 &[type='text'] {
434 width: 70%; 525 width: 70%;
435 526 }
436 } 527 }
528 }
437} 529}
438 530
439/** 531// CONTENT - LINKLIST PAGING
440 * CONTENT - LINKLIST PAGING 532// 64em -> lg
441 * 64em -> lg
442 */
443.linklist-filters { 533.linklist-filters {
444 margin: 5px 0; 534 margin: 5px 0;
445 color: #252525; 535 color: $dark-grey;
446 font-size: 0.9em; 536 font-size: .9em;
447}
448 537
449.linklist-filters a { 538 a {
450 padding: 5px 8px; 539 padding: 5px 8px;
451 text-decoration: none; 540 text-decoration: none;
452} 541 }
453 542
454.linklist-filters .filter-off { 543 .filter-off {
455 color: #252525; 544 background: $almost-white;
456 background: #f5f5f5; 545 color: $dark-grey;
457} 546 }
458 547
459.linklist-filters .filter-on { 548 .filter-on {
460 color: #b0ddce; 549 background: $main-green;
461 background: #1b926c; 550 color: $light-green;
462} 551 }
463 552
464.linklist-filters .filter-block { 553 .filter-block {
465 color: #f5f5f5; 554 background: $red;
466 background: #ac2925; 555 color: $almost-white;
556 }
467} 557}
468 558
469.linklist-pages { 559.linklist-pages {
470 margin: 5px 0; 560 margin: 5px 0;
471 color: #252525; 561 text-align: center;
472 text-align: center; 562 color: $dark-grey;
473}
474 563
475.linklist-pages a { 564 a {
476 color: #252525;
477 text-decoration: none; 565 text-decoration: none;
566 color: $dark-grey;
567
568 &:hover {
569 color: $white;
570 }
571 }
478} 572}
479 573
480.linklist-pages a:hover { 574%linksperpage-button {
481 color: #fff; 575 display: inline-block;
576 width: 20px;
577 text-align: center;
482} 578}
483 579
484.linksperpage { 580.linksperpage {
485 margin: 5px 0; 581 margin: 5px 0;
486 text-align: right; 582 text-align: right;
487 color: #252525; 583 color: $dark-grey;
488 font-size: 0.9em; 584 font-size: .9em;
489}
490 585
491.linksperpage a { 586 form {
492 padding: 5px 5px; 587 display: inline;
493 text-decoration: none; 588 }
494 color: #252525;
495 background: #f5f5f5;
496}
497 589
498.linksperpage a, .linksperpage input[type="text"] { 590 a {
499 display: inline-block; 591 @extend %linksperpage-button;
500 width: 20px;
501 text-align: center;
502}
503 592
504.linksperpage form { 593 background: $almost-white;
505 display: inline; 594 padding: 5px;
595 text-decoration: none;
596 color: $dark-grey;
597 }
598
599 input {
600 &[type='text'] {
601 @extend %linksperpage-button;
602
603 margin: 0;
604 border: medium none currentColor;
605 background: $almost-white;
606 padding: 4px 5px 3px 8px;
607 height: 20px;
608 color: $dark-grey;
609 font-size: .8em;
610 }
611 }
506} 612}
507 613
508.linksperpage input[type="text"] { 614// CONTENT - LINKLIST ITEMS
509 height: 20px; 615%private-border {
510 margin: 0; 616 display: block;
511 padding: 4px 5px 3px 8px; 617 position: absolute;
512 background: #f5f5f5; 618 top: 0;
513 border: medium none currentColor; 619 left: 3px;
514 color: #252525; 620 z-index: 1;
515 font-size: 0.8em; 621 background: $orange;
622 width: 2px;
623 height: 96%;
624 content: '';
516} 625}
517 626
518/**
519 * CONTENT - LINKLIST ITEMS
520 */
521.linklist-item { 627.linklist-item {
522 margin: 0 0 10px 0; 628 margin: 0 0 10px;
523 background: #f5f5f5; 629 box-shadow: 1px 1px 3px $light-grey;
524 box-shadow: 1px 1px 3px #797979; 630 background: $almost-white;
631
632 &.private {
633 .linklist-item-title {
634 &::before {
635 @extend %private-border;
636 margin-top: 3px;
637 }
638 }
639
640 .linklist-item-description {
641 &::before {
642 @extend %private-border;
643 height: 100%;
644 }
645 }
646 }
525} 647}
526 648
527.linklist-item-buttons { 649.linklist-item-buttons {
528 background: transparent; 650 position: relative;
529 position: relative; 651 z-index: 99;
530 width: 23px; 652 background: transparent;
531 z-index: 99; 653 width: 23px;
532} 654}
533 655
534.linklist-item-buttons-right { 656.linklist-item-buttons-right {
535 float: right; 657 float: right;
536 margin-right: -25px; 658 margin-right: -25px;
537} 659}
538 660
539.linklist-item-buttons * { 661.linklist-item-buttons * {
540 display: block; 662 display: block;
541 float: left; 663 float: left;
542 width:100%; 664 margin: auto;
543 margin: auto; 665 width: 100%;
544 text-align: center; 666 text-align: center;
545}
546
547.linklist-item-title, .linklist-item-title h2 {
548 margin: 0;
549 word-wrap: break-word;
550} 667}
551 668
552.linklist-item-title { 669.linklist-item-title {
553 position: relative; 670 position: relative;
554 background: #f5f5f5; 671 margin: 0;
555} 672 background: $almost-white;
673 word-wrap: break-word;
556 674
557.linklist-item-title h2 { 675 h2 {
558 padding: 3px 10px 0 10px; 676 margin: 0;
677 padding: 3px 10px 0;
559 line-height: 30px; 678 line-height: 30px;
560} 679 word-wrap: break-word;
561 680
562.linklist-item-title h2 a { 681 a {
563 font-size: 0.7em; 682 vertical-align: middle;
564 color: #252525; 683 text-decoration: none;
565 text-decoration: none; 684 color: $dark-grey;
566 vertical-align: middle; 685 font-size: .7em;
567} 686
687 &:visited {
688 .linklist-link {
689 color: $dark-green;
690 }
691 }
692
693 &:hover {
694 color: $dark-grey;
695 }
696 }
697 }
568 698
569.linklist-item-title .linklist-link { 699 .linklist-link {
700 color: $main-green;
570 font-size: 1.1em; 701 font-size: 1.1em;
571 color: #1b926c;
572}
573
574.linklist-item-title h2 a:visited .linklist-link {
575 color: #2a4c41;
576}
577
578.linklist-item-title h2 a:hover, .linklist-item-title .linklist-link:hover{
579 color: #252525;
580}
581 702
703 &:hover {
704 color: $dark-grey;
705 }
706 }
582 707
583.linklist-item-title .label-private { 708 .label-private {
584 border: solid 1px #F89406; 709 border: solid 1px $orange;
710 color: $orange;
585 font-family: Arial, sans-serif; 711 font-family: Arial, sans-serif;
586 font-size: 0.65em; 712 font-size: .65em;
587 color: #F89406; 713 }
588} 714}
589 715
590.fold-button { 716.fold-button {
591 display: none; 717 display: none;
592 color: #252525; 718 color: $dark-grey;
593} 719}
594 720
595.linklist-item-editbuttons { 721.linklist-item-editbuttons {
596 float: right; 722 float: right;
597 padding: 8px 5px; 723 padding: 8px 5px;
598}
599 724
600.linklist-item-editbuttons * { 725 * {
601 display: block; 726 display: block;
602 float: left; 727 float: left;
603 margin: 0 1px; 728 margin: 0 1px;
604} 729 }
605 730
606.linklist-item-editbuttons a { 731 a {
607 font-size: 1em; 732 font-size: 1em;
733 }
734
735 .delete-checkbox {
736 display: none;
737 }
608} 738}
609 739
610.edit-link { 740.edit-link {
611 font-size: 1.2em; 741 color: $blue;
612 color: #0b5ea6; 742 font-size: 1.2em;
613} 743}
614 744
615.delete-link { 745.delete-link {
616 font-size: 1.3em; 746 color: $red !important;
617 color: #ac2925 !important; 747 font-size: 1.3em;
618} 748}
619 749
620.linklist-item-description { 750.linklist-item-description {
621 position: relative; 751 position: relative;
622 padding: 0 10px; 752 padding: 0 10px;
623 word-wrap: break-word; 753 line-height: 1.3em;
624 color: #252525; 754 color: $dark-grey;
625 line-height: 1.3em; 755 word-wrap: break-word;
626}
627 756
628.linklist-item-description a { 757 a {
629 text-decoration: none; 758 text-decoration: none;
630 color: #1b926c; 759 color: $main-green;
631}
632 760
633.linklist-item-description a:hover { 761 &:hover {
634 color: #252525; 762 color: $dark-grey;
635} 763 }
636 764
637.linklist-item-description a:visited { 765 &:visited {
638 color: #14553f; 766 color: $dark-green;
767 }
768 }
639} 769}
640 770
641.linklist-item-thumbnail { 771.linklist-item-thumbnail {
642 position: relative; 772 position: relative;
643 padding: 0 0 0 5px; 773 float: right;
644 margin: 0; 774 z-index: 50;
645 float: right; 775 margin: 0;
646 z-index: 50; 776 padding: 0 0 0 5px;
647 height: 90px; 777 height: 90px;
648}
649
650.linklist-item.private .linklist-item-title::before,
651.linklist-item.private .linklist-item-description::before {
652 position: absolute;
653 left: 3px;
654 top: 0;
655 display: block;
656 content:"";
657 background: #F89406;
658 height: 96%;
659 width: 2px;
660 z-index: 1;
661}
662
663.linklist-item.private .linklist-item-description::before {
664 height: 100%;
665}
666
667.linklist-item.private .linklist-item-title::before {
668 margin-top: 3px;
669} 778}
670 779
671.linklist-item-infos { 780.linklist-item-infos {
672 padding: 4px 8px 4px 8px; 781 background: $background-linklist-info;
673 background: #ddd; 782 padding: 4px 8px;
674 color: #252525; 783 color: $dark-grey;
675}
676 784
677.linklist-item-infos a { 785 a {
678 color: #252525;
679 text-decoration: none; 786 text-decoration: none;
680} 787 color: $dark-grey;
681 788
682.linklist-item-infos a:hover { 789 &:hover {
683 color: #000; 790 color: $black;
684} 791 }
792 }
685 793
686.linklist-item-infos .linklist-item-tags { 794 .linklist-item-tags {
687 font-size: 0.8em; 795 font-size: .8em;
688} 796 }
689 797
690.linklist-item-infos .label-tag { 798 .label-tag {
691 font-size: 1em; 799 font-size: 1em;
800 }
801
802 .mobile-buttons {
803 text-align: right;
804 }
805
806 .linklist-plugin-icon {
807 display: inline-block;
808 margin: 0 2px;
809 width: 16px;
810 height: 16px;
811 }
692} 812}
693 813
694.linklist-item-infos-dateblock { 814.linklist-item-infos-dateblock {
695 font-size: 0.9em; 815 font-size: .9em;
696} 816}
697 817
698.linklist-plugin-icon { 818.linklist-plugin-icon {
699 width: 13px; 819 width: 13px;
700 height: 13px; 820 height: 13px;
701} 821}
702 822
703.linklist-item-infos-url { 823.linklist-item-infos-url {
704 text-align: right; 824 height: 23px;
705 white-space: nowrap; 825 overflow: hidden;
706 overflow: hidden; 826 text-align: right;
707 text-overflow: ellipsis; 827 text-overflow: ellipsis;
708 font-size: 0.8em; 828 line-height: 23px;
709 height:23px; 829 white-space: nowrap;
710 line-height:23px; 830 font-size: .8em;
711}
712
713.linklist-item-infos .mobile-buttons {
714 text-align: right;
715}
716
717.linklist-item-infos .linklist-plugin-icon {
718 display: inline-block;
719 margin: 0 2px;
720 width: 16px;
721 height: 16px;
722} 831}
723 832
724.linklist-item-infos-controls-group { 833.linklist-item-infos-controls-group {
725 display: inline-block; 834 display: inline-block;
726 border-right: 1px solid #5d5d5d; 835 border-right: 1px solid $light-grey;
727 padding-right: 6px; 836 padding-right: 6px;
728} 837}
729 838
730.ctrl-edit { 839.ctrl-edit {
731 margin: 0 7px; 840 margin: 0 7px;
732} 841}
733 842
734/** 64em -> lg **/ 843// 64em -> lg
735@media screen and (max-width: 64em) { 844@media screen and (max-width: 64em) {
736 .linklist-item-infos-url { 845 .linklist-item-infos-url {
737 text-align: left; 846 text-align: left;
738 } 847 }
739} 848}
740 849
741/** 850// Footer
742 * Footer 851.footer-container {
743 */ 852 margin: 20px 0;
744#footer { 853 padding: 5px;
745 margin: 20px 0; 854 text-align: center;
746 padding: 5px; 855 color: $dark-grey;
747 text-align: center;
748 color: #252525;
749}
750 856
751#footer:before { 857 &::before {
752 display: block; 858 display: block;
753 content:"";
754 background: linear-gradient(to right, #949393, #252525, #949393);
755 height: 1px;
756 width: 80%;
757 margin: 10px auto; 859 margin: 10px auto;
860 background: linear-gradient(to right, $background-color, $dark-grey, $background-color);
861 width: 80%;
862 height: 1px;
863 content: '';
864 }
865
866 a {
867 color: $dark-grey;
868 }
869}
870
871// PAGE FORM
872%page-form-input {
873 margin: 10px 0;
874 border: solid 1px $form-input-border;
875 border-radius: 2px;
876 background: $form-input-background;
877 padding: 5px 5px 3px 15px;
878 width: 90%;
879 height: 35px;
880 color: $dark-grey;
881 box-sizing: border-box;
882}
883
884%page-form-button {
885 display: inline-block;
886 margin: 15px 5px;
887 border: 0;
888 box-shadow: 1px 1px 1px $form-input-border, -1px -1px 6px $form-input-border, -1px 1px 2px $form-input-border, 1px -1px 2px $form-input-border;
889 background: $main-green;
890 min-width: 150px;
891 height: 35px;
892 vertical-align: center;
893 text-decoration: none;
894 line-height: 35px;
895 color: $almost-white;
896 font-size: 1.2em;
897 font-weight: normal;
758} 898}
759 899
760#footer a {
761 color: #252525;
762}
763
764/**
765 * PAGE FORM
766 */
767.page-form { 900.page-form {
768 margin: 20px 0 0 0; 901 margin: 20px 0 0;
769 background: #f5f5f5; 902 box-shadow: 1px 1px 2px $light-grey;
770 box-shadow: 1px 1px 2px #797979; 903 background: $almost-white;
771 color: #252525; 904 overflow: hidden;
772 overflow: hidden; 905 color: $dark-grey;
773} 906
774 907 .window-title {
775.page-form .window-title { 908 margin: 0 0 10px;
776 margin: 0 0 10px 0; 909 background: $almost-white;
777 padding: 10px 0; 910 padding: 10px 0;
778 width: 100%; 911 width: 100%;
779 color: #1b926c;
780 background: #f5f5f5;
781 text-align: center; 912 text-align: center;
782} 913 color: $main-green;
914 }
783 915
784.page-form .window-subtitle { 916 .window-subtitle {
785 text-align: center; 917 text-align: center;
786} 918 }
787 919
788.page-form a { 920 a {
789 color: #1b926c;
790 font-weight: bold;
791 text-decoration: none; 921 text-decoration: none;
792} 922 color: $main-green;
923 font-weight: bold;
793 924
794.page-form p { 925 &.button {
795 padding: 5px 10px; 926 @extend %page-form-button;
927 }
928 }
929
930 p {
796 margin: 0; 931 margin: 0;
797} 932 padding: 5px 10px;
933 }
798 934
799.page-form input[type="text"], 935 input {
800.page-form input[type="password"], 936 &[type='text'] {
801.page-form textarea { 937 @extend %page-form-input;
802 box-sizing: border-box; 938
803 margin: 10px 0; 939 &::-webkit-input-placeholder {
804 padding: 5px 5px 3px 15px; 940 color: $light-grey;
805 height: 35px; 941 }
806 width: 90%; 942 }
807 background: #eeeeee; 943
808 border: solid 1px #d8d8d8; 944 &[type='password'] {
809 border-radius: 2px; 945 @extend %page-form-input;
810 color: #252525; 946
811} 947 &::-webkit-input-placeholder {
948 color: $light-grey;
949 }
950 }
951
952 &[type='submit'] {
953 @extend %page-form-button;
954 }
955 }
956
957 textarea {
958 @extend %page-form-input;
812 959
813.page-form textarea {
814 min-height: 240px;
815 padding: 15px 5px 3px 15px; 960 padding: 15px 5px 3px 15px;
961 min-height: 240px;
816 resize: vertical; 962 resize: vertical;
817 overflow-y: auto; 963 overflow-y: auto;
818 word-wrap:break-word 964 word-wrap: break-word;
819} 965 }
820 966
821/* because chrome */ 967 select {
822.page-form input[type="text"]::-webkit-input-placeholder, 968 color: $dark-grey;
823.page-form input[type="password"]::-webkit-input-placeholder { 969 }
824 color: #777777;
825}
826 970
827.page-form input[type="submit"], .page-form a.button { 971 .button {
828 margin: 15px 5px; 972 &.button-red {
829 height: 35px; 973 background: $red;
830 line-height: 35px; 974 }
831 width: 150px; 975 }
832 background: #1b926c;
833 color: #f5f5f5;
834 border: none;
835 box-shadow: 1px 1px 1px #ddd, -1px -1px 6px #ddd, -1px 1px 2px #ddd, 1px -1px 2px #ddd;
836 font-size: 1.2em;
837 text-decoration: none;
838 vertical-align: center;
839 font-weight: normal;
840 display: inline-block;
841}
842 976
977 .submit-buttons {
978 margin-bottom: 10px;
979 }
843 980
844.page-form .button.button-red { 981 section {
845 background: #ac2925; 982 margin: 10px 0 25px;
846} 983 }
847 984
848.page-form .submit-buttons { 985 table,
849 margin-bottom: 10px; 986 th,
850} 987 td {
988 border-width: 1px 0;
989 border-style: solid;
990 border-color: $light-grey;
991 }
851 992
852@media screen and (min-width: 64em) { 993 th,
853 .page-form .submit-buttons { 994 td {
854 position: relative; 995 padding: 5px;
996 }
997
998 table {
999 margin: auto;
1000 width: 90%;
1001
1002 .order {
1003 text-decoration: none;
1004 color: $dark-grey;
855 } 1005 }
1006 }
856 1007
857 .page-form .submit-buttons .button.button-red { 1008 .awesomplete {
858 position: absolute; 1009 width: 90%;
859 right: 5%; 1010
1011 input {
1012 width: 100%;
1013 }
1014 }
1015
1016 div {
1017 .awesomplete {
1018 > ul {
1019 color: $black;
1020 }
860 } 1021 }
1022 }
1023}
1024
1025@media screen and (min-width: 64em) {
1026 .page-form {
1027 .submit-buttons {
1028 position: relative;
1029
1030 .button {
1031 &.button-red {
1032 position: absolute;
1033 right: 5%;
1034 }
1035 }
1036 }
1037 }
861} 1038}
862 1039
863@media screen and (max-width: 64em) { 1040@media screen and (max-width: 64em) {
864 .page-form .submit-buttons .button { 1041 .page-form {
1042 .submit-buttons {
1043 .button {
865 display: block; 1044 display: block;
866 margin: auto; 1045 margin: auto;
1046 }
867 } 1047 }
1048 }
868} 1049}
869 1050
870.page-form select { 1051// PAGE FORM - LIGHT
871 color: #252525; 1052.page-form-light {
872} 1053 div,
873 1054 p {
874/**
875 * PAGE FORM - LIGHT
876 */
877.page-form-light div, .page-form-light p {
878 text-align: center; 1055 text-align: center;
1056 }
879} 1057}
880 1058
881/** 1059// PAGE FORM - COMPLETE
882 * PAGE FORM - COMPLETE 1060%page-form-valign {
883 */ 1061 position: absolute;
884.page-form-complete div, .page-form-complete p { 1062 top: 50%;
885 color: #252525; 1063 transform: translateY(-50%);
886} 1064}
887 1065
888.page-form-complete .form-label, .page-form-complete .form-input { 1066.page-form-complete {
1067 div,
1068 p {
1069 color: $dark-grey;
1070 }
1071
1072 .form-label,
1073 .form-input {
889 position: relative; 1074 position: relative;
890 height: 60px; 1075 height: 60px;
891} 1076 }
892 1077
893.page-form-complete .form-label label, 1078 .form-label {
894.page-form-complete .form-input input, 1079 label {
895.page-form-complete .form-input select.align, 1080 @extend %page-form-valign;
896.page-form-complete .timezone {
897 position: absolute;
898 top: 50%;
899 transform: translateY(-50%);
900}
901 1081
902.page-form-complete .form-label label { 1082 right: 0;
903 text-align: right; 1083 padding: 0 20px;
904 right: 0; 1084 text-align: right;
905 padding: 0 20px; 1085 }
906} 1086 }
907 1087
908.page-form-complete .label-name { 1088 .label-name {
909 font-weight: bold; 1089 font-weight: bold;
910} 1090 }
911
912.page-form-complete .label-desc {
913 font-size: 0.8em;
914}
915 1091
916.page-form-complete input[type="text"], 1092 .label-desc {
917.page-form-complete input[type="password"], 1093 font-size: .8em;
918.page-form-complete textarea { 1094 }
919 margin: 0;
920}
921
922.page-form section {
923 margin: 10px 0 25px 0;
924}
925 1095
926.page-form table { 1096 .form-input {
927 margin: auto; 1097 input {
928 width: 90%; 1098 @extend %page-form-valign;
929}
930 1099
931.page-form table .order { 1100 &[type='text'],
932 text-decoration: none; 1101 &[type='password'] {
933 color: #252525; 1102 margin: 0;
934} 1103 }
1104 }
935 1105
936.page-form table, .page-form th, .page-form td { 1106 select {
937 border-width: 1px 0; 1107 &.align {
938 border-style: solid; 1108 @extend %page-form-valign;
939 border-color: #aaaaaa; 1109 }
940} 1110 }
1111 }
941 1112
942.page-form th, .page-form td { 1113 textarea {
943 padding: 5px; 1114 margin: 0;
1115 }
944 1116
1117 .timezone {
1118 @extend %page-form-valign;
1119 }
945} 1120}
946 1121
947/* Awesomeplete fix */ 1122// Awesomeplete fix
948div.awesomplete { 1123div {
1124 &.awesomplete {
949 width: inherit; 1125 width: inherit;
950}
951 1126
952div.awesomplete > input { 1127 > input {
953 display: inherit; 1128 display: inherit;
954} 1129 }
955 1130
956div.awesomplete > ul { 1131 > ul {
957 z-index: 9999; 1132 z-index: 9999;
1133 }
1134 }
958} 1135}
959 1136
960.page-form .awesomplete { 1137form {
961 width: 90%; 1138 &[name='linkform'] {
1139 &.page-form {
1140 overflow: visible;
1141 }
1142 }
962} 1143}
963 1144
964.page-form .awesomplete input { 1145@media screen and (max-width: 64em) {
965 width: 100%; 1146 %page-form-valign-mobile {
966} 1147 position: inherit;
1148 top: inherit;
1149 transform: translateY(0);
1150 }
967 1151
968.page-form div.awesomplete > ul { 1152 .page-form-complete {
969 color: black; 1153 .form-label {
970} 1154 height: inherit;
971 1155
972form[name="linkform"].page-form { 1156 label {
973 overflow: visible; 1157 @extend %page-form-valign-mobile;
974}
975 1158
976@media screen and (max-width: 64em) { 1159 display: block;
977 .page-form-complete .form-label { 1160 margin: 10px 0 0;
978 height: inherit; 1161 text-align: left;
1162 }
979 } 1163 }
980 1164
981 .page-form-complete .form-label label, 1165 .form-input {
982 .page-form-complete .form-input input, 1166 text-align: center;
983 .page-form-complete .timezone {
984 position: inherit;
985 top: inherit;
986 transform: translateY(0);
987 }
988 1167
989 .page-form-complete .form-input input[type="checkbox"] { 1168 input {
990 position: absolute; 1169 @extend %page-form-valign-mobile;
991 top: 50%;
992 right: 50%;
993 transform: translateY(-50%);
994 }
995 1170
996 .page-form-complete .form-input { 1171 &[type='checkbox'] {
997 text-align: center; 1172 position: absolute;
1173 top: 50%;
1174 right: 50%;
1175 transform: translateY(-50%);
1176 }
1177 }
998 } 1178 }
999 1179
1000 .page-form-complete .form-label label { 1180 .timezone {
1001 display: block; 1181 @extend %page-form-valign-mobile;
1002 text-align: left;
1003 margin: 10px 0 0 0;
1004 } 1182 }
1005 1183
1006 .timezone-continent:after { 1184 .radio-buttons {
1007 content:"\a\a"; 1185 padding: 5px 15px;
1008 white-space: pre; 1186 text-align: left;
1009 } 1187 }
1188 }
1010 1189
1011 .page-form-complete .radio-buttons { 1190 .timezone-continent {
1012 text-align: left; 1191 &::after {
1013 padding: 5px 15px; 1192 white-space: pre;
1193 content: '\a\a';
1014 } 1194 }
1195 }
1015} 1196}
1016 1197
1017/** 1198// Page visitor (page form extended)
1018 * Page visitor (page form extended)
1019 */
1020.page-visitor { 1199.page-visitor {
1021 color: #252525; 1200 color: $dark-grey;
1022} 1201}
1023 1202
1024#page404 { 1203.page404-container {
1025 color: #3f3f3f; 1204 color: $dark-grey;
1026} 1205}
1027 1206
1028/** 1207// EDIT LINK
1029 * EDIT LINK 1208.edit-link-container {
1030 */ 1209 .created-date {
1031#editlinkform .created-date {
1032 color: #767676;
1033 margin-bottom: 10px; 1210 margin-bottom: 10px;
1211 color: $light-grey;
1212 }
1034} 1213}
1035 1214
1036/** 1215// LOGIN
1037 * LOGIN 1216.login-form-container {
1038 */ 1217 .remember-me {
1039#login-form .remember-me {
1040 margin: 5px 0; 1218 margin: 5px 0;
1219 }
1041} 1220}
1042 1221
1043/** 1222// Search results
1044 * Search results 1223.search-result {
1045 */ 1224 a {
1046.search-result a {
1047 color: white;
1048 text-decoration: none; 1225 text-decoration: none;
1049} 1226 color: $white;
1227 }
1050 1228
1051.search-result .label-tag { 1229 .label-tag {
1052 border-color: white; 1230 border-color: $white;
1053}
1054 1231
1055.search-result .label-tag .remove { 1232 .remove {
1056 border-left: white 1px solid; 1233 margin: 0 0 0 5px;
1057 padding: 0 0 0 5px; 1234 border-left: $white 1px solid;
1058 margin: 0 0 0 5px; 1235 padding: 0 0 0 5px;
1059} 1236 }
1237 }
1060 1238
1061.search-result .label-private { 1239 .label-private {
1062 border: 1px solid white; 1240 border: 1px solid $white;
1241 }
1063} 1242}
1064 1243
1065/** 1244// TOOLS
1066 * TOOLS
1067 */
1068.tools-item { 1245.tools-item {
1069 margin: 10px 0; 1246 margin: 10px 0;
1070}
1071 1247
1072.tools-item .pure-button:hover { 1248 .pure-button {
1073 background-image: none; 1249 &:hover {
1074 background-color: #1b926c; 1250 background-color: $main-green;
1075 color: #f5f5f5; 1251 background-image: none;
1252 color: $almost-white;
1253 }
1254 }
1076} 1255}
1077 1256
1078/** 1257// PLUGIN ADMIN
1079 * PLUGIN ADMIN 1258.pluginform-container {
1080 */ 1259 .mobile-row {
1081#pluginform .mobile-row { 1260 font-size: .9em;
1082 font-size: 0.9em; 1261 }
1083}
1084 1262
1085#pluginform .more { 1263 .more {
1086 margin-top: 10px; 1264 margin-top: 10px;
1265 }
1087} 1266}
1088 1267
1089@media screen and (max-width: 64em) { 1268@media screen and (max-width: 64em) {
1090 #pluginform .main-row, #pluginform .main-row td { 1269 .pluginform-container {
1091 border-bottom-style: none; 1270 .main-row {
1092 } 1271 border-top-style: none;
1272 border-bottom-style: none;
1093 1273
1094 #pluginform .mobile-row, #pluginform .mobile-row td { 1274 td {
1095 border-top-style: none; 1275 border-top-style: none;
1276 border-bottom-style: none;
1277 }
1096 } 1278 }
1279 }
1097} 1280}
1098 1281
1099/** 1282// IMPORT
1100 * IMPORT 1283.import-field-container {
1101 */ 1284 margin: 15px 0;
1102#import-field {
1103 margin: 15px 0;
1104} 1285}
1105 1286
1106/** 1287// TAG CLOUD
1107 * TAG CLOUD 1288.cloudtag-container {
1108 */ 1289 padding: 10px;
1109#cloudtag { 1290 text-align: center;
1110 padding: 10px; 1291 text-decoration: none;
1111 text-align: center; 1292 color: $dark-grey;
1112}
1113 1293
1114#cloudtag, #cloudtag a { 1294 a {
1115 color: #252525;
1116 text-decoration: none; 1295 text-decoration: none;
1117} 1296 color: $dark-grey;
1297 }
1118 1298
1119#cloudtag .count { 1299 .count {
1120 color: #7f7f7f; 1300 color: $light-grey;
1301 }
1121} 1302}
1122 1303
1123/** 1304// TAG LIST
1124 * TAG LIST 1305.taglist-container {
1125 */ 1306 padding: 0 10px;
1126#taglist {
1127 padding: 0 10px;
1128}
1129 1307
1130#taglist a { 1308 a {
1131 color: #252525;
1132 text-decoration: none; 1309 text-decoration: none;
1133} 1310 color: $dark-grey;
1311 }
1134 1312
1135#taglist .count { 1313 .count {
1136 display: inline-block; 1314 display: inline-block;
1137 width: 35px; 1315 width: 35px;
1138 text-align: right; 1316 text-align: right;
1139 color: #7f7f7f; 1317 color: $light-grey;
1140} 1318 }
1141 1319
1142#taglist .rename-tag-form { 1320 .rename-tag-form {
1143 display: none; 1321 display: none;
1144} 1322 }
1145 1323
1146#taglist .delete-tag { 1324 .delete-tag {
1147 color: #ac2925;
1148 display: none; 1325 display: none;
1149} 1326 color: $red;
1150 1327 }
1151#taglist .rename-tag { 1328
1152 color: #0b5ea6; 1329 .rename-tag {
1153} 1330 color: $blue;
1154 1331 }
1155#taglist .validate-rename-tag { 1332
1156 color: #1b926c; 1333 .validate-rename-tag {
1157} 1334 color: $main-green;
1158 1335 }
1159/** 1336}
1160 * Picture wall CSS 1337
1161 */ 1338// Picture wall CSS
1162#picwall_container { 1339.picwall-container {
1163 margin: 0 10px 10px 10px; 1340 clear: both;
1164 color: #252525; 1341 margin: 0 10px 10px;
1165 background-color: #f5f5f5; 1342 background-color: $almost-white;
1166 clear: both; 1343 color: $dark-grey;
1167} 1344}
1168 1345
1169.picwall_pictureframe { 1346.picwall-pictureframe {
1170 margin: 2px; 1347 display: table-cell;
1171 background-color: #f5f5f5; 1348 position: relative;
1172 z-index: 5; 1349 float: left;
1173 position: relative; 1350 z-index: 5;
1174 display: table-cell; 1351 margin: 2px;
1175 vertical-align: middle; 1352 background-color: $almost-white;
1176 width: 90px; 1353 width: 90px;
1177 height: 90px; 1354 height: 90px;
1178 overflow: hidden; 1355 overflow: hidden;
1179 text-align: center; 1356 vertical-align: middle;
1180 float: left; 1357 text-align: center;
1181} 1358
1182 1359 // Adapt the width of the image
1183.b-lazy { 1360 img {
1184 -webkit-transition: opacity 500ms ease-in-out;
1185 -moz-transition: opacity 500ms ease-in-out;
1186 -o-transition: opacity 500ms ease-in-out;
1187 transition: opacity 500ms ease-in-out;
1188 opacity: 0;
1189}
1190.b-lazy.b-loaded {
1191 opacity: 1;
1192}
1193
1194.picwall_pictureframe img {
1195 max-width: 100%; 1361 max-width: 100%;
1196 height: auto; 1362 height: auto;
1197 color: transparent; 1363 color: transparent;
1198} /* Adapt the width of the image */ 1364 }
1199 1365
1200.picwall_pictureframe a { 1366 a {
1201 text-decoration: none; 1367 text-decoration: none;
1202} 1368 }
1203 1369
1204/* CSS to show title when hovering an image - no javascript required. */ 1370 span {
1205.picwall_pictureframe span.info { 1371 &.info {
1206 display: none; 1372 display: none;
1207 font-family: Arial, sans-serif; 1373 font-family: Arial, sans-serif;
1374 }
1375 }
1376
1377 // CSS to show title when hovering an image - no javascript required.
1378 &:hover {
1379 span {
1380 &.info {
1381 display: block;
1382 position: absolute;
1383 top: 0;
1384 left: 0;
1385 background-color: $dark-shadow;
1386 width: 90px;
1387 height: 90px;
1388 text-align: left;
1389 color: $almost-white;
1390 font-size: 9pt;
1391 font-weight: bold;
1392 }
1393 }
1394 }
1208} 1395}
1209 1396
1210.picwall_pictureframe:hover span.info { 1397.b-lazy {
1211 display: block; 1398 transition: opacity 500ms ease-in-out;
1212 position: absolute; 1399 opacity: 0;
1213 top: 0; 1400 -webkit-transition: opacity 500ms ease-in-out;
1214 left: 0; 1401 -moz-transition: opacity 500ms ease-in-out;
1215 width: 90px; 1402 -o-transition: opacity 500ms ease-in-out;
1216 height: 90px; 1403
1217 font-weight: bold; 1404 &.b-loaded {
1218 font-size: 9pt; 1405 opacity: 1;
1219 color: #f5f5f5; 1406 }
1220 text-align: left;
1221 background-color: rgba(0, 0, 0, 0.8);
1222} 1407}
1223 1408
1224/** 1409// DAILY
1225 * DAILY
1226 */
1227.daily-desc { 1410.daily-desc {
1228 color: #7f7f7f; 1411 color: $light-grey;
1229 font-size: 0.8em; 1412 font-size: .8em;
1230}
1231 1413
1232.daily-about a { 1414 a {
1233 color: #343434;
1234 text-decoration: none; 1415 text-decoration: none;
1235} 1416 color: $dark-grey;
1236
1237.daily-about a:hover {
1238 color: #7f7f7f;
1239}
1240 1417
1241.daily-about h3:before, .daily-about h3:after { 1418 &:hover {
1242 display: block; 1419 color: $light-grey;
1243 content:""; 1420 }
1244 background: linear-gradient(to right, #d5d4d4, #252525, #d5d4d4); 1421 }
1245 height: 1px; 1422}
1246 width: 90%; 1423
1247 margin: 10px auto; 1424.daily-about {
1425 h3 {
1426 &::before,
1427 &::after {
1428 display: block;
1429 margin: 10px auto;
1430 background: linear-gradient(to right, $background-color, $dark-grey, $background-color);
1431 width: 90%;
1432 height: 1px;
1433 content: '';
1434 }
1435 }
1248} 1436}
1249 1437
1250.daily-entry { 1438.daily-entry {
1251 padding: 0 10px; 1439 padding: 0 10px;
1252}
1253 1440
1254.daily-entry .daily-entry-title:after { 1441 .daily-entry-title {
1255 display: block; 1442 margin: 10px 0 0;
1256 content:"";
1257 background: linear-gradient(to right, #fff, #515151, #fff);
1258 height: 1px;
1259 width: 70%;
1260 margin: 5px auto;
1261}
1262 1443
1263.daily-entry .daily-entry-title { 1444 a {
1264 margin: 10px 0 0 0; 1445 text-decoration: none;
1265} 1446 color: $black;
1447 }
1266 1448
1267.daily-entry .daily-entry-title a { 1449 &::after {
1268 color: #000; 1450 display: block;
1269 text-decoration: none; 1451 margin: 5px auto;
1270} 1452 background: linear-gradient(to right, $white, $light-grey, $white);
1453 width: 70%;
1454 height: 1px;
1455 content: '';
1456 }
1457 }
1271 1458
1272.daily-entry .daily-entry-description { 1459 .daily-entry-description {
1273 padding: 5px 5px 0 5px; 1460 padding: 5px 5px 0;
1274 font-size: 0.9em;
1275 text-align: justify; 1461 text-align: justify;
1462 font-size: .9em;
1276 word-wrap: break-word; 1463 word-wrap: break-word;
1277} 1464 }
1278 1465
1279.daily-entry .daily-entry-tags { 1466 .daily-entry-tags {
1280 padding: 0 5px 5px 5px; 1467 padding: 0 5px 5px;
1281 font-size: 0.8em; 1468 font-size: .8em;
1469 }
1282} 1470}
1283 1471
1284.daily-entry-thumbnail { 1472.daily-entry-thumbnail {
1285 float: left; 1473 float: left;
1286 margin: 15px 5px 5px 15px; 1474 margin: 15px 5px 5px 15px;
1287} 1475}
1288 1476
1289.daily-entry-description a { 1477.daily-entry-description {
1478 a {
1290 text-decoration: none; 1479 text-decoration: none;
1291 color: #1b926c; 1480 color: $main-green;
1292}
1293 1481
1294.daily-entry-description a:hover { 1482 &:hover {
1295 text-shadow: 1px 1px #ddd; 1483 text-shadow: 1px 1px $background-linklist-info;
1296} 1484 }
1297 1485
1298.daily-entry-description a:visited { 1486 &:visited {
1299 color: #20b988; 1487 color: $dark-green;
1488 }
1489 }
1300} 1490}
1301 1491
1302/* 1492// Fix empty bookmarklet name in Firefox
1303 * Fix empty bookmarklet name in Firefox
1304 */
1305.pure-button { 1493.pure-button {
1306 -moz-user-select: auto; 1494 -moz-user-select: auto;
1307} 1495}
1308 1496
1309.tag-sort { 1497.tag-sort {
1310 margin-top: 30px; 1498 margin-top: 30px;
1311 text-align: center; 1499 text-align: center;
1312}
1313 1500
1314.tag-sort a { 1501 a {
1315 display: inline-block; 1502 display: inline-block;
1316 margin: 0 15px; 1503 margin: 0 15px;
1317 color: white;
1318 text-decoration: none; 1504 text-decoration: none;
1505 color: $white;
1319 font-weight: bold; 1506 font-weight: bold;
1507 }
1320} 1508}
1321 1509
1322/** 1510// Markdown
1323 * Markdown 1511.markdown {
1324 */ 1512 p {
1325.markdown p {
1326 margin: 0 !important; 1513 margin: 0 !important;
1327} 1514 }
1328 1515
1329.markdown p + p { 1516 p + p {
1330 margin: 0.5em 0 0 0 !important; 1517 margin: .5em 0 0 !important;
1331} 1518 }
1332 1519
1333.markdown *:first-child { 1520 * {
1334 margin-top: 0 !important; 1521 &:first-child {
1335} 1522 margin-top: 0 !important;
1523 }
1336 1524
1337.markdown *:last-child { 1525 &:last-child {
1338 margin-bottom: 5px !important; 1526 margin-bottom: 5px !important;
1527 }
1528 }
1339} 1529}
1340 1530
1341/** 1531// Pure Button
1342 * Pure Button
1343 */
1344.pure-button-success, 1532.pure-button-success,
1345.pure-button-error, 1533.pure-button-error,
1346.pure-button-warning, 1534.pure-button-warning,
1347.pure-button-primary, 1535.pure-button-primary,
1348.pure-button-shaarli, 1536.pure-button-shaarli,
1349.pure-button-secondary { 1537.pure-button-secondary {
1350 color: white !important; 1538 border-radius: 4px;
1351 border-radius: 4px; 1539 text-shadow: 0 1px 1px $dark-shadow;
1352 text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); 1540 color: $white !important;
1353} 1541}
1354 1542
1355.pure-button-shaarli { 1543.pure-button-shaarli {
1356 background-color: #1B926C; 1544 background-color: $main-green;
1357} 1545}
diff --git a/composer.json b/composer.json
index 15e082f8..0d4c623c 100644
--- a/composer.json
+++ b/composer.json
@@ -36,7 +36,8 @@
36 "Shaarli\\Api\\Controllers\\": "application/api/controllers", 36 "Shaarli\\Api\\Controllers\\": "application/api/controllers",
37 "Shaarli\\Api\\Exceptions\\": "application/api/exceptions", 37 "Shaarli\\Api\\Exceptions\\": "application/api/exceptions",
38 "Shaarli\\Config\\": "application/config/", 38 "Shaarli\\Config\\": "application/config/",
39 "Shaarli\\Config\\Exception\\": "application/config/exception" 39 "Shaarli\\Config\\Exception\\": "application/config/exception",
40 "Shaarli\\Security\\": "application/security"
40 } 41 }
41 } 42 }
42} 43}
diff --git a/doc/md/Bookmarklet.md b/doc/md/Bookmarklet.md
deleted file mode 100644
index 6c7f1c6a..00000000
--- a/doc/md/Bookmarklet.md
+++ /dev/null
@@ -1,29 +0,0 @@
1## Add the sharing button (_bookmarklet_) to your browser
2
3- Open your Shaarli and `Login`
4- Click the `Tools` button in the top bar
5- Drag the **`✚Shaare link` button**, and drop it to your browser's bookmarks bar.
6
7_This bookmarklet button is compatible with Firefox, Opera, Chrome and Safari. Under Opera, you can't drag'n drop the button: You have to right-click on it and add a bookmark to your personal toolbar._
8
9![](images/bookmarklet.png)
10
11## Share links using the _bookmarklet_
12
13- When you are visiting a webpage you would like to share with Shaarli, click the _bookmarklet_ you just added.
14- A window opens.
15 - You can freely edit title, description, tags... to find it later using the text search or tag filtering.
16 - You will be able to edit this link later using the ![](images/edit_icon.png) edit button.
17 - You can also check the “Private” box so that the link is saved but only visible to you.
18- Click `Save`.**Voilà! Your link is now shared.**
19
20## Troubleshooting: The bookmarklet doesn't work with a few websites (e.g. Github.com)
21
22Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunatly, there is nothing Shaarli can do about it.
23
24See [#196](https://github.com/shaarli/Shaarli/issues/196).
25
26There is an open bug for both Firefox and Chromium:
27
28- https://bugzilla.mozilla.org/show_bug.cgi?id=866522
29- https://code.google.com/p/chromium/issues/detail?id=233903
diff --git a/doc/md/Community-&-Related-software.md b/doc/md/Community-&-Related-software.md
index 207153b6..7c570acf 100644
--- a/doc/md/Community-&-Related-software.md
+++ b/doc/md/Community-&-Related-software.md
@@ -38,9 +38,11 @@ See [Theming](Theming) for a list of community-contributed themes, and an instal
38- [ShaarliOS](https://github.com/mro/ShaarliOS) - Apple iOS share extension. 38- [ShaarliOS](https://github.com/mro/ShaarliOS) - Apple iOS share extension.
39- [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider 39- [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider
40- [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add links directly into your Shaarli 40- [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add links directly into your Shaarli
41- [Stakali for Android](https://stakali.toneiv.eu) - Stakali is a personal bookmark manager which synchronizes with Shaarli
41 42
42### Browser addons 43### Browser addons
43 * [Shaarli Web Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli. 44- [Shaarli Firefox Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli.
45- [Shaarli Chrome Extension](https://github.com/octplane/Shiny-Shaarli) - toolbar button to share your current tab with Shaarli.
44 46
45### Server apps 47### Server apps
46- [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content 48- [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content
diff --git a/doc/md/Firefox-share.md b/doc/md/Firefox-share.md
deleted file mode 100644
index 9a46b185..00000000
--- a/doc/md/Firefox-share.md
+++ /dev/null
@@ -1,20 +0,0 @@
1| Note | Firefox Share is no longer available for Firefox 57 and later versions. |
2|---------|---------|
3
4### Add Shaarli as a sharing service to Firefox
5
6- Open your Shaarli and `Login`
7- Click the `Tools` button in the top bar
8- Click the `✚Add to Firefox social` button and accept the activation.
9
10
11### Sharing links using Firefox share
12
13- Add the sharing service as described above
14- When you are visiting a webpage you would like to share with Shaarli,
15 click the Firefox _Share_ button [images/firefoxshare.png](images/firefoxshare.png)
16- You can edit your link before and after saving, just like the bookmarklet above.
17
18_Your Shaarli instance must be hosted on an HTTPS (SSL/TLS secure connection)
19enabled server for Firefox Share to work. Firefox Share will not work over
20plain HTTP connections._
diff --git a/doc/md/Sharing-content.md b/doc/md/Sharing-content.md
new file mode 100644
index 00000000..faacc1f9
--- /dev/null
+++ b/doc/md/Sharing-content.md
@@ -0,0 +1,88 @@
1Content posted to Shaarli is separated in items called _Shaares_. For each Shaare,
2you can customize the following aspects:
3
4 * URL to link to
5 * Title
6 * Free-text description
7 * Tags
8 * Public/private status
9
10--------------------------------------------------------------------------------
11
12## Adding new Shaares
13
14While logged in to your Shaarli, you can add new Shaares in several ways:
15
16 * [+Shaare button]
17 * [Bookmarklet]
18 * [Firefox Share](#firefox-share)
19 * Third-party [apps and browser addons](Community-\&-Related-software.md#mobile-apps)
20 * [REST API](https://shaarli.github.io/api-documentation/)
21
22### +Shaare button
23
24 * While logged in to your Shaarli, click the **`+Shaare`** button located in the toolbar.
25 * Enter the URL of a link you want to share.
26 * Click `Add link`
27 * The `New Shaare` dialog appears, allowing you to fill in the details of your Shaare.
28 * The Description, Title, and Tags will help you find your Shaare later using tags or full-text search.
29 * You can also check the “Private” box so that the link is saved but only visible to you (the logged-in user).
30 * Click `Save`.
31
32<!-- TODO Add screenshot of add/edit link dialog -->
33
34### Bookmarklet
35
36The _Bookmarklet_ \[[1](https://en.wikipedia.org/wiki/Bookmarklet)\] is a special
37browser bookmark you can use to add new content to your Shaarli. This bookmarklet is
38compatible with Firefox, Opera, Chrome and Safari. To set it up:
39
40 * Access the `Tools` page from the button in the toolbar.
41 * Drag the **`✚Shaare link` button** to your browser's bookmarks bar.
42
43Once this is done, you can shaare any URL you are visiting simply by clicking the
44bookmarklet in your browser! The same `New Shaare` dialog as above is displayed.
45
46| Note | Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunately, there is nothing Shaarli can do about it. \[[1](https://github.com/shaarli/Shaarli/issues/196)]\ \[[2](https://bugzilla.mozilla.org/show_bug.cgi?id=866522)]\ \[[3](https://code.google.com/p/chromium/issues/detail?id=233903)]\ |
47|---------|---------|
48
49| Note | Under Opera, you can't drag'n drop the button: You have to right-click on it and add a bookmark to your personal toolbar. |
50|---------|---------|
51
52![](images/bookmarklet.png)
53
54
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--------------------------------------------------------------------------------
72
73## Editing Shaares
74
75Any Shaare can edited by clicking its ![](images/edit_icon.png) `Edit` button.
76
77Editing a Shaare will not change it's permalink, each permalink always points to the
78latest revision of a Shaare.
79
80--------------------------------------------------------------------------------
81
82## Using shaarli as a blog, notepad, pastebin...
83
84While adding or editing a link, leave the URL field blank to create a text-only
85("note") post. This allows you to post any kind of text content, such as blog
86articles, private or public notes, snippets... There is no character limit! You can
87access your Shaare from its permalink.
88
diff --git a/doc/md/images/doc-logo.png b/doc/md/images/doc-logo.png
index 3d8d1787..3da7ba57 100644
--- a/doc/md/images/doc-logo.png
+++ b/doc/md/images/doc-logo.png
Binary files differ
diff --git a/doc/md/images/firefoxshare.png b/doc/md/images/firefoxshare.png
index 98c2fdd3..8f8fdba4 100644
--- a/doc/md/images/firefoxshare.png
+++ b/doc/md/images/firefoxshare.png
Binary files differ
diff --git a/doc/md/images/install-shaarli.png b/doc/md/images/install-shaarli.png
index 7ae33816..d5d5baa7 100644
--- a/doc/md/images/install-shaarli.png
+++ b/doc/md/images/install-shaarli.png
Binary files differ
diff --git a/doc/md/images/rss-filter-1.png b/doc/md/images/rss-filter-1.png
index d2a03f67..0cf1591c 100644
--- a/doc/md/images/rss-filter-1.png
+++ b/doc/md/images/rss-filter-1.png
Binary files differ
diff --git a/doc/md/images/rss-filter-2.png b/doc/md/images/rss-filter-2.png
index 538b126e..5a40755a 100644
--- a/doc/md/images/rss-filter-2.png
+++ b/doc/md/images/rss-filter-2.png
Binary files differ
diff --git a/doc/md/index.md b/doc/md/index.md
index e77b4d3a..224070cb 100644
--- a/doc/md/index.md
+++ b/doc/md/index.md
@@ -94,13 +94,6 @@ Easily extensible by any client using the REST API exposed by Shaarli.
94 94
95See the [API documentation](http://shaarli.github.io/api-documentation/). 95See the [API documentation](http://shaarli.github.io/api-documentation/).
96 96
97### Using Shaarli as a blog, notepad, pastebin...
98- Go to your Shaarli setup and log in
99- Click the `Add Link` button
100- To share text only, do not enter any URL in the corresponding input field and click `Add Link`
101- Pick a title and enter your article, or note, in the description field; add a few tags; optionally check `Private` then click `Save`
102- Voilà! Your article is now published (privately if you selected that option) and accessible using its permalink.
103
104## About 97## About
105### Shaarli community fork 98### Shaarli community fork
106This friendly fork is maintained by the Shaarli community at https://github.com/shaarli/Shaarli 99This friendly fork is maintained by the Shaarli community at https://github.com/shaarli/Shaarli
diff --git a/inc/languages/de/LC_MESSAGES/shaarli.po b/inc/languages/de/LC_MESSAGES/shaarli.po
new file mode 100644
index 00000000..34d29ce8
--- /dev/null
+++ b/inc/languages/de/LC_MESSAGES/shaarli.po
@@ -0,0 +1,1313 @@
1msgid ""
2msgstr ""
3"Project-Id-Version: Shaarli\n"
4"Report-Msgid-Bugs-To: \n"
5"POT-Creation-Date: 2018-03-31 09:09+0200\n"
6"PO-Revision-Date: 2018-03-31 09:12+0200\n"
7"Last-Translator: \n"
8"Language-Team: Shaarli\n"
9"Language: de\n"
10"MIME-Version: 1.0\n"
11"Content-Type: text/plain; charset=UTF-8\n"
12"Content-Transfer-Encoding: 8bit\n"
13"X-Generator: Poedit 2.0.6\n"
14"X-Poedit-Basepath: ../../../..\n"
15"Plural-Forms: nplurals=2; plural=(n != 1);\n"
16"X-Poedit-SourceCharset: UTF-8\n"
17"X-Poedit-KeywordsList: t:1,2;t\n"
18"X-Poedit-SearchPath-0: .\n"
19"X-Poedit-SearchPathExcluded-0: node_modules\n"
20"X-Poedit-SearchPathExcluded-1: vendor\n"
21
22#: application/ApplicationUtils.php:153
23#, php-format
24msgid ""
25"Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus "
26"cannot run. Your PHP version has known security vulnerabilities and should "
27"be updated as soon as possible."
28msgstr ""
29"Deine PHP-Version ist veraltet! Shaarli benötigt mindestens PHP %s, und kann "
30"daher nicht laufen. Deine PHP-Version hat bekannte Sicherheitslücken und "
31"sollte so bald wie möglich aktualisiert werden."
32
33#: application/ApplicationUtils.php:183 application/ApplicationUtils.php:195
34msgid "directory is not readable"
35msgstr "Verzeichnis ist nicht lesbar"
36
37#: application/ApplicationUtils.php:198
38msgid "directory is not writable"
39msgstr "Verzeichnis ist nicht beschreibbar"
40
41#: application/ApplicationUtils.php:216
42msgid "file is not readable"
43msgstr "Datei ist nicht lesbar"
44
45#: application/ApplicationUtils.php:219
46msgid "file is not writable"
47msgstr "Datei ist nicht beschreibbar"
48
49#: application/Cache.php:16
50#, php-format
51msgid "Cannot purge %s: no directory"
52msgstr "Kann nicht löschen, %s ist kein Verzeichnis"
53
54#: application/FeedBuilder.php:151
55msgid "Direct link"
56msgstr "Direct Link"
57
58#: application/FeedBuilder.php:153
59#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88
60#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:178
61msgid "Permalink"
62msgstr "Permalink"
63
64#: application/History.php:174
65msgid "History file isn't readable or writable"
66msgstr "Protokolldatei nicht lesbar oder beschreibbar"
67
68#: application/History.php:185
69msgid "Could not parse history file"
70msgstr "Protokolldatei konnte nicht analysiert werden"
71
72#: application/Languages.php:177
73msgid "Automatic"
74msgstr "Automatisch"
75
76#: application/Languages.php:178
77msgid "English"
78msgstr "Englisch"
79
80#: application/Languages.php:179
81msgid "French"
82msgstr "Französisch"
83
84#: application/Languages.php:180
85msgid "German"
86msgstr "Deutsch"
87
88#: application/LinkDB.php:136
89msgid "You are not authorized to add a link."
90msgstr "Du bist nicht berechtigt einen Link hinzuzufügen."
91
92#: application/LinkDB.php:139
93msgid "Internal Error: A link should always have an id and URL."
94msgstr "Interner Fehler: Ein Link sollte immer eine ID und URL haben."
95
96#: application/LinkDB.php:142
97msgid "You must specify an integer as a key."
98msgstr "Du musst eine Ganzzahl als Schlüssel angeben."
99
100#: application/LinkDB.php:145
101msgid "Array offset and link ID must be equal."
102msgstr "Array-Offset und Link-ID müssen gleich sein."
103
104#: application/LinkDB.php:251
105#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
106#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
107#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14
108#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
109msgid ""
110"The personal, minimalist, super-fast, database free, bookmarking service"
111msgstr ""
112"Der persönliche, minimalistische, superschnelle, datenbankfreie "
113"Lesezeichenservice"
114
115#: application/LinkDB.php:253
116msgid ""
117"Welcome to Shaarli! This is your first public bookmark. To edit or delete "
118"me, you must first login.\n"
119"\n"
120"To learn how to use Shaarli, consult the link \"Documentation\" at the "
121"bottom of this page.\n"
122"\n"
123"You use the community supported version of the original Shaarli project, by "
124"Sebastien Sauvage."
125msgstr ""
126"Willkommen bei Shaarli! Dies ist dein erstes öffentliches Lesezeichen. Um "
127"mich zu bearbeiten oder zu löschen, musst du dich zuerst einloggen.\n"
128"\n"
129"Um zu erfahren, wie man Shaarli benutzt, öffne den Link \"Dokumentation\" am "
130"Ende dieser Seite.\n"
131"\n"
132"Du verwendest die von der Community unterstützte Version des ursprünglichen "
133"Shaarli-Projekts von Sebastien Sauvage."
134
135#: application/LinkDB.php:267
136msgid "My secret stuff... - Pastebin.com"
137msgstr "Meine geheimen Sachen... - Pastebin.com"
138
139#: application/LinkDB.php:269
140msgid "Shhhh! I'm a private link only YOU can see. You can delete me too."
141msgstr ""
142"Pssst Ich bin ein privater Link, den nur du sehen kannst. Du kannst mich "
143"auch löschen."
144
145#: application/LinkFilter.php:452
146msgid "The link you are trying to reach does not exist or has been deleted."
147msgstr ""
148"Den Link, den du versucht zu erreichen, existiert nicht oder wurde gelöscht."
149
150#: application/NetscapeBookmarkUtils.php:35
151msgid "Invalid export selection:"
152msgstr "Ungültige Exportauswahl:"
153
154#: application/NetscapeBookmarkUtils.php:81
155#, php-format
156msgid "File %s (%d bytes) "
157msgstr "Datei %s (%d bytes) "
158
159#: application/NetscapeBookmarkUtils.php:83
160msgid "has an unknown file format. Nothing was imported."
161msgstr "hat ein unbekanntes Dateiformat. Es wurde nichts importiert."
162
163#: application/NetscapeBookmarkUtils.php:86
164#, php-format
165msgid ""
166"was successfully processed in %d seconds: %d links imported, %d links "
167"overwritten, %d links skipped."
168msgstr ""
169"wurde erfolgreich in %d Sekunden verarbeitet: %d Links importiert, %d Links "
170"überschrieben, %d Links übersprungen."
171
172#: application/PageBuilder.php:168
173msgid "The page you are trying to reach does not exist or has been deleted."
174msgstr ""
175"Die Seite, die du erreichen möchtest, existiert nicht oder wurde gelöscht."
176
177#: application/PageBuilder.php:170
178msgid "404 Not Found"
179msgstr "404 Nicht gefunden"
180
181#: application/PluginManager.php:243
182#, php-format
183msgid "Plugin \"%s\" files not found."
184msgstr "Plugin \"%s\" Dateien nicht gefunden."
185
186#: application/Updater.php:76
187msgid "Couldn't retrieve Updater class methods."
188msgstr "Die Updater-Klassenmethoden konnten nicht abgerufen werden."
189
190#: application/Updater.php:532
191msgid "An error occurred while running the update "
192msgstr "Beim Ausführen des Updates ist ein Fehler aufgetreten "
193
194#: application/Updater.php:572
195msgid "Updates file path is not set, can't write updates."
196msgstr ""
197"Der Update-Dateipfad ist nicht festgelegt, es können keine Updates "
198"geschrieben werden."
199
200#: application/Updater.php:577
201msgid "Unable to write updates in "
202msgstr "Es ist nicht möglich Updates zu schreiben in "
203
204#: application/Utils.php:376 tests/UtilsTest.php:340
205msgid "Setting not set"
206msgstr "Einstellung nicht gesetzt"
207
208#: application/Utils.php:383 tests/UtilsTest.php:338 tests/UtilsTest.php:339
209msgid "Unlimited"
210msgstr "Unbegrenzt"
211
212#: application/Utils.php:386 tests/UtilsTest.php:335 tests/UtilsTest.php:336
213#: tests/UtilsTest.php:350
214msgid "B"
215msgstr "B"
216
217#: application/Utils.php:386 tests/UtilsTest.php:329 tests/UtilsTest.php:330
218#: tests/UtilsTest.php:337
219msgid "kiB"
220msgstr "kiB"
221
222#: application/Utils.php:386 tests/UtilsTest.php:331 tests/UtilsTest.php:332
223#: tests/UtilsTest.php:348 tests/UtilsTest.php:349
224msgid "MiB"
225msgstr "MiB"
226
227#: application/Utils.php:386 tests/UtilsTest.php:333 tests/UtilsTest.php:334
228msgid "GiB"
229msgstr "GiB"
230
231#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:121
232msgid ""
233"Shaarli could not create the config file. Please make sure Shaarli has the "
234"right to write in the folder is it installed in."
235msgstr ""
236"Shaarli konnte die Konfigurationsdatei nicht erstellen. Bitte stelle sicher, "
237"dass Shaarli das Recht hat, in den Ordner zu schreiben, in dem es "
238"installiert ist."
239
240#: application/config/ConfigManager.php:135
241msgid "Invalid setting key parameter. String expected, got: "
242msgstr ""
243"Ungültiger Parameter für den Einstellungsschlüssel. Zeichenfolge erwartet, "
244"erhalten: "
245
246#: application/config/exception/MissingFieldConfigException.php:21
247#, php-format
248msgid "Configuration value is required for %s"
249msgstr "Konfigurationswert erforderlich für %s"
250
251#: application/config/exception/PluginConfigOrderException.php:15
252msgid "An error occurred while trying to save plugins loading order."
253msgstr ""
254"Beim Versuch, die Ladereihenfolge der Plugins zu speichern, ist ein Fehler "
255"aufgetreten."
256
257#: application/config/exception/UnauthorizedConfigException.php:16
258msgid "You are not authorized to alter config."
259msgstr "Du bist nicht berechtigt, die Konfiguration zu ändern."
260
261#: application/exceptions/IOException.php:19
262msgid "Error accessing"
263msgstr "Fehler beim Zugriff"
264
265#: index.php:142
266msgid "Shared links on "
267msgstr "Geteilte Links auf "
268
269#: index.php:164
270msgid "Insufficient permissions:"
271msgstr "Unzureichende Berechtigungen:"
272
273#: index.php:303
274msgid "I said: NO. You are banned for the moment. Go away."
275msgstr "Ich sagte NEIN. Du bist für den Moment gesperrt. Verschwinde."
276
277#: index.php:368
278msgid "Wrong login/password."
279msgstr "Falscher Loging/Passwort."
280
281#: index.php:576 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
282#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:42
283msgid "Daily"
284msgstr "Täglich"
285
286#: index.php:681 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
287#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
288#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71
289#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:95
290#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:71
291#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:95
292msgid "Login"
293msgstr "Einloggen"
294
295#: index.php:722 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
296#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:39
297msgid "Picture wall"
298msgstr "Bildwand"
299
300#: index.php:770 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
301#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36
302#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
303msgid "Tag cloud"
304msgstr "Tag Cloud"
305
306#: index.php:803 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
307msgid "Tag list"
308msgstr "Tag Liste"
309
310#: index.php:1028 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
311#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31
312msgid "Tools"
313msgstr "Tools"
314
315#: index.php:1037
316msgid "You are not supposed to change a password on an Open Shaarli."
317msgstr "Du darfst kein Passwort für ein offenes Shaarli ändern."
318
319#: index.php:1042 index.php:1084 index.php:1160 index.php:1191 index.php:1291
320msgid "Wrong token."
321msgstr "Falsches Zeichen."
322
323#: index.php:1047
324msgid "The old password is not correct."
325msgstr "Das alte Passwort ist nicht korrekt."
326
327#: index.php:1067
328msgid "Your password has been changed"
329msgstr "Dein Passwort wurde geändert"
330
331#: index.php:1072
332#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
333#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
334msgid "Change password"
335msgstr "Passwort ändern"
336
337#: index.php:1120
338msgid "Configuration was saved."
339msgstr "Konfiguration wurde gespeichert."
340
341#: index.php:1143 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
342msgid "Configure"
343msgstr "Konfigurieren"
344
345#: index.php:1154 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
346#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
347msgid "Manage tags"
348msgstr "Tags verwalten"
349
350#: index.php:1172
351#, php-format
352msgid "The tag was removed from %d link."
353msgid_plural "The tag was removed from %d links."
354msgstr[0] "Der Tag wurde aus dem Link %d entfernt."
355msgstr[1] "Der Tag wurde aus den Links %d entfernt."
356
357#: index.php:1173
358#, php-format
359msgid "The tag was renamed in %d link."
360msgid_plural "The tag was renamed in %d links."
361msgstr[0] "Der Tag wurde im Link %d umbenannt."
362msgstr[1] "Der Tag wurde in den Links %d umbenannt."
363
364#: index.php:1181 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
365msgid "Shaare a new link"
366msgstr "Teile einen neuen Link"
367
368#: index.php:1351 tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
369#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
370msgid "Edit"
371msgstr "Bearbeiten"
372
373#: index.php:1351 index.php:1421
374#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
375#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
376#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26
377msgid "Shaare"
378msgstr "Teilen"
379
380#: index.php:1390
381msgid "Note: "
382msgstr "Notiz: "
383
384#: index.php:1430 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
385msgid "Export"
386msgstr "Exportieren"
387
388#: index.php:1492 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
389msgid "Import"
390msgstr "Importieren"
391
392#: index.php:1502
393#, php-format
394msgid ""
395"The file you are trying to upload is probably bigger than what this "
396"webserver can accept (%s). Please upload in smaller chunks."
397msgstr ""
398"Die Datei, die du hochladen möchtest, ist wahrscheinlich größer als das, was "
399"dieser Webserver akzeptieren kann (%s). Bitte lade in kleineren Blöcken hoch."
400
401#: index.php:1541 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
402#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
403msgid "Plugin administration"
404msgstr "Plugin Adminstration"
405
406#: index.php:1706
407msgid "Search: "
408msgstr "Suche: "
409
410#: index.php:1933
411#, php-format
412msgid ""
413"<pre>Sessions do not seem to work correctly on your server.<br>Make sure the "
414"variable \"session.save_path\" is set correctly in your PHP config, and that "
415"you have write access to it.<br>It currently points to %s.<br>On some "
416"browsers, accessing your server via a hostname like 'localhost' or any "
417"custom hostname without a dot causes cookie storage to fail. We recommend "
418"accessing your server via it's IP address or Fully Qualified Domain Name.<br>"
419msgstr ""
420"<pre>Sessions scheinen auf deinem Server nicht korrekt zu funktionieren. "
421"<br>Stelle sicher, dass die Variable \"session.save_path\" in deiner PHP-"
422"Konfiguration richtig eingestellt ist und dass du Schreibzugriff darauf hast."
423"<br>Es verweist aktuell auf %s.<br>Bei einigen Browsern führt der Zugriff "
424"auf deinen Server über einen Hostnamen wie \"localhost\" oder einen "
425"beliebigen benutzerdefinierten Hostnamen ohne Punkt dazu, dass der Cookie-"
426"Speicher fehlschlägt. Wir empfehlen den Zugriff auf deinen Server über die "
427"IP-Adresse oder den Fully Qualified Domain Namen.<br>"
428
429#: index.php:1943
430msgid "Click to try again."
431msgstr "Klicke um es erneut zu versuchen."
432
433#: plugins/addlink_toolbar/addlink_toolbar.php:29
434msgid "URI"
435msgstr "URI"
436
437#: plugins/addlink_toolbar/addlink_toolbar.php:33
438#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
439msgid "Add link"
440msgstr "Link hinzufügen"
441
442#: plugins/addlink_toolbar/addlink_toolbar.php:50
443msgid "Adds the addlink input on the linklist page."
444msgstr "Fügt die Link-hinzufügen-Eingabe auf der Linkliste hinzu."
445
446#: plugins/archiveorg/archiveorg.php:23
447msgid "View on archive.org"
448msgstr "Auf archive.org ansehen"
449
450#: plugins/archiveorg/archiveorg.php:36
451msgid "For each link, add an Archive.org icon."
452msgstr "Füge für jeden Link ein Archive.org Symbol hinzu."
453
454#: plugins/demo_plugin/demo_plugin.php:465
455msgid ""
456"A demo plugin covering all use cases for template designers and plugin "
457"developers."
458msgstr ""
459"Ein Demo-Plugin, das alle Anwendungsfälle für Template-Designer und Plugin-"
460"Entwickler abdeckt."
461
462#: plugins/isso/isso.php:20
463msgid ""
464"Isso plugin error: Please define the \"ISSO_SERVER\" setting in the plugin "
465"administration page."
466msgstr ""
467"Isso Plugin Fehler: Bitte definiere die Einstellung \"ISSO_SERVER\" auf der "
468"Plugin-Administrationsseite."
469
470#: plugins/isso/isso.php:63
471msgid "Let visitor comment your shaares on permalinks with Isso."
472msgstr ""
473"Lassen Sie Besucher ihre geteilten Links auf Permalinks mit Isso "
474"kommentieren."
475
476#: plugins/isso/isso.php:64
477msgid "Isso server URL (without 'http://')"
478msgstr "Isso Server URL (ohne 'http://')"
479
480#: plugins/markdown/markdown.php:158
481msgid "Description will be rendered with"
482msgstr "Die Beschreibung wird dargestellt mit"
483
484#: plugins/markdown/markdown.php:159
485msgid "Markdown syntax documentation"
486msgstr "Markdown Syntax Dokumentation"
487
488#: plugins/markdown/markdown.php:160
489msgid "Markdown syntax"
490msgstr "Markdown Syntax"
491
492#: plugins/markdown/markdown.php:339
493msgid ""
494"Render shaare description with Markdown syntax.<br><strong>Warning</"
495"strong>:\n"
496"If your shaared descriptions contained HTML tags before enabling the "
497"markdown plugin,\n"
498"enabling it might break your page.\n"
499"See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/"
500"markdown#html-rendering\">README</a>."
501msgstr ""
502"Übertrage Teilen Beschreibung mit Markdown-Syntax.<br><strong>Warnung</"
503"strong>:\n"
504"Wenn deine Teilen Beschreibungen HTML-Tags enthielten, bevor das Markdown-"
505"Plugin aktiviert wurde,\n"
506"kann es deine Seite beschädigen, solltest du es aktivieren.\n"
507"Weitere Informationen findest du in der <a href=\"https://github.com/shaarli/"
508"Shaarli/tree/master/plugins/markdown#html-rendering\">README</a>."
509
510#: plugins/piwik/piwik.php:21
511msgid ""
512"Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin "
513"administration page."
514msgstr ""
515"Piwik-Plugin-Fehler: Bitte definiere die PIWIK_URL und PIWIK_SITEID auf der "
516"Plugin-Administrationsseite."
517
518#: plugins/piwik/piwik.php:70
519msgid "A plugin that adds Piwik tracking code to Shaarli pages."
520msgstr ""
521"Ein Plugin, das einen Piwik-Tracking-Code auf Shaarli-Seiten hinzufügt."
522
523#: plugins/piwik/piwik.php:71
524msgid "Piwik URL"
525msgstr "Piwik URL"
526
527#: plugins/piwik/piwik.php:72
528msgid "Piwik site ID"
529msgstr "Piwik site ID"
530
531#: plugins/playvideos/playvideos.php:22
532msgid "Video player"
533msgstr "Videoplayer"
534
535#: plugins/playvideos/playvideos.php:25
536msgid "Play Videos"
537msgstr "Videos abspielen"
538
539#: plugins/playvideos/playvideos.php:56
540msgid "Add a button in the toolbar allowing to watch all videos."
541msgstr ""
542"Fügt eine Schaltfläche in der Symbolleiste hinzu, mit der man alle Videos "
543"ansehen kann."
544
545#: plugins/playvideos/youtube_playlist.js:214
546msgid "plugins/playvideos/jquery-1.11.2.min.js"
547msgstr "plugins/playvideos/jquery-1.11.2.min.js"
548
549#: plugins/pubsubhubbub/pubsubhubbub.php:69
550#, php-format
551msgid "Could not publish to PubSubHubbub: %s"
552msgstr "Veröffentlichung auf PubSubHubbub nicht möglich: %s"
553
554#: plugins/pubsubhubbub/pubsubhubbub.php:95
555#, php-format
556msgid "Could not post to %s"
557msgstr "Kann nicht posten auf %s"
558
559#: plugins/pubsubhubbub/pubsubhubbub.php:99
560#, php-format
561msgid "Bad response from the hub %s"
562msgstr "Ungültige Antwort vom Hub %s"
563
564#: plugins/pubsubhubbub/pubsubhubbub.php:110
565msgid "Enable PubSubHubbub feed publishing."
566msgstr "Aktiviere PubSubHubbub Feed Veröffentlichung."
567
568#: plugins/qrcode/qrcode.php:69 plugins/wallabag/wallabag.php:68
569msgid "For each link, add a QRCode icon."
570msgstr "Für jeden Link, füge eine QRCode Icon hinzu."
571
572#: plugins/wallabag/wallabag.php:21
573msgid ""
574"Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the "
575"plugin administration page."
576msgstr ""
577"Wallabag Plugin Fehler: Bitte definiere die Einstellung \"WALLABAG_URL\" auf "
578"der Plugin Administrationsseite."
579
580#: plugins/wallabag/wallabag.php:47
581msgid "Save to wallabag"
582msgstr "Auf Wallabag speichern"
583
584#: plugins/wallabag/wallabag.php:69
585msgid "Wallabag API URL"
586msgstr "Wallabag API URL"
587
588#: plugins/wallabag/wallabag.php:70
589msgid "Wallabag API version (1 or 2)"
590msgstr "Wallabag API version (1 oder 2)"
591
592#: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227
593#: tests/languages/fr/LanguagesFrTest.php:160
594#: tests/languages/fr/LanguagesFrTest.php:173
595#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81
596#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:81
597msgid "Search"
598msgid_plural "Search"
599msgstr[0] "Suche"
600msgstr[1] "Suchen"
601
602#: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
603msgid "Sorry, nothing to see here."
604msgstr "Entschuldige, hier gibt es nichts zu sehen."
605
606#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
607msgid "URL or leave empty to post a note"
608msgstr "URL oder leer lassen um eine Notiz hinzuzufügen"
609
610#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
611msgid "Current password"
612msgstr "Aktuelles Passwort"
613
614#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
615msgid "New password"
616msgstr "Neues Passwort"
617
618#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
619msgid "Change"
620msgstr "Wechseln"
621
622#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
623#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
624msgid "Tag"
625msgstr "Tag"
626
627#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
628msgid "New name"
629msgstr "Neuer Name"
630
631#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
632msgid "Case sensitive"
633msgstr "Groß- / Kleinschreibung-unterscheidend"
634
635#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
636msgid "Rename"
637msgstr "Umbenennen"
638
639#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
640#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79
641#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:172
642msgid "Delete"
643msgstr "Löschen"
644
645#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
646msgid "You can also edit tags in the"
647msgstr "Du kannst auch Tags bearbeiten in der"
648
649#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
650msgid "tag list"
651msgstr "Tag Liste"
652
653#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
654msgid "title"
655msgstr "Titel"
656
657#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
658msgid "Home link"
659msgstr "Home Link"
660
661#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
662msgid "Default value"
663msgstr "Standardwert"
664
665#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
666msgid "Theme"
667msgstr "Thema"
668
669#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
670#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
671msgid "Language"
672msgstr "Sprache"
673
674#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116
675#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
676msgid "Timezone"
677msgstr "Zeitzone"
678
679#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
680#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
681msgid "Continent"
682msgstr "Kontinent"
683
684#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
685#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
686msgid "City"
687msgstr "Stadt"
688
689#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164
690msgid "Disable session cookie hijacking protection"
691msgstr "Deaktiviere Session Cookie Hijacking Schutz"
692
693#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166
694msgid "Check this if you get disconnected or if your IP address changes often"
695msgstr ""
696"Überprüfe dies, wenn die Verbindung getrennt wird oder wenn sich deine IP-"
697"Adresse häufig ändert"
698
699#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
700msgid "Private links by default"
701msgstr "Standardmäßig Private Links"
702
703#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:184
704msgid "All new links are private by default"
705msgstr "Alle neuen Links sind standardmäßig privat"
706
707#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
708msgid "RSS direct links"
709msgstr "RSS Direkt Links"
710
711#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:200
712msgid "Check this to use direct URL instead of permalink in feeds"
713msgstr ""
714"Aktivieren diese Option, um direkte URLs anstelle von Permalinks in Feeds zu "
715"verwenden"
716
717#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:215
718msgid "Hide public links"
719msgstr "Verstecke öffentliche Links"
720
721#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:216
722msgid "Do not show any links if the user is not logged in"
723msgstr "Zeige keine Links, wenn der Benutzer nicht angemeldet ist"
724
725#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231
726#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
727msgid "Check updates"
728msgstr "Auf Updates prüfen"
729
730#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232
731#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152
732msgid "Notify me when a new release is ready"
733msgstr "Benachrichtige mich, wenn eine neue Version zur Verfügung steht"
734
735#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247
736#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
737msgid "Enable REST API"
738msgstr "Aktiviere REST API"
739
740#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248
741#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
742msgid "Allow third party software to use Shaarli such as mobile application"
743msgstr ""
744"Erlaube Software von Drittanbietern für Shaarli, wie z.B. die mobile "
745"Anwendung"
746
747#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263
748msgid "API secret"
749msgstr "API secret"
750
751#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274
752#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
753#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
754#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
755msgid "Save"
756msgstr "Speichern"
757
758#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
759msgid "The Daily Shaarli"
760msgstr "Der tägliche Shaarli"
761
762#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
763msgid "1 RSS entry per day"
764msgstr "1 RSS Eintrag pro Tag"
765
766#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37
767msgid "Previous day"
768msgstr "Vorheriger Tag"
769
770#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
771msgid "All links of one day in a single page."
772msgstr "Alle Links eines Tages auf einer Seite."
773
774#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
775msgid "Next day"
776msgstr "Nächster Tag"
777
778#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
779msgid "Created:"
780msgstr "Erstellt:"
781
782#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
783msgid "URL"
784msgstr "URL"
785
786#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
787msgid "Title"
788msgstr "Titel"
789
790#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
791#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
792#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
793#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99
794#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124
795msgid "Description"
796msgstr "Beschreibung"
797
798#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
799msgid "Tags"
800msgstr "Tags"
801
802#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59
803#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
804#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
805msgid "Private"
806msgstr "Privat"
807
808#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
809msgid "Apply Changes"
810msgstr "Änderungen übernehmen"
811
812#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
813msgid "Export Database"
814msgstr "Exportiere Datenbank"
815
816#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
817msgid "Selection"
818msgstr "Beschreibung"
819
820#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
821msgid "All"
822msgstr "Alle"
823
824#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
825msgid "Public"
826msgstr "Öffentlich"
827
828#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52
829msgid "Prepend note permalinks with this Shaarli instance's URL"
830msgstr "Voranstellen von Notizen-Permalinks mit der URL dieser Shaarli-Instanz"
831
832#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
833msgid "Useful to import bookmarks in a web browser"
834msgstr "Sinnvoll Lesezeichen im Browser zu importieren"
835
836#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
837msgid "Import Database"
838msgstr "Importiere Datenbank"
839
840#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
841msgid "Maximum size allowed:"
842msgstr "Maximale Größe erlaubt:"
843
844#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
845msgid "Visibility"
846msgstr "Sichtbarkeit"
847
848#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
849msgid "Use values from the imported file, default to public"
850msgstr "Verwende Werte aus der importierten Datei, standardmäßig öffentlich"
851
852#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
853msgid "Import all bookmarks as private"
854msgstr "Importiere alle Lesezeichen als Privat"
855
856#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
857msgid "Import all bookmarks as public"
858msgstr "Importiere alles Lesezeichen als öffentlich"
859
860#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57
861msgid "Overwrite existing bookmarks"
862msgstr "Überschreibe alle bestehenden Lesezeichen"
863
864#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
865msgid "Duplicates based on URL"
866msgstr "Duplikate basierend auf URL"
867
868#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
869msgid "Add default tags"
870msgstr "Standard-Tag hinzufügen"
871
872#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
873msgid "Install Shaarli"
874msgstr "Installiere Shaarli"
875
876#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
877msgid "It looks like it's the first time you run Shaarli. Please configure it."
878msgstr ""
879"Es sieht so aus, als ob du Shaarli das erste mal verwendest. Bitte "
880"konfiguriere es."
881
882#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33
883#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
884#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
885#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147
886msgid "Username"
887msgstr "Benutzername"
888
889#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
890#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
891#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148
892#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:148
893msgid "Password"
894msgstr "Passwort"
895
896#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
897msgid "Shaarli title"
898msgstr "Shaarli Titel"
899
900#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69
901msgid "My links"
902msgstr "Meine Links"
903
904#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182
905msgid "Install"
906msgstr "Installiere"
907
908#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
909#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80
910msgid "shaare"
911msgid_plural "shaares"
912msgstr[0] "Teile"
913msgstr[1] "Teilen"
914
915#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18
916#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84
917msgid "private link"
918msgid_plural "private links"
919msgstr[0] "Privater Link"
920msgstr[1] "Private Links"
921
922#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
923#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
924#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:117
925msgid "Search text"
926msgstr "Text durchsuchen"
927
928#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
929#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124
930#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:124
931#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
932#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64
933#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
934#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
935msgid "Filter by tag"
936msgstr "Nach Tag filtern"
937
938#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111
939msgid "Nothing found."
940msgstr "Nichts gefunden."
941
942#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:119
943#, php-format
944msgid "%s result"
945msgid_plural "%s results"
946msgstr[0] "%s Ergebnis"
947msgstr[1] "%s Ergebnisse"
948
949#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123
950msgid "for"
951msgstr "für"
952
953#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130
954msgid "tagged"
955msgstr "markiert"
956
957#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
958msgid "Remove tag"
959msgstr "Tag entfernen"
960
961#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143
962msgid "with status"
963msgstr "mit Status"
964
965#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154
966msgid "without any tag"
967msgstr "ohne irgendeinen Tag"
968
969#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:174
970#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
971#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42
972msgid "Fold"
973msgstr "Ablegen"
974
975#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176
976msgid "Edited: "
977msgstr "Bearbeitet: "
978
979#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:180
980msgid "permalink"
981msgstr "Permalink"
982
983#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182
984msgid "Add tag"
985msgstr "Tag hinzufügen"
986
987#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
988#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:7
989msgid "Filters"
990msgstr "Filter"
991
992#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
993#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:12
994msgid "Only display private links"
995msgstr "Zeige nur private Links"
996
997#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
998#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:15
999msgid "Only display public links"
1000msgstr "Zeige nur öffentliche Links"
1001
1002#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
1003#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:20
1004msgid "Filter untagged links"
1005msgstr "Unmarkierte Tags filtern"
1006
1007#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
1008#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
1009#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:24
1010#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:76
1011#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
1012#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43
1013msgid "Fold all"
1014msgstr "Alles ablegen"
1015
1016#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69
1017#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:69
1018msgid "Links per page"
1019msgstr "Links pro Seite"
1020
1021#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
1022msgid ""
1023"You have been banned after too many failed login attempts. Try again later."
1024msgstr ""
1025"Du wurdest nach zu vielen fehlgeschlagenen Anmeldeversuchen gesperrt. "
1026"Versuche es später noch einmal."
1027
1028#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
1029#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151
1030#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:151
1031msgid "Remember me"
1032msgstr "Erinnere dich an mich"
1033
1034#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
1035#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
1036#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14
1037#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
1038msgid "by the Shaarli community"
1039msgstr "von der Shaarli Community"
1040
1041#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
1042#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
1043msgid "Documentation"
1044msgstr "Dokumentation"
1045
1046#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
1047#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44
1048msgid "Expand"
1049msgstr "Erweitern"
1050
1051#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45
1052#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45
1053msgid "Expand all"
1054msgstr "Alles erweitern"
1055
1056#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
1057#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46
1058msgid "Are you sure you want to delete this link?"
1059msgstr "Bist du sicher das du diesen Link löschen möchtest?"
1060
1061#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61
1062#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
1063#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:61
1064#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:86
1065msgid "RSS Feed"
1066msgstr "RSS Feed"
1067
1068#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66
1069#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
1070#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:66
1071#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:102
1072msgid "Logout"
1073msgstr "Ausloggen"
1074
1075#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
1076#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:169
1077msgid "is available"
1078msgstr "ist verfügbar"
1079
1080#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176
1081#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:176
1082msgid "Error"
1083msgstr "Fehler"
1084
1085#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
1086msgid "Picture Wall"
1087msgstr "Bildwand"
1088
1089#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
1090msgid "pics"
1091msgstr "Bilder"
1092
1093#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
1094msgid "You need to enable Javascript to change plugin loading order."
1095msgstr ""
1096"Du musst Javascript aktivieren um die Ladereihenfolge der Plugins zu ändern."
1097
1098#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
1099msgid "Enabled Plugins"
1100msgstr "Aktivierte Plugins"
1101
1102#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
1103#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
1104msgid "No plugin enabled."
1105msgstr "Kein Plugin aktiviert."
1106
1107#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
1108#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:73
1109msgid "Disable"
1110msgstr "Deaktivieren"
1111
1112#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
1113#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
1114#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:98
1115#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123
1116msgid "Name"
1117msgstr "Name"
1118
1119#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
1120#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
1121msgid "Order"
1122msgstr "Reihenfolge"
1123
1124#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
1125msgid "Disabled Plugins"
1126msgstr "Deaktivierte Plugins"
1127
1128#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:91
1129msgid "No plugin disabled."
1130msgstr "Kein Plugin deaktiviert."
1131
1132#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:97
1133#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
1134msgid "Enable"
1135msgstr "Aktiviere"
1136
1137#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
1138msgid "More plugins available"
1139msgstr "Weitere Plugins verfügbar"
1140
1141#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:136
1142msgid "in the documentation"
1143msgstr "In der Dokumentation"
1144
1145#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
1146msgid "Plugin configuration"
1147msgstr "Plugin Konfiguration"
1148
1149#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:195
1150msgid "No parameter available."
1151msgstr "Kein Parameter verfügbar."
1152
1153#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
1154#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
1155msgid "tags"
1156msgstr "Tags"
1157
1158#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
1159#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
1160msgid "List all links with those tags"
1161msgstr "Zeige alle Links mit diesen Tags"
1162
1163#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3
1164#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3
1165msgid "Sort by:"
1166msgstr "Sortiere nach:"
1167
1168#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5
1169#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:5
1170msgid "Cloud"
1171msgstr "Cloud"
1172
1173#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:6
1174#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:6
1175msgid "Most used"
1176msgstr "Am meisten verwendet"
1177
1178#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
1179#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:7
1180msgid "Alphabetical"
1181msgstr "Alphabetisch"
1182
1183#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
1184msgid "Settings"
1185msgstr "Einstellungen"
1186
1187#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
1188msgid "Change Shaarli settings: title, timezone, etc."
1189msgstr "Shaarli Einstellungen ändern: Titel, Zeitzone, usw."
1190
1191#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
1192msgid "Configure your Shaarli"
1193msgstr "Shaarli konfigurieren"
1194
1195#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21
1196msgid "Enable, disable and configure plugins"
1197msgstr "Plugins aktivieren, deaktivieren und konfigurieren"
1198
1199#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
1200msgid "Change your password"
1201msgstr "Ändere dein Passwort"
1202
1203#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
1204msgid "Rename or delete a tag in all links"
1205msgstr "Umbenennen oder löschen eines Tags in allen Links"
1206
1207#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
1208msgid ""
1209"Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, "
1210"delicious...)"
1211msgstr ""
1212"Importiere Netscape Lesezeichen (wie aus Firefox exportiert, Chrome, Opera, "
1213"delicious...)"
1214
1215#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
1216msgid "Import links"
1217msgstr "Importiere Links"
1218
1219#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
1220msgid ""
1221"Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, "
1222"Opera, delicious...)"
1223msgstr ""
1224"Exportiere Netscape HTML Lesezeichen (welche in Firefox importiert werden "
1225"können, Chrome, Opera, delicious...)"
1226
1227#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
1228msgid "Export database"
1229msgstr "Exportiere Datenbank"
1230
1231#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71
1232msgid ""
1233"Drag one of these button to your bookmarks toolbar or right-click it and "
1234"\"Bookmark This Link\""
1235msgstr ""
1236"Ziehe eine dieser Schaltflächen in deine Lesezeichen-Symbolleiste oder "
1237"klicke mit der rechten Maustaste darauf und \"Speichere diesen Link als "
1238"Lesezeichen\""
1239
1240#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
1241msgid "then click on the bookmarklet in any page you want to share."
1242msgstr ""
1243"Klicke dann auf das Bookmarklet auf jeder Seite, welches du teilen möchtest."
1244
1245#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
1246#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:100
1247msgid ""
1248"Drag this link to your bookmarks toolbar or right-click it and Bookmark This "
1249"Link"
1250msgstr ""
1251"Ziehe diese Link in deine Lesezeichen-Symbolleiste oder klicke mit der "
1252"rechten Maustaste darauf und \"Speichere diesen Link als Lesezeichen\""
1253
1254#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
1255msgid "then click ✚Shaare link button in any page you want to share"
1256msgstr ""
1257"klicke dann auf die Schaltfläche ✚Teilen auf jeder Seite, die du teilen "
1258"möchtest"
1259
1260#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
1261#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108
1262msgid "The selected text is too long, it will be truncated."
1263msgstr "Der ausgewählte Text ist zu lang, er wird gekürzt."
1264
1265#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96
1266msgid "Shaare link"
1267msgstr "Teile Link"
1268
1269#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
1270msgid ""
1271"Then click ✚Add Note button anytime to start composing a private Note (text "
1272"post) to your Shaarli"
1273msgstr ""
1274"Klicke auf ✚Notiz hinzufügen um eine private Notiz (Textnachricht) zu "
1275"Shaarli hinzuzufügen"
1276
1277#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
1278msgid "Add Note"
1279msgstr "Notiz hinzufügen"
1280
1281#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:129
1282msgid ""
1283"You need to browse your Shaarli over <strong>HTTPS</strong> to use this "
1284"functionality."
1285msgstr ""
1286"Um diese Funktion nutzen zu können, musst du Shaarli über <strong>HTTPS</"
1287"strong> aufrufen."
1288
1289#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
1290msgid "Add to"
1291msgstr "Hinzufügen zu"
1292
1293#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:145
1294msgid "3rd party"
1295msgstr "Von Dritten"
1296
1297#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
1298#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153
1299msgid "Plugin"
1300msgstr "Plugin"
1301
1302#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148
1303#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154
1304msgid "plugin"
1305msgstr "Plugin"
1306
1307#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175
1308msgid ""
1309"Drag this link to your bookmarks toolbar, or right-click it and choose "
1310"Bookmark This Link"
1311msgstr ""
1312"Ziehe diesen Link in deine Lesezeichen-Symbolleiste oder klicke mit der "
1313"rechten Maustaste darauf und wähle \"Speichere diesen Link als Lesezeichen\""
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po
index fd47217e..2ebeccbc 100644
--- a/inc/languages/fr/LC_MESSAGES/shaarli.po
+++ b/inc/languages/fr/LC_MESSAGES/shaarli.po
@@ -1,9 +1,8 @@
1msgid "" 1msgid ""
2msgstr "" 2msgstr ""
3"Project-Id-Version: Shaarli\n" 3"Project-Id-Version: Shaarli\n"
4"Report-Msgid-Bugs-To: \n" 4"POT-Creation-Date: 2018-01-24 18:43+0100\n"
5"POT-Creation-Date: 2018-02-24 12:39+0100\n" 5"PO-Revision-Date: 2018-03-06 18:44+0100\n"
6"PO-Revision-Date: 2018-02-24 12:43+0100\n"
7"Last-Translator: \n" 6"Last-Translator: \n"
8"Language-Team: Shaarli\n" 7"Language-Team: Shaarli\n"
9"Language: fr_FR\n" 8"Language: fr_FR\n"
@@ -764,6 +763,12 @@ msgstr "Tous les liens d'un jour sur une page."
764msgid "Next day" 763msgid "Next day"
765msgstr "Jour suivant" 764msgstr "Jour suivant"
766 765
766#: tpl/editlink.html
767msgid "Edit Shaare"
768msgstr "Modifier le Shaare"
769msgid "New Shaare"
770msgstr "Nouveau Shaare"
771
767#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 772#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
768msgid "Created:" 773msgid "Created:"
769msgstr "Création :" 774msgstr "Création :"
diff --git a/index.php b/index.php
index dbc2bb3b..c34434dd 100644
--- a/index.php
+++ b/index.php
@@ -78,8 +78,8 @@ require_once 'application/Updater.php';
78use \Shaarli\Languages; 78use \Shaarli\Languages;
79use \Shaarli\ThemeUtils; 79use \Shaarli\ThemeUtils;
80use \Shaarli\Config\ConfigManager; 80use \Shaarli\Config\ConfigManager;
81use \Shaarli\LoginManager; 81use \Shaarli\Security\LoginManager;
82use \Shaarli\SessionManager; 82use \Shaarli\Security\SessionManager;
83 83
84// Ensure the PHP version is supported 84// Ensure the PHP version is supported
85try { 85try {
@@ -101,8 +101,6 @@ if (dirname($_SERVER['SCRIPT_NAME']) != '/') {
101// Set default cookie expiration and path. 101// Set default cookie expiration and path.
102session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['SERVER_NAME']); 102session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['SERVER_NAME']);
103// Set session parameters on server side. 103// Set session parameters on server side.
104// If the user does not access any page within this time, his/her session is considered expired.
105define('INACTIVITY_TIMEOUT', 3600); // in seconds.
106// Use cookies to store session. 104// Use cookies to store session.
107ini_set('session.use_cookies', 1); 105ini_set('session.use_cookies', 1);
108// Force cookies for session (phpsessionID forbidden in URL). 106// Force cookies for session (phpsessionID forbidden in URL).
@@ -123,8 +121,10 @@ if (isset($_COOKIE['shaarli']) && !SessionManager::checkId($_COOKIE['shaarli']))
123} 121}
124 122
125$conf = new ConfigManager(); 123$conf = new ConfigManager();
126$loginManager = new LoginManager($GLOBALS, $conf);
127$sessionManager = new SessionManager($_SESSION, $conf); 124$sessionManager = new SessionManager($_SESSION, $conf);
125$loginManager = new LoginManager($GLOBALS, $conf, $sessionManager);
126$loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']);
127$clientIpId = client_ip_id($_SERVER);
128 128
129// LC_MESSAGES isn't defined without php-intl, in this case use LC_COLLATE locale instead. 129// LC_MESSAGES isn't defined without php-intl, in this case use LC_COLLATE locale instead.
130if (! defined('LC_MESSAGES')) { 130if (! defined('LC_MESSAGES')) {
@@ -177,157 +177,61 @@ if (! is_file($conf->getConfigFileExt())) {
177 install($conf, $sessionManager); 177 install($conf, $sessionManager);
178} 178}
179 179
180// a token depending of deployment salt, user password, and the current ip 180$loginManager->checkLoginState($_COOKIE, $clientIpId);
181define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt')));
182 181
183/** 182/**
184 * Checking session state (i.e. is the user still logged in) 183 * Adapter function to ensure compatibility with third-party templates
185 * 184 *
186 * @param ConfigManager $conf The configuration manager. 185 * @see https://github.com/shaarli/Shaarli/pull/1086
187 * 186 *
188 * @return bool: true if the user is logged in, false otherwise. 187 * @return bool true when the user is logged in, false otherwise
189 */ 188 */
190function setup_login_state($conf)
191{
192 if ($conf->get('security.open_shaarli')) {
193 return true;
194 }
195 $userIsLoggedIn = false; // By default, we do not consider the user as logged in;
196 $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met.
197 if (! $conf->exists('credentials.login')) {
198 $userIsLoggedIn = false; // Shaarli is not configured yet.
199 $loginFailure = true;
200 }
201 if (isset($_COOKIE['shaarli_staySignedIn']) &&
202 $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN &&
203 !$loginFailure)
204 {
205 fillSessionInfo($conf);
206 $userIsLoggedIn = true;
207 }
208 // If session does not exist on server side, or IP address has changed, or session has expired, logout.
209 if (empty($_SESSION['uid'])
210 || ($conf->get('security.session_protection_disabled') === false && $_SESSION['ip'] != allIPs())
211 || time() >= $_SESSION['expires_on'])
212 {
213 logout();
214 $userIsLoggedIn = false;
215 $loginFailure = true;
216 }
217 if (!empty($_SESSION['longlastingsession'])) {
218 $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked.
219 }
220 else {
221 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date.
222 }
223 if (!$loginFailure) {
224 $userIsLoggedIn = true;
225 }
226
227 return $userIsLoggedIn;
228}
229$userIsLoggedIn = setup_login_state($conf);
230
231// ------------------------------------------------------------------------------------------
232// Session management
233
234// Returns the IP address of the client (Used to prevent session cookie hijacking.)
235function allIPs()
236{
237 $ip = $_SERVER['REMOTE_ADDR'];
238 // Then we use more HTTP headers to prevent session hijacking from users behind the same proxy.
239 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip=$ip.'_'.$_SERVER['HTTP_X_FORWARDED_FOR']; }
240 if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip=$ip.'_'.$_SERVER['HTTP_CLIENT_IP']; }
241 return $ip;
242}
243
244/**
245 * Load user session.
246 *
247 * @param ConfigManager $conf Configuration Manager instance.
248 */
249function fillSessionInfo($conf)
250{
251 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid)
252 $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked.
253 $_SESSION['username']= $conf->get('credentials.login');
254 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration.
255}
256
257/**
258 * Check that user/password is correct.
259 *
260 * @param string $login Username
261 * @param string $password User password
262 * @param ConfigManager $conf Configuration Manager instance.
263 *
264 * @return bool: authentication successful or not.
265 */
266function check_auth($login, $password, $conf)
267{
268 $hash = sha1($password . $login . $conf->get('credentials.salt'));
269 if ($login == $conf->get('credentials.login') && $hash == $conf->get('credentials.hash'))
270 { // Login/password is correct.
271 fillSessionInfo($conf);
272 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login successful');
273 return true;
274 }
275 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login failed for user '.$login);
276 return false;
277}
278
279// Returns true if the user is logged in.
280function isLoggedIn() 189function isLoggedIn()
281{ 190{
282 global $userIsLoggedIn; 191 global $loginManager;
283 return $userIsLoggedIn; 192 return $loginManager->isLoggedIn();
284} 193}
285 194
286// Force logout.
287function logout() {
288 if (isset($_SESSION)) {
289 unset($_SESSION['uid']);
290 unset($_SESSION['ip']);
291 unset($_SESSION['username']);
292 unset($_SESSION['visibility']);
293 unset($_SESSION['untaggedonly']);
294 }
295 setcookie('shaarli_staySignedIn', FALSE, 0, WEB_PATH);
296}
297 195
298// ------------------------------------------------------------------------------------------ 196// ------------------------------------------------------------------------------------------
299// Process login form: Check if login/password is correct. 197// Process login form: Check if login/password is correct.
300if (isset($_POST['login'])) 198if (isset($_POST['login'])) {
301{
302 if (! $loginManager->canLogin($_SERVER)) { 199 if (! $loginManager->canLogin($_SERVER)) {
303 die(t('I said: NO. You are banned for the moment. Go away.')); 200 die(t('I said: NO. You are banned for the moment. Go away.'));
304 } 201 }
305 if (isset($_POST['password']) 202 if (isset($_POST['password'])
306 && $sessionManager->checkToken($_POST['token']) 203 && $sessionManager->checkToken($_POST['token'])
307 && (check_auth($_POST['login'], $_POST['password'], $conf)) 204 && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password'])
308 ) { 205 ) {
309 // Login/password is OK.
310 $loginManager->handleSuccessfulLogin($_SERVER); 206 $loginManager->handleSuccessfulLogin($_SERVER);
311 207
312 // If user wants to keep the session cookie even after the browser closes: 208 $cookiedir = '';
313 if (!empty($_POST['longlastingsession'])) { 209 if (dirname($_SERVER['SCRIPT_NAME']) != '/') {
314 $_SESSION['longlastingsession'] = 31536000; // (31536000 seconds = 1 year)
315 $expiration = time() + $_SESSION['longlastingsession']; // calculate relative cookie expiration (1 year from now)
316 setcookie('shaarli_staySignedIn', STAY_SIGNED_IN_TOKEN, $expiration, WEB_PATH);
317 $_SESSION['expires_on'] = $expiration; // Set session expiration on server-side.
318
319 $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
320 session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['SERVER_NAME']); // Set session cookie expiration on client side
321 // Note: Never forget the trailing slash on the cookie path! 210 // Note: Never forget the trailing slash on the cookie path!
322 session_regenerate_id(true); // Send cookie with new expiration date to browser. 211 $cookiedir = dirname($_SERVER["SCRIPT_NAME"]) . '/';
323 } 212 }
324 else // Standard session expiration (=when browser closes) 213
325 { 214 if (!empty($_POST['longlastingsession'])) {
326 $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/'; 215 // Keep the session cookie even after the browser closes
327 session_set_cookie_params(0,$cookiedir,$_SERVER['SERVER_NAME']); // 0 means "When browser closes" 216 $sessionManager->setStaySignedIn(true);
328 session_regenerate_id(true); 217 $expirationTime = $sessionManager->extendSession();
218
219 setcookie(
220 $loginManager::$STAY_SIGNED_IN_COOKIE,
221 $loginManager->getStaySignedInToken(),
222 $expirationTime,
223 WEB_PATH
224 );
225
226 } else {
227 // Standard session expiration (=when browser closes)
228 $expirationTime = 0;
329 } 229 }
330 230
231 // Send cookie with the new expiration date to the browser
232 session_set_cookie_params($expirationTime, $cookiedir, $_SERVER['SERVER_NAME']);
233 session_regenerate_id(true);
234
331 // Optional redirect after login: 235 // Optional redirect after login:
332 if (isset($_GET['post'])) { 236 if (isset($_GET['post'])) {
333 $uri = '?post='. urlencode($_GET['post']); 237 $uri = '?post='. urlencode($_GET['post']);
@@ -380,15 +284,16 @@ if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are atta
380 * Gives the last 7 days (which have links). 284 * Gives the last 7 days (which have links).
381 * This RSS feed cannot be filtered. 285 * This RSS feed cannot be filtered.
382 * 286 *
383 * @param ConfigManager $conf Configuration Manager instance. 287 * @param ConfigManager $conf Configuration Manager instance
288 * @param LoginManager $loginManager LoginManager instance
384 */ 289 */
385function showDailyRSS($conf) { 290function showDailyRSS($conf, $loginManager) {
386 // Cache system 291 // Cache system
387 $query = $_SERVER['QUERY_STRING']; 292 $query = $_SERVER['QUERY_STRING'];
388 $cache = new CachedPage( 293 $cache = new CachedPage(
389 $conf->get('config.PAGE_CACHE'), 294 $conf->get('config.PAGE_CACHE'),
390 page_url($_SERVER), 295 page_url($_SERVER),
391 startsWith($query,'do=dailyrss') && !isLoggedIn() 296 startsWith($query,'do=dailyrss') && !$loginManager->isLoggedIn()
392 ); 297 );
393 $cached = $cache->cachedVersion(); 298 $cached = $cache->cachedVersion();
394 if (!empty($cached)) { 299 if (!empty($cached)) {
@@ -400,7 +305,7 @@ function showDailyRSS($conf) {
400 // Read links from database (and filter private links if used it not logged in). 305 // Read links from database (and filter private links if used it not logged in).
401 $LINKSDB = new LinkDB( 306 $LINKSDB = new LinkDB(
402 $conf->get('resource.datastore'), 307 $conf->get('resource.datastore'),
403 isLoggedIn(), 308 $loginManager->isLoggedIn(),
404 $conf->get('privacy.hide_public_links'), 309 $conf->get('privacy.hide_public_links'),
405 $conf->get('redirector.url'), 310 $conf->get('redirector.url'),
406 $conf->get('redirector.encode_url') 311 $conf->get('redirector.encode_url')
@@ -482,9 +387,10 @@ function showDailyRSS($conf) {
482 * @param PageBuilder $pageBuilder Template engine wrapper. 387 * @param PageBuilder $pageBuilder Template engine wrapper.
483 * @param LinkDB $LINKSDB LinkDB instance. 388 * @param LinkDB $LINKSDB LinkDB instance.
484 * @param ConfigManager $conf Configuration Manager instance. 389 * @param ConfigManager $conf Configuration Manager instance.
485 * @param PluginManager $pluginManager Plugin Manager instane. 390 * @param PluginManager $pluginManager Plugin Manager instance.
391 * @param LoginManager $loginManager Login Manager instance
486 */ 392 */
487function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager) 393function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
488{ 394{
489 $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD. 395 $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
490 if (isset($_GET['day'])) { 396 if (isset($_GET['day'])) {
@@ -542,7 +448,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
542 448
543 /* Hook is called before column construction so that plugins don't have 449 /* Hook is called before column construction so that plugins don't have
544 to deal with columns. */ 450 to deal with columns. */
545 $pluginManager->executeHooks('render_daily', $data, array('loggedin' => isLoggedIn())); 451 $pluginManager->executeHooks('render_daily', $data, array('loggedin' => $loginManager->isLoggedIn()));
546 452
547 /* We need to spread the articles on 3 columns. 453 /* We need to spread the articles on 3 columns.
548 I did not want to use a JavaScript lib like http://masonry.desandro.com/ 454 I did not want to use a JavaScript lib like http://masonry.desandro.com/
@@ -586,8 +492,8 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
586 * @param ConfigManager $conf Configuration Manager instance. 492 * @param ConfigManager $conf Configuration Manager instance.
587 * @param PluginManager $pluginManager Plugin Manager instance. 493 * @param PluginManager $pluginManager Plugin Manager instance.
588 */ 494 */
589function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager) { 495function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager) {
590 buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager); // Compute list of links to display 496 buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager, $loginManager);
591 $PAGE->renderPage('linklist'); 497 $PAGE->renderPage('linklist');
592} 498}
593 499
@@ -607,7 +513,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
607 read_updates_file($conf->get('resource.updates')), 513 read_updates_file($conf->get('resource.updates')),
608 $LINKSDB, 514 $LINKSDB,
609 $conf, 515 $conf,
610 isLoggedIn() 516 $loginManager->isLoggedIn()
611 ); 517 );
612 try { 518 try {
613 $newUpdates = $updater->update(); 519 $newUpdates = $updater->update();
@@ -622,18 +528,18 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
622 die($e->getMessage()); 528 die($e->getMessage());
623 } 529 }
624 530
625 $PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken()); 531 $PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken(), $loginManager->isLoggedIn());
626 $PAGE->assign('linkcount', count($LINKSDB)); 532 $PAGE->assign('linkcount', count($LINKSDB));
627 $PAGE->assign('privateLinkcount', count_private($LINKSDB)); 533 $PAGE->assign('privateLinkcount', count_private($LINKSDB));
628 $PAGE->assign('plugin_errors', $pluginManager->getErrors()); 534 $PAGE->assign('plugin_errors', $pluginManager->getErrors());
629 535
630 // Determine which page will be rendered. 536 // Determine which page will be rendered.
631 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; 537 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
632 $targetPage = Router::findPage($query, $_GET, isLoggedIn()); 538 $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn());
633 539
634 if ( 540 if (
635 // if the user isn't logged in 541 // if the user isn't logged in
636 !isLoggedIn() && 542 !$loginManager->isLoggedIn() &&
637 // and Shaarli doesn't have public content... 543 // and Shaarli doesn't have public content...
638 $conf->get('privacy.hide_public_links') && 544 $conf->get('privacy.hide_public_links') &&
639 // and is configured to enforce the login 545 // and is configured to enforce the login
@@ -661,7 +567,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
661 $pluginManager->executeHooks('render_' . $name, $plugin_data, 567 $pluginManager->executeHooks('render_' . $name, $plugin_data,
662 array( 568 array(
663 'target' => $targetPage, 569 'target' => $targetPage,
664 'loggedin' => isLoggedIn() 570 'loggedin' => $loginManager->isLoggedIn()
665 ) 571 )
666 ); 572 );
667 $PAGE->assign('plugins_' . $name, $plugin_data); 573 $PAGE->assign('plugins_' . $name, $plugin_data);
@@ -686,7 +592,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
686 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) 592 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout'))
687 { 593 {
688 invalidateCaches($conf->get('resource.page_cache')); 594 invalidateCaches($conf->get('resource.page_cache'));
689 logout(); 595 $sessionManager->logout();
596 setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH);
690 header('Location: ?'); 597 header('Location: ?');
691 exit; 598 exit;
692 } 599 }
@@ -713,7 +620,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
713 $data = array( 620 $data = array(
714 'linksToDisplay' => $linksToDisplay, 621 'linksToDisplay' => $linksToDisplay,
715 ); 622 );
716 $pluginManager->executeHooks('render_picwall', $data, array('loggedin' => isLoggedIn())); 623 $pluginManager->executeHooks('render_picwall', $data, array('loggedin' => $loginManager->isLoggedIn()));
717 624
718 foreach ($data as $key => $value) { 625 foreach ($data as $key => $value) {
719 $PAGE->assign($key, $value); 626 $PAGE->assign($key, $value);
@@ -760,7 +667,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
760 'search_tags' => $searchTags, 667 'search_tags' => $searchTags,
761 'tags' => $tagList, 668 'tags' => $tagList,
762 ); 669 );
763 $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn())); 670 $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => $loginManager->isLoggedIn()));
764 671
765 foreach ($data as $key => $value) { 672 foreach ($data as $key => $value) {
766 $PAGE->assign($key, $value); 673 $PAGE->assign($key, $value);
@@ -793,7 +700,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
793 'search_tags' => $searchTags, 700 'search_tags' => $searchTags,
794 'tags' => $tags, 701 'tags' => $tags,
795 ]; 702 ];
796 $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]); 703 $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => $loginManager->isLoggedIn()]);
797 704
798 foreach ($data as $key => $value) { 705 foreach ($data as $key => $value) {
799 $PAGE->assign($key, $value); 706 $PAGE->assign($key, $value);
@@ -807,7 +714,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
807 714
808 // Daily page. 715 // Daily page.
809 if ($targetPage == Router::$PAGE_DAILY) { 716 if ($targetPage == Router::$PAGE_DAILY) {
810 showDaily($PAGE, $LINKSDB, $conf, $pluginManager); 717 showDaily($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
811 } 718 }
812 719
813 // ATOM and RSS feed. 720 // ATOM and RSS feed.
@@ -820,7 +727,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
820 $cache = new CachedPage( 727 $cache = new CachedPage(
821 $conf->get('resource.page_cache'), 728 $conf->get('resource.page_cache'),
822 page_url($_SERVER), 729 page_url($_SERVER),
823 startsWith($query,'do='. $targetPage) && !isLoggedIn() 730 startsWith($query,'do='. $targetPage) && !$loginManager->isLoggedIn()
824 ); 731 );
825 $cached = $cache->cachedVersion(); 732 $cached = $cache->cachedVersion();
826 if (!empty($cached)) { 733 if (!empty($cached)) {
@@ -829,15 +736,15 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
829 } 736 }
830 737
831 // Generate data. 738 // Generate data.
832 $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn()); 739 $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, $loginManager->isLoggedIn());
833 $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); 740 $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
834 $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !isLoggedIn()); 741 $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !$loginManager->isLoggedIn());
835 $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks')); 742 $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks'));
836 $data = $feedGenerator->buildData(); 743 $data = $feedGenerator->buildData();
837 744
838 // Process plugin hook. 745 // Process plugin hook.
839 $pluginManager->executeHooks('render_feed', $data, array( 746 $pluginManager->executeHooks('render_feed', $data, array(
840 'loggedin' => isLoggedIn(), 747 'loggedin' => $loginManager->isLoggedIn(),
841 'target' => $targetPage, 748 'target' => $targetPage,
842 )); 749 ));
843 750
@@ -985,7 +892,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
985 } 892 }
986 893
987 // -------- Handle other actions allowed for non-logged in users: 894 // -------- Handle other actions allowed for non-logged in users:
988 if (!isLoggedIn()) 895 if (!$loginManager->isLoggedIn())
989 { 896 {
990 // User tries to post new link but is not logged in: 897 // User tries to post new link but is not logged in:
991 // Show login screen, then redirect to ?post=... 898 // Show login screen, then redirect to ?post=...
@@ -1001,7 +908,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1001 exit; 908 exit;
1002 } 909 }
1003 910
1004 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager); 911 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
1005 if (isset($_GET['edit_link'])) { 912 if (isset($_GET['edit_link'])) {
1006 header('Location: ?do=login&edit_link='. escape($_GET['edit_link'])); 913 header('Location: ?do=login&edit_link='. escape($_GET['edit_link']));
1007 exit; 914 exit;
@@ -1052,7 +959,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1052 $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); 959 $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
1053 $conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt'))); 960 $conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt')));
1054 try { 961 try {
1055 $conf->write(isLoggedIn()); 962 $conf->write($loginManager->isLoggedIn());
1056 } 963 }
1057 catch(Exception $e) { 964 catch(Exception $e) {
1058 error_log( 965 error_log(
@@ -1103,7 +1010,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1103 $conf->set('translation.language', escape($_POST['language'])); 1010 $conf->set('translation.language', escape($_POST['language']));
1104 1011
1105 try { 1012 try {
1106 $conf->write(isLoggedIn()); 1013 $conf->write($loginManager->isLoggedIn());
1107 $history->updateSettings(); 1014 $history->updateSettings();
1108 invalidateCaches($conf->get('resource.page_cache')); 1015 invalidateCaches($conf->get('resource.page_cache'));
1109 } 1016 }
@@ -1376,8 +1283,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1376 // The callback will fill $charset and $title with data from the downloaded page. 1283 // The callback will fill $charset and $title with data from the downloaded page.
1377 get_http_response( 1284 get_http_response(
1378 $url, 1285 $url,
1379 $conf->get('general.download_max_size', 4194304),
1380 $conf->get('general.download_timeout', 30), 1286 $conf->get('general.download_timeout', 30),
1287 $conf->get('general.download_max_size', 4194304),
1381 get_curl_download_callback($charset, $title) 1288 get_curl_download_callback($charset, $title)
1382 ); 1289 );
1383 if (! empty($title) && strtolower($charset) != 'utf-8') { 1290 if (! empty($title) && strtolower($charset) != 'utf-8') {
@@ -1555,7 +1462,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1555 else { 1462 else {
1556 $conf->set('general.enabled_plugins', save_plugin_config($_POST)); 1463 $conf->set('general.enabled_plugins', save_plugin_config($_POST));
1557 } 1464 }
1558 $conf->write(isLoggedIn()); 1465 $conf->write($loginManager->isLoggedIn());
1559 $history->updateSettings(); 1466 $history->updateSettings();
1560 } 1467 }
1561 catch (Exception $e) { 1468 catch (Exception $e) {
@@ -1580,7 +1487,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1580 } 1487 }
1581 1488
1582 // -------- Otherwise, simply display search form and links: 1489 // -------- Otherwise, simply display search form and links:
1583 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager); 1490 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
1584 exit; 1491 exit;
1585} 1492}
1586 1493
@@ -1592,8 +1499,9 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1592 * @param LinkDB $LINKSDB LinkDB instance. 1499 * @param LinkDB $LINKSDB LinkDB instance.
1593 * @param ConfigManager $conf Configuration Manager instance. 1500 * @param ConfigManager $conf Configuration Manager instance.
1594 * @param PluginManager $pluginManager Plugin Manager instance. 1501 * @param PluginManager $pluginManager Plugin Manager instance.
1502 * @param LoginManager $loginManager LoginManager instance
1595 */ 1503 */
1596function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager) 1504function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
1597{ 1505{
1598 // Used in templates 1506 // Used in templates
1599 if (isset($_GET['searchtags'])) { 1507 if (isset($_GET['searchtags'])) {
@@ -1632,8 +1540,6 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
1632 $keys[] = $key; 1540 $keys[] = $key;
1633 } 1541 }
1634 1542
1635
1636
1637 // Select articles according to paging. 1543 // Select articles according to paging.
1638 $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']); 1544 $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']);
1639 $pagecount = $pagecount == 0 ? 1 : $pagecount; 1545 $pagecount = $pagecount == 0 ? 1 : $pagecount;
@@ -1714,7 +1620,7 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
1714 $data['pagetitle'] .= '- '. $conf->get('general.title'); 1620 $data['pagetitle'] .= '- '. $conf->get('general.title');
1715 } 1621 }
1716 1622
1717 $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn())); 1623 $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => $loginManager->isLoggedIn()));
1718 1624
1719 foreach ($data as $key => $value) { 1625 foreach ($data as $key => $value) {
1720 $PAGE->assign($key, $value); 1626 $PAGE->assign($key, $value);
@@ -1985,7 +1891,7 @@ function install($conf, $sessionManager) {
1985 ); 1891 );
1986 try { 1892 try {
1987 // Everything is ok, let's create config file. 1893 // Everything is ok, let's create config file.
1988 $conf->write(isLoggedIn()); 1894 $conf->write($loginManager->isLoggedIn());
1989 } 1895 }
1990 catch(Exception $e) { 1896 catch(Exception $e) {
1991 error_log( 1897 error_log(
@@ -2249,7 +2155,7 @@ try {
2249 2155
2250$linkDb = new LinkDB( 2156$linkDb = new LinkDB(
2251 $conf->get('resource.datastore'), 2157 $conf->get('resource.datastore'),
2252 isLoggedIn(), 2158 $loginManager->isLoggedIn(),
2253 $conf->get('privacy.hide_public_links'), 2159 $conf->get('privacy.hide_public_links'),
2254 $conf->get('redirector.url'), 2160 $conf->get('redirector.url'),
2255 $conf->get('redirector.encode_url') 2161 $conf->get('redirector.encode_url')
diff --git a/mkdocs.yml b/mkdocs.yml
index 443c3a08..8ba25540 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -22,9 +22,8 @@ pages:
22 - Reverse proxy configuration: docker/reverse-proxy-configuration.md 22 - Reverse proxy configuration: docker/reverse-proxy-configuration.md
23 - Docker resources: docker/resources.md 23 - Docker resources: docker/resources.md
24- Usage: 24- Usage:
25 - Bookmarklet: Bookmarklet.md
26 - Browsing and searching: Browsing-and-searching.md 25 - Browsing and searching: Browsing-and-searching.md
27 - Firefox share: Firefox-share.md 26 - Sharing content: Sharing-content.md
28 - RSS feeds: RSS-feeds.md 27 - RSS feeds: RSS-feeds.md
29 - REST API: REST-API.md 28 - REST API: REST-API.md
30 - Community & Related software: Community-&-Related-software.md 29 - Community & Related software: Community-&-Related-software.md
diff --git a/package.json b/package.json
index ba997c9a..3dd1e0fc 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
22 "extract-text-webpack-plugin": "^3.0.2", 22 "extract-text-webpack-plugin": "^3.0.2",
23 "file-loader": "^1.1.6", 23 "file-loader": "^1.1.6",
24 "node-sass": "^4.7.2", 24 "node-sass": "^4.7.2",
25 "sass-lint": "^1.12.1",
25 "sass-loader": "^6.0.6", 26 "sass-loader": "^6.0.6",
26 "style-loader": "^0.19.1", 27 "style-loader": "^0.19.1",
27 "url-loader": "^0.6.2", 28 "url-loader": "^0.6.2",
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php
index 2f24e417..821bb125 100644
--- a/plugins/markdown/markdown.php
+++ b/plugins/markdown/markdown.php
@@ -6,6 +6,8 @@
6 * Shaare's descriptions are parsed with Markdown. 6 * Shaare's descriptions are parsed with Markdown.
7 */ 7 */
8 8
9use Shaarli\Config\ConfigManager;
10
9/* 11/*
10 * If this tag is used on a shaare, the description won't be processed by Parsedown. 12 * If this tag is used on a shaare, the description won't be processed by Parsedown.
11 */ 13 */
@@ -50,6 +52,7 @@ function hook_markdown_render_feed($data, $conf)
50 $value = stripNoMarkdownTag($value); 52 $value = stripNoMarkdownTag($value);
51 continue; 53 continue;
52 } 54 }
55 $value['description'] = reverse_feed_permalink($value['description']);
53 $value['description'] = process_markdown( 56 $value['description'] = process_markdown(
54 $value['description'], 57 $value['description'],
55 $conf->get('security.markdown_escape', true), 58 $conf->get('security.markdown_escape', true),
@@ -244,6 +247,11 @@ function reverse_space2nbsp($description)
244 return preg_replace('/(^| )&nbsp;/m', '$1 ', $description); 247 return preg_replace('/(^| )&nbsp;/m', '$1 ', $description);
245} 248}
246 249
250function reverse_feed_permalink($description)
251{
252 return preg_replace('@&#8212; <a href="([^"]+)" title="[^"]+">(\w+)</a>$@im', '&#8212; [$2]($1)', $description);
253}
254
247/** 255/**
248 * Replace not whitelisted protocols with http:// in given description. 256 * Replace not whitelisted protocols with http:// in given description.
249 * 257 *
diff --git a/tests/HttpUtils/ClientIpIdTest.php b/tests/HttpUtils/ClientIpIdTest.php
new file mode 100644
index 00000000..c15ac5cc
--- /dev/null
+++ b/tests/HttpUtils/ClientIpIdTest.php
@@ -0,0 +1,52 @@
1<?php
2/**
3 * HttpUtils' tests
4 */
5
6require_once 'application/HttpUtils.php';
7
8/**
9 * Unitary tests for client_ip_id()
10 */
11class ClientIpIdTest extends PHPUnit_Framework_TestCase
12{
13 /**
14 * Get a remote client ID based on its IP
15 */
16 public function testClientIpIdRemote()
17 {
18 $this->assertEquals(
19 '10.1.167.42',
20 client_ip_id(['REMOTE_ADDR' => '10.1.167.42'])
21 );
22 }
23
24 /**
25 * Get a remote client ID based on its IP and proxy information (1)
26 */
27 public function testClientIpIdRemoteForwarded()
28 {
29 $this->assertEquals(
30 '10.1.167.42_127.0.1.47',
31 client_ip_id([
32 'REMOTE_ADDR' => '10.1.167.42',
33 'HTTP_X_FORWARDED_FOR' => '127.0.1.47'
34 ])
35 );
36 }
37
38 /**
39 * Get a remote client ID based on its IP and proxy information (2)
40 */
41 public function testClientIpIdRemoteForwardedClient()
42 {
43 $this->assertEquals(
44 '10.1.167.42_10.1.167.56_127.0.1.47',
45 client_ip_id([
46 'REMOTE_ADDR' => '10.1.167.42',
47 'HTTP_X_FORWARDED_FOR' => '10.1.167.56',
48 'HTTP_CLIENT_IP' => '127.0.1.47'
49 ])
50 );
51 }
52}
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 5b2f3667..3b980878 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -542,4 +542,104 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
542 $this->assertEquals(3, count($res)); 542 $this->assertEquals(3, count($res));
543 $this->assertNotContains('cartoon', $linkDB[4]['tags']); 543 $this->assertNotContains('cartoon', $linkDB[4]['tags']);
544 } 544 }
545
546 /**
547 * Test linksCountPerTag all tags without filter.
548 * Equal occurrences should be sorted alphabetically.
549 */
550 public function testCountLinkPerTagAllNoFilter()
551 {
552 $expected = [
553 'web' => 4,
554 'cartoon' => 3,
555 'dev' => 2,
556 'gnu' => 2,
557 'hashtag' => 2,
558 'sTuff' => 2,
559 '-exclude' => 1,
560 '.hidden' => 1,
561 'Mercurial' => 1,
562 'css' => 1,
563 'free' => 1,
564 'html' => 1,
565 'media' => 1,
566 'samba' => 1,
567 'software' => 1,
568 'stallman' => 1,
569 'tag1' => 1,
570 'tag2' => 1,
571 'tag3' => 1,
572 'tag4' => 1,
573 'ut' => 1,
574 'w3c' => 1,
575 ];
576 $tags = self::$privateLinkDB->linksCountPerTag();
577
578 $this->assertEquals($expected, $tags, var_export($tags, true));
579 }
580
581 /**
582 * Test linksCountPerTag all tags with filter.
583 * Equal occurrences should be sorted alphabetically.
584 */
585 public function testCountLinkPerTagAllWithFilter()
586 {
587 $expected = [
588 'gnu' => 2,
589 'hashtag' => 2,
590 '-exclude' => 1,
591 '.hidden' => 1,
592 'free' => 1,
593 'media' => 1,
594 'software' => 1,
595 'stallman' => 1,
596 'stuff' => 1,
597 'web' => 1,
598 ];
599 $tags = self::$privateLinkDB->linksCountPerTag(['gnu']);
600
601 $this->assertEquals($expected, $tags, var_export($tags, true));
602 }
603
604 /**
605 * Test linksCountPerTag public tags with filter.
606 * Equal occurrences should be sorted alphabetically.
607 */
608 public function testCountLinkPerTagPublicWithFilter()
609 {
610 $expected = [
611 'gnu' => 2,
612 'hashtag' => 2,
613 '-exclude' => 1,
614 '.hidden' => 1,
615 'free' => 1,
616 'media' => 1,
617 'software' => 1,
618 'stallman' => 1,
619 'stuff' => 1,
620 'web' => 1,
621 ];
622 $tags = self::$privateLinkDB->linksCountPerTag(['gnu'], 'public');
623
624 $this->assertEquals($expected, $tags, var_export($tags, true));
625 }
626
627 /**
628 * Test linksCountPerTag public tags with filter.
629 * Equal occurrences should be sorted alphabetically.
630 */
631 public function testCountLinkPerTagPrivateWithFilter()
632 {
633 $expected = [
634 'cartoon' => 1,
635 'dev' => 1,
636 'tag1' => 1,
637 'tag2' => 1,
638 'tag3' => 1,
639 'tag4' => 1,
640 ];
641 $tags = self::$privateLinkDB->linksCountPerTag(['dev'], 'private');
642
643 $this->assertEquals($expected, $tags, var_export($tags, true));
644 }
545} 645}
diff --git a/tests/SessionManagerTest.php b/tests/SessionManagerTest.php
deleted file mode 100644
index aa75962a..00000000
--- a/tests/SessionManagerTest.php
+++ /dev/null
@@ -1,149 +0,0 @@
1<?php
2require_once 'tests/utils/FakeConfigManager.php';
3
4// Initialize reference data _before_ PHPUnit starts a session
5require_once 'tests/utils/ReferenceSessionIdHashes.php';
6ReferenceSessionIdHashes::genAllHashes();
7
8use \Shaarli\SessionManager;
9use \PHPUnit\Framework\TestCase;
10
11
12/**
13 * Test coverage for SessionManager
14 */
15class SessionManagerTest extends TestCase
16{
17 // Session ID hashes
18 protected static $sidHashes = null;
19
20 // Fake ConfigManager
21 protected static $conf = null;
22
23 /**
24 * Assign reference data
25 */
26 public static function setUpBeforeClass()
27 {
28 self::$sidHashes = ReferenceSessionIdHashes::getHashes();
29 self::$conf = new FakeConfigManager();
30 }
31
32 /**
33 * Generate a session token
34 */
35 public function testGenerateToken()
36 {
37 $session = [];
38 $sessionManager = new SessionManager($session, self::$conf);
39
40 $token = $sessionManager->generateToken();
41
42 $this->assertEquals(1, $session['tokens'][$token]);
43 $this->assertEquals(40, strlen($token));
44 }
45
46 /**
47 * Check a session token
48 */
49 public function testCheckToken()
50 {
51 $token = '4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b';
52 $session = [
53 'tokens' => [
54 $token => 1,
55 ],
56 ];
57 $sessionManager = new SessionManager($session, self::$conf);
58
59 // check and destroy the token
60 $this->assertTrue($sessionManager->checkToken($token));
61 $this->assertFalse(isset($session['tokens'][$token]));
62
63 // ensure the token has been destroyed
64 $this->assertFalse($sessionManager->checkToken($token));
65 }
66
67 /**
68 * Generate and check a session token
69 */
70 public function testGenerateAndCheckToken()
71 {
72 $session = [];
73 $sessionManager = new SessionManager($session, self::$conf);
74
75 $token = $sessionManager->generateToken();
76
77 // ensure a token has been generated
78 $this->assertEquals(1, $session['tokens'][$token]);
79 $this->assertEquals(40, strlen($token));
80
81 // check and destroy the token
82 $this->assertTrue($sessionManager->checkToken($token));
83 $this->assertFalse(isset($session['tokens'][$token]));
84
85 // ensure the token has been destroyed
86 $this->assertFalse($sessionManager->checkToken($token));
87 }
88
89 /**
90 * Check an invalid session token
91 */
92 public function testCheckInvalidToken()
93 {
94 $session = [];
95 $sessionManager = new SessionManager($session, self::$conf);
96
97 $this->assertFalse($sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b'));
98 }
99
100 /**
101 * Test SessionManager::checkId with a valid ID - TEST ALL THE HASHES!
102 *
103 * This tests extensively covers all hash algorithms / bit representations
104 */
105 public function testIsAnyHashSessionIdValid()
106 {
107 foreach (self::$sidHashes as $algo => $bpcs) {
108 foreach ($bpcs as $bpc => $hash) {
109 $this->assertTrue(SessionManager::checkId($hash));
110 }
111 }
112 }
113
114 /**
115 * Test checkId with a valid ID - SHA-1 hashes
116 */
117 public function testIsSha1SessionIdValid()
118 {
119 $this->assertTrue(SessionManager::checkId(sha1('shaarli')));
120 }
121
122 /**
123 * Test checkId with a valid ID - SHA-256 hashes
124 */
125 public function testIsSha256SessionIdValid()
126 {
127 $this->assertTrue(SessionManager::checkId(hash('sha256', 'shaarli')));
128 }
129
130 /**
131 * Test checkId with a valid ID - SHA-512 hashes
132 */
133 public function testIsSha512SessionIdValid()
134 {
135 $this->assertTrue(SessionManager::checkId(hash('sha512', 'shaarli')));
136 }
137
138 /**
139 * Test checkId with invalid IDs.
140 */
141 public function testIsSessionIdInvalid()
142 {
143 $this->assertFalse(SessionManager::checkId(''));
144 $this->assertFalse(SessionManager::checkId([]));
145 $this->assertFalse(
146 SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
147 );
148 }
149}
diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php
index ddc2728d..b31e817f 100644
--- a/tests/plugins/PluginMarkdownTest.php
+++ b/tests/plugins/PluginMarkdownTest.php
@@ -50,6 +50,30 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
50 } 50 }
51 51
52 /** 52 /**
53 * Test render_feed hook.
54 */
55 public function testMarkdownFeed()
56 {
57 $markdown = '# My title' . PHP_EOL . 'Very interesting content.';
58 $markdown .= '&#8212; <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>';
59 $data = array(
60 'links' => array(
61 0 => array(
62 'description' => $markdown,
63 ),
64 ),
65 );
66
67 $data = hook_markdown_render_feed($data, $this->conf);
68 $this->assertNotFalse(strpos($data['links'][0]['description'], '<h1>'));
69 $this->assertNotFalse(strpos($data['links'][0]['description'], '<p>'));
70 $this->assertStringEndsWith(
71 '&#8212; <a href="http://domain.tld/?0oc_VQ">Permalien</a></p></div>',
72 $data['links'][0]['description']
73 );
74 }
75
76 /**
53 * Test render_daily hook. 77 * Test render_daily hook.
54 * Only check that there is basic markdown rendering. 78 * Only check that there is basic markdown rendering.
55 */ 79 */
@@ -104,6 +128,37 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
104 $this->assertEquals($text, $reversedText); 128 $this->assertEquals($text, $reversedText);
105 } 129 }
106 130
131 public function testReverseFeedPermalink()
132 {
133 $text = 'Description... ';
134 $text .= '&#8212; <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>';
135 $expected = 'Description... &#8212; [Permalien](http://domain.tld/?0oc_VQ)';
136 $processedText = reverse_feed_permalink($text);
137
138 $this->assertEquals($expected, $processedText);
139 }
140
141 public function testReverseLastFeedPermalink()
142 {
143 $text = 'Description... ';
144 $text .= '<br>&#8212; <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>';
145 $expected = $text;
146 $text .= '<br>&#8212; <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>';
147 $expected .= '<br>&#8212; [Permalien](http://domain.tld/?0oc_VQ)';
148 $processedText = reverse_feed_permalink($text);
149
150 $this->assertEquals($expected, $processedText);
151 }
152
153 public function testReverseNoFeedPermalink()
154 {
155 $text = 'Hello! Where are you from?';
156 $expected = $text;
157 $processedText = reverse_feed_permalink($text);
158
159 $this->assertEquals($expected, $processedText);
160 }
161
107 /** 162 /**
108 * Test sanitize_html(). 163 * Test sanitize_html().
109 */ 164 */
diff --git a/tests/LoginManagerTest.php b/tests/security/LoginManagerTest.php
index 4159038e..f26cd1eb 100644
--- a/tests/LoginManagerTest.php
+++ b/tests/security/LoginManagerTest.php
@@ -1,5 +1,5 @@
1<?php 1<?php
2namespace Shaarli; 2namespace Shaarli\Security;
3 3
4require_once 'tests/utils/FakeConfigManager.php'; 4require_once 'tests/utils/FakeConfigManager.php';
5use \PHPUnit\Framework\TestCase; 5use \PHPUnit\Framework\TestCase;
@@ -9,15 +9,54 @@ use \PHPUnit\Framework\TestCase;
9 */ 9 */
10class LoginManagerTest extends TestCase 10class LoginManagerTest extends TestCase
11{ 11{
12 /** @var \FakeConfigManager Configuration Manager instance */
12 protected $configManager = null; 13 protected $configManager = null;
14
15 /** @var LoginManager Login Manager instance */
13 protected $loginManager = null; 16 protected $loginManager = null;
17
18 /** @var SessionManager Session Manager instance */
19 protected $sessionManager = null;
20
21 /** @var string Banned IP filename */
14 protected $banFile = 'sandbox/ipbans.php'; 22 protected $banFile = 'sandbox/ipbans.php';
23
24 /** @var string Log filename */
15 protected $logFile = 'sandbox/shaarli.log'; 25 protected $logFile = 'sandbox/shaarli.log';
26
27 /** @var array Simulates the $_COOKIE array */
28 protected $cookie = [];
29
30 /** @var array Simulates the $GLOBALS array */
16 protected $globals = []; 31 protected $globals = [];
17 protected $ipAddr = '127.0.0.1'; 32
33 /** @var array Simulates the $_SERVER array */
18 protected $server = []; 34 protected $server = [];
35
36 /** @var array Simulates the $_SESSION array */
37 protected $session = [];
38
39 /** @var string Advertised client IP address */
40 protected $clientIpAddress = '10.1.47.179';
41
42 /** @var string Local client IP address */
43 protected $ipAddr = '127.0.0.1';
44
45 /** @var string Trusted proxy IP address */
19 protected $trustedProxy = '10.1.1.100'; 46 protected $trustedProxy = '10.1.1.100';
20 47
48 /** @var string User login */
49 protected $login = 'johndoe';
50
51 /** @var string User password */
52 protected $password = 'IC4nHazL0g1n?';
53
54 /** @var string Hash of the salted user password */
55 protected $passwordHash = '';
56
57 /** @var string Salt used by hash functions */
58 protected $salt = '669e24fa9c5a59a613f98e8e38327384504a4af2';
59
21 /** 60 /**
22 * Prepare or reset test resources 61 * Prepare or reset test resources
23 */ 62 */
@@ -27,7 +66,12 @@ class LoginManagerTest extends TestCase
27 unlink($this->banFile); 66 unlink($this->banFile);
28 } 67 }
29 68
69 $this->passwordHash = sha1($this->password . $this->login . $this->salt);
70
30 $this->configManager = new \FakeConfigManager([ 71 $this->configManager = new \FakeConfigManager([
72 'credentials.login' => $this->login,
73 'credentials.hash' => $this->passwordHash,
74 'credentials.salt' => $this->salt,
31 'resource.ban_file' => $this->banFile, 75 'resource.ban_file' => $this->banFile,
32 'resource.log' => $this->logFile, 76 'resource.log' => $this->logFile,
33 'security.ban_after' => 4, 77 'security.ban_after' => 4,
@@ -35,10 +79,15 @@ class LoginManagerTest extends TestCase
35 'security.trusted_proxies' => [$this->trustedProxy], 79 'security.trusted_proxies' => [$this->trustedProxy],
36 ]); 80 ]);
37 81
82 $this->cookie = [];
83
38 $this->globals = &$GLOBALS; 84 $this->globals = &$GLOBALS;
39 unset($this->globals['IPBANS']); 85 unset($this->globals['IPBANS']);
40 86
41 $this->loginManager = new LoginManager($this->globals, $this->configManager); 87 $this->session = [];
88
89 $this->sessionManager = new SessionManager($this->session, $this->configManager);
90 $this->loginManager = new LoginManager($this->globals, $this->configManager, $this->sessionManager);
42 $this->server['REMOTE_ADDR'] = $this->ipAddr; 91 $this->server['REMOTE_ADDR'] = $this->ipAddr;
43 } 92 }
44 93
@@ -59,7 +108,7 @@ class LoginManagerTest extends TestCase
59 $this->banFile, 108 $this->banFile,
60 "<?php\n\$GLOBALS['IPBANS']=array('FAILURES' => array('127.0.0.1' => 99));\n?>" 109 "<?php\n\$GLOBALS['IPBANS']=array('FAILURES' => array('127.0.0.1' => 99));\n?>"
61 ); 110 );
62 new LoginManager($this->globals, $this->configManager); 111 new LoginManager($this->globals, $this->configManager, null);
63 $this->assertEquals(99, $this->globals['IPBANS']['FAILURES']['127.0.0.1']); 112 $this->assertEquals(99, $this->globals['IPBANS']['FAILURES']['127.0.0.1']);
64 } 113 }
65 114
@@ -196,4 +245,130 @@ class LoginManagerTest extends TestCase
196 $this->globals['IPBANS']['BANS'][$this->ipAddr] = time() - 3600; 245 $this->globals['IPBANS']['BANS'][$this->ipAddr] = time() - 3600;
197 $this->assertTrue($this->loginManager->canLogin($this->server)); 246 $this->assertTrue($this->loginManager->canLogin($this->server));
198 } 247 }
248
249 /**
250 * Generate a token depending on the user credentials and client IP
251 */
252 public function testGenerateStaySignedInToken()
253 {
254 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
255
256 $this->assertEquals(
257 sha1($this->passwordHash . $this->clientIpAddress . $this->salt),
258 $this->loginManager->getStaySignedInToken()
259 );
260 }
261
262 /**
263 * Check user login - Shaarli has not yet been configured
264 */
265 public function testCheckLoginStateNotConfigured()
266 {
267 $configManager = new \FakeConfigManager([
268 'resource.ban_file' => $this->banFile,
269 ]);
270 $loginManager = new LoginManager($this->globals, $configManager, null);
271 $loginManager->checkLoginState([], '');
272
273 $this->assertFalse($loginManager->isLoggedIn());
274 }
275
276 /**
277 * Check user login - the client cookie does not match the server token
278 */
279 public function testCheckLoginStateStaySignedInWithInvalidToken()
280 {
281 // simulate a previous login
282 $this->session = [
283 'ip' => $this->clientIpAddress,
284 'expires_on' => time() + 100,
285 ];
286 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
287 $this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = 'nope';
288
289 $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
290
291 $this->assertTrue($this->loginManager->isLoggedIn());
292 $this->assertTrue(empty($this->session['username']));
293 }
294
295 /**
296 * Check user login - the client cookie matches the server token
297 */
298 public function testCheckLoginStateStaySignedInWithValidToken()
299 {
300 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
301 $this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = $this->loginManager->getStaySignedInToken();
302
303 $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
304
305 $this->assertTrue($this->loginManager->isLoggedIn());
306 $this->assertEquals($this->login, $this->session['username']);
307 $this->assertEquals($this->clientIpAddress, $this->session['ip']);
308 }
309
310 /**
311 * Check user login - the session has expired
312 */
313 public function testCheckLoginStateSessionExpired()
314 {
315 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
316 $this->session['expires_on'] = time() - 100;
317
318 $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
319
320 $this->assertFalse($this->loginManager->isLoggedIn());
321 }
322
323 /**
324 * Check user login - the remote client IP has changed
325 */
326 public function testCheckLoginStateClientIpChanged()
327 {
328 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
329
330 $this->loginManager->checkLoginState($this->cookie, '10.7.157.98');
331
332 $this->assertFalse($this->loginManager->isLoggedIn());
333 }
334
335 /**
336 * Check user credentials - wrong login supplied
337 */
338 public function testCheckCredentialsWrongLogin()
339 {
340 $this->assertFalse(
341 $this->loginManager->checkCredentials('', '', 'b4dl0g1n', $this->password)
342 );
343 }
344
345 /**
346 * Check user credentials - wrong password supplied
347 */
348 public function testCheckCredentialsWrongPassword()
349 {
350 $this->assertFalse(
351 $this->loginManager->checkCredentials('', '', $this->login, 'b4dp455wd')
352 );
353 }
354
355 /**
356 * Check user credentials - wrong login and password supplied
357 */
358 public function testCheckCredentialsWrongLoginAndPassword()
359 {
360 $this->assertFalse(
361 $this->loginManager->checkCredentials('', '', 'b4dl0g1n', 'b4dp455wd')
362 );
363 }
364
365 /**
366 * Check user credentials - correct login and password supplied
367 */
368 public function testCheckCredentialsGoodLoginAndPassword()
369 {
370 $this->assertTrue(
371 $this->loginManager->checkCredentials('', '', $this->login, $this->password)
372 );
373 }
199} 374}
diff --git a/tests/security/SessionManagerTest.php b/tests/security/SessionManagerTest.php
new file mode 100644
index 00000000..9bd868f8
--- /dev/null
+++ b/tests/security/SessionManagerTest.php
@@ -0,0 +1,273 @@
1<?php
2require_once 'tests/utils/FakeConfigManager.php';
3
4// Initialize reference data _before_ PHPUnit starts a session
5require_once 'tests/utils/ReferenceSessionIdHashes.php';
6ReferenceSessionIdHashes::genAllHashes();
7
8use \Shaarli\Security\SessionManager;
9use \PHPUnit\Framework\TestCase;
10
11
12/**
13 * Test coverage for SessionManager
14 */
15class SessionManagerTest extends TestCase
16{
17 /** @var array Session ID hashes */
18 protected static $sidHashes = null;
19
20 /** @var \FakeConfigManager ConfigManager substitute for testing */
21 protected $conf = null;
22
23 /** @var array $_SESSION array for testing */
24 protected $session = [];
25
26 /** @var SessionManager Server-side session management abstraction */
27 protected $sessionManager = null;
28
29 /**
30 * Assign reference data
31 */
32 public static function setUpBeforeClass()
33 {
34 self::$sidHashes = ReferenceSessionIdHashes::getHashes();
35 }
36
37 /**
38 * Initialize or reset test resources
39 */
40 public function setUp()
41 {
42 $this->conf = new FakeConfigManager([
43 'credentials.login' => 'johndoe',
44 'credentials.salt' => 'salt',
45 'security.session_protection_disabled' => false,
46 ]);
47 $this->session = [];
48 $this->sessionManager = new SessionManager($this->session, $this->conf);
49 }
50
51 /**
52 * Generate a session token
53 */
54 public function testGenerateToken()
55 {
56 $token = $this->sessionManager->generateToken();
57
58 $this->assertEquals(1, $this->session['tokens'][$token]);
59 $this->assertEquals(40, strlen($token));
60 }
61
62 /**
63 * Check a session token
64 */
65 public function testCheckToken()
66 {
67 $token = '4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b';
68 $session = [
69 'tokens' => [
70 $token => 1,
71 ],
72 ];
73 $sessionManager = new SessionManager($session, $this->conf);
74
75 // check and destroy the token
76 $this->assertTrue($sessionManager->checkToken($token));
77 $this->assertFalse(isset($session['tokens'][$token]));
78
79 // ensure the token has been destroyed
80 $this->assertFalse($sessionManager->checkToken($token));
81 }
82
83 /**
84 * Generate and check a session token
85 */
86 public function testGenerateAndCheckToken()
87 {
88 $token = $this->sessionManager->generateToken();
89
90 // ensure a token has been generated
91 $this->assertEquals(1, $this->session['tokens'][$token]);
92 $this->assertEquals(40, strlen($token));
93
94 // check and destroy the token
95 $this->assertTrue($this->sessionManager->checkToken($token));
96 $this->assertFalse(isset($this->session['tokens'][$token]));
97
98 // ensure the token has been destroyed
99 $this->assertFalse($this->sessionManager->checkToken($token));
100 }
101
102 /**
103 * Check an invalid session token
104 */
105 public function testCheckInvalidToken()
106 {
107 $this->assertFalse($this->sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b'));
108 }
109
110 /**
111 * Test SessionManager::checkId with a valid ID - TEST ALL THE HASHES!
112 *
113 * This tests extensively covers all hash algorithms / bit representations
114 */
115 public function testIsAnyHashSessionIdValid()
116 {
117 foreach (self::$sidHashes as $algo => $bpcs) {
118 foreach ($bpcs as $bpc => $hash) {
119 $this->assertTrue(SessionManager::checkId($hash));
120 }
121 }
122 }
123
124 /**
125 * Test checkId with a valid ID - SHA-1 hashes
126 */
127 public function testIsSha1SessionIdValid()
128 {
129 $this->assertTrue(SessionManager::checkId(sha1('shaarli')));
130 }
131
132 /**
133 * Test checkId with a valid ID - SHA-256 hashes
134 */
135 public function testIsSha256SessionIdValid()
136 {
137 $this->assertTrue(SessionManager::checkId(hash('sha256', 'shaarli')));
138 }
139
140 /**
141 * Test checkId with a valid ID - SHA-512 hashes
142 */
143 public function testIsSha512SessionIdValid()
144 {
145 $this->assertTrue(SessionManager::checkId(hash('sha512', 'shaarli')));
146 }
147
148 /**
149 * Test checkId with invalid IDs.
150 */
151 public function testIsSessionIdInvalid()
152 {
153 $this->assertFalse(SessionManager::checkId(''));
154 $this->assertFalse(SessionManager::checkId([]));
155 $this->assertFalse(
156 SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
157 );
158 }
159
160 /**
161 * Store login information after a successful login
162 */
163 public function testStoreLoginInfo()
164 {
165 $this->sessionManager->storeLoginInfo('ip_id');
166
167 $this->assertGreaterThan(time(), $this->session['expires_on']);
168 $this->assertEquals('ip_id', $this->session['ip']);
169 $this->assertEquals('johndoe', $this->session['username']);
170 }
171
172 /**
173 * Extend a server-side session by SessionManager::$SHORT_TIMEOUT
174 */
175 public function testExtendSession()
176 {
177 $this->sessionManager->extendSession();
178
179 $this->assertGreaterThan(time(), $this->session['expires_on']);
180 $this->assertLessThanOrEqual(
181 time() + SessionManager::$SHORT_TIMEOUT,
182 $this->session['expires_on']
183 );
184 }
185
186 /**
187 * Extend a server-side session by SessionManager::$LONG_TIMEOUT
188 */
189 public function testExtendSessionStaySignedIn()
190 {
191 $this->sessionManager->setStaySignedIn(true);
192 $this->sessionManager->extendSession();
193
194 $this->assertGreaterThan(time(), $this->session['expires_on']);
195 $this->assertGreaterThan(
196 time() + SessionManager::$LONG_TIMEOUT - 10,
197 $this->session['expires_on']
198 );
199 $this->assertLessThanOrEqual(
200 time() + SessionManager::$LONG_TIMEOUT,
201 $this->session['expires_on']
202 );
203 }
204
205 /**
206 * Unset session variables after logging out
207 */
208 public function testLogout()
209 {
210 $this->session = [
211 'ip' => 'ip_id',
212 'expires_on' => time() + 1000,
213 'username' => 'johndoe',
214 'visibility' => 'public',
215 'untaggedonly' => false,
216 ];
217 $this->sessionManager->logout();
218
219 $this->assertFalse(isset($this->session['ip']));
220 $this->assertFalse(isset($this->session['expires_on']));
221 $this->assertFalse(isset($this->session['username']));
222 $this->assertFalse(isset($this->session['visibility']));
223 $this->assertFalse(isset($this->session['untaggedonly']));
224 }
225
226 /**
227 * The session is active and expiration time has been reached
228 */
229 public function testHasExpiredTimeElapsed()
230 {
231 $this->session['expires_on'] = time() - 10;
232
233 $this->assertTrue($this->sessionManager->hasSessionExpired());
234 }
235
236 /**
237 * The session is active and expiration time has not been reached
238 */
239 public function testHasNotExpired()
240 {
241 $this->session['expires_on'] = time() + 1000;
242
243 $this->assertFalse($this->sessionManager->hasSessionExpired());
244 }
245
246 /**
247 * Session hijacking protection is disabled, we assume the IP has not changed
248 */
249 public function testHasClientIpChangedNoSessionProtection()
250 {
251 $this->conf->set('security.session_protection_disabled', true);
252
253 $this->assertFalse($this->sessionManager->hasClientIpChanged(''));
254 }
255
256 /**
257 * The client IP identifier has not changed
258 */
259 public function testHasClientIpChangedNope()
260 {
261 $this->session['ip'] = 'ip_id';
262 $this->assertFalse($this->sessionManager->hasClientIpChanged('ip_id'));
263 }
264
265 /**
266 * The client IP identifier has changed
267 */
268 public function testHasClientIpChanged()
269 {
270 $this->session['ip'] = 'ip_id_one';
271 $this->assertTrue($this->sessionManager->hasClientIpChanged('ip_id_two'));
272 }
273}
diff --git a/tests/utils/FakeConfigManager.php b/tests/utils/FakeConfigManager.php
index 85434de7..360b34a9 100644
--- a/tests/utils/FakeConfigManager.php
+++ b/tests/utils/FakeConfigManager.php
@@ -42,4 +42,16 @@ class FakeConfigManager
42 } 42 }
43 return $key; 43 return $key;
44 } 44 }
45
46 /**
47 * Check if a setting exists
48 *
49 * @param string $setting Asked setting, keys separated with dots
50 *
51 * @return bool true if the setting exists, false otherwise
52 */
53 public function exists($setting)
54 {
55 return array_key_exists($setting, $this->values);
56 }
45} 57}
diff --git a/tpl/default/404.html b/tpl/default/404.html
index 2de6b6da..fd337cad 100644
--- a/tpl/default/404.html
+++ b/tpl/default/404.html
@@ -6,7 +6,7 @@
6<body> 6<body>
7<div id="pageheader"> 7<div id="pageheader">
8 {include="page.header"} 8 {include="page.header"}
9<div class="center" id="page404"> 9<div class="center" id="page404" class="page404-container">
10 <h2>{'Sorry, nothing to see here.'|t}</h2> 10 <h2>{'Sorry, nothing to see here.'|t}</h2>
11 <img src="img/sad_star.png"> 11 <img src="img/sad_star.png">
12 <p>{$error_message}</p> 12 <p>{$error_message}</p>
diff --git a/tpl/default/editlink.html b/tpl/default/editlink.html
index d03fd72f..d8c57155 100644
--- a/tpl/default/editlink.html
+++ b/tpl/default/editlink.html
@@ -5,12 +5,11 @@
5</head> 5</head>
6<body> 6<body>
7 {include="page.header"} 7 {include="page.header"}
8 <div id="editlinkform" class="pure-g"> 8 <div id="editlinkform" class="edit-link-container" class="pure-g">
9 <div class="pure-u-lg-1-5 pure-u-1-24"></div> 9 <div class="pure-u-lg-1-5 pure-u-1-24"></div>
10 <form method="post" name="linkform" class="page-form pure-u-lg-3-5 pure-u-22-24 page-form page-form-light"> 10 <form method="post" name="linkform" class="page-form pure-u-lg-3-5 pure-u-22-24 page-form page-form-light">
11 <h2 class="window-title"> 11 <h2 class="window-title">
12 {if="!$link_is_new"}{'Edit'|t}{/if} 12 {if="!$link_is_new"}{'Edit Shaare'|t}{else}{'New Shaare'|t}{/if}
13 {'Shaare'|t}
14 </h2> 13 </h2>
15 <input type="hidden" name="lf_linkdate" value="{$link.linkdate}"> 14 <input type="hidden" name="lf_linkdate" value="{$link.linkdate}">
16 {if="isset($link.id)"} 15 {if="isset($link.id)"}
diff --git a/tpl/default/import.html b/tpl/default/import.html
index 000a50ac..bdc9086e 100644
--- a/tpl/default/import.html
+++ b/tpl/default/import.html
@@ -15,7 +15,7 @@
15 </div> 15 </div>
16 16
17 <input type="hidden" name="token" value="{$token}"> 17 <input type="hidden" name="token" value="{$token}">
18 <div class="center" id="import-field"> 18 <div class="center import-field-container" id="import-field">
19 <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}"> 19 <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}">
20 <input type="file" name="filetoupload"> 20 <input type="file" name="filetoupload">
21 <p><br>{'Maximum size allowed:'|t} <strong>{$maxfilesizeHuman}</strong></p> 21 <p><br>{'Maximum size allowed:'|t} <strong>{$maxfilesizeHuman}</strong></p>
diff --git a/tpl/default/linklist.html b/tpl/default/linklist.html
index 933c1ef2..322cddd5 100644
--- a/tpl/default/linklist.html
+++ b/tpl/default/linklist.html
@@ -16,7 +16,7 @@
16</div> 16</div>
17 17
18<input type="hidden" name="token" value="{$token}"> 18<input type="hidden" name="token" value="{$token}">
19<div id="search-linklist"> 19<div id="search-linklist" class="searchform-block search-linklist">
20 20
21 <form method="GET" class="pure-form searchform" name="searchform"> 21 <form method="GET" class="pure-form searchform" name="searchform">
22 <input type="text" tabindex="1" name="searchterm" class="searchterm" placeholder="{'Search text'|t}" 22 <input type="text" tabindex="1" name="searchterm" class="searchterm" placeholder="{'Search text'|t}"
@@ -136,7 +136,7 @@
136 <div class="linklist-item-thumbnail">{$thumb}</div> 136 <div class="linklist-item-thumbnail">{$thumb}</div>
137 {/if} 137 {/if}
138 138
139 {if="isLoggedIn()"} 139 {if="$is_logged_in"}
140 <div class="linklist-item-editbuttons"> 140 <div class="linklist-item-editbuttons">
141 {if="$value.private"} 141 {if="$value.private"}
142 <span class="label label-private">{$strPrivate}</span> 142 <span class="label label-private">{$strPrivate}</span>
@@ -179,7 +179,7 @@
179 179
180 <div class="linklist-item-infos-date-url-block pure-g"> 180 <div class="linklist-item-infos-date-url-block pure-g">
181 <div class="linklist-item-infos-dateblock pure-u-lg-7-12 pure-u-1"> 181 <div class="linklist-item-infos-dateblock pure-u-lg-7-12 pure-u-1">
182 {if="isLoggedIn()"} 182 {if="$is_logged_in"}
183 <div class="linklist-item-infos-controls-group pure-u-0 pure-u-lg-visible"> 183 <div class="linklist-item-infos-controls-group pure-u-0 pure-u-lg-visible">
184 <span class="linklist-item-infos-controls-item ctrl-checkbox"> 184 <span class="linklist-item-infos-controls-item ctrl-checkbox">
185 <input type="checkbox" class="delete-checkbox" value="{$value.id}"> 185 <input type="checkbox" class="delete-checkbox" value="{$value.id}">
@@ -196,7 +196,7 @@
196 </div> 196 </div>
197 {/if} 197 {/if}
198 <a href="?{$value.shorturl}" title="{$strPermalink}"> 198 <a href="?{$value.shorturl}" title="{$strPermalink}">
199 {if="!$hide_timestamps || isLoggedIn()"} 199 {if="!$hide_timestamps || $is_logged_in"}
200 {$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink} 200 {$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink}
201 <span class="linkdate" title="{$updated}"> 201 <span class="linkdate" title="{$updated}">
202 <i class="fa fa-clock-o"></i> 202 <i class="fa fa-clock-o"></i>
@@ -236,7 +236,7 @@
236 {if="$link_plugin_counter - 1 != $counter"}&middot;{/if} 236 {if="$link_plugin_counter - 1 != $counter"}&middot;{/if}
237 {/loop} 237 {/loop}
238 {/if} 238 {/if}
239 {if="isLoggedIn()"} 239 {if="$is_logged_in"}
240 &middot; 240 &middot;
241 <a href="?delete_link&amp;lf_linkdate={$value.id}&amp;token={$token}" 241 <a href="?delete_link&amp;lf_linkdate={$value.id}&amp;token={$token}"
242 title="{$strDelete}" class="delete-link confirm-delete"> 242 title="{$strDelete}" class="delete-link confirm-delete">
diff --git a/tpl/default/linklist.paging.html b/tpl/default/linklist.paging.html
index 72bdd931..5309e348 100644
--- a/tpl/default/linklist.paging.html
+++ b/tpl/default/linklist.paging.html
@@ -1,11 +1,11 @@
1<div class="linklist-paging"> 1<div class="linklist-paging">
2 <div class="paging pure-g"> 2 <div class="paging pure-g">
3 <div class="linklist-filters pure-u-1-3"> 3 <div class="linklist-filters pure-u-1-3">
4 {if="isLoggedIn() or !empty($action_plugin)"} 4 {if="$is_logged_in or !empty($action_plugin)"}
5 <span class="linklist-filters-text pure-u-0 pure-u-lg-visible"> 5 <span class="linklist-filters-text pure-u-0 pure-u-lg-visible">
6 {'Filters'|t} 6 {'Filters'|t}
7 </span> 7 </span>
8 {if="isLoggedIn()"} 8 {if="$is_logged_in"}
9 <a href="?visibility=private" title="{'Only display private links'|t}" 9 <a href="?visibility=private" title="{'Only display private links'|t}"
10 class="{if="$visibility==='private'"}filter-on{else}filter-off{/if}" 10 class="{if="$visibility==='private'"}filter-on{else}filter-off{/if}"
11 ><i class="fa fa-user-secret"></i></a> 11 ><i class="fa fa-user-secret"></i></a>
diff --git a/tpl/default/loginform.html b/tpl/default/loginform.html
index d481f452..3cdab65a 100644
--- a/tpl/default/loginform.html
+++ b/tpl/default/loginform.html
@@ -18,7 +18,7 @@
18{else} 18{else}
19 <div class="pure-g"> 19 <div class="pure-g">
20 <div class="pure-u-lg-1-3 pure-u-1-24"></div> 20 <div class="pure-u-lg-1-3 pure-u-1-24"></div>
21 <div id="login-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24"> 21 <div id="login-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24 login-form-container">
22 <form method="post" name="loginform"> 22 <form method="post" name="loginform">
23 <h2 class="window-title">{'Login'|t}</h2> 23 <h2 class="window-title">{'Login'|t}</h2>
24 <div> 24 <div>
diff --git a/tpl/default/page.footer.html b/tpl/default/page.footer.html
index f4f09e92..5af39be7 100644
--- a/tpl/default/page.footer.html
+++ b/tpl/default/page.footer.html
@@ -2,9 +2,9 @@
2 2
3<div class="pure-g"> 3<div class="pure-g">
4 <div class="pure-u-2-24"></div> 4 <div class="pure-u-2-24"></div>
5 <div id="footer" class="pure-u-20-24"> 5 <div id="footer" class="pure-u-20-24 footer-container">
6 <strong><a href="https://github.com/shaarli/Shaarli">Shaarli</a></strong> 6 <strong><a href="https://github.com/shaarli/Shaarli">Shaarli</a></strong>
7 {if="isLoggedIn()===true"} 7 {if="$is_logged_in===true"}
8 {$version} 8 {$version}
9 {/if} 9 {/if}
10 &middot; 10 &middot;
diff --git a/tpl/default/page.header.html b/tpl/default/page.header.html
index 6f15c1c5..82568d63 100644
--- a/tpl/default/page.header.html
+++ b/tpl/default/page.header.html
@@ -17,7 +17,7 @@
17 {$shaarlititle} 17 {$shaarlititle}
18 </a> 18 </a>
19 </li> 19 </li>
20 {if="isLoggedIn() || $openshaarli"} 20 {if="$is_logged_in || $openshaarli"}
21 <li class="pure-menu-item"> 21 <li class="pure-menu-item">
22 <a href="?do=addlink" class="pure-menu-link" id="shaarli-menu-shaare"> 22 <a href="?do=addlink" class="pure-menu-link" id="shaarli-menu-shaare">
23 <i class="fa fa-plus" ></i> {'Shaare'|t} 23 <i class="fa fa-plus" ></i> {'Shaare'|t}
@@ -50,7 +50,7 @@
50 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss"> 50 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss">
51 <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a> 51 <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a>
52 </li> 52 </li>
53 {if="isLoggedIn()"} 53 {if="$is_logged_in"}
54 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout"> 54 <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout">
55 <a href="?do=logout" class="pure-menu-link">{'Logout'|t}</a> 55 <a href="?do=logout" class="pure-menu-link">{'Logout'|t}</a>
56 </li> 56 </li>
@@ -74,7 +74,7 @@
74 <i class="fa fa-rss"></i> 74 <i class="fa fa-rss"></i>
75 </a> 75 </a>
76 </li> 76 </li>
77 {if="!isLoggedIn()"} 77 {if="!$is_logged_in"}
78 <li class="pure-menu-item" id="shaarli-menu-desktop-login"> 78 <li class="pure-menu-item" id="shaarli-menu-desktop-login">
79 <a href="?do=login" class="pure-menu-link" 79 <a href="?do=login" class="pure-menu-link"
80 data-open-id="header-login-form" 80 data-open-id="header-login-form"
@@ -95,8 +95,8 @@
95 </div> 95 </div>
96</div> 96</div>
97 97
98<div id="content"> 98<div id="content" class="container">
99 <div id="search" class="subheader-form"> 99 <div id="search" class="subheader-form searchform-block header-search">
100 <form method="GET" class="pure-form searchform" name="searchform"> 100 <form method="GET" class="pure-form searchform" name="searchform">
101 <input type="text" tabindex="1" id="searchform_value" name="searchterm" placeholder="{'Search text'|t}" 101 <input type="text" tabindex="1" id="searchform_value" name="searchterm" placeholder="{'Search text'|t}"
102 {if="!empty($search_term)"} 102 {if="!empty($search_term)"}
@@ -120,9 +120,9 @@
120 </div> 120 </div>
121 </div> 121 </div>
122 </div> 122 </div>
123 {if="!isLoggedIn()"} 123 {if="!$is_logged_in"}
124 <form method="post" name="loginform"> 124 <form method="post" name="loginform">
125 <div class="subheader-form" id="header-login-form"> 125 <div class="subheader-form header-login-form" id="header-login-form">
126 <input type="text" name="login" placeholder="{'Username'|t}" tabindex="3"> 126 <input type="text" name="login" placeholder="{'Username'|t}" tabindex="3">
127 <input type="password" name="password" placeholder="{'Password'|t}" tabindex="5"> 127 <input type="password" name="password" placeholder="{'Password'|t}" tabindex="5">
128 <div class="remember-me"> 128 <div class="remember-me">
@@ -155,7 +155,7 @@
155 </div> 155 </div>
156{/if} 156{/if}
157 157
158{if="!empty($plugin_errors) && isLoggedIn()"} 158{if="!empty($plugin_errors) && $is_logged_in"}
159 <div class="pure-g new-version-message pure-alert pure-alert-error pure-alert-closable" id="shaarli-errors-alert"> 159 <div class="pure-g new-version-message pure-alert pure-alert-error pure-alert-closable" id="shaarli-errors-alert">
160 <div class="pure-u-2-24"></div> 160 <div class="pure-u-2-24"></div>
161 <div class="pure-u-20-24"> 161 <div class="pure-u-20-24">
diff --git a/tpl/default/picwall.html b/tpl/default/picwall.html
index 23796ac9..2f7e03dc 100644
--- a/tpl/default/picwall.html
+++ b/tpl/default/picwall.html
@@ -18,9 +18,9 @@
18 {/loop} 18 {/loop}
19 </div> 19 </div>
20 20
21 <div id="picwall_container"> 21 <div id="picwall_container" class="picwall-container">
22 {loop="$linksToDisplay"} 22 {loop="$linksToDisplay"}
23 <div class="picwall_pictureframe"> 23 <div class="picwall-pictureframe">
24 {$value.thumbnail}<a href="{$value.real_url}"><span class="info">{$value.title}</span></a> 24 {$value.thumbnail}<a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
25 {loop="$value.picwall_plugin"} 25 {loop="$value.picwall_plugin"}
26 {$value} 26 {$value}
diff --git a/tpl/default/pluginsadmin.html b/tpl/default/pluginsadmin.html
index fa26c859..8f2597df 100644
--- a/tpl/default/pluginsadmin.html
+++ b/tpl/default/pluginsadmin.html
@@ -16,7 +16,7 @@
16 <div class="clear"></div> 16 <div class="clear"></div>
17</noscript> 17</noscript>
18 18
19<form method="POST" action="?do=save_pluginadmin" name="pluginform" id="pluginform"> 19<form method="POST" action="?do=save_pluginadmin" name="pluginform" id="pluginform" class="pluginform-container">
20 <div class="pure-g"> 20 <div class="pure-g">
21 <div class="pure-u-lg-1-8 pure-u-1-24"></div> 21 <div class="pure-u-lg-1-8 pure-u-1-24"></div>
22 <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete"> 22 <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete">
diff --git a/tpl/default/tag.cloud.html b/tpl/default/tag.cloud.html
index 12701465..9e52158d 100644
--- a/tpl/default/tag.cloud.html
+++ b/tpl/default/tag.cloud.html
@@ -45,7 +45,7 @@
45 {/loop} 45 {/loop}
46 </div> 46 </div>
47 47
48 <div id="cloudtag"> 48 <div id="cloudtag" class="cloudtag-container">
49 {loop="tags"} 49 {loop="tags"}
50 <a href="?searchtags={$key|urlencode} {$search_tags|urlencode}" style="font-size:{$value.size}em;">{$key}</a 50 <a href="?searchtags={$key|urlencode} {$search_tags|urlencode}" style="font-size:{$value.size}em;">{$key}</a
51 ><a href="?addtag={$key|urlencode}" title="{'Filter by tag'|t}" class="count">{$value.count}</a> 51 ><a href="?addtag={$key|urlencode}" title="{'Filter by tag'|t}" class="count">{$value.count}</a>
diff --git a/tpl/default/tag.list.html b/tpl/default/tag.list.html
index 7140c67a..bcddcd56 100644
--- a/tpl/default/tag.list.html
+++ b/tpl/default/tag.list.html
@@ -21,7 +21,7 @@
21 </p> 21 </p>
22 {/if} 22 {/if}
23 23
24 <div id="search-tagcloud" class="pure-g"> 24 <div id="search-tagcloud" class="pure-g searchform-block search-tagcloud">
25 <div class="pure-u-lg-1-4"></div> 25 <div class="pure-u-lg-1-4"></div>
26 <div class="pure-u-1 pure-u-lg-1-2"> 26 <div class="pure-u-1 pure-u-lg-1-2">
27 <form method="GET"> 27 <form method="GET">
@@ -45,11 +45,11 @@
45 {/loop} 45 {/loop}
46 </div> 46 </div>
47 47
48 <div id="taglist"> 48 <div id="taglist" class="taglist-container">
49 {loop="tags"} 49 {loop="tags"}
50 <div class="tag-list-item pure-g" data-tag="{$key}"> 50 <div class="tag-list-item pure-g" data-tag="{$key}">
51 <div class="pure-u-1"> 51 <div class="pure-u-1">
52 {if="isLoggedIn()===true"} 52 {if="$is_logged_in===true"}
53 <a href="#" class="delete-tag"><i class="fa fa-trash"></i></a>&nbsp;&nbsp; 53 <a href="#" class="delete-tag"><i class="fa fa-trash"></i></a>&nbsp;&nbsp;
54 <a href="?do=changetag&fromtag={$key|urlencode}" class="rename-tag"> 54 <a href="?do=changetag&fromtag={$key|urlencode}" class="rename-tag">
55 <i class="fa fa-pencil-square-o {$key}"></i> 55 <i class="fa fa-pencil-square-o {$key}"></i>
@@ -63,7 +63,7 @@
63 {$value} 63 {$value}
64 {/loop} 64 {/loop}
65 </div> 65 </div>
66 {if="isLoggedIn()===true"} 66 {if="$is_logged_in===true"}
67 <div class="rename-tag-form pure-u-1"> 67 <div class="rename-tag-form pure-u-1">
68 <input type="text" name="{$key}" value="{$key}" class="rename-tag-input" /> 68 <input type="text" name="{$key}" value="{$key}" class="rename-tag-input" />
69 <a href="#" class="validate-rename-tag"><i class="fa fa-check"></i></a> 69 <a href="#" class="validate-rename-tag"><i class="fa fa-check"></i></a>
@@ -81,7 +81,7 @@
81 </div> 81 </div>
82</div> 82</div>
83 83
84{if="isLoggedIn()===true"} 84{if="$is_logged_in===true"}
85 <input type="hidden" name="taglist" value="{loop="$tags"}{$key} {/loop}" 85 <input type="hidden" name="taglist" value="{loop="$tags"}{$key} {/loop}"
86{/if} 86{/if}
87 87
diff --git a/tpl/default/tools.html b/tpl/default/tools.html
index 72fd58af..ece66884 100644
--- a/tpl/default/tools.html
+++ b/tpl/default/tools.html
@@ -146,8 +146,14 @@
146 </div> 146 </div>
147 <div class="tools-item"> 147 <div class="tools-item">
148 <a href="https://play.google.com/store/apps/details?id=com.dimtion.shaarlier&hl=fr" 148 <a href="https://play.google.com/store/apps/details?id=com.dimtion.shaarlier&hl=fr"
149 title="Android"> 149 title="Android Shaarlier">
150 <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Android</span> 150 <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Android Shaarlier</span>
151 </a>
152 </div>
153 <div class="tools-item">
154 <a href="https://stakali.toneiv.eu/"
155 title="Android Stakali">
156 <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Android Stakali</span>
151 </a> 157 </a>
152 </div> 158 </div>
153 <div class="tools-item"> 159 <div class="tools-item">
diff --git a/tpl/vintage/daily.html b/tpl/vintage/daily.html
index 42db16a7..ede35910 100644
--- a/tpl/vintage/daily.html
+++ b/tpl/vintage/daily.html
@@ -53,7 +53,7 @@
53 <img src="img/squiggle.png" width="25" height="26" title="permalink" alt="permalink"> 53 <img src="img/squiggle.png" width="25" height="26" title="permalink" alt="permalink">
54 </a> 54 </a>
55 </div> 55 </div>
56 {if="!$hide_timestamps || isLoggedIn()"} 56 {if="!$hide_timestamps || $is_logged_in"}
57 <div class="dailyEntryLinkdate"> 57 <div class="dailyEntryLinkdate">
58 <a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a> 58 <a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a>
59 </div> 59 </div>
diff --git a/tpl/vintage/linklist.html b/tpl/vintage/linklist.html
index e7137246..1ca51be3 100644
--- a/tpl/vintage/linklist.html
+++ b/tpl/vintage/linklist.html
@@ -82,7 +82,7 @@
82 <a id="{$value.shorturl}"></a> 82 <a id="{$value.shorturl}"></a>
83 <div class="thumbnail">{$value.url|thumbnail}</div> 83 <div class="thumbnail">{$value.url|thumbnail}</div>
84 <div class="linkcontainer"> 84 <div class="linkcontainer">
85 {if="isLoggedIn()"} 85 {if="$is_logged_in"}
86 <div class="linkeditbuttons"> 86 <div class="linkeditbuttons">
87 <form method="GET" class="buttoneditform"> 87 <form method="GET" class="buttoneditform">
88 <input type="hidden" name="edit_link" value="{$value.id}"> 88 <input type="hidden" name="edit_link" value="{$value.id}">
@@ -102,7 +102,7 @@
102 </span> 102 </span>
103 <br> 103 <br>
104 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if} 104 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
105 {if="!$hide_timestamps || isLoggedIn()"} 105 {if="!$hide_timestamps || $is_logged_in"}
106 {$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'} 106 {$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'}
107 <span class="linkdate" title="Permalink"> 107 <span class="linkdate" title="Permalink">
108 <a href="?{$value.shorturl}"> 108 <a href="?{$value.shorturl}">
diff --git a/tpl/vintage/linklist.paging.html b/tpl/vintage/linklist.paging.html
index e3b88ee6..35149a6b 100644
--- a/tpl/vintage/linklist.paging.html
+++ b/tpl/vintage/linklist.paging.html
@@ -1,5 +1,5 @@
1<div class="paging"> 1<div class="paging">
2{if="isLoggedIn()"} 2{if="$is_logged_in"}
3 <div class="paging_privatelinks"> 3 <div class="paging_privatelinks">
4 <a href="?visibility=private"> 4 <a href="?visibility=private">
5 {if="$visibility=='private'"} 5 {if="$visibility=='private'"}
diff --git a/tpl/vintage/page.footer.html b/tpl/vintage/page.footer.html
index 1485e1ce..f409721e 100644
--- a/tpl/vintage/page.footer.html
+++ b/tpl/vintage/page.footer.html
@@ -25,7 +25,7 @@
25 25
26<script src="js/shaarli.min.js"></script> 26<script src="js/shaarli.min.js"></script>
27 27
28{if="isLoggedIn()"} 28{if="$is_logged_in"}
29<script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script> 29<script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script>
30{/if} 30{/if}
31 31
diff --git a/tpl/vintage/page.header.html b/tpl/vintage/page.header.html
index 8a58844e..40c53e5b 100644
--- a/tpl/vintage/page.header.html
+++ b/tpl/vintage/page.header.html
@@ -17,7 +17,7 @@
17 {ignore} When called as a popup from bookmarklet, do not display menu. {/ignore} 17 {ignore} When called as a popup from bookmarklet, do not display menu. {/ignore}
18{else} 18{else}
19<li><a href="{$titleLink}" class="nomobile">Home</a></li> 19<li><a href="{$titleLink}" class="nomobile">Home</a></li>
20 {if="isLoggedIn()"} 20 {if="$is_logged_in"}
21 <li><a href="?do=logout">Logout</a></li> 21 <li><a href="?do=logout">Logout</a></li>
22 <li><a href="?do=tools">Tools</a></li> 22 <li><a href="?do=tools">Tools</a></li>
23 <li><a href="?do=addlink">Add link</a></li> 23 <li><a href="?do=addlink">Add link</a></li>
@@ -46,7 +46,7 @@
46 </ul> 46 </ul>
47</div> 47</div>
48 48
49{if="!empty($plugin_errors) && isLoggedIn()"} 49{if="!empty($plugin_errors) && $is_logged_in"}
50 <ul class="errors"> 50 <ul class="errors">
51 {loop="$plugin_errors"} 51 {loop="$plugin_errors"}
52 <li>{$value}</li> 52 <li>{$value}</li>
diff --git a/yarn.lock b/yarn.lock
index aeeef8a7..f1c8e9cf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -30,11 +30,19 @@ acorn@^5.0.0, acorn@^5.4.0:
30 version "5.4.1" 30 version "5.4.1"
31 resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102" 31 resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102"
32 32
33acorn@^5.5.0:
34 version "5.5.3"
35 resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
36
37ajv-keywords@^1.0.0:
38 version "1.5.1"
39 resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
40
33ajv-keywords@^2.0.0, ajv-keywords@^2.1.0: 41ajv-keywords@^2.0.0, ajv-keywords@^2.1.0:
34 version "2.1.1" 42 version "2.1.1"
35 resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" 43 resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
36 44
37ajv@^4.9.1: 45ajv@^4.7.0, ajv@^4.9.1:
38 version "4.11.8" 46 version "4.11.8"
39 resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" 47 resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
40 dependencies: 48 dependencies:
@@ -66,6 +74,10 @@ amdefine@>=0.0.4:
66 version "1.0.1" 74 version "1.0.1"
67 resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" 75 resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
68 76
77ansi-escapes@^1.1.0:
78 version "1.4.0"
79 resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
80
69ansi-escapes@^3.0.0: 81ansi-escapes@^3.0.0:
70 version "3.0.0" 82 version "3.0.0"
71 resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" 83 resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
@@ -1001,6 +1013,10 @@ browserslist@^2.1.2:
1001 caniuse-lite "^1.0.30000792" 1013 caniuse-lite "^1.0.30000792"
1002 electron-to-chromium "^1.3.30" 1014 electron-to-chromium "^1.3.30"
1003 1015
1016buffer-from@^1.0.0:
1017 version "1.0.0"
1018 resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
1019
1004buffer-xor@^1.0.3: 1020buffer-xor@^1.0.3:
1005 version "1.0.3" 1021 version "1.0.3"
1006 resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" 1022 resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -1086,7 +1102,7 @@ center-align@^0.1.1:
1086 align-text "^0.1.3" 1102 align-text "^0.1.3"
1087 lazy-cache "^1.0.3" 1103 lazy-cache "^1.0.3"
1088 1104
1089chalk@^1.1.1, chalk@^1.1.3: 1105chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
1090 version "1.1.3" 1106 version "1.1.3"
1091 resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 1107 resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
1092 dependencies: 1108 dependencies:
@@ -1140,6 +1156,12 @@ clap@^1.0.9:
1140 dependencies: 1156 dependencies:
1141 chalk "^1.1.3" 1157 chalk "^1.1.3"
1142 1158
1159cli-cursor@^1.0.1:
1160 version "1.0.2"
1161 resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
1162 dependencies:
1163 restore-cursor "^1.0.1"
1164
1143cli-cursor@^2.1.0: 1165cli-cursor@^2.1.0:
1144 version "2.1.0" 1166 version "2.1.0"
1145 resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" 1167 resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
@@ -1235,6 +1257,10 @@ combined-stream@^1.0.5, combined-stream@~1.0.5:
1235 dependencies: 1257 dependencies:
1236 delayed-stream "~1.0.0" 1258 delayed-stream "~1.0.0"
1237 1259
1260commander@^2.8.1:
1261 version "2.15.1"
1262 resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
1263
1238commander@^2.9.0: 1264commander@^2.9.0:
1239 version "2.13.0" 1265 version "2.13.0"
1240 resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" 1266 resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
@@ -1247,6 +1273,15 @@ concat-map@0.0.1:
1247 version "0.0.1" 1273 version "0.0.1"
1248 resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 1274 resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
1249 1275
1276concat-stream@^1.4.6:
1277 version "1.6.2"
1278 resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
1279 dependencies:
1280 buffer-from "^1.0.0"
1281 inherits "^2.0.3"
1282 readable-stream "^2.2.2"
1283 typedarray "^0.0.6"
1284
1250concat-stream@^1.6.0: 1285concat-stream@^1.6.0:
1251 version "1.6.0" 1286 version "1.6.0"
1252 resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" 1287 resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
@@ -1456,7 +1491,7 @@ date-now@^0.1.4:
1456 version "0.1.4" 1491 version "0.1.4"
1457 resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" 1492 resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
1458 1493
1459debug@^2.2.0, debug@^2.6.8, debug@^2.6.9: 1494debug@^2.1.1, debug@^2.2.0, debug@^2.6.8, debug@^2.6.9:
1460 version "2.6.9" 1495 version "2.6.9"
1461 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 1496 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
1462 dependencies: 1497 dependencies:
@@ -1529,7 +1564,7 @@ diffie-hellman@^5.0.0:
1529 miller-rabin "^4.0.0" 1564 miller-rabin "^4.0.0"
1530 randombytes "^2.0.0" 1565 randombytes "^2.0.0"
1531 1566
1532doctrine@1.5.0: 1567doctrine@1.5.0, doctrine@^1.2.2:
1533 version "1.5.0" 1568 version "1.5.0"
1534 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" 1569 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
1535 dependencies: 1570 dependencies:
@@ -1708,6 +1743,44 @@ eslint-visitor-keys@^1.0.0:
1708 version "1.0.0" 1743 version "1.0.0"
1709 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" 1744 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
1710 1745
1746eslint@^2.7.0:
1747 version "2.13.1"
1748 resolved "https://registry.yarnpkg.com/eslint/-/eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11"
1749 dependencies:
1750 chalk "^1.1.3"
1751 concat-stream "^1.4.6"
1752 debug "^2.1.1"
1753 doctrine "^1.2.2"
1754 es6-map "^0.1.3"
1755 escope "^3.6.0"
1756 espree "^3.1.6"
1757 estraverse "^4.2.0"
1758 esutils "^2.0.2"
1759 file-entry-cache "^1.1.1"
1760 glob "^7.0.3"
1761 globals "^9.2.0"
1762 ignore "^3.1.2"
1763 imurmurhash "^0.1.4"
1764 inquirer "^0.12.0"
1765 is-my-json-valid "^2.10.0"
1766 is-resolvable "^1.0.0"
1767 js-yaml "^3.5.1"
1768 json-stable-stringify "^1.0.0"
1769 levn "^0.3.0"
1770 lodash "^4.0.0"
1771 mkdirp "^0.5.0"
1772 optionator "^0.8.1"
1773 path-is-absolute "^1.0.0"
1774 path-is-inside "^1.0.1"
1775 pluralize "^1.2.1"
1776 progress "^1.1.8"
1777 require-uncached "^1.0.2"
1778 shelljs "^0.6.0"
1779 strip-json-comments "~1.0.1"
1780 table "^3.7.8"
1781 text-table "~0.2.0"
1782 user-home "^2.0.0"
1783
1711eslint@^4.16.0: 1784eslint@^4.16.0:
1712 version "4.17.0" 1785 version "4.17.0"
1713 resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.17.0.tgz#dc24bb51ede48df629be7031c71d9dc0ee4f3ddf" 1786 resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.17.0.tgz#dc24bb51ede48df629be7031c71d9dc0ee4f3ddf"
@@ -1750,6 +1823,13 @@ eslint@^4.16.0:
1750 table "^4.0.1" 1823 table "^4.0.1"
1751 text-table "~0.2.0" 1824 text-table "~0.2.0"
1752 1825
1826espree@^3.1.6:
1827 version "3.5.4"
1828 resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
1829 dependencies:
1830 acorn "^5.5.0"
1831 acorn-jsx "^3.0.0"
1832
1753espree@^3.5.2: 1833espree@^3.5.2:
1754 version "3.5.3" 1834 version "3.5.3"
1755 resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.3.tgz#931e0af64e7fbbed26b050a29daad1fc64799fa6" 1835 resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.3.tgz#931e0af64e7fbbed26b050a29daad1fc64799fa6"
@@ -1778,7 +1858,7 @@ esrecurse@^4.1.0:
1778 estraverse "^4.1.0" 1858 estraverse "^4.1.0"
1779 object-assign "^4.0.1" 1859 object-assign "^4.0.1"
1780 1860
1781estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: 1861estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
1782 version "4.2.0" 1862 version "4.2.0"
1783 resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" 1863 resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
1784 1864
@@ -1816,6 +1896,10 @@ execa@^0.7.0:
1816 signal-exit "^3.0.0" 1896 signal-exit "^3.0.0"
1817 strip-eof "^1.0.0" 1897 strip-eof "^1.0.0"
1818 1898
1899exit-hook@^1.0.0:
1900 version "1.1.1"
1901 resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
1902
1819expand-brackets@^0.1.4: 1903expand-brackets@^0.1.4:
1820 version "0.1.5" 1904 version "0.1.5"
1821 resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" 1905 resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
@@ -1879,12 +1963,26 @@ fastparse@^1.1.1:
1879 version "1.1.1" 1963 version "1.1.1"
1880 resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" 1964 resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
1881 1965
1966figures@^1.3.5:
1967 version "1.7.0"
1968 resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
1969 dependencies:
1970 escape-string-regexp "^1.0.5"
1971 object-assign "^4.1.0"
1972
1882figures@^2.0.0: 1973figures@^2.0.0:
1883 version "2.0.0" 1974 version "2.0.0"
1884 resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" 1975 resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
1885 dependencies: 1976 dependencies:
1886 escape-string-regexp "^1.0.5" 1977 escape-string-regexp "^1.0.5"
1887 1978
1979file-entry-cache@^1.1.1:
1980 version "1.3.1"
1981 resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-1.3.1.tgz#44c61ea607ae4be9c1402f41f44270cbfe334ff8"
1982 dependencies:
1983 flat-cache "^1.2.1"
1984 object-assign "^4.0.1"
1985
1888file-entry-cache@^2.0.0: 1986file-entry-cache@^2.0.0:
1889 version "2.0.0" 1987 version "2.0.0"
1890 resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" 1988 resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
@@ -1991,6 +2089,20 @@ form-data@~2.3.1:
1991 combined-stream "^1.0.5" 2089 combined-stream "^1.0.5"
1992 mime-types "^2.1.12" 2090 mime-types "^2.1.12"
1993 2091
2092front-matter@2.1.2:
2093 version "2.1.2"
2094 resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-2.1.2.tgz#f75983b9f2f413be658c93dfd7bd8ce4078f5cdb"
2095 dependencies:
2096 js-yaml "^3.4.6"
2097
2098fs-extra@^3.0.1:
2099 version "3.0.1"
2100 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291"
2101 dependencies:
2102 graceful-fs "^4.1.2"
2103 jsonfile "^3.0.0"
2104 universalify "^0.1.0"
2105
1994fs.realpath@^1.0.0: 2106fs.realpath@^1.0.0:
1995 version "1.0.0" 2107 version "1.0.0"
1996 resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 2108 resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2112,7 +2224,7 @@ globals@^11.0.1:
2112 version "11.3.0" 2224 version "11.3.0"
2113 resolved "https://registry.yarnpkg.com/globals/-/globals-11.3.0.tgz#e04fdb7b9796d8adac9c8f64c14837b2313378b0" 2225 resolved "https://registry.yarnpkg.com/globals/-/globals-11.3.0.tgz#e04fdb7b9796d8adac9c8f64c14837b2313378b0"
2114 2226
2115globals@^9.18.0: 2227globals@^9.18.0, globals@^9.2.0:
2116 version "9.18.0" 2228 version "9.18.0"
2117 resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" 2229 resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
2118 2230
@@ -2135,7 +2247,13 @@ globule@^1.0.0:
2135 lodash "~4.17.4" 2247 lodash "~4.17.4"
2136 minimatch "~3.0.2" 2248 minimatch "~3.0.2"
2137 2249
2138graceful-fs@^4.1.2: 2250gonzales-pe-sl@^4.2.3:
2251 version "4.2.3"
2252 resolved "https://registry.yarnpkg.com/gonzales-pe-sl/-/gonzales-pe-sl-4.2.3.tgz#6a868bc380645f141feeb042c6f97fcc71b59fe6"
2253 dependencies:
2254 minimist "1.1.x"
2255
2256graceful-fs@^4.1.2, graceful-fs@^4.1.6:
2139 version "4.1.11" 2257 version "4.1.11"
2140 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 2258 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
2141 2259
@@ -2301,7 +2419,7 @@ ieee754@^1.1.4:
2301 version "1.1.8" 2419 version "1.1.8"
2302 resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" 2420 resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
2303 2421
2304ignore@^3.3.3: 2422ignore@^3.1.2, ignore@^3.3.3:
2305 version "3.3.7" 2423 version "3.3.7"
2306 resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" 2424 resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
2307 2425
@@ -2346,6 +2464,24 @@ ini@~1.3.0:
2346 version "1.3.5" 2464 version "1.3.5"
2347 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" 2465 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
2348 2466
2467inquirer@^0.12.0:
2468 version "0.12.0"
2469 resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
2470 dependencies:
2471 ansi-escapes "^1.1.0"
2472 ansi-regex "^2.0.0"
2473 chalk "^1.0.0"
2474 cli-cursor "^1.0.1"
2475 cli-width "^2.0.0"
2476 figures "^1.3.5"
2477 lodash "^4.3.0"
2478 readline2 "^1.0.1"
2479 run-async "^0.1.0"
2480 rx-lite "^3.1.2"
2481 string-width "^1.0.1"
2482 strip-ansi "^3.0.0"
2483 through "^2.3.6"
2484
2349inquirer@^3.0.6: 2485inquirer@^3.0.6:
2350 version "3.3.0" 2486 version "3.3.0"
2351 resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" 2487 resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
@@ -2443,6 +2579,20 @@ is-glob@^2.0.0, is-glob@^2.0.1:
2443 dependencies: 2579 dependencies:
2444 is-extglob "^1.0.0" 2580 is-extglob "^1.0.0"
2445 2581
2582is-my-ip-valid@^1.0.0:
2583 version "1.0.0"
2584 resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
2585
2586is-my-json-valid@^2.10.0:
2587 version "2.17.2"
2588 resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c"
2589 dependencies:
2590 generate-function "^2.0.0"
2591 generate-object-property "^1.1.0"
2592 is-my-ip-valid "^1.0.0"
2593 jsonpointer "^4.0.0"
2594 xtend "^4.0.0"
2595
2446is-my-json-valid@^2.12.4: 2596is-my-json-valid@^2.12.4:
2447 version "2.17.1" 2597 version "2.17.1"
2448 resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471" 2598 resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471"
@@ -2558,6 +2708,13 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
2558 version "3.0.2" 2708 version "3.0.2"
2559 resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 2709 resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
2560 2710
2711js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4:
2712 version "3.11.0"
2713 resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
2714 dependencies:
2715 argparse "^1.0.7"
2716 esprima "^4.0.0"
2717
2561js-yaml@^3.9.1: 2718js-yaml@^3.9.1:
2562 version "3.10.0" 2719 version "3.10.0"
2563 resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" 2720 resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
@@ -2600,7 +2757,7 @@ json-stable-stringify-without-jsonify@^1.0.1:
2600 version "1.0.1" 2757 version "1.0.1"
2601 resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 2758 resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
2602 2759
2603json-stable-stringify@^1.0.1: 2760json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
2604 version "1.0.1" 2761 version "1.0.1"
2605 resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" 2762 resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
2606 dependencies: 2763 dependencies:
@@ -2614,6 +2771,12 @@ json5@^0.5.0, json5@^0.5.1:
2614 version "0.5.1" 2771 version "0.5.1"
2615 resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" 2772 resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
2616 2773
2774jsonfile@^3.0.0:
2775 version "3.0.1"
2776 resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
2777 optionalDependencies:
2778 graceful-fs "^4.1.6"
2779
2617jsonify@~0.0.0: 2780jsonify@~0.0.0:
2618 version "0.0.0" 2781 version "0.0.0"
2619 resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 2782 resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
@@ -2649,6 +2812,10 @@ kind-of@^4.0.0:
2649 dependencies: 2812 dependencies:
2650 is-buffer "^1.1.5" 2813 is-buffer "^1.1.5"
2651 2814
2815known-css-properties@^0.3.0:
2816 version "0.3.0"
2817 resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.3.0.tgz#a3d135bbfc60ee8c6eacf2f7e7e6f2d4755e49a4"
2818
2652lazy-cache@^0.2.3: 2819lazy-cache@^0.2.3:
2653 version "0.2.7" 2820 version "0.2.7"
2654 resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" 2821 resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65"
@@ -2716,6 +2883,10 @@ lodash.camelcase@^4.3.0:
2716 version "4.3.0" 2883 version "4.3.0"
2717 resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" 2884 resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
2718 2885
2886lodash.capitalize@^4.1.0:
2887 version "4.2.1"
2888 resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
2889
2719lodash.clonedeep@^4.3.2: 2890lodash.clonedeep@^4.3.2:
2720 version "4.5.0" 2891 version "4.5.0"
2721 resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" 2892 resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
@@ -2728,6 +2899,10 @@ lodash.isplainobject@^4.0.6:
2728 version "4.0.6" 2899 version "4.0.6"
2729 resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" 2900 resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
2730 2901
2902lodash.kebabcase@^4.0.0:
2903 version "4.1.1"
2904 resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
2905
2731lodash.memoize@^4.1.2: 2906lodash.memoize@^4.1.2:
2732 version "4.1.2" 2907 version "4.1.2"
2733 resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" 2908 resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -2829,6 +3004,10 @@ meow@^3.7.0:
2829 redent "^1.0.0" 3004 redent "^1.0.0"
2830 trim-newlines "^1.0.0" 3005 trim-newlines "^1.0.0"
2831 3006
3007merge@^1.2.0:
3008 version "1.2.0"
3009 resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
3010
2832micromatch@^2.1.5: 3011micromatch@^2.1.5:
2833 version "2.3.11" 3012 version "2.3.11"
2834 resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" 3013 resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
@@ -2890,6 +3069,10 @@ minimist@0.0.8:
2890 version "0.0.8" 3069 version "0.0.8"
2891 resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 3070 resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
2892 3071
3072minimist@1.1.x:
3073 version "1.1.3"
3074 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8"
3075
2893minimist@^1.1.3, minimist@^1.2.0: 3076minimist@^1.1.3, minimist@^1.2.0:
2894 version "1.2.0" 3077 version "1.2.0"
2895 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 3078 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -2911,6 +3094,10 @@ ms@2.0.0:
2911 version "2.0.0" 3094 version "2.0.0"
2912 resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 3095 resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
2913 3096
3097mute-stream@0.0.5:
3098 version "0.0.5"
3099 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
3100
2914mute-stream@0.0.7: 3101mute-stream@0.0.7:
2915 version "0.0.7" 3102 version "0.0.7"
2916 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" 3103 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
@@ -3094,13 +3281,17 @@ once@^1.3.0, once@^1.3.3:
3094 dependencies: 3281 dependencies:
3095 wrappy "1" 3282 wrappy "1"
3096 3283
3284onetime@^1.0.0:
3285 version "1.1.0"
3286 resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
3287
3097onetime@^2.0.0: 3288onetime@^2.0.0:
3098 version "2.0.1" 3289 version "2.0.1"
3099 resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" 3290 resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
3100 dependencies: 3291 dependencies:
3101 mimic-fn "^1.0.0" 3292 mimic-fn "^1.0.0"
3102 3293
3103optionator@^0.8.2: 3294optionator@^0.8.1, optionator@^0.8.2:
3104 version "0.8.2" 3295 version "0.8.2"
3105 resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" 3296 resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
3106 dependencies: 3297 dependencies:
@@ -3285,6 +3476,10 @@ pkg-dir@^2.0.0:
3285 dependencies: 3476 dependencies:
3286 find-up "^2.1.0" 3477 find-up "^2.1.0"
3287 3478
3479pluralize@^1.2.1:
3480 version "1.2.1"
3481 resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
3482
3288pluralize@^7.0.0: 3483pluralize@^7.0.0:
3289 version "7.0.0" 3484 version "7.0.0"
3290 resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" 3485 resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
@@ -3559,6 +3754,10 @@ process@^0.11.10:
3559 version "0.11.10" 3754 version "0.11.10"
3560 resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 3755 resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
3561 3756
3757progress@^1.1.8:
3758 version "1.1.8"
3759 resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
3760
3562progress@^2.0.0: 3761progress@^2.0.0:
3563 version "2.0.0" 3762 version "2.0.0"
3564 resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" 3763 resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
@@ -3708,6 +3907,14 @@ readdirp@^2.0.0:
3708 readable-stream "^2.0.2" 3907 readable-stream "^2.0.2"
3709 set-immediate-shim "^1.0.1" 3908 set-immediate-shim "^1.0.1"
3710 3909
3910readline2@^1.0.1:
3911 version "1.0.1"
3912 resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35"
3913 dependencies:
3914 code-point-at "^1.0.0"
3915 is-fullwidth-code-point "^1.0.0"
3916 mute-stream "0.0.5"
3917
3711redent@^1.0.0: 3918redent@^1.0.0:
3712 version "1.0.0" 3919 version "1.0.0"
3713 resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" 3920 resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
@@ -3882,7 +4089,7 @@ require-main-filename@^1.0.1:
3882 version "1.0.1" 4089 version "1.0.1"
3883 resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" 4090 resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
3884 4091
3885require-uncached@^1.0.3: 4092require-uncached@^1.0.2, require-uncached@^1.0.3:
3886 version "1.0.3" 4093 version "1.0.3"
3887 resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" 4094 resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
3888 dependencies: 4095 dependencies:
@@ -3899,6 +4106,13 @@ resolve@^1.5.0:
3899 dependencies: 4106 dependencies:
3900 path-parse "^1.0.5" 4107 path-parse "^1.0.5"
3901 4108
4109restore-cursor@^1.0.1:
4110 version "1.0.1"
4111 resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
4112 dependencies:
4113 exit-hook "^1.0.0"
4114 onetime "^1.0.0"
4115
3902restore-cursor@^2.0.0: 4116restore-cursor@^2.0.0:
3903 version "2.0.0" 4117 version "2.0.0"
3904 resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" 4118 resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@@ -3925,6 +4139,12 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
3925 hash-base "^2.0.0" 4139 hash-base "^2.0.0"
3926 inherits "^2.0.1" 4140 inherits "^2.0.1"
3927 4141
4142run-async@^0.1.0:
4143 version "0.1.0"
4144 resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
4145 dependencies:
4146 once "^1.3.0"
4147
3928run-async@^2.2.0: 4148run-async@^2.2.0:
3929 version "2.3.0" 4149 version "2.3.0"
3930 resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" 4150 resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
@@ -3941,6 +4161,10 @@ rx-lite@*, rx-lite@^4.0.8:
3941 version "4.0.8" 4161 version "4.0.8"
3942 resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" 4162 resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
3943 4163
4164rx-lite@^3.1.2:
4165 version "3.1.2"
4166 resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
4167
3944safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: 4168safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
3945 version "5.1.1" 4169 version "5.1.1"
3946 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 4170 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -3954,6 +4178,25 @@ sass-graph@^2.2.4:
3954 scss-tokenizer "^0.2.3" 4178 scss-tokenizer "^0.2.3"
3955 yargs "^7.0.0" 4179 yargs "^7.0.0"
3956 4180
4181sass-lint@^1.12.1:
4182 version "1.12.1"
4183 resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.12.1.tgz#630f69c216aa206b8232fb2aa907bdf3336b6d83"
4184 dependencies:
4185 commander "^2.8.1"
4186 eslint "^2.7.0"
4187 front-matter "2.1.2"
4188 fs-extra "^3.0.1"
4189 glob "^7.0.0"
4190 globule "^1.0.0"
4191 gonzales-pe-sl "^4.2.3"
4192 js-yaml "^3.5.4"
4193 known-css-properties "^0.3.0"
4194 lodash.capitalize "^4.1.0"
4195 lodash.kebabcase "^4.0.0"
4196 merge "^1.2.0"
4197 path-is-absolute "^1.0.0"
4198 util "^0.10.3"
4199
3957sass-loader@^6.0.6: 4200sass-loader@^6.0.6:
3958 version "6.0.6" 4201 version "6.0.6"
3959 resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9" 4202 resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9"
@@ -4027,6 +4270,10 @@ shebang-regex@^1.0.0:
4027 version "1.0.0" 4270 version "1.0.0"
4028 resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 4271 resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
4029 4272
4273shelljs@^0.6.0:
4274 version "0.6.1"
4275 resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8"
4276
4030signal-exit@^3.0.0, signal-exit@^3.0.2: 4277signal-exit@^3.0.0, signal-exit@^3.0.2:
4031 version "3.0.2" 4278 version "3.0.2"
4032 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 4279 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
@@ -4035,6 +4282,10 @@ slash@^1.0.0:
4035 version "1.0.0" 4282 version "1.0.0"
4036 resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" 4283 resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
4037 4284
4285slice-ansi@0.0.4:
4286 version "0.0.4"
4287 resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
4288
4038slice-ansi@1.0.0: 4289slice-ansi@1.0.0:
4039 version "1.0.0" 4290 version "1.0.0"
4040 resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" 4291 resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d"
@@ -4199,6 +4450,10 @@ strip-indent@^1.0.1:
4199 dependencies: 4450 dependencies:
4200 get-stdin "^4.0.1" 4451 get-stdin "^4.0.1"
4201 4452
4453strip-json-comments@~1.0.1:
4454 version "1.0.4"
4455 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
4456
4202strip-json-comments@~2.0.1: 4457strip-json-comments@~2.0.1:
4203 version "2.0.1" 4458 version "2.0.1"
4204 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 4459 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@@ -4244,6 +4499,17 @@ svgo@^0.7.0:
4244 sax "~1.2.1" 4499 sax "~1.2.1"
4245 whet.extend "~0.9.9" 4500 whet.extend "~0.9.9"
4246 4501
4502table@^3.7.8:
4503 version "3.8.3"
4504 resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
4505 dependencies:
4506 ajv "^4.7.0"
4507 ajv-keywords "^1.0.0"
4508 chalk "^1.1.1"
4509 lodash "^4.0.0"
4510 slice-ansi "0.0.4"
4511 string-width "^2.0.0"
4512
4247table@^4.0.1: 4513table@^4.0.1:
4248 version "4.0.2" 4514 version "4.0.2"
4249 resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" 4515 resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
@@ -4395,6 +4661,10 @@ uniqs@^2.0.0:
4395 version "2.0.0" 4661 version "2.0.0"
4396 resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" 4662 resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
4397 4663
4664universalify@^0.1.0:
4665 version "0.1.1"
4666 resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
4667
4398url-loader@^0.6.2: 4668url-loader@^0.6.2:
4399 version "0.6.2" 4669 version "0.6.2"
4400 resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7" 4670 resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7"
@@ -4410,6 +4680,12 @@ url@^0.11.0:
4410 punycode "1.3.2" 4680 punycode "1.3.2"
4411 querystring "0.2.0" 4681 querystring "0.2.0"
4412 4682
4683user-home@^2.0.0:
4684 version "2.0.0"
4685 resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
4686 dependencies:
4687 os-homedir "^1.0.0"
4688
4413util-deprecate@~1.0.1: 4689util-deprecate@~1.0.1:
4414 version "1.0.2" 4690 version "1.0.2"
4415 resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 4691 resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"