--- /dev/null
+options:
+ max-warnings: 0
+rules:
+ property-sort-order:
+ - 1
+ -
+ order: 'concentric'
+ no-important:
+ - 0
+ no-vendor-prefixes:
+ - 0 # this will be fixed with v2: see https://github.com/sasstools/sass-lint/pull/1137
+ nesting-depth:
+ - 1
+ -
+ max-depth: 4
indent_style = space
indent_size = 4
-[*.{htaccess,html,js,json,xml}]
+[*.{htaccess,html,scss,js,json,xml,yml}]
indent_size = 2
[*.php]
# Exclude from Git archives
.editorconfig export-ignore
+.dev export-ignore
.gitattributes export-ignore
.github export-ignore
.gitignore export-ignore
sudo: false
dist: trusty
-language: php
+
+matrix:
+ include:
+ - language: php
+ php: 7.2
+ - language: php
+ php: 7.1
+ - language: php
+ php: 7.0
+ - language: php
+ php: 5.6
+ - language: node_js
+ node_js: 8
+ cache:
+ yarn: true
+ directories:
+ - $HOME/.cache/yarn
+
+ install:
+ - yarn install
+
+ before_script:
+ - PATH=${PATH//:\.\/node_modules\/\.bin/}
+
+ script:
+ - yarn run build # Just to be sure that the build isn't broken
+ - make eslint
+ - make sasslint
+
cache:
- yarn: true
directories:
- $HOME/.composer/cache
- - $HOME/.cache/yarn
-php:
- - 7.2
- - 7.1
- - 7.0
- - 5.6
+
install:
- - yarn install
- composer install --prefer-dist
+
before_script:
- PATH=${PATH//:\.\/node_modules\/\.bin/}
+
script:
- make clean
- make check_permissions
- - make eslint
- make all_tests
### Run ESLint check against Shaarli's JS files
eslint:
- @yarn run eslint assets/vintage/js/
- @yarn run eslint assets/default/js/
+ @yarn run eslint -c .dev/.eslintrc.js assets/vintage/js/
+ @yarn run eslint -c .dev/.eslintrc.js assets/default/js/
+
+### Run CSSLint check against Shaarli's SCSS files
+sasslint:
+ @yarn run sass-lint -c .dev/.sasslintrc 'assets/default/scss/*.scss' -v -q
<?php
/**
* GET an HTTP URL to retrieve its content
- * Uses the cURL library or a fallback method
+ * Uses the cURL library or a fallback method
*
* @param string $url URL to get (http://...)
* @param int $timeout network timeout (in seconds)
return array_pop($ips);
}
+
+/**
+ * Return an identifier based on the advertised client IP address(es)
+ *
+ * This aims at preventing session hijacking from users behind the same proxy
+ * by relying on HTTP headers.
+ *
+ * See:
+ * - https://secure.php.net/manual/en/reserved.variables.server.php
+ * - https://stackoverflow.com/questions/3003145/how-to-get-the-client-ip-address-in-php
+ * - https://stackoverflow.com/questions/12233406/preventing-session-hijacking
+ * - https://stackoverflow.com/questions/21354859/trusting-x-forwarded-for-to-identify-a-visitor
+ *
+ * @param array $server The $_SERVER array
+ *
+ * @return string An identifier based on client IP address information
+ */
+function client_ip_id($server)
+{
+ $ip = $server['REMOTE_ADDR'];
+
+ if (isset($server['HTTP_X_FORWARDED_FOR'])) {
+ $ip = $ip . '_' . $server['HTTP_X_FORWARDED_FOR'];
+ }
+ if (isset($server['HTTP_CLIENT_IP'])) {
+ $ip = $ip . '_' . $server['HTTP_CLIENT_IP'];
+ }
+ return $ip;
+}
+
+
/**
* Returns true if Shaarli's currently browsed in HTTPS.
* Supports reverse proxies (if the headers are correctly set).
'auto' => t('Automatic'),
'en' => t('English'),
'fr' => t('French'),
+ 'de' => t('German'),
];
}
}
/**
* Returns the list tags appearing in the links with the given tags
- * @param $filteringTags: tags selecting the links to consider
- * @param $visibility: process only all/private/public links
- * @return: a tag=>linksCount array
+ *
+ * @param array $filteringTags tags selecting the links to consider
+ * @param string $visibility process only all/private/public links
+ *
+ * @return array tag => linksCount
*/
public function linksCountPerTag($filteringTags = [], $visibility = 'all')
{
- $links = empty($filteringTags) ? $this->links : $this->filterSearch(['searchtags' => $filteringTags], false, $visibility);
- $tags = array();
- $caseMapping = array();
+ $links = $this->filterSearch(['searchtags' => $filteringTags], false, $visibility);
+ $tags = [];
+ $caseMapping = [];
foreach ($links as $link) {
foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) {
if (empty($tag)) {
$tags[$caseMapping[strtolower($tag)]]++;
}
}
- // Sort tags by usage (most used tag first)
- arsort($tags);
+
+ /*
+ * Formerly used arsort(), which doesn't define the sort behaviour for equal values.
+ * Also, this function doesn't produce the same result between PHP 5.6 and 7.
+ *
+ * So we now use array_multisort() to sort tags by DESC occurrences,
+ * then ASC alphabetically for equal values.
+ *
+ * @see https://github.com/shaarli/Shaarli/issues/1142
+ */
+ $keys = array_keys($tags);
+ $tmpTags = array_combine($keys, $keys);
+ array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
return $tags;
}
*/
function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_getinfo')
{
+ $isRedirected = false;
/**
* cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
*
*
* @return int|bool length of $data or false if we need to stop the download
*/
- return function(&$ch, $data) use ($curlGetInfo, &$charset, &$title) {
+ return function(&$ch, $data) use ($curlGetInfo, &$charset, &$title, &$isRedirected) {
$responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
- if (!empty($responseCode) && $responseCode != 200) {
+ if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
+ $isRedirected = true;
+ return strlen($data);
+ }
+ if (!empty($responseCode) && $responseCode !== 200) {
return false;
}
- $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE);
+ // After a redirection, the content type will keep the previous request value
+ // until it finds the next content-type header.
+ if (! $isRedirected || strpos(strtolower($data), 'content-type') !== false) {
+ $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE);
+ }
if (!empty($contentType) && strpos($contentType, 'text/html') === false) {
return false;
}
- if (empty($charset)) {
+ if (!empty($contentType) && empty($charset)) {
$charset = header_extract_charset($contentType);
}
if (empty($charset)) {
+++ /dev/null
-<?php
-namespace Shaarli;
-
-/**
- * User login management
- */
-class LoginManager
-{
- protected $globals = [];
- protected $configManager = null;
- protected $banFile = '';
-
- /**
- * Constructor
- *
- * @param array $globals The $GLOBALS array (reference)
- * @param ConfigManager $configManager Configuration Manager instance.
- */
- public function __construct(& $globals, $configManager)
- {
- $this->globals = &$globals;
- $this->configManager = $configManager;
- $this->banFile = $this->configManager->get('resource.ban_file', 'data/ipbans.php');
- $this->readBanFile();
- }
-
- /**
- * Read a file containing banned IPs
- */
- protected function readBanFile()
- {
- if (! file_exists($this->banFile)) {
- return;
- }
- include $this->banFile;
- }
-
- /**
- * Write the banned IPs to a file
- */
- protected function writeBanFile()
- {
- if (! array_key_exists('IPBANS', $this->globals)) {
- return;
- }
- file_put_contents(
- $this->banFile,
- "<?php\n\$GLOBALS['IPBANS']=" . var_export($this->globals['IPBANS'], true) . ";\n?>"
- );
- }
-
- /**
- * Handle a failed login and ban the IP after too many failed attempts
- *
- * @param array $server The $_SERVER array
- */
- public function handleFailedLogin($server)
- {
- $ip = $server['REMOTE_ADDR'];
- $trusted = $this->configManager->get('security.trusted_proxies', []);
-
- if (in_array($ip, $trusted)) {
- $ip = getIpAddressFromProxy($server, $trusted);
- if (! $ip) {
- // the IP is behind a trusted forward proxy, but is not forwarded
- // in the HTTP headers, so we do nothing
- return;
- }
- }
-
- // increment the fail count for this IP
- if (isset($this->globals['IPBANS']['FAILURES'][$ip])) {
- $this->globals['IPBANS']['FAILURES'][$ip]++;
- } else {
- $this->globals['IPBANS']['FAILURES'][$ip] = 1;
- }
-
- if ($this->globals['IPBANS']['FAILURES'][$ip] >= $this->configManager->get('security.ban_after')) {
- $this->globals['IPBANS']['BANS'][$ip] = time() + $this->configManager->get('security.ban_duration', 1800);
- logm(
- $this->configManager->get('resource.log'),
- $server['REMOTE_ADDR'],
- 'IP address banned from login'
- );
- }
- $this->writeBanFile();
- }
-
- /**
- * Handle a successful login
- *
- * @param array $server The $_SERVER array
- */
- public function handleSuccessfulLogin($server)
- {
- $ip = $server['REMOTE_ADDR'];
- // FIXME unban when behind a trusted proxy?
-
- unset($this->globals['IPBANS']['FAILURES'][$ip]);
- unset($this->globals['IPBANS']['BANS'][$ip]);
-
- $this->writeBanFile();
- }
-
- /**
- * Check if the user can login from this IP
- *
- * @param array $server The $_SERVER array
- *
- * @return bool true if the user is allowed to login
- */
- public function canLogin($server)
- {
- $ip = $server['REMOTE_ADDR'];
-
- if (! isset($this->globals['IPBANS']['BANS'][$ip])) {
- // the user is not banned
- return true;
- }
-
- if ($this->globals['IPBANS']['BANS'][$ip] > time()) {
- // the user is still banned
- return false;
- }
-
- // the ban has expired, the user can attempt to log in again
- logm($this->configManager->get('resource.log'), $server['REMOTE_ADDR'], 'Ban lifted.');
- unset($this->globals['IPBANS']['FAILURES'][$ip]);
- unset($this->globals['IPBANS']['BANS'][$ip]);
-
- $this->writeBanFile();
- return true;
- }
-}
* @var LinkDB $linkDB instance.
*/
protected $linkDB;
+
+ /** @var bool $isLoggedIn Whether the user is logged in **/
+ protected $isLoggedIn = false;
/**
* PageBuilder constructor.
* @param LinkDB $linkDB instance.
* @param string $token Session token
*/
- public function __construct(&$conf, $linkDB = null, $token = null)
+ public function __construct(&$conf, $linkDB = null, $token = null, $isLoggedIn = false)
{
$this->tpl = false;
$this->conf = $conf;
$this->linkDB = $linkDB;
$this->token = $token;
+ $this->isLoggedIn = $isLoggedIn;
}
/**
$this->conf->get('resource.update_check'),
$this->conf->get('updates.check_updates_interval'),
$this->conf->get('updates.check_updates'),
- isLoggedIn(),
+ $this->isLoggedIn,
$this->conf->get('updates.check_updates_branch')
);
$this->tpl->assign('newVersion', escape($version));
$this->tpl->assign('versionError', escape($exc->getMessage()));
}
+ $this->tpl->assign('is_logged_in', $this->isLoggedIn);
$this->tpl->assign('feedurl', escape(index_url($_SERVER)));
$searchcrits = ''; // Search criteria
if (!empty($_GET['searchtags'])) {
+++ /dev/null
-<?php
-namespace Shaarli;
-
-/**
- * Manages the server-side session
- */
-class SessionManager
-{
- protected $session = [];
-
- /**
- * Constructor
- *
- * @param array $session The $_SESSION array (reference)
- * @param ConfigManager $conf ConfigManager instance
- */
- public function __construct(& $session, $conf)
- {
- $this->session = &$session;
- $this->conf = $conf;
- }
-
- /**
- * Generates a session token
- *
- * @return string token
- */
- public function generateToken()
- {
- $token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt'));
- $this->session['tokens'][$token] = 1;
- return $token;
- }
-
- /**
- * Checks the validity of a session token, and destroys it afterwards
- *
- * @param string $token The token to check
- *
- * @return bool true if the token is valid, else false
- */
- public function checkToken($token)
- {
- if (! isset($this->session['tokens'][$token])) {
- // the token is wrong, or has already been used
- return false;
- }
-
- // destroy the token to prevent future use
- unset($this->session['tokens'][$token]);
- return true;
- }
-
- /**
- * Validate session ID to prevent Full Path Disclosure.
- *
- * See #298.
- * The session ID's format depends on the hash algorithm set in PHP settings
- *
- * @param string $sessionId Session ID
- *
- * @return true if valid, false otherwise.
- *
- * @see http://php.net/manual/en/function.hash-algos.php
- * @see http://php.net/manual/en/session.configuration.php
- */
- public static function checkId($sessionId)
- {
- if (empty($sessionId)) {
- return false;
- }
-
- if (!$sessionId) {
- return false;
- }
-
- if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
- return false;
- }
-
- return true;
- }
-}
--- /dev/null
+<?php
+namespace Shaarli\Security;
+
+use Shaarli\Config\ConfigManager;
+
+/**
+ * User login management
+ */
+class LoginManager
+{
+ /** @var string Name of the cookie set after logging in **/
+ public static $STAY_SIGNED_IN_COOKIE = 'shaarli_staySignedIn';
+
+ /** @var array A reference to the $_GLOBALS array */
+ protected $globals = [];
+
+ /** @var ConfigManager Configuration Manager instance **/
+ protected $configManager = null;
+
+ /** @var SessionManager Session Manager instance **/
+ protected $sessionManager = null;
+
+ /** @var string Path to the file containing IP bans */
+ protected $banFile = '';
+
+ /** @var bool Whether the user is logged in **/
+ protected $isLoggedIn = false;
+
+ /** @var bool Whether the Shaarli instance is open to public edition **/
+ protected $openShaarli = false;
+
+ /** @var string User sign-in token depending on remote IP and credentials */
+ protected $staySignedInToken = '';
+
+ /**
+ * Constructor
+ *
+ * @param array $globals The $GLOBALS array (reference)
+ * @param ConfigManager $configManager Configuration Manager instance
+ * @param SessionManager $sessionManager SessionManager instance
+ */
+ public function __construct(& $globals, $configManager, $sessionManager)
+ {
+ $this->globals = &$globals;
+ $this->configManager = $configManager;
+ $this->sessionManager = $sessionManager;
+ $this->banFile = $this->configManager->get('resource.ban_file', 'data/ipbans.php');
+ $this->readBanFile();
+ if ($this->configManager->get('security.open_shaarli') === true) {
+ $this->openShaarli = true;
+ }
+ }
+
+ /**
+ * Generate a token depending on deployment salt, user password and client IP
+ *
+ * @param string $clientIpAddress The remote client IP address
+ */
+ public function generateStaySignedInToken($clientIpAddress)
+ {
+ $this->staySignedInToken = sha1(
+ $this->configManager->get('credentials.hash')
+ . $clientIpAddress
+ . $this->configManager->get('credentials.salt')
+ );
+ }
+
+ /**
+ * Return the user's client stay-signed-in token
+ *
+ * @return string User's client stay-signed-in token
+ */
+ public function getStaySignedInToken()
+ {
+ return $this->staySignedInToken;
+ }
+
+ /**
+ * Check user session state and validity (expiration)
+ *
+ * @param array $cookie The $_COOKIE array
+ * @param string $clientIpId Client IP address identifier
+ */
+ public function checkLoginState($cookie, $clientIpId)
+ {
+ if (! $this->configManager->exists('credentials.login')) {
+ // Shaarli is not configured yet
+ $this->isLoggedIn = false;
+ return;
+ }
+
+ if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE])
+ && $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken
+ ) {
+ // The user client has a valid stay-signed-in cookie
+ // Session information is updated with the current client information
+ $this->sessionManager->storeLoginInfo($clientIpId);
+
+ } elseif ($this->sessionManager->hasSessionExpired()
+ || $this->sessionManager->hasClientIpChanged($clientIpId)
+ ) {
+ $this->sessionManager->logout();
+ $this->isLoggedIn = false;
+ return;
+ }
+
+ $this->isLoggedIn = true;
+ $this->sessionManager->extendSession();
+ }
+
+ /**
+ * Return whether the user is currently logged in
+ *
+ * @return true when the user is logged in, false otherwise
+ */
+ public function isLoggedIn()
+ {
+ if ($this->openShaarli) {
+ return true;
+ }
+ return $this->isLoggedIn;
+ }
+
+ /**
+ * Check user credentials are valid
+ *
+ * @param string $remoteIp Remote client IP address
+ * @param string $clientIpId Client IP address identifier
+ * @param string $login Username
+ * @param string $password Password
+ *
+ * @return bool true if the provided credentials are valid, false otherwise
+ */
+ public function checkCredentials($remoteIp, $clientIpId, $login, $password)
+ {
+ $hash = sha1($password . $login . $this->configManager->get('credentials.salt'));
+
+ if ($login != $this->configManager->get('credentials.login')
+ || $hash != $this->configManager->get('credentials.hash')
+ ) {
+ logm(
+ $this->configManager->get('resource.log'),
+ $remoteIp,
+ 'Login failed for user ' . $login
+ );
+ return false;
+ }
+
+ $this->sessionManager->storeLoginInfo($clientIpId);
+ logm(
+ $this->configManager->get('resource.log'),
+ $remoteIp,
+ 'Login successful'
+ );
+ return true;
+ }
+
+ /**
+ * Read a file containing banned IPs
+ */
+ protected function readBanFile()
+ {
+ if (! file_exists($this->banFile)) {
+ return;
+ }
+ include $this->banFile;
+ }
+
+ /**
+ * Write the banned IPs to a file
+ */
+ protected function writeBanFile()
+ {
+ if (! array_key_exists('IPBANS', $this->globals)) {
+ return;
+ }
+ file_put_contents(
+ $this->banFile,
+ "<?php\n\$GLOBALS['IPBANS']=" . var_export($this->globals['IPBANS'], true) . ";\n?>"
+ );
+ }
+
+ /**
+ * Handle a failed login and ban the IP after too many failed attempts
+ *
+ * @param array $server The $_SERVER array
+ */
+ public function handleFailedLogin($server)
+ {
+ $ip = $server['REMOTE_ADDR'];
+ $trusted = $this->configManager->get('security.trusted_proxies', []);
+
+ if (in_array($ip, $trusted)) {
+ $ip = getIpAddressFromProxy($server, $trusted);
+ if (! $ip) {
+ // the IP is behind a trusted forward proxy, but is not forwarded
+ // in the HTTP headers, so we do nothing
+ return;
+ }
+ }
+
+ // increment the fail count for this IP
+ if (isset($this->globals['IPBANS']['FAILURES'][$ip])) {
+ $this->globals['IPBANS']['FAILURES'][$ip]++;
+ } else {
+ $this->globals['IPBANS']['FAILURES'][$ip] = 1;
+ }
+
+ if ($this->globals['IPBANS']['FAILURES'][$ip] >= $this->configManager->get('security.ban_after')) {
+ $this->globals['IPBANS']['BANS'][$ip] = time() + $this->configManager->get('security.ban_duration', 1800);
+ logm(
+ $this->configManager->get('resource.log'),
+ $server['REMOTE_ADDR'],
+ 'IP address banned from login'
+ );
+ }
+ $this->writeBanFile();
+ }
+
+ /**
+ * Handle a successful login
+ *
+ * @param array $server The $_SERVER array
+ */
+ public function handleSuccessfulLogin($server)
+ {
+ $ip = $server['REMOTE_ADDR'];
+ // FIXME unban when behind a trusted proxy?
+
+ unset($this->globals['IPBANS']['FAILURES'][$ip]);
+ unset($this->globals['IPBANS']['BANS'][$ip]);
+
+ $this->writeBanFile();
+ }
+
+ /**
+ * Check if the user can login from this IP
+ *
+ * @param array $server The $_SERVER array
+ *
+ * @return bool true if the user is allowed to login
+ */
+ public function canLogin($server)
+ {
+ $ip = $server['REMOTE_ADDR'];
+
+ if (! isset($this->globals['IPBANS']['BANS'][$ip])) {
+ // the user is not banned
+ return true;
+ }
+
+ if ($this->globals['IPBANS']['BANS'][$ip] > time()) {
+ // the user is still banned
+ return false;
+ }
+
+ // the ban has expired, the user can attempt to log in again
+ logm($this->configManager->get('resource.log'), $server['REMOTE_ADDR'], 'Ban lifted.');
+ unset($this->globals['IPBANS']['FAILURES'][$ip]);
+ unset($this->globals['IPBANS']['BANS'][$ip]);
+
+ $this->writeBanFile();
+ return true;
+ }
+}
--- /dev/null
+<?php
+namespace Shaarli\Security;
+
+use Shaarli\Config\ConfigManager;
+
+/**
+ * Manages the server-side session
+ */
+class SessionManager
+{
+ /** @var int Session expiration timeout, in seconds */
+ public static $SHORT_TIMEOUT = 3600; // 1 hour
+
+ /** @var int Session expiration timeout, in seconds */
+ public static $LONG_TIMEOUT = 31536000; // 1 year
+
+ /** @var array Local reference to the global $_SESSION array */
+ protected $session = [];
+
+ /** @var ConfigManager Configuration Manager instance **/
+ protected $conf = null;
+
+ /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */
+ protected $staySignedIn = false;
+
+ /**
+ * Constructor
+ *
+ * @param array $session The $_SESSION array (reference)
+ * @param ConfigManager $conf ConfigManager instance
+ */
+ public function __construct(& $session, $conf)
+ {
+ $this->session = &$session;
+ $this->conf = $conf;
+ }
+
+ /**
+ * Define whether the user should stay signed in across browser sessions
+ *
+ * @param bool $staySignedIn Keep the user signed in
+ */
+ public function setStaySignedIn($staySignedIn)
+ {
+ $this->staySignedIn = $staySignedIn;
+ }
+
+ /**
+ * Generates a session token
+ *
+ * @return string token
+ */
+ public function generateToken()
+ {
+ $token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt'));
+ $this->session['tokens'][$token] = 1;
+ return $token;
+ }
+
+ /**
+ * Checks the validity of a session token, and destroys it afterwards
+ *
+ * @param string $token The token to check
+ *
+ * @return bool true if the token is valid, else false
+ */
+ public function checkToken($token)
+ {
+ if (! isset($this->session['tokens'][$token])) {
+ // the token is wrong, or has already been used
+ return false;
+ }
+
+ // destroy the token to prevent future use
+ unset($this->session['tokens'][$token]);
+ return true;
+ }
+
+ /**
+ * Validate session ID to prevent Full Path Disclosure.
+ *
+ * See #298.
+ * The session ID's format depends on the hash algorithm set in PHP settings
+ *
+ * @param string $sessionId Session ID
+ *
+ * @return true if valid, false otherwise.
+ *
+ * @see http://php.net/manual/en/function.hash-algos.php
+ * @see http://php.net/manual/en/session.configuration.php
+ */
+ public static function checkId($sessionId)
+ {
+ if (empty($sessionId)) {
+ return false;
+ }
+
+ if (!$sessionId) {
+ return false;
+ }
+
+ if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Store user login information after a successful login
+ *
+ * @param string $clientIpId Client IP address identifier
+ */
+ public function storeLoginInfo($clientIpId)
+ {
+ $this->session['ip'] = $clientIpId;
+ $this->session['username'] = $this->conf->get('credentials.login');
+ $this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
+ }
+
+ /**
+ * Extend session validity
+ */
+ public function extendSession()
+ {
+ if ($this->staySignedIn) {
+ return $this->extendTimeValidityBy(self::$LONG_TIMEOUT);
+ }
+ return $this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
+ }
+
+ /**
+ * Extend expiration time
+ *
+ * @param int $duration Expiration time extension (seconds)
+ *
+ * @return int New session expiration time
+ */
+ protected function extendTimeValidityBy($duration)
+ {
+ $expirationTime = time() + $duration;
+ $this->session['expires_on'] = $expirationTime;
+ return $expirationTime;
+ }
+
+ /**
+ * Logout a user by unsetting all login information
+ *
+ * See:
+ * - https://secure.php.net/manual/en/function.setcookie.php
+ */
+ public function logout()
+ {
+ if (isset($this->session)) {
+ unset($this->session['ip']);
+ unset($this->session['expires_on']);
+ unset($this->session['username']);
+ unset($this->session['visibility']);
+ unset($this->session['untaggedonly']);
+ }
+ }
+
+ /**
+ * Check whether the session has expired
+ *
+ * @param string $clientIpId Client IP address identifier
+ *
+ * @return bool true if the session has expired, false otherwise
+ */
+ public function hasSessionExpired()
+ {
+ if (empty($this->session['expires_on'])) {
+ return true;
+ }
+ if (time() >= $this->session['expires_on']) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the client IP address has changed
+ *
+ * @param string $clientIpId Client IP address identifier
+ *
+ * @return bool true if the IP has changed, false if it has not, or
+ * if session protection has been disabled
+ */
+ public function hasClientIpChanged($clientIpId)
+ {
+ if ($this->conf->get('security.session_protection_disabled') === true) {
+ return false;
+ }
+ if (isset($this->session['ip']) && $this->session['ip'] === $clientIpId) {
+ return false;
+ }
+ return true;
+ }
+}
-$fa-font-path: "~font-awesome/fonts";
+$fa-font-path: '~font-awesome/fonts';
-@import "~font-awesome/scss/font-awesome.scss";
+@import '~font-awesome/scss/font-awesome';
@import '~purecss/build/pure.css';
@import '~purecss/build/grids-responsive.css';
@import '~pure-extras/css/pure-extras.css';
@import '~awesomplete/awesomplete.css';
-/**
- * General
- */
+$white: #fff;
+$black: #000;
+$almost-white: #f5f5f5;
+$dark-grey: #252525;
+$light-grey: #797979;
+$main-green: #1b926c;
+$light-green: #b0ddce;
+$dark-green: #2a4c41;
+$red: #ac2925;
+$orange: #f89406;
+$blue: #0b5ea6;
+$background-color: #d0d0d0;
+$background-linklist-info: #ddd;
+$light-shadow: rgba(255, 255, 255, .078);
+$dark-shadow: rgba(0, 0, 0, .298);
+$warning-text: #97600d;
+$form-input-border: #d8d8d8;
+$form-input-background: #eee;
+
+// General
body {
- background: #d0d0d0;
+ background: $background-color;
}
.strong {
- font-weight: bold;
+ font-weight: bold;
}
.clear {
- clear: both;
+ clear: both;
}
.center {
- text-align: center;
- margin: auto;
+ margin: auto;
+ text-align: center;
}
.label {
- display: inline-block;
- padding: .25em .4em;
- font-size: 75%;
- font-weight: 700;
- line-height: 1;
- text-align: center;
- white-space: nowrap;
- vertical-align: baseline;
- border-radius: .25rem;
+ display: inline-block;
+ border-radius: .25rem;
+ padding: .25em .4em;
+ vertical-align: baseline;
+ text-align: center;
+ line-height: 1;
+ white-space: nowrap;
+ font-size: 75%;
+ font-weight: 700;
}
pre {
- max-width: 100%;
+ max-width: 100%;
}
@font-face {
- font-family: 'Roboto';
- font-weight: 400;
- font-style: normal;
- src:
- local('Roboto'),
- local('Roboto-Regular'),
- url('../fonts/Roboto-Regular.woff2') format('woff2'),
- url('../fonts/Roboto-Regular.woff') format('woff');
+ font-family: 'Roboto';
+ font-weight: 400;
+ font-style: normal;
+ src: local('Roboto'),
+ local('Roboto-Regular'),
+ url('../fonts/Roboto-Regular.woff2') format('woff2'),
+ url('../fonts/Roboto-Regular.woff') format('woff');
}
@font-face {
- font-family: 'Roboto';
- font-weight: 700;
- font-style: normal;
- src:
- local('Roboto'),
- local('Roboto-Bold'),
- url('../fonts/Roboto-Bold.woff2') format('woff2'),
- url('../fonts/Roboto-Bold.woff') format('woff');
-}
-
-body, .pure-g [class*="pure-u"] {
- font-family: Roboto, Arial, sans-serif;
-}
-
-/**
- * Extends Pure grids responsive to hide items.
- * Use xx-0 to hide an item on xx screen.
- * Display it at any level with xx-visible.
- */
-.pure-u-0 { display: none !important; }
+ font-family: 'Roboto';
+ font-weight: 700;
+ font-style: normal;
+ src: local('Roboto'),
+ local('Roboto-Bold'),
+ url('../fonts/Roboto-Bold.woff2') format('woff2'),
+ url('../fonts/Roboto-Bold.woff') format('woff');
+}
+
+body,
+.pure-g [class*='pure-u'] {
+ font-family: Roboto, Arial, sans-serif;
+}
+
+// Extends Pure grids responsive to hide items.
+// Use xx-0 to hide an item on xx screen.
+// Display it at any level with xx-visible.
+.pure-u-0 {
+ display: none !important;
+}
+
@media screen and (min-width: 35.5em) {
- .pure-u-sm-0 { display: none !important; }
- .pure-u-sm-visible { display: inline-block !important; }
+ .pure-u-sm-0 {
+ display: none !important;
+ }
+
+ .pure-u-sm-visible {
+ display: inline-block !important;
+ }
}
+
@media screen and (min-width: 48em) {
- .pure-u-md-0 { display: none !important; }
- .pure-u-md-visible { display: inline-block !important; }
+ .pure-u-md-0 {
+ display: none !important;
+ }
+
+ .pure-u-md-visible {
+ display: inline-block !important;
+ }
}
+
@media screen and (min-width: 64em) {
- .pure-u-lg-0 { display: none !important; }
- .pure-u-lg-visible { display: inline-block !important; }
+ .pure-u-lg-0 {
+ display: none !important;
+ }
+
+ .pure-u-lg-visible {
+ display: inline-block !important;
+ }
}
+
@media screen and (min-width: 80em) {
- .pure-u-xl-0 { display: none !important; }
- .pure-u-xl-visible { display: inline-block !important; }
+ .pure-u-xl-0 {
+ display: none !important;
+ }
+
+ .pure-u-xl-visible {
+ display: inline-block !important;
+ }
}
-/**
- * Make pure-extras alert closable.
- */
-.pure-alert-closable .fa-times {
+// Make pure-extras alert closable.
+.pure-alert-closable {
+ .fa-times {
float: right;
+ }
}
+
.pure-alert-close {
- cursor: pointer;
+ cursor: pointer;
}
.pure-alert-success {
- background-color: #1b926c;
+ background-color: $main-green;
}
-.anchor:target {
+.anchor {
+ &:target {
padding-top: 40px;
+ }
}
-/**
- * MENU
- **/
+
+// MENU
.shaarli-menu {
- position: fixed;
- top: 0;
- width: 100%;
- --height: 50px;
- background: #1b926c;
- -webkit-font-smoothing: antialiased;
- /* Hack to transition with auto height: http://stackoverflow.com/a/8331169/1484919 */
- max-height: 45px;
- transition: max-height 0.5s;
- overflow: hidden;
- z-index: 999;
+ position: fixed;
+ top: 0;
+ transition: max-height .5s;
+ z-index: 999;
+ background: $main-green;
+ width: 100%;
+ // Hack to transition with auto height: http://stackoverflow.com/a/8331169/1484919
+ max-height: 45px;
+ overflow: hidden;
+ -webkit-font-smoothing: antialiased;
+
+ &.open {
+ transition: max-height .75s;
+ max-height: 500px;
+ }
}
-/* Chrome bugfix: with 100% height, it only displays the first element. */
.pure-menu-item {
- height: 45px;
-}
-
-.shaarli-menu.open {
- max-height: 500px;
- transition: max-height 0.75s;
+ // Chrome bugfix: with 100% height, it only displays the first element.
+ height: 45px;
+
+ &:hover {
+ &::after {
+ display: block;
+ margin: -4px auto 0;
+ background: $white;
+ width: 100%;
+ height: 4px;
+ content: '';
+ }
+ }
}
.head-logo {
- float: left;
- margin: 0 5px 0 0;
+ float: left;
+ margin: 0 5px 0 0;
}
-.pure-menu-link,
-.pure-menu-link:visited,
-.pure-menu-selected .pure-menu-link,
-.pure-menu-selected .pure-menu-link:visited {
- padding: 0.8em 1em;
- color: #f5f5f5;
+%menu-link {
+ padding: .8em 1em;
+ color: $almost-white;
}
-.pure-menu-link:hover, .pure-menu-link:focus,
-.pure-menu-selected .pure-menu-link:hover,
-.pure-menu-selected .pure-menu-link:focus {
- color: #fff;
- background: transparent;
+%menu-link-hover {
+ background: transparent;
+ color: $white;
}
-.pure-menu-item:hover::after {
- margin: -4px auto 0 auto;
- display: block;
- content:"";
- background: #fff;
- height: 4px;
- width: 100%;
+.pure-menu-link {
+ @extend %menu-link;
+
+ &:visited {
+ @extend %menu-link;
+ }
+
+ &:hover,
+ &:focus {
+ @extend %menu-link-hover;
+ }
}
-.menu-toggle {
- width: 34px;
- height: 45px;
- position: absolute;
- top: 5px;
- right: 0;
- display: none;
+.pure-menu-selected {
+ .pure-menu-link {
+ @extend %menu-link;
+
+ &:visited {
+ @extend %menu-link;
+ }
+
+ &:hover,
+ &:focus {
+ @extend %menu-link-hover;
+ }
+ }
}
-.menu-toggle .bar {
- background-color: #b0ddce;
+.menu-toggle {
+ display: none;
+ position: absolute;
+ top: 5px;
+ right: 0;
+ width: 34px;
+ height: 45px;
+
+ .bar {
display: block;
- width: 20px;
- height: 2px;
- border-radius: 100px;
position: absolute;
top: 18px;
right: 7px;
- transition: all 0.5s;
-}
+ border-radius: 100px;
+ background-color: $light-green;
+ width: 20px;
+ height: 2px;
+ transition-duration: .5s;
-.menu-toggle .bar:first-child {
- transform: translateY(-6px);
-}
+ &:first-child {
+ transform: translateY(-6px);
+ }
+ }
-.menu-toggle.x .bar {
- transform: rotate(45deg);
-}
+ &.x {
+ .bar {
+ transform: rotate(45deg);
-.menu-toggle.x .bar:first-child {
- transform: rotate(-45deg);
+ &:first-child {
+ transform: rotate(-45deg);
+ }
+ }
+ }
}
@media screen and (max-width: 64em) {
- .menu-toggle {
- display: block;
- }
+ .menu-toggle {
+ display: block;
+ }
}
.header-buttons {
- text-align: right;
+ text-align: right;
}
.linkcount {
- color: #252525;
- font-size: 0.8em;
+ color: $dark-grey;
+ font-size: .8em;
}
@media screen and (min-width: 64em) {
- .linkcount {
- position: absolute;
- right: 5px;
+ .linkcount {
+ position: absolute;
+ right: 5px;
+ }
+}
+
+.searchform-block {
+ width: 100%;
+ text-align: center;
+
+ input {
+ &[type='text'] {
+ border: medium none currentColor;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 $light-shadow, 0 1px 1px $dark-shadow inset;
+ background: $almost-white;
+ padding: 0 5px;
+ width: 260px;
+ height: 30px;
+ color: $dark-grey;
+
+ &::-webkit-input-placeholder {
+ color: $light-grey;
+ }
}
-}
-
-#search, #search-linklist, #search-tagcloud {
- text-align: center;
- width: 100%;
-}
+ }
-#search input[type="text"], #search-linklist input[type="text"] {
- padding: 0 5px;
- height: 30px;
- width: 260px;
- background: #f5f5f5;
- border: medium none currentColor;
- box-shadow: 0 1px 0 rgba(255, 255, 255, 0.078), 0 1px 1px rgba(0, 0, 0, 0.298) inset;
+ button {
+ border: 0;
border-radius: 2px;
- color: #252525;
-}
-@media screen and (max-width: 64em) {
- .searchform {
- max-width: 260px;
- margin: 0 auto;
- }
-}
-
-/* because chrome */
-#search input[type="text"]::-webkit-input-placeholder,
-#search-linklist input[type="text"]::-webkit-input-placeholder {
- color: #777777;
+ background-color: $main-green;
+ padding: 4px 8px 6px;
+ color: $almost-white;
+ }
}
-#search button,
-#search-tagcloud button,
-#search-linklist button {
- padding: 4px 8px 6px 8px;
- background-color: #1B926C;
- color: #f5f5f5;
- border: none;
- border-radius: 2px;
+@media screen and (max-width: 64em) {
+ .searchform {
+ margin: 0 auto;
+ max-width: 260px;
+ }
}
-#search-tagcloud button {
+.search-tagcloud {
+ button {
width: 90%;
+ }
}
@media screen and (max-width: 64em) {
- #search-linklist button {
- width: 100%;
+ .search-linklist {
+ button {
+ width: 100%;
}
- #search-linklist .awesomplete {
- margin: 5px 0;
- }
-}
-#search button:hover,
-#search-linklist button:hover,
-#search-tagcloud button:hover {
- color: #d0d0d0;
-}
-
-#search,
-#search-linklist {
- padding: 6px 0;
-}
-
-@media screen and (max-width: 64em) {
- #search, #search * {
- visibility: hidden;
+ .awesomplete {
+ margin: 5px 0;
}
+ }
}
-.subheader-form a.button {
- color: #f5f5f5;
- font-weight: bold;
- text-decoration: none;
- border: 2px solid #f5f5f5;
- border-radius: 5px;
- padding: 3px 10px;
+.header-search,
+.search-linklist,
+.search-tagcloud {
+ button {
+ &:hover {
+ color: $background-color;
+ }
+ }
}
-.linklist-item-editbuttons .delete-checkbox {
- display: none;
+.header-search,
+.search-linklist {
+ padding: 6px 0;
}
-#header-login-form input[type="text"], #header-login-form input[type="password"] {
- width: 200px;
+@media screen and (max-width: 64em) {
+ .header-search ,
+ .header-search * {
+ visibility: hidden;
+ }
}
-/* because chrome */
-#header-login-form input[type="text"]::-webkit-input-placeholder,
-#header-login-form input[type="password"]::-webkit-input-placeholder {
- color: #777777;
+%subheader-form-input {
+ border: medium none currentColor;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 $light-shadow, 0 1px 4px $dark-shadow inset;
+ background: $almost-white;
+ padding: 5px 5px 3px 15px;
+ width: 20%;
+ height: 20px;
+ color: $dark-grey;
}
.subheader-form {
- visibility: hidden;
- position: fixed;
- width: 100%;
- text-align: center;
- background: #1b926c;
- display: block;
- z-index: 999;
- height: 30px;
- padding: 5px 0;
-}
-
-@media screen and (min-width: 64em) {
- .subheader-form.open, .subheader-form.open * {
- visibility: visible;
+ display: block;
+ position: fixed;
+ visibility: hidden;
+ z-index: 999;
+ background: $main-green;
+ padding: 5px 0;
+ width: 100%;
+ height: 30px;
+ text-align: center;
+
+ input {
+ &[type='text'],
+ &[type='password'] {
+ @extend %subheader-form-input;
+
+ &::-webkit-input-placeholder {
+ color: $dark-grey;
+ }
}
-}
+ }
-.subheader-form input[type="text"], .subheader-form input[type="password"], .subheader-form .remember-me {
- padding: 5px 5px 3px 15px;
- height: 20px;
- width: 20%;
- background: #f5f5f5;
- border: medium none currentColor;
+ &[type='submit'] {
+ display: inline-block;
+ margin: 0 0 5px;
+ border: 1px solid $almost-white;
border-radius: 2px;
- box-shadow: 0 1px 0 rgba(255, 255, 255, 0.078), 0 1px 4px rgba(0, 0, 0, 0.298) inset;
- color: #252525;
-}
+ background: $main-green;
+ padding: 4px 0;
+ width: 100px;
+ height: 28px;
+ color: $almost-white;
-/* because chrome */
-.subheader-form input[type="text"]::-webkit-input-placeholder,
-.subheader-form input[type="password"]::-webkit-input-placeholder
-{
- color: #252525;
-}
+ &:hover {
+ background: $almost-white;
+ color: $main-green;
+ }
+ }
+
+ .remember-me {
+ @extend %subheader-form-input;
-.subheader-form .remember-me {
display: inline-block;
- width: auto;
- padding: 5px 20px 3px 20px;
cursor: pointer;
-}
+ padding: 5px 20px 3px;
+ width: auto;
-.subheader-form .remember-me label, .subheader-form .remember-me input {
- cursor: pointer;
+ label,
+ input {
+ cursor: pointer;
+ }
+ }
+
+ a {
+ &.button {
+ border: 2px solid $almost-white;
+ border-radius: 5px;
+ padding: 3px 10px;
+ text-decoration: none;
+ color: $almost-white;
+ font-weight: bold;
+ }
+ }
}
-.subheader-form input[type="submit"] {
- display: inline-block;
- margin: 0 0 5px 0;
- padding: 4px 0 4px 0;
- height: 28px;
- width: 100px;
- background: #1b926c;
- border: 1px solid #f5f5f5;
- color: #f5f5f5;
- border-radius: 2px;
+.header-login-form {
+ input {
+ &[type='text'],
+ &[type='password'] {
+ width: 200px;
+
+ // because chrome
+ &::-webkit-input-placeholder {
+ color: $light-grey;
+ }
+ }
+ }
}
-.subheader-form input[type="submit"]:hover {
- background: #f5f5f5;
- color: #1b926c;
+@media screen and (min-width: 64em) {
+ .subheader-form {
+ &.open {
+ visibility: visible;
+
+ * {
+ visibility: visible;
+ }
+ }
+ }
}
.new-version-message {
- text-align: center;
-}
+ text-align: center;
-.new-version-message a {
- color: rgb(151, 96, 13);
+ a {
+ color: $warning-text;
font-weight: bold;
+ }
}
-/**
- * CONTENT - GENERAL
- */
-#content {
- position: relative;
- z-index: 2;
- margin-top: 45px;
+// CONTENT - GENERAL
+.container {
+ position: relative;
+ z-index: 2;
+ margin-top: 45px;
}
-/**
- * Plugins additional forms
- */
+// Plugins additional forms
.toolbar-plugin {
- margin: 5px 0;
- text-align: center;
-}
-
-.toolbar-plugin input[type="text"] {
- padding: 0 5px;
- height: 30px;
- width: 300px;
- background: #f5f5f5;
- border: medium none currentColor;
- box-shadow: 0 1px 0 rgba(255, 255, 255, 0.078), 0 1px 1px rgba(0, 0, 0, 0.298) inset;
- border-radius: 2px;
- color: #252525;
-}
-
-/* because chrome */
-.toolbar-plugin input[type="text"]::-webkit-input-placeholder {
- color: #777777;
-}
-
-.toolbar-plugin input[type="submit"] {
- padding: 0 10px;
- height: 30px;
- background: #f5f5f5;
- border: medium none currentColor;
- border-radius: 2px;
- color: #252525;
-}
+ margin: 5px 0;
+ text-align: center;
+
+ input {
+ &[type='text'] {
+ border: medium none currentColor;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 $light-shadow, 0 1px 1px $dark-shadow inset;
+ background: $almost-white;
+ padding: 0 5px;
+ width: 300px;
+ height: 30px;
+ color: $dark-grey;
+
+ &::-webkit-input-placeholder {
+ color: $light-grey;
+ }
+ }
-.toolbar-plugin input[type="submit"]:hover {
- background: #fff;
+ &[type='submit'] {
+ border: medium none currentColor;
+ border-radius: 2px;
+ background: $almost-white;
+ padding: 0 10px;
+ height: 30px;
+ color: $dark-grey;
+
+ &:hover {
+ background: $white;
+ }
+ }
+ }
}
@media screen and (max-width: 64em) {
- .toolbar-plugin input[type="text"] {
+ .toolbar-plugin {
+ input {
+ &[type='text'] {
width: 70%;
-
+ }
}
+ }
}
-/**
- * CONTENT - LINKLIST PAGING
- * 64em -> lg
- */
+// CONTENT - LINKLIST PAGING
+// 64em -> lg
.linklist-filters {
- margin: 5px 0;
- color: #252525;
- font-size: 0.9em;
-}
+ margin: 5px 0;
+ color: $dark-grey;
+ font-size: .9em;
-.linklist-filters a {
+ a {
padding: 5px 8px;
text-decoration: none;
-}
+ }
-.linklist-filters .filter-off {
- color: #252525;
- background: #f5f5f5;
-}
+ .filter-off {
+ background: $almost-white;
+ color: $dark-grey;
+ }
-.linklist-filters .filter-on {
- color: #b0ddce;
- background: #1b926c;
-}
+ .filter-on {
+ background: $main-green;
+ color: $light-green;
+ }
-.linklist-filters .filter-block {
- color: #f5f5f5;
- background: #ac2925;
+ .filter-block {
+ background: $red;
+ color: $almost-white;
+ }
}
.linklist-pages {
- margin: 5px 0;
- color: #252525;
- text-align: center;
-}
+ margin: 5px 0;
+ text-align: center;
+ color: $dark-grey;
-.linklist-pages a {
- color: #252525;
+ a {
text-decoration: none;
+ color: $dark-grey;
+
+ &:hover {
+ color: $white;
+ }
+ }
}
-.linklist-pages a:hover {
- color: #fff;
+%linksperpage-button {
+ display: inline-block;
+ width: 20px;
+ text-align: center;
}
.linksperpage {
- margin: 5px 0;
- text-align: right;
- color: #252525;
- font-size: 0.9em;
-}
+ margin: 5px 0;
+ text-align: right;
+ color: $dark-grey;
+ font-size: .9em;
-.linksperpage a {
- padding: 5px 5px;
- text-decoration: none;
- color: #252525;
- background: #f5f5f5;
-}
+ form {
+ display: inline;
+ }
-.linksperpage a, .linksperpage input[type="text"] {
- display: inline-block;
- width: 20px;
- text-align: center;
-}
+ a {
+ @extend %linksperpage-button;
-.linksperpage form {
- display: inline;
+ background: $almost-white;
+ padding: 5px;
+ text-decoration: none;
+ color: $dark-grey;
+ }
+
+ input {
+ &[type='text'] {
+ @extend %linksperpage-button;
+
+ margin: 0;
+ border: medium none currentColor;
+ background: $almost-white;
+ padding: 4px 5px 3px 8px;
+ height: 20px;
+ color: $dark-grey;
+ font-size: .8em;
+ }
+ }
}
-.linksperpage input[type="text"] {
- height: 20px;
- margin: 0;
- padding: 4px 5px 3px 8px;
- background: #f5f5f5;
- border: medium none currentColor;
- color: #252525;
- font-size: 0.8em;
+// CONTENT - LINKLIST ITEMS
+%private-border {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 3px;
+ z-index: 1;
+ background: $orange;
+ width: 2px;
+ height: 96%;
+ content: '';
}
-/**
- * CONTENT - LINKLIST ITEMS
- */
.linklist-item {
- margin: 0 0 10px 0;
- background: #f5f5f5;
- box-shadow: 1px 1px 3px #797979;
+ margin: 0 0 10px;
+ box-shadow: 1px 1px 3px $light-grey;
+ background: $almost-white;
+
+ &.private {
+ .linklist-item-title {
+ &::before {
+ @extend %private-border;
+ margin-top: 3px;
+ }
+ }
+
+ .linklist-item-description {
+ &::before {
+ @extend %private-border;
+ height: 100%;
+ }
+ }
+ }
}
.linklist-item-buttons {
- background: transparent;
- position: relative;
- width: 23px;
- z-index: 99;
+ position: relative;
+ z-index: 99;
+ background: transparent;
+ width: 23px;
}
.linklist-item-buttons-right {
- float: right;
- margin-right: -25px;
+ float: right;
+ margin-right: -25px;
}
.linklist-item-buttons * {
- display: block;
- float: left;
- width:100%;
- margin: auto;
- text-align: center;
-}
-
-.linklist-item-title, .linklist-item-title h2 {
- margin: 0;
- word-wrap: break-word;
+ display: block;
+ float: left;
+ margin: auto;
+ width: 100%;
+ text-align: center;
}
.linklist-item-title {
- position: relative;
- background: #f5f5f5;
-}
+ position: relative;
+ margin: 0;
+ background: $almost-white;
+ word-wrap: break-word;
-.linklist-item-title h2 {
- padding: 3px 10px 0 10px;
+ h2 {
+ margin: 0;
+ padding: 3px 10px 0;
line-height: 30px;
-}
+ word-wrap: break-word;
-.linklist-item-title h2 a {
- font-size: 0.7em;
- color: #252525;
- text-decoration: none;
- vertical-align: middle;
-}
+ a {
+ vertical-align: middle;
+ text-decoration: none;
+ color: $dark-grey;
+ font-size: .7em;
+
+ &:visited {
+ .linklist-link {
+ color: $dark-green;
+ }
+ }
+
+ &:hover {
+ color: $dark-grey;
+ }
+ }
+ }
-.linklist-item-title .linklist-link {
+ .linklist-link {
+ color: $main-green;
font-size: 1.1em;
- color: #1b926c;
-}
-
-.linklist-item-title h2 a:visited .linklist-link {
- color: #2a4c41;
-}
-
-.linklist-item-title h2 a:hover, .linklist-item-title .linklist-link:hover{
- color: #252525;
-}
+ &:hover {
+ color: $dark-grey;
+ }
+ }
-.linklist-item-title .label-private {
- border: solid 1px #F89406;
+ .label-private {
+ border: solid 1px $orange;
+ color: $orange;
font-family: Arial, sans-serif;
- font-size: 0.65em;
- color: #F89406;
+ font-size: .65em;
+ }
}
.fold-button {
- display: none;
- color: #252525;
+ display: none;
+ color: $dark-grey;
}
.linklist-item-editbuttons {
- float: right;
- padding: 8px 5px;
-}
+ float: right;
+ padding: 8px 5px;
-.linklist-item-editbuttons * {
+ * {
display: block;
float: left;
margin: 0 1px;
-}
+ }
-.linklist-item-editbuttons a {
+ a {
font-size: 1em;
+ }
+
+ .delete-checkbox {
+ display: none;
+ }
}
.edit-link {
- font-size: 1.2em;
- color: #0b5ea6;
+ color: $blue;
+ font-size: 1.2em;
}
.delete-link {
- font-size: 1.3em;
- color: #ac2925 !important;
+ color: $red !important;
+ font-size: 1.3em;
}
.linklist-item-description {
- position: relative;
- padding: 0 10px;
- word-wrap: break-word;
- color: #252525;
- line-height: 1.3em;
-}
+ position: relative;
+ padding: 0 10px;
+ line-height: 1.3em;
+ color: $dark-grey;
+ word-wrap: break-word;
-.linklist-item-description a {
+ a {
text-decoration: none;
- color: #1b926c;
-}
+ color: $main-green;
-.linklist-item-description a:hover {
- color: #252525;
-}
+ &:hover {
+ color: $dark-grey;
+ }
-.linklist-item-description a:visited {
- color: #14553f;
+ &:visited {
+ color: $dark-green;
+ }
+ }
}
.linklist-item-thumbnail {
- position: relative;
- padding: 0 0 0 5px;
- margin: 0;
- float: right;
- z-index: 50;
- height: 90px;
-}
-
-.linklist-item.private .linklist-item-title::before,
-.linklist-item.private .linklist-item-description::before {
- position: absolute;
- left: 3px;
- top: 0;
- display: block;
- content:"";
- background: #F89406;
- height: 96%;
- width: 2px;
- z-index: 1;
-}
-
-.linklist-item.private .linklist-item-description::before {
- height: 100%;
-}
-
-.linklist-item.private .linklist-item-title::before {
- margin-top: 3px;
+ position: relative;
+ float: right;
+ z-index: 50;
+ margin: 0;
+ padding: 0 0 0 5px;
+ height: 90px;
}
.linklist-item-infos {
- padding: 4px 8px 4px 8px;
- background: #ddd;
- color: #252525;
-}
+ background: $background-linklist-info;
+ padding: 4px 8px;
+ color: $dark-grey;
-.linklist-item-infos a {
- color: #252525;
+ a {
text-decoration: none;
-}
+ color: $dark-grey;
-.linklist-item-infos a:hover {
- color: #000;
-}
+ &:hover {
+ color: $black;
+ }
+ }
-.linklist-item-infos .linklist-item-tags {
- font-size: 0.8em;
-}
+ .linklist-item-tags {
+ font-size: .8em;
+ }
-.linklist-item-infos .label-tag {
+ .label-tag {
font-size: 1em;
+ }
+
+ .mobile-buttons {
+ text-align: right;
+ }
+
+ .linklist-plugin-icon {
+ display: inline-block;
+ margin: 0 2px;
+ width: 16px;
+ height: 16px;
+ }
}
.linklist-item-infos-dateblock {
- font-size: 0.9em;
+ font-size: .9em;
}
.linklist-plugin-icon {
- width: 13px;
- height: 13px;
+ width: 13px;
+ height: 13px;
}
.linklist-item-infos-url {
- text-align: right;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- font-size: 0.8em;
- height:23px;
- line-height:23px;
-}
-
-.linklist-item-infos .mobile-buttons {
- text-align: right;
-}
-
-.linklist-item-infos .linklist-plugin-icon {
- display: inline-block;
- margin: 0 2px;
- width: 16px;
- height: 16px;
+ height: 23px;
+ overflow: hidden;
+ text-align: right;
+ text-overflow: ellipsis;
+ line-height: 23px;
+ white-space: nowrap;
+ font-size: .8em;
}
.linklist-item-infos-controls-group {
- display: inline-block;
- border-right: 1px solid #5d5d5d;
- padding-right: 6px;
+ display: inline-block;
+ border-right: 1px solid $light-grey;
+ padding-right: 6px;
}
.ctrl-edit {
- margin: 0 7px;
+ margin: 0 7px;
}
-/** 64em -> lg **/
+// 64em -> lg
@media screen and (max-width: 64em) {
- .linklist-item-infos-url {
- text-align: left;
- }
+ .linklist-item-infos-url {
+ text-align: left;
+ }
}
-/**
- * Footer
- */
-#footer {
- margin: 20px 0;
- padding: 5px;
- text-align: center;
- color: #252525;
-}
+// Footer
+.footer-container {
+ margin: 20px 0;
+ padding: 5px;
+ text-align: center;
+ color: $dark-grey;
-#footer:before {
+ &::before {
display: block;
- content:"";
- background: linear-gradient(to right, #949393, #252525, #949393);
- height: 1px;
- width: 80%;
margin: 10px auto;
+ background: linear-gradient(to right, $background-color, $dark-grey, $background-color);
+ width: 80%;
+ height: 1px;
+ content: '';
+ }
+
+ a {
+ color: $dark-grey;
+ }
+}
+
+// PAGE FORM
+%page-form-input {
+ margin: 10px 0;
+ border: solid 1px $form-input-border;
+ border-radius: 2px;
+ background: $form-input-background;
+ padding: 5px 5px 3px 15px;
+ width: 90%;
+ height: 35px;
+ color: $dark-grey;
+ box-sizing: border-box;
+}
+
+%page-form-button {
+ display: inline-block;
+ margin: 15px 5px;
+ border: 0;
+ 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;
+ background: $main-green;
+ min-width: 150px;
+ height: 35px;
+ vertical-align: center;
+ text-decoration: none;
+ line-height: 35px;
+ color: $almost-white;
+ font-size: 1.2em;
+ font-weight: normal;
}
-#footer a {
- color: #252525;
-}
-
-/**
- * PAGE FORM
- */
.page-form {
- margin: 20px 0 0 0;
- background: #f5f5f5;
- box-shadow: 1px 1px 2px #797979;
- color: #252525;
- overflow: hidden;
-}
-
-.page-form .window-title {
- margin: 0 0 10px 0;
+ margin: 20px 0 0;
+ box-shadow: 1px 1px 2px $light-grey;
+ background: $almost-white;
+ overflow: hidden;
+ color: $dark-grey;
+
+ .window-title {
+ margin: 0 0 10px;
+ background: $almost-white;
padding: 10px 0;
width: 100%;
- color: #1b926c;
- background: #f5f5f5;
text-align: center;
-}
+ color: $main-green;
+ }
-.page-form .window-subtitle {
+ .window-subtitle {
text-align: center;
-}
+ }
-.page-form a {
- color: #1b926c;
- font-weight: bold;
+ a {
text-decoration: none;
-}
+ color: $main-green;
+ font-weight: bold;
-.page-form p {
- padding: 5px 10px;
+ &.button {
+ @extend %page-form-button;
+ }
+ }
+
+ p {
margin: 0;
-}
+ padding: 5px 10px;
+ }
-.page-form input[type="text"],
-.page-form input[type="password"],
-.page-form textarea {
- box-sizing: border-box;
- margin: 10px 0;
- padding: 5px 5px 3px 15px;
- height: 35px;
- width: 90%;
- background: #eeeeee;
- border: solid 1px #d8d8d8;
- border-radius: 2px;
- color: #252525;
-}
+ input {
+ &[type='text'] {
+ @extend %page-form-input;
+
+ &::-webkit-input-placeholder {
+ color: $light-grey;
+ }
+ }
+
+ &[type='password'] {
+ @extend %page-form-input;
+
+ &::-webkit-input-placeholder {
+ color: $light-grey;
+ }
+ }
+
+ &[type='submit'] {
+ @extend %page-form-button;
+ }
+ }
+
+ textarea {
+ @extend %page-form-input;
-.page-form textarea {
- min-height: 240px;
padding: 15px 5px 3px 15px;
+ min-height: 240px;
resize: vertical;
overflow-y: auto;
- word-wrap:break-word
-}
+ word-wrap: break-word;
+ }
-/* because chrome */
-.page-form input[type="text"]::-webkit-input-placeholder,
-.page-form input[type="password"]::-webkit-input-placeholder {
- color: #777777;
-}
+ select {
+ color: $dark-grey;
+ }
-.page-form input[type="submit"], .page-form a.button {
- margin: 15px 5px;
- height: 35px;
- line-height: 35px;
- width: 150px;
- background: #1b926c;
- color: #f5f5f5;
- border: none;
- box-shadow: 1px 1px 1px #ddd, -1px -1px 6px #ddd, -1px 1px 2px #ddd, 1px -1px 2px #ddd;
- font-size: 1.2em;
- text-decoration: none;
- vertical-align: center;
- font-weight: normal;
- display: inline-block;
-}
+ .button {
+ &.button-red {
+ background: $red;
+ }
+ }
+ .submit-buttons {
+ margin-bottom: 10px;
+ }
-.page-form .button.button-red {
- background: #ac2925;
-}
+ section {
+ margin: 10px 0 25px;
+ }
-.page-form .submit-buttons {
- margin-bottom: 10px;
-}
+ table,
+ th,
+ td {
+ border-width: 1px 0;
+ border-style: solid;
+ border-color: $light-grey;
+ }
-@media screen and (min-width: 64em) {
- .page-form .submit-buttons {
- position: relative;
+ th,
+ td {
+ padding: 5px;
+ }
+
+ table {
+ margin: auto;
+ width: 90%;
+
+ .order {
+ text-decoration: none;
+ color: $dark-grey;
}
+ }
- .page-form .submit-buttons .button.button-red {
- position: absolute;
- right: 5%;
+ .awesomplete {
+ width: 90%;
+
+ input {
+ width: 100%;
+ }
+ }
+
+ div {
+ .awesomplete {
+ > ul {
+ color: $black;
+ }
}
+ }
+}
+
+@media screen and (min-width: 64em) {
+ .page-form {
+ .submit-buttons {
+ position: relative;
+
+ .button {
+ &.button-red {
+ position: absolute;
+ right: 5%;
+ }
+ }
+ }
+ }
}
@media screen and (max-width: 64em) {
- .page-form .submit-buttons .button {
+ .page-form {
+ .submit-buttons {
+ .button {
display: block;
margin: auto;
+ }
}
+ }
}
-.page-form select {
- color: #252525;
-}
-
-/**
- * PAGE FORM - LIGHT
- */
-.page-form-light div, .page-form-light p {
+// PAGE FORM - LIGHT
+.page-form-light {
+ div,
+ p {
text-align: center;
+ }
}
-/**
- * PAGE FORM - COMPLETE
- */
-.page-form-complete div, .page-form-complete p {
- color: #252525;
+// PAGE FORM - COMPLETE
+%page-form-valign {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
}
-.page-form-complete .form-label, .page-form-complete .form-input {
+.page-form-complete {
+ div,
+ p {
+ color: $dark-grey;
+ }
+
+ .form-label,
+ .form-input {
position: relative;
height: 60px;
-}
+ }
-.page-form-complete .form-label label,
-.page-form-complete .form-input input,
-.page-form-complete .form-input select.align,
-.page-form-complete .timezone {
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
-}
+ .form-label {
+ label {
+ @extend %page-form-valign;
-.page-form-complete .form-label label {
- text-align: right;
- right: 0;
- padding: 0 20px;
-}
+ right: 0;
+ padding: 0 20px;
+ text-align: right;
+ }
+ }
-.page-form-complete .label-name {
+ .label-name {
font-weight: bold;
-}
-
-.page-form-complete .label-desc {
- font-size: 0.8em;
-}
+ }
-.page-form-complete input[type="text"],
-.page-form-complete input[type="password"],
-.page-form-complete textarea {
- margin: 0;
-}
-
-.page-form section {
- margin: 10px 0 25px 0;
-}
+ .label-desc {
+ font-size: .8em;
+ }
-.page-form table {
- margin: auto;
- width: 90%;
-}
+ .form-input {
+ input {
+ @extend %page-form-valign;
-.page-form table .order {
- text-decoration: none;
- color: #252525;
-}
+ &[type='text'],
+ &[type='password'] {
+ margin: 0;
+ }
+ }
-.page-form table, .page-form th, .page-form td {
- border-width: 1px 0;
- border-style: solid;
- border-color: #aaaaaa;
-}
+ select {
+ &.align {
+ @extend %page-form-valign;
+ }
+ }
+ }
-.page-form th, .page-form td {
- padding: 5px;
+ textarea {
+ margin: 0;
+ }
+ .timezone {
+ @extend %page-form-valign;
+ }
}
-/* Awesomeplete fix */
-div.awesomplete {
+// Awesomeplete fix
+div {
+ &.awesomplete {
width: inherit;
-}
-div.awesomplete > input {
- display: inherit;
-}
+ > input {
+ display: inherit;
+ }
-div.awesomplete > ul {
- z-index: 9999;
+ > ul {
+ z-index: 9999;
+ }
+ }
}
-.page-form .awesomplete {
- width: 90%;
+form {
+ &[name='linkform'] {
+ &.page-form {
+ overflow: visible;
+ }
+ }
}
-.page-form .awesomplete input {
- width: 100%;
-}
+@media screen and (max-width: 64em) {
+ %page-form-valign-mobile {
+ position: inherit;
+ top: inherit;
+ transform: translateY(0);
+ }
-.page-form div.awesomplete > ul {
- color: black;
-}
+ .page-form-complete {
+ .form-label {
+ height: inherit;
-form[name="linkform"].page-form {
- overflow: visible;
-}
+ label {
+ @extend %page-form-valign-mobile;
-@media screen and (max-width: 64em) {
- .page-form-complete .form-label {
- height: inherit;
+ display: block;
+ margin: 10px 0 0;
+ text-align: left;
+ }
}
- .page-form-complete .form-label label,
- .page-form-complete .form-input input,
- .page-form-complete .timezone {
- position: inherit;
- top: inherit;
- transform: translateY(0);
- }
+ .form-input {
+ text-align: center;
- .page-form-complete .form-input input[type="checkbox"] {
- position: absolute;
- top: 50%;
- right: 50%;
- transform: translateY(-50%);
- }
+ input {
+ @extend %page-form-valign-mobile;
- .page-form-complete .form-input {
- text-align: center;
+ &[type='checkbox'] {
+ position: absolute;
+ top: 50%;
+ right: 50%;
+ transform: translateY(-50%);
+ }
+ }
}
- .page-form-complete .form-label label {
- display: block;
- text-align: left;
- margin: 10px 0 0 0;
+ .timezone {
+ @extend %page-form-valign-mobile;
}
- .timezone-continent:after {
- content:"\a\a";
- white-space: pre;
+ .radio-buttons {
+ padding: 5px 15px;
+ text-align: left;
}
+ }
- .page-form-complete .radio-buttons {
- text-align: left;
- padding: 5px 15px;
+ .timezone-continent {
+ &::after {
+ white-space: pre;
+ content: '\a\a';
}
+ }
}
-/**
- * Page visitor (page form extended)
- */
+// Page visitor (page form extended)
.page-visitor {
- color: #252525;
+ color: $dark-grey;
}
-#page404 {
- color: #3f3f3f;
+.page404-container {
+ color: $dark-grey;
}
-/**
- * EDIT LINK
- */
-#editlinkform .created-date {
- color: #767676;
+// EDIT LINK
+.edit-link-container {
+ .created-date {
margin-bottom: 10px;
+ color: $light-grey;
+ }
}
-/**
- * LOGIN
- */
-#login-form .remember-me {
+// LOGIN
+.login-form-container {
+ .remember-me {
margin: 5px 0;
+ }
}
-/**
- * Search results
- */
-.search-result a {
- color: white;
+// Search results
+.search-result {
+ a {
text-decoration: none;
-}
+ color: $white;
+ }
-.search-result .label-tag {
- border-color: white;
-}
+ .label-tag {
+ border-color: $white;
-.search-result .label-tag .remove {
- border-left: white 1px solid;
- padding: 0 0 0 5px;
- margin: 0 0 0 5px;
-}
+ .remove {
+ margin: 0 0 0 5px;
+ border-left: $white 1px solid;
+ padding: 0 0 0 5px;
+ }
+ }
-.search-result .label-private {
- border: 1px solid white;
+ .label-private {
+ border: 1px solid $white;
+ }
}
-/**
- * TOOLS
- */
+// TOOLS
.tools-item {
- margin: 10px 0;
-}
+ margin: 10px 0;
-.tools-item .pure-button:hover {
- background-image: none;
- background-color: #1b926c;
- color: #f5f5f5;
+ .pure-button {
+ &:hover {
+ background-color: $main-green;
+ background-image: none;
+ color: $almost-white;
+ }
+ }
}
-/**
- * PLUGIN ADMIN
- */
-#pluginform .mobile-row {
- font-size: 0.9em;
-}
+// PLUGIN ADMIN
+.pluginform-container {
+ .mobile-row {
+ font-size: .9em;
+ }
-#pluginform .more {
+ .more {
margin-top: 10px;
+ }
}
@media screen and (max-width: 64em) {
- #pluginform .main-row, #pluginform .main-row td {
- border-bottom-style: none;
- }
+ .pluginform-container {
+ .main-row {
+ border-top-style: none;
+ border-bottom-style: none;
- #pluginform .mobile-row, #pluginform .mobile-row td {
+ td {
border-top-style: none;
+ border-bottom-style: none;
+ }
}
+ }
}
-/**
- * IMPORT
- */
-#import-field {
- margin: 15px 0;
+// IMPORT
+.import-field-container {
+ margin: 15px 0;
}
-/**
- * TAG CLOUD
- */
-#cloudtag {
- padding: 10px;
- text-align: center;
-}
+// TAG CLOUD
+.cloudtag-container {
+ padding: 10px;
+ text-align: center;
+ text-decoration: none;
+ color: $dark-grey;
-#cloudtag, #cloudtag a {
- color: #252525;
+ a {
text-decoration: none;
-}
+ color: $dark-grey;
+ }
-#cloudtag .count {
- color: #7f7f7f;
+ .count {
+ color: $light-grey;
+ }
}
-/**
- * TAG LIST
- */
-#taglist {
- padding: 0 10px;
-}
+// TAG LIST
+.taglist-container {
+ padding: 0 10px;
-#taglist a {
- color: #252525;
+ a {
text-decoration: none;
-}
+ color: $dark-grey;
+ }
-#taglist .count {
+ .count {
display: inline-block;
width: 35px;
text-align: right;
- color: #7f7f7f;
-}
+ color: $light-grey;
+ }
-#taglist .rename-tag-form {
+ .rename-tag-form {
display: none;
-}
+ }
-#taglist .delete-tag {
- color: #ac2925;
+ .delete-tag {
display: none;
-}
-
-#taglist .rename-tag {
- color: #0b5ea6;
-}
-
-#taglist .validate-rename-tag {
- color: #1b926c;
-}
-
-/**
- * Picture wall CSS
- */
-#picwall_container {
- margin: 0 10px 10px 10px;
- color: #252525;
- background-color: #f5f5f5;
- clear: both;
-}
-
-.picwall_pictureframe {
- margin: 2px;
- background-color: #f5f5f5;
- z-index: 5;
- position: relative;
- display: table-cell;
- vertical-align: middle;
- width: 90px;
- height: 90px;
- overflow: hidden;
- text-align: center;
- float: left;
-}
-
-.b-lazy {
- -webkit-transition: opacity 500ms ease-in-out;
- -moz-transition: opacity 500ms ease-in-out;
- -o-transition: opacity 500ms ease-in-out;
- transition: opacity 500ms ease-in-out;
- opacity: 0;
-}
-.b-lazy.b-loaded {
- opacity: 1;
-}
-
-.picwall_pictureframe img {
+ color: $red;
+ }
+
+ .rename-tag {
+ color: $blue;
+ }
+
+ .validate-rename-tag {
+ color: $main-green;
+ }
+}
+
+// Picture wall CSS
+.picwall-container {
+ clear: both;
+ margin: 0 10px 10px;
+ background-color: $almost-white;
+ color: $dark-grey;
+}
+
+.picwall-pictureframe {
+ display: table-cell;
+ position: relative;
+ float: left;
+ z-index: 5;
+ margin: 2px;
+ background-color: $almost-white;
+ width: 90px;
+ height: 90px;
+ overflow: hidden;
+ vertical-align: middle;
+ text-align: center;
+
+ // Adapt the width of the image
+ img {
max-width: 100%;
height: auto;
color: transparent;
-} /* Adapt the width of the image */
+ }
-.picwall_pictureframe a {
+ a {
text-decoration: none;
-}
+ }
-/* CSS to show title when hovering an image - no javascript required. */
-.picwall_pictureframe span.info {
- display: none;
- font-family: Arial, sans-serif;
+ span {
+ &.info {
+ display: none;
+ font-family: Arial, sans-serif;
+ }
+ }
+
+ // CSS to show title when hovering an image - no javascript required.
+ &:hover {
+ span {
+ &.info {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: $dark-shadow;
+ width: 90px;
+ height: 90px;
+ text-align: left;
+ color: $almost-white;
+ font-size: 9pt;
+ font-weight: bold;
+ }
+ }
+ }
}
-.picwall_pictureframe:hover span.info {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
- width: 90px;
- height: 90px;
- font-weight: bold;
- font-size: 9pt;
- color: #f5f5f5;
- text-align: left;
- background-color: rgba(0, 0, 0, 0.8);
+.b-lazy {
+ transition: opacity 500ms ease-in-out;
+ opacity: 0;
+ -webkit-transition: opacity 500ms ease-in-out;
+ -moz-transition: opacity 500ms ease-in-out;
+ -o-transition: opacity 500ms ease-in-out;
+
+ &.b-loaded {
+ opacity: 1;
+ }
}
-/**
- * DAILY
- */
+// DAILY
.daily-desc {
- color: #7f7f7f;
- font-size: 0.8em;
-}
+ color: $light-grey;
+ font-size: .8em;
-.daily-about a {
- color: #343434;
+ a {
text-decoration: none;
-}
-
-.daily-about a:hover {
- color: #7f7f7f;
-}
+ color: $dark-grey;
-.daily-about h3:before, .daily-about h3:after {
- display: block;
- content:"";
- background: linear-gradient(to right, #d5d4d4, #252525, #d5d4d4);
- height: 1px;
- width: 90%;
- margin: 10px auto;
+ &:hover {
+ color: $light-grey;
+ }
+ }
+}
+
+.daily-about {
+ h3 {
+ &::before,
+ &::after {
+ display: block;
+ margin: 10px auto;
+ background: linear-gradient(to right, $background-color, $dark-grey, $background-color);
+ width: 90%;
+ height: 1px;
+ content: '';
+ }
+ }
}
.daily-entry {
- padding: 0 10px;
-}
+ padding: 0 10px;
-.daily-entry .daily-entry-title:after {
- display: block;
- content:"";
- background: linear-gradient(to right, #fff, #515151, #fff);
- height: 1px;
- width: 70%;
- margin: 5px auto;
-}
+ .daily-entry-title {
+ margin: 10px 0 0;
-.daily-entry .daily-entry-title {
- margin: 10px 0 0 0;
-}
+ a {
+ text-decoration: none;
+ color: $black;
+ }
-.daily-entry .daily-entry-title a {
- color: #000;
- text-decoration: none;
-}
+ &::after {
+ display: block;
+ margin: 5px auto;
+ background: linear-gradient(to right, $white, $light-grey, $white);
+ width: 70%;
+ height: 1px;
+ content: '';
+ }
+ }
-.daily-entry .daily-entry-description {
- padding: 5px 5px 0 5px;
- font-size: 0.9em;
+ .daily-entry-description {
+ padding: 5px 5px 0;
text-align: justify;
+ font-size: .9em;
word-wrap: break-word;
-}
+ }
-.daily-entry .daily-entry-tags {
- padding: 0 5px 5px 5px;
- font-size: 0.8em;
+ .daily-entry-tags {
+ padding: 0 5px 5px;
+ font-size: .8em;
+ }
}
.daily-entry-thumbnail {
- float: left;
- margin: 15px 5px 5px 15px;
+ float: left;
+ margin: 15px 5px 5px 15px;
}
-.daily-entry-description a {
+.daily-entry-description {
+ a {
text-decoration: none;
- color: #1b926c;
-}
+ color: $main-green;
-.daily-entry-description a:hover {
- text-shadow: 1px 1px #ddd;
-}
+ &:hover {
+ text-shadow: 1px 1px $background-linklist-info;
+ }
-.daily-entry-description a:visited {
- color: #20b988;
+ &:visited {
+ color: $dark-green;
+ }
+ }
}
-/*
- * Fix empty bookmarklet name in Firefox
- */
+// Fix empty bookmarklet name in Firefox
.pure-button {
- -moz-user-select: auto;
+ -moz-user-select: auto;
}
.tag-sort {
- margin-top: 30px;
- text-align: center;
-}
+ margin-top: 30px;
+ text-align: center;
-.tag-sort a {
+ a {
display: inline-block;
margin: 0 15px;
- color: white;
text-decoration: none;
+ color: $white;
font-weight: bold;
+ }
}
-/**
- * Markdown
- */
-.markdown p {
+// Markdown
+.markdown {
+ p {
margin: 0 !important;
-}
+ }
-.markdown p + p {
- margin: 0.5em 0 0 0 !important;
-}
+ p + p {
+ margin: .5em 0 0 !important;
+ }
-.markdown *:first-child {
- margin-top: 0 !important;
-}
+ * {
+ &:first-child {
+ margin-top: 0 !important;
+ }
-.markdown *:last-child {
- margin-bottom: 5px !important;
+ &:last-child {
+ margin-bottom: 5px !important;
+ }
+ }
}
-/**
- * Pure Button
- */
+// Pure Button
.pure-button-success,
.pure-button-error,
.pure-button-warning,
.pure-button-primary,
.pure-button-shaarli,
.pure-button-secondary {
- color: white !important;
- border-radius: 4px;
- text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+ border-radius: 4px;
+ text-shadow: 0 1px 1px $dark-shadow;
+ color: $white !important;
}
.pure-button-shaarli {
- background-color: #1B926C;
+ background-color: $main-green;
}
"Shaarli\\Api\\Controllers\\": "application/api/controllers",
"Shaarli\\Api\\Exceptions\\": "application/api/exceptions",
"Shaarli\\Config\\": "application/config/",
- "Shaarli\\Config\\Exception\\": "application/config/exception"
+ "Shaarli\\Config\\Exception\\": "application/config/exception",
+ "Shaarli\\Security\\": "application/security"
}
}
}
+++ /dev/null
-## Add the sharing button (_bookmarklet_) to your browser
-
-- Open your Shaarli and `Login`
-- Click the `Tools` button in the top bar
-- Drag the **`✚Shaare link` button**, and drop it to your browser's bookmarks bar.
-
-_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._
-
-![](images/bookmarklet.png)
-
-## Share links using the _bookmarklet_
-
-- When you are visiting a webpage you would like to share with Shaarli, click the _bookmarklet_ you just added.
-- A window opens.
- - You can freely edit title, description, tags... to find it later using the text search or tag filtering.
- - You will be able to edit this link later using the ![](images/edit_icon.png) edit button.
- - You can also check the “Private” box so that the link is saved but only visible to you.
-- Click `Save`.**Voilà! Your link is now shared.**
-
-## Troubleshooting: The bookmarklet doesn't work with a few websites (e.g. Github.com)
-
-Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunatly, there is nothing Shaarli can do about it.
-
-See [#196](https://github.com/shaarli/Shaarli/issues/196).
-
-There is an open bug for both Firefox and Chromium:
-
-- https://bugzilla.mozilla.org/show_bug.cgi?id=866522
-- https://code.google.com/p/chromium/issues/detail?id=233903
- [ShaarliOS](https://github.com/mro/ShaarliOS) - Apple iOS share extension.
- [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider
- [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add links directly into your Shaarli
+- [Stakali for Android](https://stakali.toneiv.eu) - Stakali is a personal bookmark manager which synchronizes with Shaarli
### Browser addons
- * [Shaarli Web Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli.
+- [Shaarli Firefox Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli.
+- [Shaarli Chrome Extension](https://github.com/octplane/Shiny-Shaarli) - toolbar button to share your current tab with Shaarli.
### Server apps
- [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content
+++ /dev/null
-| Note | Firefox Share is no longer available for Firefox 57 and later versions. |
-|---------|---------|
-
-### Add Shaarli as a sharing service to Firefox
-
-- Open your Shaarli and `Login`
-- Click the `Tools` button in the top bar
-- Click the `✚Add to Firefox social` button and accept the activation.
-
-
-### Sharing links using Firefox share
-
-- Add the sharing service as described above
-- When you are visiting a webpage you would like to share with Shaarli,
- click the Firefox _Share_ button [images/firefoxshare.png](images/firefoxshare.png)
-- You can edit your link before and after saving, just like the bookmarklet above.
-
-_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
-plain HTTP connections._
--- /dev/null
+Content posted to Shaarli is separated in items called _Shaares_. For each Shaare,
+you can customize the following aspects:
+
+ * URL to link to
+ * Title
+ * Free-text description
+ * Tags
+ * Public/private status
+
+--------------------------------------------------------------------------------
+
+## Adding new Shaares
+
+While logged in to your Shaarli, you can add new Shaares in several ways:
+
+ * [+Shaare button]
+ * [Bookmarklet]
+ * [Firefox Share](#firefox-share)
+ * Third-party [apps and browser addons](Community-\&-Related-software.md#mobile-apps)
+ * [REST API](https://shaarli.github.io/api-documentation/)
+
+### +Shaare button
+
+ * While logged in to your Shaarli, click the **`+Shaare`** button located in the toolbar.
+ * Enter the URL of a link you want to share.
+ * Click `Add link`
+ * The `New Shaare` dialog appears, allowing you to fill in the details of your Shaare.
+ * The Description, Title, and Tags will help you find your Shaare later using tags or full-text search.
+ * You can also check the “Private” box so that the link is saved but only visible to you (the logged-in user).
+ * Click `Save`.
+
+<!-- TODO Add screenshot of add/edit link dialog -->
+
+### Bookmarklet
+
+The _Bookmarklet_ \[[1](https://en.wikipedia.org/wiki/Bookmarklet)\] is a special
+browser bookmark you can use to add new content to your Shaarli. This bookmarklet is
+compatible with Firefox, Opera, Chrome and Safari. To set it up:
+
+ * Access the `Tools` page from the button in the toolbar.
+ * Drag the **`✚Shaare link` button** to your browser's bookmarks bar.
+
+Once this is done, you can shaare any URL you are visiting simply by clicking the
+bookmarklet in your browser! The same `New Shaare` dialog as above is displayed.
+
+| 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)]\ |
+|---------|---------|
+
+| 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. |
+|---------|---------|
+
+![](images/bookmarklet.png)
+
+
+### Firefox Share
+
+Before using Firefox Share, you must first add Shaarli as a sharing provider:
+
+- Click the `Tools` button in the top bar
+- Click the `✚Add to Firefox social` button and accept the activation.
+
+Once this is done, you can share any URL you are visiting by clicking the Firefox
+_Share_ button [images/firefoxshare.png](images/firefoxshare.png)
+
+| Note | Firefox Share is no longer available for Firefox 57 and later versions. |
+|---------|---------|
+
+| 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. |
+|---------|---------|
+
+--------------------------------------------------------------------------------
+
+## Editing Shaares
+
+Any Shaare can edited by clicking its ![](images/edit_icon.png) `Edit` button.
+
+Editing a Shaare will not change it's permalink, each permalink always points to the
+latest revision of a Shaare.
+
+--------------------------------------------------------------------------------
+
+## Using shaarli as a blog, notepad, pastebin...
+
+While adding or editing a link, leave the URL field blank to create a text-only
+("note") post. This allows you to post any kind of text content, such as blog
+articles, private or public notes, snippets... There is no character limit! You can
+access your Shaare from its permalink.
+
See the [API documentation](http://shaarli.github.io/api-documentation/).
-### Using Shaarli as a blog, notepad, pastebin...
-- Go to your Shaarli setup and log in
-- Click the `Add Link` button
-- To share text only, do not enter any URL in the corresponding input field and click `Add Link`
-- Pick a title and enter your article, or note, in the description field; add a few tags; optionally check `Private` then click `Save`
-- Voilà! Your article is now published (privately if you selected that option) and accessible using its permalink.
-
## About
### Shaarli community fork
This friendly fork is maintained by the Shaarli community at https://github.com/shaarli/Shaarli
--- /dev/null
+msgid ""
+msgstr ""
+"Project-Id-Version: Shaarli\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-03-31 09:09+0200\n"
+"PO-Revision-Date: 2018-03-31 09:12+0200\n"
+"Last-Translator: \n"
+"Language-Team: Shaarli\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.6\n"
+"X-Poedit-Basepath: ../../../..\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"X-Poedit-KeywordsList: t:1,2;t\n"
+"X-Poedit-SearchPath-0: .\n"
+"X-Poedit-SearchPathExcluded-0: node_modules\n"
+"X-Poedit-SearchPathExcluded-1: vendor\n"
+
+#: application/ApplicationUtils.php:153
+#, php-format
+msgid ""
+"Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus "
+"cannot run. Your PHP version has known security vulnerabilities and should "
+"be updated as soon as possible."
+msgstr ""
+"Deine PHP-Version ist veraltet! Shaarli benötigt mindestens PHP %s, und kann "
+"daher nicht laufen. Deine PHP-Version hat bekannte Sicherheitslücken und "
+"sollte so bald wie möglich aktualisiert werden."
+
+#: application/ApplicationUtils.php:183 application/ApplicationUtils.php:195
+msgid "directory is not readable"
+msgstr "Verzeichnis ist nicht lesbar"
+
+#: application/ApplicationUtils.php:198
+msgid "directory is not writable"
+msgstr "Verzeichnis ist nicht beschreibbar"
+
+#: application/ApplicationUtils.php:216
+msgid "file is not readable"
+msgstr "Datei ist nicht lesbar"
+
+#: application/ApplicationUtils.php:219
+msgid "file is not writable"
+msgstr "Datei ist nicht beschreibbar"
+
+#: application/Cache.php:16
+#, php-format
+msgid "Cannot purge %s: no directory"
+msgstr "Kann nicht löschen, %s ist kein Verzeichnis"
+
+#: application/FeedBuilder.php:151
+msgid "Direct link"
+msgstr "Direct Link"
+
+#: application/FeedBuilder.php:153
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:178
+msgid "Permalink"
+msgstr "Permalink"
+
+#: application/History.php:174
+msgid "History file isn't readable or writable"
+msgstr "Protokolldatei nicht lesbar oder beschreibbar"
+
+#: application/History.php:185
+msgid "Could not parse history file"
+msgstr "Protokolldatei konnte nicht analysiert werden"
+
+#: application/Languages.php:177
+msgid "Automatic"
+msgstr "Automatisch"
+
+#: application/Languages.php:178
+msgid "English"
+msgstr "Englisch"
+
+#: application/Languages.php:179
+msgid "French"
+msgstr "Französisch"
+
+#: application/Languages.php:180
+msgid "German"
+msgstr "Deutsch"
+
+#: application/LinkDB.php:136
+msgid "You are not authorized to add a link."
+msgstr "Du bist nicht berechtigt einen Link hinzuzufügen."
+
+#: application/LinkDB.php:139
+msgid "Internal Error: A link should always have an id and URL."
+msgstr "Interner Fehler: Ein Link sollte immer eine ID und URL haben."
+
+#: application/LinkDB.php:142
+msgid "You must specify an integer as a key."
+msgstr "Du musst eine Ganzzahl als Schlüssel angeben."
+
+#: application/LinkDB.php:145
+msgid "Array offset and link ID must be equal."
+msgstr "Array-Offset und Link-ID müssen gleich sein."
+
+#: application/LinkDB.php:251
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
+msgid ""
+"The personal, minimalist, super-fast, database free, bookmarking service"
+msgstr ""
+"Der persönliche, minimalistische, superschnelle, datenbankfreie "
+"Lesezeichenservice"
+
+#: application/LinkDB.php:253
+msgid ""
+"Welcome to Shaarli! This is your first public bookmark. To edit or delete "
+"me, you must first login.\n"
+"\n"
+"To learn how to use Shaarli, consult the link \"Documentation\" at the "
+"bottom of this page.\n"
+"\n"
+"You use the community supported version of the original Shaarli project, by "
+"Sebastien Sauvage."
+msgstr ""
+"Willkommen bei Shaarli! Dies ist dein erstes öffentliches Lesezeichen. Um "
+"mich zu bearbeiten oder zu löschen, musst du dich zuerst einloggen.\n"
+"\n"
+"Um zu erfahren, wie man Shaarli benutzt, öffne den Link \"Dokumentation\" am "
+"Ende dieser Seite.\n"
+"\n"
+"Du verwendest die von der Community unterstützte Version des ursprünglichen "
+"Shaarli-Projekts von Sebastien Sauvage."
+
+#: application/LinkDB.php:267
+msgid "My secret stuff... - Pastebin.com"
+msgstr "Meine geheimen Sachen... - Pastebin.com"
+
+#: application/LinkDB.php:269
+msgid "Shhhh! I'm a private link only YOU can see. You can delete me too."
+msgstr ""
+"Pssst Ich bin ein privater Link, den nur du sehen kannst. Du kannst mich "
+"auch löschen."
+
+#: application/LinkFilter.php:452
+msgid "The link you are trying to reach does not exist or has been deleted."
+msgstr ""
+"Den Link, den du versucht zu erreichen, existiert nicht oder wurde gelöscht."
+
+#: application/NetscapeBookmarkUtils.php:35
+msgid "Invalid export selection:"
+msgstr "Ungültige Exportauswahl:"
+
+#: application/NetscapeBookmarkUtils.php:81
+#, php-format
+msgid "File %s (%d bytes) "
+msgstr "Datei %s (%d bytes) "
+
+#: application/NetscapeBookmarkUtils.php:83
+msgid "has an unknown file format. Nothing was imported."
+msgstr "hat ein unbekanntes Dateiformat. Es wurde nichts importiert."
+
+#: application/NetscapeBookmarkUtils.php:86
+#, php-format
+msgid ""
+"was successfully processed in %d seconds: %d links imported, %d links "
+"overwritten, %d links skipped."
+msgstr ""
+"wurde erfolgreich in %d Sekunden verarbeitet: %d Links importiert, %d Links "
+"überschrieben, %d Links übersprungen."
+
+#: application/PageBuilder.php:168
+msgid "The page you are trying to reach does not exist or has been deleted."
+msgstr ""
+"Die Seite, die du erreichen möchtest, existiert nicht oder wurde gelöscht."
+
+#: application/PageBuilder.php:170
+msgid "404 Not Found"
+msgstr "404 Nicht gefunden"
+
+#: application/PluginManager.php:243
+#, php-format
+msgid "Plugin \"%s\" files not found."
+msgstr "Plugin \"%s\" Dateien nicht gefunden."
+
+#: application/Updater.php:76
+msgid "Couldn't retrieve Updater class methods."
+msgstr "Die Updater-Klassenmethoden konnten nicht abgerufen werden."
+
+#: application/Updater.php:532
+msgid "An error occurred while running the update "
+msgstr "Beim Ausführen des Updates ist ein Fehler aufgetreten "
+
+#: application/Updater.php:572
+msgid "Updates file path is not set, can't write updates."
+msgstr ""
+"Der Update-Dateipfad ist nicht festgelegt, es können keine Updates "
+"geschrieben werden."
+
+#: application/Updater.php:577
+msgid "Unable to write updates in "
+msgstr "Es ist nicht möglich Updates zu schreiben in "
+
+#: application/Utils.php:376 tests/UtilsTest.php:340
+msgid "Setting not set"
+msgstr "Einstellung nicht gesetzt"
+
+#: application/Utils.php:383 tests/UtilsTest.php:338 tests/UtilsTest.php:339
+msgid "Unlimited"
+msgstr "Unbegrenzt"
+
+#: application/Utils.php:386 tests/UtilsTest.php:335 tests/UtilsTest.php:336
+#: tests/UtilsTest.php:350
+msgid "B"
+msgstr "B"
+
+#: application/Utils.php:386 tests/UtilsTest.php:329 tests/UtilsTest.php:330
+#: tests/UtilsTest.php:337
+msgid "kiB"
+msgstr "kiB"
+
+#: application/Utils.php:386 tests/UtilsTest.php:331 tests/UtilsTest.php:332
+#: tests/UtilsTest.php:348 tests/UtilsTest.php:349
+msgid "MiB"
+msgstr "MiB"
+
+#: application/Utils.php:386 tests/UtilsTest.php:333 tests/UtilsTest.php:334
+msgid "GiB"
+msgstr "GiB"
+
+#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:121
+msgid ""
+"Shaarli could not create the config file. Please make sure Shaarli has the "
+"right to write in the folder is it installed in."
+msgstr ""
+"Shaarli konnte die Konfigurationsdatei nicht erstellen. Bitte stelle sicher, "
+"dass Shaarli das Recht hat, in den Ordner zu schreiben, in dem es "
+"installiert ist."
+
+#: application/config/ConfigManager.php:135
+msgid "Invalid setting key parameter. String expected, got: "
+msgstr ""
+"Ungültiger Parameter für den Einstellungsschlüssel. Zeichenfolge erwartet, "
+"erhalten: "
+
+#: application/config/exception/MissingFieldConfigException.php:21
+#, php-format
+msgid "Configuration value is required for %s"
+msgstr "Konfigurationswert erforderlich für %s"
+
+#: application/config/exception/PluginConfigOrderException.php:15
+msgid "An error occurred while trying to save plugins loading order."
+msgstr ""
+"Beim Versuch, die Ladereihenfolge der Plugins zu speichern, ist ein Fehler "
+"aufgetreten."
+
+#: application/config/exception/UnauthorizedConfigException.php:16
+msgid "You are not authorized to alter config."
+msgstr "Du bist nicht berechtigt, die Konfiguration zu ändern."
+
+#: application/exceptions/IOException.php:19
+msgid "Error accessing"
+msgstr "Fehler beim Zugriff"
+
+#: index.php:142
+msgid "Shared links on "
+msgstr "Geteilte Links auf "
+
+#: index.php:164
+msgid "Insufficient permissions:"
+msgstr "Unzureichende Berechtigungen:"
+
+#: index.php:303
+msgid "I said: NO. You are banned for the moment. Go away."
+msgstr "Ich sagte NEIN. Du bist für den Moment gesperrt. Verschwinde."
+
+#: index.php:368
+msgid "Wrong login/password."
+msgstr "Falscher Loging/Passwort."
+
+#: index.php:576 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:42
+msgid "Daily"
+msgstr "Täglich"
+
+#: index.php:681 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:95
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:71
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:95
+msgid "Login"
+msgstr "Einloggen"
+
+#: index.php:722 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:39
+msgid "Picture wall"
+msgstr "Bildwand"
+
+#: index.php:770 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "Tag cloud"
+msgstr "Tag Cloud"
+
+#: index.php:803 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "Tag list"
+msgstr "Tag Liste"
+
+#: index.php:1028 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31
+msgid "Tools"
+msgstr "Tools"
+
+#: index.php:1037
+msgid "You are not supposed to change a password on an Open Shaarli."
+msgstr "Du darfst kein Passwort für ein offenes Shaarli ändern."
+
+#: index.php:1042 index.php:1084 index.php:1160 index.php:1191 index.php:1291
+msgid "Wrong token."
+msgstr "Falsches Zeichen."
+
+#: index.php:1047
+msgid "The old password is not correct."
+msgstr "Das alte Passwort ist nicht korrekt."
+
+#: index.php:1067
+msgid "Your password has been changed"
+msgstr "Dein Passwort wurde geändert"
+
+#: index.php:1072
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+msgid "Change password"
+msgstr "Passwort ändern"
+
+#: index.php:1120
+msgid "Configuration was saved."
+msgstr "Konfiguration wurde gespeichert."
+
+#: index.php:1143 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+msgid "Configure"
+msgstr "Konfigurieren"
+
+#: index.php:1154 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+msgid "Manage tags"
+msgstr "Tags verwalten"
+
+#: index.php:1172
+#, php-format
+msgid "The tag was removed from %d link."
+msgid_plural "The tag was removed from %d links."
+msgstr[0] "Der Tag wurde aus dem Link %d entfernt."
+msgstr[1] "Der Tag wurde aus den Links %d entfernt."
+
+#: index.php:1173
+#, php-format
+msgid "The tag was renamed in %d link."
+msgid_plural "The tag was renamed in %d links."
+msgstr[0] "Der Tag wurde im Link %d umbenannt."
+msgstr[1] "Der Tag wurde in den Links %d umbenannt."
+
+#: index.php:1181 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
+msgid "Shaare a new link"
+msgstr "Teile einen neuen Link"
+
+#: index.php:1351 tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: index.php:1351 index.php:1421
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26
+msgid "Shaare"
+msgstr "Teilen"
+
+#: index.php:1390
+msgid "Note: "
+msgstr "Notiz: "
+
+#: index.php:1430 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
+msgid "Export"
+msgstr "Exportieren"
+
+#: index.php:1492 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
+msgid "Import"
+msgstr "Importieren"
+
+#: index.php:1502
+#, php-format
+msgid ""
+"The file you are trying to upload is probably bigger than what this "
+"webserver can accept (%s). Please upload in smaller chunks."
+msgstr ""
+"Die Datei, die du hochladen möchtest, ist wahrscheinlich größer als das, was "
+"dieser Webserver akzeptieren kann (%s). Bitte lade in kleineren Blöcken hoch."
+
+#: index.php:1541 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
+msgid "Plugin administration"
+msgstr "Plugin Adminstration"
+
+#: index.php:1706
+msgid "Search: "
+msgstr "Suche: "
+
+#: index.php:1933
+#, php-format
+msgid ""
+"<pre>Sessions do not seem to work correctly on your server.<br>Make sure the "
+"variable \"session.save_path\" is set correctly in your PHP config, and that "
+"you have write access to it.<br>It currently points to %s.<br>On some "
+"browsers, accessing your server via a hostname like 'localhost' or any "
+"custom hostname without a dot causes cookie storage to fail. We recommend "
+"accessing your server via it's IP address or Fully Qualified Domain Name.<br>"
+msgstr ""
+"<pre>Sessions scheinen auf deinem Server nicht korrekt zu funktionieren. "
+"<br>Stelle sicher, dass die Variable \"session.save_path\" in deiner PHP-"
+"Konfiguration richtig eingestellt ist und dass du Schreibzugriff darauf hast."
+"<br>Es verweist aktuell auf %s.<br>Bei einigen Browsern führt der Zugriff "
+"auf deinen Server über einen Hostnamen wie \"localhost\" oder einen "
+"beliebigen benutzerdefinierten Hostnamen ohne Punkt dazu, dass der Cookie-"
+"Speicher fehlschlägt. Wir empfehlen den Zugriff auf deinen Server über die "
+"IP-Adresse oder den Fully Qualified Domain Namen.<br>"
+
+#: index.php:1943
+msgid "Click to try again."
+msgstr "Klicke um es erneut zu versuchen."
+
+#: plugins/addlink_toolbar/addlink_toolbar.php:29
+msgid "URI"
+msgstr "URI"
+
+#: plugins/addlink_toolbar/addlink_toolbar.php:33
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "Add link"
+msgstr "Link hinzufügen"
+
+#: plugins/addlink_toolbar/addlink_toolbar.php:50
+msgid "Adds the addlink input on the linklist page."
+msgstr "Fügt die Link-hinzufügen-Eingabe auf der Linkliste hinzu."
+
+#: plugins/archiveorg/archiveorg.php:23
+msgid "View on archive.org"
+msgstr "Auf archive.org ansehen"
+
+#: plugins/archiveorg/archiveorg.php:36
+msgid "For each link, add an Archive.org icon."
+msgstr "Füge für jeden Link ein Archive.org Symbol hinzu."
+
+#: plugins/demo_plugin/demo_plugin.php:465
+msgid ""
+"A demo plugin covering all use cases for template designers and plugin "
+"developers."
+msgstr ""
+"Ein Demo-Plugin, das alle Anwendungsfälle für Template-Designer und Plugin-"
+"Entwickler abdeckt."
+
+#: plugins/isso/isso.php:20
+msgid ""
+"Isso plugin error: Please define the \"ISSO_SERVER\" setting in the plugin "
+"administration page."
+msgstr ""
+"Isso Plugin Fehler: Bitte definiere die Einstellung \"ISSO_SERVER\" auf der "
+"Plugin-Administrationsseite."
+
+#: plugins/isso/isso.php:63
+msgid "Let visitor comment your shaares on permalinks with Isso."
+msgstr ""
+"Lassen Sie Besucher ihre geteilten Links auf Permalinks mit Isso "
+"kommentieren."
+
+#: plugins/isso/isso.php:64
+msgid "Isso server URL (without 'http://')"
+msgstr "Isso Server URL (ohne 'http://')"
+
+#: plugins/markdown/markdown.php:158
+msgid "Description will be rendered with"
+msgstr "Die Beschreibung wird dargestellt mit"
+
+#: plugins/markdown/markdown.php:159
+msgid "Markdown syntax documentation"
+msgstr "Markdown Syntax Dokumentation"
+
+#: plugins/markdown/markdown.php:160
+msgid "Markdown syntax"
+msgstr "Markdown Syntax"
+
+#: plugins/markdown/markdown.php:339
+msgid ""
+"Render shaare description with Markdown syntax.<br><strong>Warning</"
+"strong>:\n"
+"If your shaared descriptions contained HTML tags before enabling the "
+"markdown plugin,\n"
+"enabling it might break your page.\n"
+"See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/"
+"markdown#html-rendering\">README</a>."
+msgstr ""
+"Übertrage Teilen Beschreibung mit Markdown-Syntax.<br><strong>Warnung</"
+"strong>:\n"
+"Wenn deine Teilen Beschreibungen HTML-Tags enthielten, bevor das Markdown-"
+"Plugin aktiviert wurde,\n"
+"kann es deine Seite beschädigen, solltest du es aktivieren.\n"
+"Weitere Informationen findest du in der <a href=\"https://github.com/shaarli/"
+"Shaarli/tree/master/plugins/markdown#html-rendering\">README</a>."
+
+#: plugins/piwik/piwik.php:21
+msgid ""
+"Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin "
+"administration page."
+msgstr ""
+"Piwik-Plugin-Fehler: Bitte definiere die PIWIK_URL und PIWIK_SITEID auf der "
+"Plugin-Administrationsseite."
+
+#: plugins/piwik/piwik.php:70
+msgid "A plugin that adds Piwik tracking code to Shaarli pages."
+msgstr ""
+"Ein Plugin, das einen Piwik-Tracking-Code auf Shaarli-Seiten hinzufügt."
+
+#: plugins/piwik/piwik.php:71
+msgid "Piwik URL"
+msgstr "Piwik URL"
+
+#: plugins/piwik/piwik.php:72
+msgid "Piwik site ID"
+msgstr "Piwik site ID"
+
+#: plugins/playvideos/playvideos.php:22
+msgid "Video player"
+msgstr "Videoplayer"
+
+#: plugins/playvideos/playvideos.php:25
+msgid "Play Videos"
+msgstr "Videos abspielen"
+
+#: plugins/playvideos/playvideos.php:56
+msgid "Add a button in the toolbar allowing to watch all videos."
+msgstr ""
+"Fügt eine Schaltfläche in der Symbolleiste hinzu, mit der man alle Videos "
+"ansehen kann."
+
+#: plugins/playvideos/youtube_playlist.js:214
+msgid "plugins/playvideos/jquery-1.11.2.min.js"
+msgstr "plugins/playvideos/jquery-1.11.2.min.js"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:69
+#, php-format
+msgid "Could not publish to PubSubHubbub: %s"
+msgstr "Veröffentlichung auf PubSubHubbub nicht möglich: %s"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:95
+#, php-format
+msgid "Could not post to %s"
+msgstr "Kann nicht posten auf %s"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:99
+#, php-format
+msgid "Bad response from the hub %s"
+msgstr "Ungültige Antwort vom Hub %s"
+
+#: plugins/pubsubhubbub/pubsubhubbub.php:110
+msgid "Enable PubSubHubbub feed publishing."
+msgstr "Aktiviere PubSubHubbub Feed Veröffentlichung."
+
+#: plugins/qrcode/qrcode.php:69 plugins/wallabag/wallabag.php:68
+msgid "For each link, add a QRCode icon."
+msgstr "Für jeden Link, füge eine QRCode Icon hinzu."
+
+#: plugins/wallabag/wallabag.php:21
+msgid ""
+"Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the "
+"plugin administration page."
+msgstr ""
+"Wallabag Plugin Fehler: Bitte definiere die Einstellung \"WALLABAG_URL\" auf "
+"der Plugin Administrationsseite."
+
+#: plugins/wallabag/wallabag.php:47
+msgid "Save to wallabag"
+msgstr "Auf Wallabag speichern"
+
+#: plugins/wallabag/wallabag.php:69
+msgid "Wallabag API URL"
+msgstr "Wallabag API URL"
+
+#: plugins/wallabag/wallabag.php:70
+msgid "Wallabag API version (1 or 2)"
+msgstr "Wallabag API version (1 oder 2)"
+
+#: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227
+#: tests/languages/fr/LanguagesFrTest.php:160
+#: tests/languages/fr/LanguagesFrTest.php:173
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:81
+msgid "Search"
+msgid_plural "Search"
+msgstr[0] "Suche"
+msgstr[1] "Suchen"
+
+#: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
+msgid "Sorry, nothing to see here."
+msgstr "Entschuldige, hier gibt es nichts zu sehen."
+
+#: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "URL or leave empty to post a note"
+msgstr "URL oder leer lassen um eine Notiz hinzuzufügen"
+
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "Current password"
+msgstr "Aktuelles Passwort"
+
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "New password"
+msgstr "Neues Passwort"
+
+#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
+msgid "Change"
+msgstr "Wechseln"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
+msgid "Tag"
+msgstr "Tag"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+msgid "New name"
+msgstr "Neuer Name"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
+msgid "Case sensitive"
+msgstr "Groß- / Kleinschreibung-unterscheidend"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
+msgid "Rename"
+msgstr "Umbenennen"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:172
+msgid "Delete"
+msgstr "Löschen"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
+msgid "You can also edit tags in the"
+msgstr "Du kannst auch Tags bearbeiten in der"
+
+#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39
+msgid "tag list"
+msgstr "Tag Liste"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+msgid "title"
+msgstr "Titel"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
+msgid "Home link"
+msgstr "Home Link"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
+msgid "Default value"
+msgstr "Standardwert"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+msgid "Theme"
+msgstr "Thema"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78
+msgid "Language"
+msgstr "Sprache"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+msgid "Timezone"
+msgstr "Zeitzone"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+msgid "Continent"
+msgstr "Kontinent"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
+msgid "City"
+msgstr "Stadt"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164
+msgid "Disable session cookie hijacking protection"
+msgstr "Deaktiviere Session Cookie Hijacking Schutz"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166
+msgid "Check this if you get disconnected or if your IP address changes often"
+msgstr ""
+"Überprüfe dies, wenn die Verbindung getrennt wird oder wenn sich deine IP-"
+"Adresse häufig ändert"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
+msgid "Private links by default"
+msgstr "Standardmäßig Private Links"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:184
+msgid "All new links are private by default"
+msgstr "Alle neuen Links sind standardmäßig privat"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
+msgid "RSS direct links"
+msgstr "RSS Direkt Links"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:200
+msgid "Check this to use direct URL instead of permalink in feeds"
+msgstr ""
+"Aktivieren diese Option, um direkte URLs anstelle von Permalinks in Feeds zu "
+"verwenden"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:215
+msgid "Hide public links"
+msgstr "Verstecke öffentliche Links"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:216
+msgid "Do not show any links if the user is not logged in"
+msgstr "Zeige keine Links, wenn der Benutzer nicht angemeldet ist"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
+msgid "Check updates"
+msgstr "Auf Updates prüfen"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152
+msgid "Notify me when a new release is ready"
+msgstr "Benachrichtige mich, wenn eine neue Version zur Verfügung steht"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
+msgid "Enable REST API"
+msgstr "Aktiviere REST API"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
+msgid "Allow third party software to use Shaarli such as mobile application"
+msgstr ""
+"Erlaube Software von Drittanbietern für Shaarli, wie z.B. die mobile "
+"Anwendung"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263
+msgid "API secret"
+msgstr "API secret"
+
+#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
+msgid "Save"
+msgstr "Speichern"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+msgid "The Daily Shaarli"
+msgstr "Der tägliche Shaarli"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
+msgid "1 RSS entry per day"
+msgstr "1 RSS Eintrag pro Tag"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37
+msgid "Previous day"
+msgstr "Vorheriger Tag"
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
+msgid "All links of one day in a single page."
+msgstr "Alle Links eines Tages auf einer Seite."
+
+#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51
+msgid "Next day"
+msgstr "Nächster Tag"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
+msgid "Created:"
+msgstr "Erstellt:"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+msgid "URL"
+msgstr "URL"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
+msgid "Title"
+msgstr "Titel"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124
+msgid "Description"
+msgstr "Beschreibung"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+msgid "Tags"
+msgstr "Tags"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
+msgid "Private"
+msgstr "Privat"
+
+#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
+msgid "Apply Changes"
+msgstr "Änderungen übernehmen"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "Export Database"
+msgstr "Exportiere Datenbank"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+msgid "Selection"
+msgstr "Beschreibung"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
+msgid "All"
+msgstr "Alle"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+msgid "Public"
+msgstr "Öffentlich"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52
+msgid "Prepend note permalinks with this Shaarli instance's URL"
+msgstr "Voranstellen von Notizen-Permalinks mit der URL dieser Shaarli-Instanz"
+
+#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53
+msgid "Useful to import bookmarks in a web browser"
+msgstr "Sinnvoll Lesezeichen im Browser zu importieren"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "Import Database"
+msgstr "Importiere Datenbank"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
+msgid "Maximum size allowed:"
+msgstr "Maximale Größe erlaubt:"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+msgid "Visibility"
+msgstr "Sichtbarkeit"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+msgid "Use values from the imported file, default to public"
+msgstr "Verwende Werte aus der importierten Datei, standardmäßig öffentlich"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+msgid "Import all bookmarks as private"
+msgstr "Importiere alle Lesezeichen als Privat"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+msgid "Import all bookmarks as public"
+msgstr "Importiere alles Lesezeichen als öffentlich"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57
+msgid "Overwrite existing bookmarks"
+msgstr "Überschreibe alle bestehenden Lesezeichen"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
+msgid "Duplicates based on URL"
+msgstr "Duplikate basierend auf URL"
+
+#: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
+msgid "Add default tags"
+msgstr "Standard-Tag hinzufügen"
+
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
+msgid "Install Shaarli"
+msgstr "Installiere Shaarli"
+
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
+msgid "It looks like it's the first time you run Shaarli. Please configure it."
+msgstr ""
+"Es sieht so aus, als ob du Shaarli das erste mal verwendest. Bitte "
+"konfiguriere es."
+
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147
+msgid "Username"
+msgstr "Benutzername"
+
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:148
+msgid "Password"
+msgstr "Passwort"
+
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63
+msgid "Shaarli title"
+msgstr "Shaarli Titel"
+
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69
+msgid "My links"
+msgstr "Meine Links"
+
+#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182
+msgid "Install"
+msgstr "Installiere"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80
+msgid "shaare"
+msgid_plural "shaares"
+msgstr[0] "Teile"
+msgstr[1] "Teilen"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84
+msgid "private link"
+msgid_plural "private links"
+msgstr[0] "Privater Link"
+msgstr[1] "Private Links"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:117
+msgid "Search text"
+msgstr "Text durchsuchen"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:124
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
+msgid "Filter by tag"
+msgstr "Nach Tag filtern"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111
+msgid "Nothing found."
+msgstr "Nichts gefunden."
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:119
+#, php-format
+msgid "%s result"
+msgid_plural "%s results"
+msgstr[0] "%s Ergebnis"
+msgstr[1] "%s Ergebnisse"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123
+msgid "for"
+msgstr "für"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130
+msgid "tagged"
+msgstr "markiert"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
+msgid "Remove tag"
+msgstr "Tag entfernen"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143
+msgid "with status"
+msgstr "mit Status"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154
+msgid "without any tag"
+msgstr "ohne irgendeinen Tag"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:174
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42
+msgid "Fold"
+msgstr "Ablegen"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176
+msgid "Edited: "
+msgstr "Bearbeitet: "
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:180
+msgid "permalink"
+msgstr "Permalink"
+
+#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182
+msgid "Add tag"
+msgstr "Tag hinzufügen"
+
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:7
+msgid "Filters"
+msgstr "Filter"
+
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:12
+msgid "Only display private links"
+msgstr "Zeige nur private Links"
+
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:15
+msgid "Only display public links"
+msgstr "Zeige nur öffentliche Links"
+
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:20
+msgid "Filter untagged links"
+msgstr "Unmarkierte Tags filtern"
+
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:24
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:76
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43
+msgid "Fold all"
+msgstr "Alles ablegen"
+
+#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69
+#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:69
+msgid "Links per page"
+msgstr "Links pro Seite"
+
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+msgid ""
+"You have been banned after too many failed login attempts. Try again later."
+msgstr ""
+"Du wurdest nach zu vielen fehlgeschlagenen Anmeldeversuchen gesperrt. "
+"Versuche es später noch einmal."
+
+#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:151
+msgid "Remember me"
+msgstr "Erinnere dich an mich"
+
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
+msgid "by the Shaarli community"
+msgstr "von der Shaarli Community"
+
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
+msgid "Documentation"
+msgstr "Dokumentation"
+
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44
+msgid "Expand"
+msgstr "Erweitern"
+
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45
+msgid "Expand all"
+msgstr "Alles erweitern"
+
+#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
+#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46
+msgid "Are you sure you want to delete this link?"
+msgstr "Bist du sicher das du diesen Link löschen möchtest?"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:61
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:86
+msgid "RSS Feed"
+msgstr "RSS Feed"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:66
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:102
+msgid "Logout"
+msgstr "Ausloggen"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:169
+msgid "is available"
+msgstr "ist verfügbar"
+
+#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176
+#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:176
+msgid "Error"
+msgstr "Fehler"
+
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "Picture Wall"
+msgstr "Bildwand"
+
+#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "pics"
+msgstr "Bilder"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
+msgid "You need to enable Javascript to change plugin loading order."
+msgstr ""
+"Du musst Javascript aktivieren um die Ladereihenfolge der Plugins zu ändern."
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
+msgid "Enabled Plugins"
+msgstr "Aktivierte Plugins"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
+msgid "No plugin enabled."
+msgstr "Kein Plugin aktiviert."
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:73
+msgid "Disable"
+msgstr "Deaktivieren"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:98
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123
+msgid "Name"
+msgstr "Name"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
+msgid "Order"
+msgstr "Reihenfolge"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
+msgid "Disabled Plugins"
+msgstr "Deaktivierte Plugins"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:91
+msgid "No plugin disabled."
+msgstr "Kein Plugin deaktiviert."
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:97
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
+msgid "Enable"
+msgstr "Aktiviere"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
+msgid "More plugins available"
+msgstr "Weitere Plugins verfügbar"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:136
+msgid "in the documentation"
+msgstr "In der Dokumentation"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
+msgid "Plugin configuration"
+msgstr "Plugin Konfiguration"
+
+#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:195
+msgid "No parameter available."
+msgstr "Kein Parameter verfügbar."
+
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
+msgid "tags"
+msgstr "Tags"
+
+#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
+msgid "List all links with those tags"
+msgstr "Zeige alle Links mit diesen Tags"
+
+#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3
+#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3
+msgid "Sort by:"
+msgstr "Sortiere nach:"
+
+#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5
+#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:5
+msgid "Cloud"
+msgstr "Cloud"
+
+#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:6
+#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:6
+msgid "Most used"
+msgstr "Am meisten verwendet"
+
+#: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7
+#: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:7
+msgid "Alphabetical"
+msgstr "Alphabetisch"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
+msgid "Settings"
+msgstr "Einstellungen"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
+msgid "Change Shaarli settings: title, timezone, etc."
+msgstr "Shaarli Einstellungen ändern: Titel, Zeitzone, usw."
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17
+msgid "Configure your Shaarli"
+msgstr "Shaarli konfigurieren"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21
+msgid "Enable, disable and configure plugins"
+msgstr "Plugins aktivieren, deaktivieren und konfigurieren"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
+msgid "Change your password"
+msgstr "Ändere dein Passwort"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
+msgid "Rename or delete a tag in all links"
+msgstr "Umbenennen oder löschen eines Tags in allen Links"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
+msgid ""
+"Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, "
+"delicious...)"
+msgstr ""
+"Importiere Netscape Lesezeichen (wie aus Firefox exportiert, Chrome, Opera, "
+"delicious...)"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
+msgid "Import links"
+msgstr "Importiere Links"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
+msgid ""
+"Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, "
+"Opera, delicious...)"
+msgstr ""
+"Exportiere Netscape HTML Lesezeichen (welche in Firefox importiert werden "
+"können, Chrome, Opera, delicious...)"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
+msgid "Export database"
+msgstr "Exportiere Datenbank"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71
+msgid ""
+"Drag one of these button to your bookmarks toolbar or right-click it and "
+"\"Bookmark This Link\""
+msgstr ""
+"Ziehe eine dieser Schaltflächen in deine Lesezeichen-Symbolleiste oder "
+"klicke mit der rechten Maustaste darauf und \"Speichere diesen Link als "
+"Lesezeichen\""
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
+msgid "then click on the bookmarklet in any page you want to share."
+msgstr ""
+"Klicke dann auf das Bookmarklet auf jeder Seite, welches du teilen möchtest."
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:100
+msgid ""
+"Drag this link to your bookmarks toolbar or right-click it and Bookmark This "
+"Link"
+msgstr ""
+"Ziehe diese Link in deine Lesezeichen-Symbolleiste oder klicke mit der "
+"rechten Maustaste darauf und \"Speichere diesen Link als Lesezeichen\""
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
+msgid "then click ✚Shaare link button in any page you want to share"
+msgstr ""
+"klicke dann auf die Schaltfläche ✚Teilen auf jeder Seite, die du teilen "
+"möchtest"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108
+msgid "The selected text is too long, it will be truncated."
+msgstr "Der ausgewählte Text ist zu lang, er wird gekürzt."
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96
+msgid "Shaare link"
+msgstr "Teile Link"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
+msgid ""
+"Then click ✚Add Note button anytime to start composing a private Note (text "
+"post) to your Shaarli"
+msgstr ""
+"Klicke auf ✚Notiz hinzufügen um eine private Notiz (Textnachricht) zu "
+"Shaarli hinzuzufügen"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117
+msgid "Add Note"
+msgstr "Notiz hinzufügen"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:129
+msgid ""
+"You need to browse your Shaarli over <strong>HTTPS</strong> to use this "
+"functionality."
+msgstr ""
+"Um diese Funktion nutzen zu können, musst du Shaarli über <strong>HTTPS</"
+"strong> aufrufen."
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
+msgid "Add to"
+msgstr "Hinzufügen zu"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:145
+msgid "3rd party"
+msgstr "Von Dritten"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153
+msgid "Plugin"
+msgstr "Plugin"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154
+msgid "plugin"
+msgstr "Plugin"
+
+#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175
+msgid ""
+"Drag this link to your bookmarks toolbar, or right-click it and choose "
+"Bookmark This Link"
+msgstr ""
+"Ziehe diesen Link in deine Lesezeichen-Symbolleiste oder klicke mit der "
+"rechten Maustaste darauf und wähle \"Speichere diesen Link als Lesezeichen\""
msgid ""
msgstr ""
"Project-Id-Version: Shaarli\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-24 12:39+0100\n"
-"PO-Revision-Date: 2018-02-24 12:43+0100\n"
+"POT-Creation-Date: 2018-01-24 18:43+0100\n"
+"PO-Revision-Date: 2018-03-06 18:44+0100\n"
"Last-Translator: \n"
"Language-Team: Shaarli\n"
"Language: fr_FR\n"
msgid "Next day"
msgstr "Jour suivant"
+#: tpl/editlink.html
+msgid "Edit Shaare"
+msgstr "Modifier le Shaare"
+msgid "New Shaare"
+msgstr "Nouveau Shaare"
+
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
msgid "Created:"
msgstr "Création :"
use \Shaarli\Languages;
use \Shaarli\ThemeUtils;
use \Shaarli\Config\ConfigManager;
-use \Shaarli\LoginManager;
-use \Shaarli\SessionManager;
+use \Shaarli\Security\LoginManager;
+use \Shaarli\Security\SessionManager;
// Ensure the PHP version is supported
try {
// Set default cookie expiration and path.
session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['SERVER_NAME']);
// Set session parameters on server side.
-// If the user does not access any page within this time, his/her session is considered expired.
-define('INACTIVITY_TIMEOUT', 3600); // in seconds.
// Use cookies to store session.
ini_set('session.use_cookies', 1);
// Force cookies for session (phpsessionID forbidden in URL).
}
$conf = new ConfigManager();
-$loginManager = new LoginManager($GLOBALS, $conf);
$sessionManager = new SessionManager($_SESSION, $conf);
+$loginManager = new LoginManager($GLOBALS, $conf, $sessionManager);
+$loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']);
+$clientIpId = client_ip_id($_SERVER);
// LC_MESSAGES isn't defined without php-intl, in this case use LC_COLLATE locale instead.
if (! defined('LC_MESSAGES')) {
install($conf, $sessionManager);
}
-// a token depending of deployment salt, user password, and the current ip
-define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt')));
+$loginManager->checkLoginState($_COOKIE, $clientIpId);
/**
- * Checking session state (i.e. is the user still logged in)
+ * Adapter function to ensure compatibility with third-party templates
*
- * @param ConfigManager $conf The configuration manager.
+ * @see https://github.com/shaarli/Shaarli/pull/1086
*
- * @return bool: true if the user is logged in, false otherwise.
+ * @return bool true when the user is logged in, false otherwise
*/
-function setup_login_state($conf)
-{
- if ($conf->get('security.open_shaarli')) {
- return true;
- }
- $userIsLoggedIn = false; // By default, we do not consider the user as logged in;
- $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met.
- if (! $conf->exists('credentials.login')) {
- $userIsLoggedIn = false; // Shaarli is not configured yet.
- $loginFailure = true;
- }
- if (isset($_COOKIE['shaarli_staySignedIn']) &&
- $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN &&
- !$loginFailure)
- {
- fillSessionInfo($conf);
- $userIsLoggedIn = true;
- }
- // If session does not exist on server side, or IP address has changed, or session has expired, logout.
- if (empty($_SESSION['uid'])
- || ($conf->get('security.session_protection_disabled') === false && $_SESSION['ip'] != allIPs())
- || time() >= $_SESSION['expires_on'])
- {
- logout();
- $userIsLoggedIn = false;
- $loginFailure = true;
- }
- if (!empty($_SESSION['longlastingsession'])) {
- $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked.
- }
- else {
- $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date.
- }
- if (!$loginFailure) {
- $userIsLoggedIn = true;
- }
-
- return $userIsLoggedIn;
-}
-$userIsLoggedIn = setup_login_state($conf);
-
-// ------------------------------------------------------------------------------------------
-// Session management
-
-// Returns the IP address of the client (Used to prevent session cookie hijacking.)
-function allIPs()
-{
- $ip = $_SERVER['REMOTE_ADDR'];
- // Then we use more HTTP headers to prevent session hijacking from users behind the same proxy.
- if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip=$ip.'_'.$_SERVER['HTTP_X_FORWARDED_FOR']; }
- if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip=$ip.'_'.$_SERVER['HTTP_CLIENT_IP']; }
- return $ip;
-}
-
-/**
- * Load user session.
- *
- * @param ConfigManager $conf Configuration Manager instance.
- */
-function fillSessionInfo($conf)
-{
- $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid)
- $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked.
- $_SESSION['username']= $conf->get('credentials.login');
- $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration.
-}
-
-/**
- * Check that user/password is correct.
- *
- * @param string $login Username
- * @param string $password User password
- * @param ConfigManager $conf Configuration Manager instance.
- *
- * @return bool: authentication successful or not.
- */
-function check_auth($login, $password, $conf)
-{
- $hash = sha1($password . $login . $conf->get('credentials.salt'));
- if ($login == $conf->get('credentials.login') && $hash == $conf->get('credentials.hash'))
- { // Login/password is correct.
- fillSessionInfo($conf);
- logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login successful');
- return true;
- }
- logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login failed for user '.$login);
- return false;
-}
-
-// Returns true if the user is logged in.
function isLoggedIn()
{
- global $userIsLoggedIn;
- return $userIsLoggedIn;
+ global $loginManager;
+ return $loginManager->isLoggedIn();
}
-// Force logout.
-function logout() {
- if (isset($_SESSION)) {
- unset($_SESSION['uid']);
- unset($_SESSION['ip']);
- unset($_SESSION['username']);
- unset($_SESSION['visibility']);
- unset($_SESSION['untaggedonly']);
- }
- setcookie('shaarli_staySignedIn', FALSE, 0, WEB_PATH);
-}
// ------------------------------------------------------------------------------------------
// Process login form: Check if login/password is correct.
-if (isset($_POST['login']))
-{
+if (isset($_POST['login'])) {
if (! $loginManager->canLogin($_SERVER)) {
die(t('I said: NO. You are banned for the moment. Go away.'));
}
if (isset($_POST['password'])
&& $sessionManager->checkToken($_POST['token'])
- && (check_auth($_POST['login'], $_POST['password'], $conf))
+ && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password'])
) {
- // Login/password is OK.
$loginManager->handleSuccessfulLogin($_SERVER);
- // If user wants to keep the session cookie even after the browser closes:
- if (!empty($_POST['longlastingsession'])) {
- $_SESSION['longlastingsession'] = 31536000; // (31536000 seconds = 1 year)
- $expiration = time() + $_SESSION['longlastingsession']; // calculate relative cookie expiration (1 year from now)
- setcookie('shaarli_staySignedIn', STAY_SIGNED_IN_TOKEN, $expiration, WEB_PATH);
- $_SESSION['expires_on'] = $expiration; // Set session expiration on server-side.
-
- $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
- session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['SERVER_NAME']); // Set session cookie expiration on client side
+ $cookiedir = '';
+ if (dirname($_SERVER['SCRIPT_NAME']) != '/') {
// Note: Never forget the trailing slash on the cookie path!
- session_regenerate_id(true); // Send cookie with new expiration date to browser.
+ $cookiedir = dirname($_SERVER["SCRIPT_NAME"]) . '/';
}
- else // Standard session expiration (=when browser closes)
- {
- $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
- session_set_cookie_params(0,$cookiedir,$_SERVER['SERVER_NAME']); // 0 means "When browser closes"
- session_regenerate_id(true);
+
+ if (!empty($_POST['longlastingsession'])) {
+ // Keep the session cookie even after the browser closes
+ $sessionManager->setStaySignedIn(true);
+ $expirationTime = $sessionManager->extendSession();
+
+ setcookie(
+ $loginManager::$STAY_SIGNED_IN_COOKIE,
+ $loginManager->getStaySignedInToken(),
+ $expirationTime,
+ WEB_PATH
+ );
+
+ } else {
+ // Standard session expiration (=when browser closes)
+ $expirationTime = 0;
}
+ // Send cookie with the new expiration date to the browser
+ session_set_cookie_params($expirationTime, $cookiedir, $_SERVER['SERVER_NAME']);
+ session_regenerate_id(true);
+
// Optional redirect after login:
if (isset($_GET['post'])) {
$uri = '?post='. urlencode($_GET['post']);
* Gives the last 7 days (which have links).
* This RSS feed cannot be filtered.
*
- * @param ConfigManager $conf Configuration Manager instance.
+ * @param ConfigManager $conf Configuration Manager instance
+ * @param LoginManager $loginManager LoginManager instance
*/
-function showDailyRSS($conf) {
+function showDailyRSS($conf, $loginManager) {
// Cache system
$query = $_SERVER['QUERY_STRING'];
$cache = new CachedPage(
$conf->get('config.PAGE_CACHE'),
page_url($_SERVER),
- startsWith($query,'do=dailyrss') && !isLoggedIn()
+ startsWith($query,'do=dailyrss') && !$loginManager->isLoggedIn()
);
$cached = $cache->cachedVersion();
if (!empty($cached)) {
// Read links from database (and filter private links if used it not logged in).
$LINKSDB = new LinkDB(
$conf->get('resource.datastore'),
- isLoggedIn(),
+ $loginManager->isLoggedIn(),
$conf->get('privacy.hide_public_links'),
$conf->get('redirector.url'),
$conf->get('redirector.encode_url')
* @param PageBuilder $pageBuilder Template engine wrapper.
* @param LinkDB $LINKSDB LinkDB instance.
* @param ConfigManager $conf Configuration Manager instance.
- * @param PluginManager $pluginManager Plugin Manager instane.
+ * @param PluginManager $pluginManager Plugin Manager instance.
+ * @param LoginManager $loginManager Login Manager instance
*/
-function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
+function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
{
$day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
if (isset($_GET['day'])) {
/* Hook is called before column construction so that plugins don't have
to deal with columns. */
- $pluginManager->executeHooks('render_daily', $data, array('loggedin' => isLoggedIn()));
+ $pluginManager->executeHooks('render_daily', $data, array('loggedin' => $loginManager->isLoggedIn()));
/* We need to spread the articles on 3 columns.
I did not want to use a JavaScript lib like http://masonry.desandro.com/
* @param ConfigManager $conf Configuration Manager instance.
* @param PluginManager $pluginManager Plugin Manager instance.
*/
-function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager) {
- buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager); // Compute list of links to display
+function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager) {
+ buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager, $loginManager);
$PAGE->renderPage('linklist');
}
read_updates_file($conf->get('resource.updates')),
$LINKSDB,
$conf,
- isLoggedIn()
+ $loginManager->isLoggedIn()
);
try {
$newUpdates = $updater->update();
die($e->getMessage());
}
- $PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken());
+ $PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken(), $loginManager->isLoggedIn());
$PAGE->assign('linkcount', count($LINKSDB));
$PAGE->assign('privateLinkcount', count_private($LINKSDB));
$PAGE->assign('plugin_errors', $pluginManager->getErrors());
// Determine which page will be rendered.
$query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
- $targetPage = Router::findPage($query, $_GET, isLoggedIn());
+ $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn());
if (
// if the user isn't logged in
- !isLoggedIn() &&
+ !$loginManager->isLoggedIn() &&
// and Shaarli doesn't have public content...
$conf->get('privacy.hide_public_links') &&
// and is configured to enforce the login
$pluginManager->executeHooks('render_' . $name, $plugin_data,
array(
'target' => $targetPage,
- 'loggedin' => isLoggedIn()
+ 'loggedin' => $loginManager->isLoggedIn()
)
);
$PAGE->assign('plugins_' . $name, $plugin_data);
if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout'))
{
invalidateCaches($conf->get('resource.page_cache'));
- logout();
+ $sessionManager->logout();
+ setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH);
header('Location: ?');
exit;
}
$data = array(
'linksToDisplay' => $linksToDisplay,
);
- $pluginManager->executeHooks('render_picwall', $data, array('loggedin' => isLoggedIn()));
+ $pluginManager->executeHooks('render_picwall', $data, array('loggedin' => $loginManager->isLoggedIn()));
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
'search_tags' => $searchTags,
'tags' => $tagList,
);
- $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn()));
+ $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => $loginManager->isLoggedIn()));
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
'search_tags' => $searchTags,
'tags' => $tags,
];
- $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]);
+ $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => $loginManager->isLoggedIn()]);
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
// Daily page.
if ($targetPage == Router::$PAGE_DAILY) {
- showDaily($PAGE, $LINKSDB, $conf, $pluginManager);
+ showDaily($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
}
// ATOM and RSS feed.
$cache = new CachedPage(
$conf->get('resource.page_cache'),
page_url($_SERVER),
- startsWith($query,'do='. $targetPage) && !isLoggedIn()
+ startsWith($query,'do='. $targetPage) && !$loginManager->isLoggedIn()
);
$cached = $cache->cachedVersion();
if (!empty($cached)) {
}
// Generate data.
- $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn());
+ $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, $loginManager->isLoggedIn());
$feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
- $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !isLoggedIn());
+ $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !$loginManager->isLoggedIn());
$feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks'));
$data = $feedGenerator->buildData();
// Process plugin hook.
$pluginManager->executeHooks('render_feed', $data, array(
- 'loggedin' => isLoggedIn(),
+ 'loggedin' => $loginManager->isLoggedIn(),
'target' => $targetPage,
));
}
// -------- Handle other actions allowed for non-logged in users:
- if (!isLoggedIn())
+ if (!$loginManager->isLoggedIn())
{
// User tries to post new link but is not logged in:
// Show login screen, then redirect to ?post=...
exit;
}
- showLinkList($PAGE, $LINKSDB, $conf, $pluginManager);
+ showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
if (isset($_GET['edit_link'])) {
header('Location: ?do=login&edit_link='. escape($_GET['edit_link']));
exit;
$conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
$conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt')));
try {
- $conf->write(isLoggedIn());
+ $conf->write($loginManager->isLoggedIn());
}
catch(Exception $e) {
error_log(
$conf->set('translation.language', escape($_POST['language']));
try {
- $conf->write(isLoggedIn());
+ $conf->write($loginManager->isLoggedIn());
$history->updateSettings();
invalidateCaches($conf->get('resource.page_cache'));
}
// The callback will fill $charset and $title with data from the downloaded page.
get_http_response(
$url,
- $conf->get('general.download_max_size', 4194304),
$conf->get('general.download_timeout', 30),
+ $conf->get('general.download_max_size', 4194304),
get_curl_download_callback($charset, $title)
);
if (! empty($title) && strtolower($charset) != 'utf-8') {
else {
$conf->set('general.enabled_plugins', save_plugin_config($_POST));
}
- $conf->write(isLoggedIn());
+ $conf->write($loginManager->isLoggedIn());
$history->updateSettings();
}
catch (Exception $e) {
}
// -------- Otherwise, simply display search form and links:
- showLinkList($PAGE, $LINKSDB, $conf, $pluginManager);
+ showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
exit;
}
* @param LinkDB $LINKSDB LinkDB instance.
* @param ConfigManager $conf Configuration Manager instance.
* @param PluginManager $pluginManager Plugin Manager instance.
+ * @param LoginManager $loginManager LoginManager instance
*/
-function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
+function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
{
// Used in templates
if (isset($_GET['searchtags'])) {
$keys[] = $key;
}
-
-
// Select articles according to paging.
$pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']);
$pagecount = $pagecount == 0 ? 1 : $pagecount;
$data['pagetitle'] .= '- '. $conf->get('general.title');
}
- $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn()));
+ $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => $loginManager->isLoggedIn()));
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
);
try {
// Everything is ok, let's create config file.
- $conf->write(isLoggedIn());
+ $conf->write($loginManager->isLoggedIn());
}
catch(Exception $e) {
error_log(
$linkDb = new LinkDB(
$conf->get('resource.datastore'),
- isLoggedIn(),
+ $loginManager->isLoggedIn(),
$conf->get('privacy.hide_public_links'),
$conf->get('redirector.url'),
$conf->get('redirector.encode_url')
- Reverse proxy configuration: docker/reverse-proxy-configuration.md
- Docker resources: docker/resources.md
- Usage:
- - Bookmarklet: Bookmarklet.md
- Browsing and searching: Browsing-and-searching.md
- - Firefox share: Firefox-share.md
+ - Sharing content: Sharing-content.md
- RSS feeds: RSS-feeds.md
- REST API: REST-API.md
- Community & Related software: Community-&-Related-software.md
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6",
"node-sass": "^4.7.2",
+ "sass-lint": "^1.12.1",
"sass-loader": "^6.0.6",
"style-loader": "^0.19.1",
"url-loader": "^0.6.2",
* Shaare's descriptions are parsed with Markdown.
*/
+use Shaarli\Config\ConfigManager;
+
/*
* If this tag is used on a shaare, the description won't be processed by Parsedown.
*/
$value = stripNoMarkdownTag($value);
continue;
}
+ $value['description'] = reverse_feed_permalink($value['description']);
$value['description'] = process_markdown(
$value['description'],
$conf->get('security.markdown_escape', true),
return preg_replace('/(^| ) /m', '$1 ', $description);
}
+function reverse_feed_permalink($description)
+{
+ return preg_replace('@— <a href="([^"]+)" title="[^"]+">(\w+)</a>$@im', '— [$2]($1)', $description);
+}
+
/**
* Replace not whitelisted protocols with http:// in given description.
*
--- /dev/null
+<?php
+/**
+ * HttpUtils' tests
+ */
+
+require_once 'application/HttpUtils.php';
+
+/**
+ * Unitary tests for client_ip_id()
+ */
+class ClientIpIdTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Get a remote client ID based on its IP
+ */
+ public function testClientIpIdRemote()
+ {
+ $this->assertEquals(
+ '10.1.167.42',
+ client_ip_id(['REMOTE_ADDR' => '10.1.167.42'])
+ );
+ }
+
+ /**
+ * Get a remote client ID based on its IP and proxy information (1)
+ */
+ public function testClientIpIdRemoteForwarded()
+ {
+ $this->assertEquals(
+ '10.1.167.42_127.0.1.47',
+ client_ip_id([
+ 'REMOTE_ADDR' => '10.1.167.42',
+ 'HTTP_X_FORWARDED_FOR' => '127.0.1.47'
+ ])
+ );
+ }
+
+ /**
+ * Get a remote client ID based on its IP and proxy information (2)
+ */
+ public function testClientIpIdRemoteForwardedClient()
+ {
+ $this->assertEquals(
+ '10.1.167.42_10.1.167.56_127.0.1.47',
+ client_ip_id([
+ 'REMOTE_ADDR' => '10.1.167.42',
+ 'HTTP_X_FORWARDED_FOR' => '10.1.167.56',
+ 'HTTP_CLIENT_IP' => '127.0.1.47'
+ ])
+ );
+ }
+}
$this->assertEquals(3, count($res));
$this->assertNotContains('cartoon', $linkDB[4]['tags']);
}
+
+ /**
+ * Test linksCountPerTag all tags without filter.
+ * Equal occurrences should be sorted alphabetically.
+ */
+ public function testCountLinkPerTagAllNoFilter()
+ {
+ $expected = [
+ 'web' => 4,
+ 'cartoon' => 3,
+ 'dev' => 2,
+ 'gnu' => 2,
+ 'hashtag' => 2,
+ 'sTuff' => 2,
+ '-exclude' => 1,
+ '.hidden' => 1,
+ 'Mercurial' => 1,
+ 'css' => 1,
+ 'free' => 1,
+ 'html' => 1,
+ 'media' => 1,
+ 'samba' => 1,
+ 'software' => 1,
+ 'stallman' => 1,
+ 'tag1' => 1,
+ 'tag2' => 1,
+ 'tag3' => 1,
+ 'tag4' => 1,
+ 'ut' => 1,
+ 'w3c' => 1,
+ ];
+ $tags = self::$privateLinkDB->linksCountPerTag();
+
+ $this->assertEquals($expected, $tags, var_export($tags, true));
+ }
+
+ /**
+ * Test linksCountPerTag all tags with filter.
+ * Equal occurrences should be sorted alphabetically.
+ */
+ public function testCountLinkPerTagAllWithFilter()
+ {
+ $expected = [
+ 'gnu' => 2,
+ 'hashtag' => 2,
+ '-exclude' => 1,
+ '.hidden' => 1,
+ 'free' => 1,
+ 'media' => 1,
+ 'software' => 1,
+ 'stallman' => 1,
+ 'stuff' => 1,
+ 'web' => 1,
+ ];
+ $tags = self::$privateLinkDB->linksCountPerTag(['gnu']);
+
+ $this->assertEquals($expected, $tags, var_export($tags, true));
+ }
+
+ /**
+ * Test linksCountPerTag public tags with filter.
+ * Equal occurrences should be sorted alphabetically.
+ */
+ public function testCountLinkPerTagPublicWithFilter()
+ {
+ $expected = [
+ 'gnu' => 2,
+ 'hashtag' => 2,
+ '-exclude' => 1,
+ '.hidden' => 1,
+ 'free' => 1,
+ 'media' => 1,
+ 'software' => 1,
+ 'stallman' => 1,
+ 'stuff' => 1,
+ 'web' => 1,
+ ];
+ $tags = self::$privateLinkDB->linksCountPerTag(['gnu'], 'public');
+
+ $this->assertEquals($expected, $tags, var_export($tags, true));
+ }
+
+ /**
+ * Test linksCountPerTag public tags with filter.
+ * Equal occurrences should be sorted alphabetically.
+ */
+ public function testCountLinkPerTagPrivateWithFilter()
+ {
+ $expected = [
+ 'cartoon' => 1,
+ 'dev' => 1,
+ 'tag1' => 1,
+ 'tag2' => 1,
+ 'tag3' => 1,
+ 'tag4' => 1,
+ ];
+ $tags = self::$privateLinkDB->linksCountPerTag(['dev'], 'private');
+
+ $this->assertEquals($expected, $tags, var_export($tags, true));
+ }
}
+++ /dev/null
-<?php
-require_once 'tests/utils/FakeConfigManager.php';
-
-// Initialize reference data _before_ PHPUnit starts a session
-require_once 'tests/utils/ReferenceSessionIdHashes.php';
-ReferenceSessionIdHashes::genAllHashes();
-
-use \Shaarli\SessionManager;
-use \PHPUnit\Framework\TestCase;
-
-
-/**
- * Test coverage for SessionManager
- */
-class SessionManagerTest extends TestCase
-{
- // Session ID hashes
- protected static $sidHashes = null;
-
- // Fake ConfigManager
- protected static $conf = null;
-
- /**
- * Assign reference data
- */
- public static function setUpBeforeClass()
- {
- self::$sidHashes = ReferenceSessionIdHashes::getHashes();
- self::$conf = new FakeConfigManager();
- }
-
- /**
- * Generate a session token
- */
- public function testGenerateToken()
- {
- $session = [];
- $sessionManager = new SessionManager($session, self::$conf);
-
- $token = $sessionManager->generateToken();
-
- $this->assertEquals(1, $session['tokens'][$token]);
- $this->assertEquals(40, strlen($token));
- }
-
- /**
- * Check a session token
- */
- public function testCheckToken()
- {
- $token = '4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b';
- $session = [
- 'tokens' => [
- $token => 1,
- ],
- ];
- $sessionManager = new SessionManager($session, self::$conf);
-
- // check and destroy the token
- $this->assertTrue($sessionManager->checkToken($token));
- $this->assertFalse(isset($session['tokens'][$token]));
-
- // ensure the token has been destroyed
- $this->assertFalse($sessionManager->checkToken($token));
- }
-
- /**
- * Generate and check a session token
- */
- public function testGenerateAndCheckToken()
- {
- $session = [];
- $sessionManager = new SessionManager($session, self::$conf);
-
- $token = $sessionManager->generateToken();
-
- // ensure a token has been generated
- $this->assertEquals(1, $session['tokens'][$token]);
- $this->assertEquals(40, strlen($token));
-
- // check and destroy the token
- $this->assertTrue($sessionManager->checkToken($token));
- $this->assertFalse(isset($session['tokens'][$token]));
-
- // ensure the token has been destroyed
- $this->assertFalse($sessionManager->checkToken($token));
- }
-
- /**
- * Check an invalid session token
- */
- public function testCheckInvalidToken()
- {
- $session = [];
- $sessionManager = new SessionManager($session, self::$conf);
-
- $this->assertFalse($sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b'));
- }
-
- /**
- * Test SessionManager::checkId with a valid ID - TEST ALL THE HASHES!
- *
- * This tests extensively covers all hash algorithms / bit representations
- */
- public function testIsAnyHashSessionIdValid()
- {
- foreach (self::$sidHashes as $algo => $bpcs) {
- foreach ($bpcs as $bpc => $hash) {
- $this->assertTrue(SessionManager::checkId($hash));
- }
- }
- }
-
- /**
- * Test checkId with a valid ID - SHA-1 hashes
- */
- public function testIsSha1SessionIdValid()
- {
- $this->assertTrue(SessionManager::checkId(sha1('shaarli')));
- }
-
- /**
- * Test checkId with a valid ID - SHA-256 hashes
- */
- public function testIsSha256SessionIdValid()
- {
- $this->assertTrue(SessionManager::checkId(hash('sha256', 'shaarli')));
- }
-
- /**
- * Test checkId with a valid ID - SHA-512 hashes
- */
- public function testIsSha512SessionIdValid()
- {
- $this->assertTrue(SessionManager::checkId(hash('sha512', 'shaarli')));
- }
-
- /**
- * Test checkId with invalid IDs.
- */
- public function testIsSessionIdInvalid()
- {
- $this->assertFalse(SessionManager::checkId(''));
- $this->assertFalse(SessionManager::checkId([]));
- $this->assertFalse(
- SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
- );
- }
-}
$this->assertNotFalse(strpos($data['links'][0]['description'], '<p>'));
}
+ /**
+ * Test render_feed hook.
+ */
+ public function testMarkdownFeed()
+ {
+ $markdown = '# My title' . PHP_EOL . 'Very interesting content.';
+ $markdown .= '— <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>';
+ $data = array(
+ 'links' => array(
+ 0 => array(
+ 'description' => $markdown,
+ ),
+ ),
+ );
+
+ $data = hook_markdown_render_feed($data, $this->conf);
+ $this->assertNotFalse(strpos($data['links'][0]['description'], '<h1>'));
+ $this->assertNotFalse(strpos($data['links'][0]['description'], '<p>'));
+ $this->assertStringEndsWith(
+ '— <a href="http://domain.tld/?0oc_VQ">Permalien</a></p></div>',
+ $data['links'][0]['description']
+ );
+ }
+
/**
* Test render_daily hook.
* Only check that there is basic markdown rendering.
$this->assertEquals($text, $reversedText);
}
+ public function testReverseFeedPermalink()
+ {
+ $text = 'Description... ';
+ $text .= '— <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>';
+ $expected = 'Description... — [Permalien](http://domain.tld/?0oc_VQ)';
+ $processedText = reverse_feed_permalink($text);
+
+ $this->assertEquals($expected, $processedText);
+ }
+
+ public function testReverseLastFeedPermalink()
+ {
+ $text = 'Description... ';
+ $text .= '<br>— <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>';
+ $expected = $text;
+ $text .= '<br>— <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>';
+ $expected .= '<br>— [Permalien](http://domain.tld/?0oc_VQ)';
+ $processedText = reverse_feed_permalink($text);
+
+ $this->assertEquals($expected, $processedText);
+ }
+
+ public function testReverseNoFeedPermalink()
+ {
+ $text = 'Hello! Where are you from?';
+ $expected = $text;
+ $processedText = reverse_feed_permalink($text);
+
+ $this->assertEquals($expected, $processedText);
+ }
+
/**
* Test sanitize_html().
*/
<?php
-namespace Shaarli;
+namespace Shaarli\Security;
require_once 'tests/utils/FakeConfigManager.php';
use \PHPUnit\Framework\TestCase;
*/
class LoginManagerTest extends TestCase
{
+ /** @var \FakeConfigManager Configuration Manager instance */
protected $configManager = null;
+
+ /** @var LoginManager Login Manager instance */
protected $loginManager = null;
+
+ /** @var SessionManager Session Manager instance */
+ protected $sessionManager = null;
+
+ /** @var string Banned IP filename */
protected $banFile = 'sandbox/ipbans.php';
+
+ /** @var string Log filename */
protected $logFile = 'sandbox/shaarli.log';
+
+ /** @var array Simulates the $_COOKIE array */
+ protected $cookie = [];
+
+ /** @var array Simulates the $GLOBALS array */
protected $globals = [];
- protected $ipAddr = '127.0.0.1';
+
+ /** @var array Simulates the $_SERVER array */
protected $server = [];
+
+ /** @var array Simulates the $_SESSION array */
+ protected $session = [];
+
+ /** @var string Advertised client IP address */
+ protected $clientIpAddress = '10.1.47.179';
+
+ /** @var string Local client IP address */
+ protected $ipAddr = '127.0.0.1';
+
+ /** @var string Trusted proxy IP address */
protected $trustedProxy = '10.1.1.100';
+ /** @var string User login */
+ protected $login = 'johndoe';
+
+ /** @var string User password */
+ protected $password = 'IC4nHazL0g1n?';
+
+ /** @var string Hash of the salted user password */
+ protected $passwordHash = '';
+
+ /** @var string Salt used by hash functions */
+ protected $salt = '669e24fa9c5a59a613f98e8e38327384504a4af2';
+
/**
* Prepare or reset test resources
*/
unlink($this->banFile);
}
+ $this->passwordHash = sha1($this->password . $this->login . $this->salt);
+
$this->configManager = new \FakeConfigManager([
+ 'credentials.login' => $this->login,
+ 'credentials.hash' => $this->passwordHash,
+ 'credentials.salt' => $this->salt,
'resource.ban_file' => $this->banFile,
'resource.log' => $this->logFile,
'security.ban_after' => 4,
'security.trusted_proxies' => [$this->trustedProxy],
]);
+ $this->cookie = [];
+
$this->globals = &$GLOBALS;
unset($this->globals['IPBANS']);
- $this->loginManager = new LoginManager($this->globals, $this->configManager);
+ $this->session = [];
+
+ $this->sessionManager = new SessionManager($this->session, $this->configManager);
+ $this->loginManager = new LoginManager($this->globals, $this->configManager, $this->sessionManager);
$this->server['REMOTE_ADDR'] = $this->ipAddr;
}
$this->banFile,
"<?php\n\$GLOBALS['IPBANS']=array('FAILURES' => array('127.0.0.1' => 99));\n?>"
);
- new LoginManager($this->globals, $this->configManager);
+ new LoginManager($this->globals, $this->configManager, null);
$this->assertEquals(99, $this->globals['IPBANS']['FAILURES']['127.0.0.1']);
}
$this->globals['IPBANS']['BANS'][$this->ipAddr] = time() - 3600;
$this->assertTrue($this->loginManager->canLogin($this->server));
}
+
+ /**
+ * Generate a token depending on the user credentials and client IP
+ */
+ public function testGenerateStaySignedInToken()
+ {
+ $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
+
+ $this->assertEquals(
+ sha1($this->passwordHash . $this->clientIpAddress . $this->salt),
+ $this->loginManager->getStaySignedInToken()
+ );
+ }
+
+ /**
+ * Check user login - Shaarli has not yet been configured
+ */
+ public function testCheckLoginStateNotConfigured()
+ {
+ $configManager = new \FakeConfigManager([
+ 'resource.ban_file' => $this->banFile,
+ ]);
+ $loginManager = new LoginManager($this->globals, $configManager, null);
+ $loginManager->checkLoginState([], '');
+
+ $this->assertFalse($loginManager->isLoggedIn());
+ }
+
+ /**
+ * Check user login - the client cookie does not match the server token
+ */
+ public function testCheckLoginStateStaySignedInWithInvalidToken()
+ {
+ // simulate a previous login
+ $this->session = [
+ 'ip' => $this->clientIpAddress,
+ 'expires_on' => time() + 100,
+ ];
+ $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
+ $this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = 'nope';
+
+ $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
+
+ $this->assertTrue($this->loginManager->isLoggedIn());
+ $this->assertTrue(empty($this->session['username']));
+ }
+
+ /**
+ * Check user login - the client cookie matches the server token
+ */
+ public function testCheckLoginStateStaySignedInWithValidToken()
+ {
+ $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
+ $this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = $this->loginManager->getStaySignedInToken();
+
+ $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
+
+ $this->assertTrue($this->loginManager->isLoggedIn());
+ $this->assertEquals($this->login, $this->session['username']);
+ $this->assertEquals($this->clientIpAddress, $this->session['ip']);
+ }
+
+ /**
+ * Check user login - the session has expired
+ */
+ public function testCheckLoginStateSessionExpired()
+ {
+ $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
+ $this->session['expires_on'] = time() - 100;
+
+ $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
+
+ $this->assertFalse($this->loginManager->isLoggedIn());
+ }
+
+ /**
+ * Check user login - the remote client IP has changed
+ */
+ public function testCheckLoginStateClientIpChanged()
+ {
+ $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
+
+ $this->loginManager->checkLoginState($this->cookie, '10.7.157.98');
+
+ $this->assertFalse($this->loginManager->isLoggedIn());
+ }
+
+ /**
+ * Check user credentials - wrong login supplied
+ */
+ public function testCheckCredentialsWrongLogin()
+ {
+ $this->assertFalse(
+ $this->loginManager->checkCredentials('', '', 'b4dl0g1n', $this->password)
+ );
+ }
+
+ /**
+ * Check user credentials - wrong password supplied
+ */
+ public function testCheckCredentialsWrongPassword()
+ {
+ $this->assertFalse(
+ $this->loginManager->checkCredentials('', '', $this->login, 'b4dp455wd')
+ );
+ }
+
+ /**
+ * Check user credentials - wrong login and password supplied
+ */
+ public function testCheckCredentialsWrongLoginAndPassword()
+ {
+ $this->assertFalse(
+ $this->loginManager->checkCredentials('', '', 'b4dl0g1n', 'b4dp455wd')
+ );
+ }
+
+ /**
+ * Check user credentials - correct login and password supplied
+ */
+ public function testCheckCredentialsGoodLoginAndPassword()
+ {
+ $this->assertTrue(
+ $this->loginManager->checkCredentials('', '', $this->login, $this->password)
+ );
+ }
}
--- /dev/null
+<?php
+require_once 'tests/utils/FakeConfigManager.php';
+
+// Initialize reference data _before_ PHPUnit starts a session
+require_once 'tests/utils/ReferenceSessionIdHashes.php';
+ReferenceSessionIdHashes::genAllHashes();
+
+use \Shaarli\Security\SessionManager;
+use \PHPUnit\Framework\TestCase;
+
+
+/**
+ * Test coverage for SessionManager
+ */
+class SessionManagerTest extends TestCase
+{
+ /** @var array Session ID hashes */
+ protected static $sidHashes = null;
+
+ /** @var \FakeConfigManager ConfigManager substitute for testing */
+ protected $conf = null;
+
+ /** @var array $_SESSION array for testing */
+ protected $session = [];
+
+ /** @var SessionManager Server-side session management abstraction */
+ protected $sessionManager = null;
+
+ /**
+ * Assign reference data
+ */
+ public static function setUpBeforeClass()
+ {
+ self::$sidHashes = ReferenceSessionIdHashes::getHashes();
+ }
+
+ /**
+ * Initialize or reset test resources
+ */
+ public function setUp()
+ {
+ $this->conf = new FakeConfigManager([
+ 'credentials.login' => 'johndoe',
+ 'credentials.salt' => 'salt',
+ 'security.session_protection_disabled' => false,
+ ]);
+ $this->session = [];
+ $this->sessionManager = new SessionManager($this->session, $this->conf);
+ }
+
+ /**
+ * Generate a session token
+ */
+ public function testGenerateToken()
+ {
+ $token = $this->sessionManager->generateToken();
+
+ $this->assertEquals(1, $this->session['tokens'][$token]);
+ $this->assertEquals(40, strlen($token));
+ }
+
+ /**
+ * Check a session token
+ */
+ public function testCheckToken()
+ {
+ $token = '4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b';
+ $session = [
+ 'tokens' => [
+ $token => 1,
+ ],
+ ];
+ $sessionManager = new SessionManager($session, $this->conf);
+
+ // check and destroy the token
+ $this->assertTrue($sessionManager->checkToken($token));
+ $this->assertFalse(isset($session['tokens'][$token]));
+
+ // ensure the token has been destroyed
+ $this->assertFalse($sessionManager->checkToken($token));
+ }
+
+ /**
+ * Generate and check a session token
+ */
+ public function testGenerateAndCheckToken()
+ {
+ $token = $this->sessionManager->generateToken();
+
+ // ensure a token has been generated
+ $this->assertEquals(1, $this->session['tokens'][$token]);
+ $this->assertEquals(40, strlen($token));
+
+ // check and destroy the token
+ $this->assertTrue($this->sessionManager->checkToken($token));
+ $this->assertFalse(isset($this->session['tokens'][$token]));
+
+ // ensure the token has been destroyed
+ $this->assertFalse($this->sessionManager->checkToken($token));
+ }
+
+ /**
+ * Check an invalid session token
+ */
+ public function testCheckInvalidToken()
+ {
+ $this->assertFalse($this->sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b'));
+ }
+
+ /**
+ * Test SessionManager::checkId with a valid ID - TEST ALL THE HASHES!
+ *
+ * This tests extensively covers all hash algorithms / bit representations
+ */
+ public function testIsAnyHashSessionIdValid()
+ {
+ foreach (self::$sidHashes as $algo => $bpcs) {
+ foreach ($bpcs as $bpc => $hash) {
+ $this->assertTrue(SessionManager::checkId($hash));
+ }
+ }
+ }
+
+ /**
+ * Test checkId with a valid ID - SHA-1 hashes
+ */
+ public function testIsSha1SessionIdValid()
+ {
+ $this->assertTrue(SessionManager::checkId(sha1('shaarli')));
+ }
+
+ /**
+ * Test checkId with a valid ID - SHA-256 hashes
+ */
+ public function testIsSha256SessionIdValid()
+ {
+ $this->assertTrue(SessionManager::checkId(hash('sha256', 'shaarli')));
+ }
+
+ /**
+ * Test checkId with a valid ID - SHA-512 hashes
+ */
+ public function testIsSha512SessionIdValid()
+ {
+ $this->assertTrue(SessionManager::checkId(hash('sha512', 'shaarli')));
+ }
+
+ /**
+ * Test checkId with invalid IDs.
+ */
+ public function testIsSessionIdInvalid()
+ {
+ $this->assertFalse(SessionManager::checkId(''));
+ $this->assertFalse(SessionManager::checkId([]));
+ $this->assertFalse(
+ SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
+ );
+ }
+
+ /**
+ * Store login information after a successful login
+ */
+ public function testStoreLoginInfo()
+ {
+ $this->sessionManager->storeLoginInfo('ip_id');
+
+ $this->assertGreaterThan(time(), $this->session['expires_on']);
+ $this->assertEquals('ip_id', $this->session['ip']);
+ $this->assertEquals('johndoe', $this->session['username']);
+ }
+
+ /**
+ * Extend a server-side session by SessionManager::$SHORT_TIMEOUT
+ */
+ public function testExtendSession()
+ {
+ $this->sessionManager->extendSession();
+
+ $this->assertGreaterThan(time(), $this->session['expires_on']);
+ $this->assertLessThanOrEqual(
+ time() + SessionManager::$SHORT_TIMEOUT,
+ $this->session['expires_on']
+ );
+ }
+
+ /**
+ * Extend a server-side session by SessionManager::$LONG_TIMEOUT
+ */
+ public function testExtendSessionStaySignedIn()
+ {
+ $this->sessionManager->setStaySignedIn(true);
+ $this->sessionManager->extendSession();
+
+ $this->assertGreaterThan(time(), $this->session['expires_on']);
+ $this->assertGreaterThan(
+ time() + SessionManager::$LONG_TIMEOUT - 10,
+ $this->session['expires_on']
+ );
+ $this->assertLessThanOrEqual(
+ time() + SessionManager::$LONG_TIMEOUT,
+ $this->session['expires_on']
+ );
+ }
+
+ /**
+ * Unset session variables after logging out
+ */
+ public function testLogout()
+ {
+ $this->session = [
+ 'ip' => 'ip_id',
+ 'expires_on' => time() + 1000,
+ 'username' => 'johndoe',
+ 'visibility' => 'public',
+ 'untaggedonly' => false,
+ ];
+ $this->sessionManager->logout();
+
+ $this->assertFalse(isset($this->session['ip']));
+ $this->assertFalse(isset($this->session['expires_on']));
+ $this->assertFalse(isset($this->session['username']));
+ $this->assertFalse(isset($this->session['visibility']));
+ $this->assertFalse(isset($this->session['untaggedonly']));
+ }
+
+ /**
+ * The session is active and expiration time has been reached
+ */
+ public function testHasExpiredTimeElapsed()
+ {
+ $this->session['expires_on'] = time() - 10;
+
+ $this->assertTrue($this->sessionManager->hasSessionExpired());
+ }
+
+ /**
+ * The session is active and expiration time has not been reached
+ */
+ public function testHasNotExpired()
+ {
+ $this->session['expires_on'] = time() + 1000;
+
+ $this->assertFalse($this->sessionManager->hasSessionExpired());
+ }
+
+ /**
+ * Session hijacking protection is disabled, we assume the IP has not changed
+ */
+ public function testHasClientIpChangedNoSessionProtection()
+ {
+ $this->conf->set('security.session_protection_disabled', true);
+
+ $this->assertFalse($this->sessionManager->hasClientIpChanged(''));
+ }
+
+ /**
+ * The client IP identifier has not changed
+ */
+ public function testHasClientIpChangedNope()
+ {
+ $this->session['ip'] = 'ip_id';
+ $this->assertFalse($this->sessionManager->hasClientIpChanged('ip_id'));
+ }
+
+ /**
+ * The client IP identifier has changed
+ */
+ public function testHasClientIpChanged()
+ {
+ $this->session['ip'] = 'ip_id_one';
+ $this->assertTrue($this->sessionManager->hasClientIpChanged('ip_id_two'));
+ }
+}
}
return $key;
}
+
+ /**
+ * Check if a setting exists
+ *
+ * @param string $setting Asked setting, keys separated with dots
+ *
+ * @return bool true if the setting exists, false otherwise
+ */
+ public function exists($setting)
+ {
+ return array_key_exists($setting, $this->values);
+ }
}
<body>
<div id="pageheader">
{include="page.header"}
-<div class="center" id="page404">
+<div class="center" id="page404" class="page404-container">
<h2>{'Sorry, nothing to see here.'|t}</h2>
<img src="img/sad_star.png">
<p>{$error_message}</p>
</head>
<body>
{include="page.header"}
- <div id="editlinkform" class="pure-g">
+ <div id="editlinkform" class="edit-link-container" class="pure-g">
<div class="pure-u-lg-1-5 pure-u-1-24"></div>
<form method="post" name="linkform" class="page-form pure-u-lg-3-5 pure-u-22-24 page-form page-form-light">
<h2 class="window-title">
- {if="!$link_is_new"}{'Edit'|t}{/if}
- {'Shaare'|t}
+ {if="!$link_is_new"}{'Edit Shaare'|t}{else}{'New Shaare'|t}{/if}
</h2>
<input type="hidden" name="lf_linkdate" value="{$link.linkdate}">
{if="isset($link.id)"}
</div>
<input type="hidden" name="token" value="{$token}">
- <div class="center" id="import-field">
+ <div class="center import-field-container" id="import-field">
<input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}">
<input type="file" name="filetoupload">
<p><br>{'Maximum size allowed:'|t} <strong>{$maxfilesizeHuman}</strong></p>
</div>
<input type="hidden" name="token" value="{$token}">
-<div id="search-linklist">
+<div id="search-linklist" class="searchform-block search-linklist">
<form method="GET" class="pure-form searchform" name="searchform">
<input type="text" tabindex="1" name="searchterm" class="searchterm" placeholder="{'Search text'|t}"
<div class="linklist-item-thumbnail">{$thumb}</div>
{/if}
- {if="isLoggedIn()"}
+ {if="$is_logged_in"}
<div class="linklist-item-editbuttons">
{if="$value.private"}
<span class="label label-private">{$strPrivate}</span>
<div class="linklist-item-infos-date-url-block pure-g">
<div class="linklist-item-infos-dateblock pure-u-lg-7-12 pure-u-1">
- {if="isLoggedIn()"}
+ {if="$is_logged_in"}
<div class="linklist-item-infos-controls-group pure-u-0 pure-u-lg-visible">
<span class="linklist-item-infos-controls-item ctrl-checkbox">
<input type="checkbox" class="delete-checkbox" value="{$value.id}">
</div>
{/if}
<a href="?{$value.shorturl}" title="{$strPermalink}">
- {if="!$hide_timestamps || isLoggedIn()"}
+ {if="!$hide_timestamps || $is_logged_in"}
{$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink}
<span class="linkdate" title="{$updated}">
<i class="fa fa-clock-o"></i>
{if="$link_plugin_counter - 1 != $counter"}·{/if}
{/loop}
{/if}
- {if="isLoggedIn()"}
+ {if="$is_logged_in"}
·
<a href="?delete_link&lf_linkdate={$value.id}&token={$token}"
title="{$strDelete}" class="delete-link confirm-delete">
<div class="linklist-paging">
<div class="paging pure-g">
<div class="linklist-filters pure-u-1-3">
- {if="isLoggedIn() or !empty($action_plugin)"}
+ {if="$is_logged_in or !empty($action_plugin)"}
<span class="linklist-filters-text pure-u-0 pure-u-lg-visible">
{'Filters'|t}
</span>
- {if="isLoggedIn()"}
+ {if="$is_logged_in"}
<a href="?visibility=private" title="{'Only display private links'|t}"
class="{if="$visibility==='private'"}filter-on{else}filter-off{/if}"
><i class="fa fa-user-secret"></i></a>
{else}
<div class="pure-g">
<div class="pure-u-lg-1-3 pure-u-1-24"></div>
- <div id="login-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24">
+ <div id="login-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24 login-form-container">
<form method="post" name="loginform">
<h2 class="window-title">{'Login'|t}</h2>
<div>
<div class="pure-g">
<div class="pure-u-2-24"></div>
- <div id="footer" class="pure-u-20-24">
+ <div id="footer" class="pure-u-20-24 footer-container">
<strong><a href="https://github.com/shaarli/Shaarli">Shaarli</a></strong>
- {if="isLoggedIn()===true"}
+ {if="$is_logged_in===true"}
{$version}
{/if}
·
{$shaarlititle}
</a>
</li>
- {if="isLoggedIn() || $openshaarli"}
+ {if="$is_logged_in || $openshaarli"}
<li class="pure-menu-item">
<a href="?do=addlink" class="pure-menu-link" id="shaarli-menu-shaare">
<i class="fa fa-plus" ></i> {'Shaare'|t}
<li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss">
<a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a>
</li>
- {if="isLoggedIn()"}
+ {if="$is_logged_in"}
<li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout">
<a href="?do=logout" class="pure-menu-link">{'Logout'|t}</a>
</li>
<i class="fa fa-rss"></i>
</a>
</li>
- {if="!isLoggedIn()"}
+ {if="!$is_logged_in"}
<li class="pure-menu-item" id="shaarli-menu-desktop-login">
<a href="?do=login" class="pure-menu-link"
data-open-id="header-login-form"
</div>
</div>
-<div id="content">
- <div id="search" class="subheader-form">
+<div id="content" class="container">
+ <div id="search" class="subheader-form searchform-block header-search">
<form method="GET" class="pure-form searchform" name="searchform">
<input type="text" tabindex="1" id="searchform_value" name="searchterm" placeholder="{'Search text'|t}"
{if="!empty($search_term)"}
</div>
</div>
</div>
- {if="!isLoggedIn()"}
+ {if="!$is_logged_in"}
<form method="post" name="loginform">
- <div class="subheader-form" id="header-login-form">
+ <div class="subheader-form header-login-form" id="header-login-form">
<input type="text" name="login" placeholder="{'Username'|t}" tabindex="3">
<input type="password" name="password" placeholder="{'Password'|t}" tabindex="5">
<div class="remember-me">
</div>
{/if}
-{if="!empty($plugin_errors) && isLoggedIn()"}
+{if="!empty($plugin_errors) && $is_logged_in"}
<div class="pure-g new-version-message pure-alert pure-alert-error pure-alert-closable" id="shaarli-errors-alert">
<div class="pure-u-2-24"></div>
<div class="pure-u-20-24">
{/loop}
</div>
- <div id="picwall_container">
+ <div id="picwall_container" class="picwall-container">
{loop="$linksToDisplay"}
- <div class="picwall_pictureframe">
+ <div class="picwall-pictureframe">
{$value.thumbnail}<a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
{loop="$value.picwall_plugin"}
{$value}
<div class="clear"></div>
</noscript>
-<form method="POST" action="?do=save_pluginadmin" name="pluginform" id="pluginform">
+<form method="POST" action="?do=save_pluginadmin" name="pluginform" id="pluginform" class="pluginform-container">
<div class="pure-g">
<div class="pure-u-lg-1-8 pure-u-1-24"></div>
<div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete">
{/loop}
</div>
- <div id="cloudtag">
+ <div id="cloudtag" class="cloudtag-container">
{loop="tags"}
<a href="?searchtags={$key|urlencode} {$search_tags|urlencode}" style="font-size:{$value.size}em;">{$key}</a
><a href="?addtag={$key|urlencode}" title="{'Filter by tag'|t}" class="count">{$value.count}</a>
</p>
{/if}
- <div id="search-tagcloud" class="pure-g">
+ <div id="search-tagcloud" class="pure-g searchform-block search-tagcloud">
<div class="pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-1-2">
<form method="GET">
{/loop}
</div>
- <div id="taglist">
+ <div id="taglist" class="taglist-container">
{loop="tags"}
<div class="tag-list-item pure-g" data-tag="{$key}">
<div class="pure-u-1">
- {if="isLoggedIn()===true"}
+ {if="$is_logged_in===true"}
<a href="#" class="delete-tag"><i class="fa fa-trash"></i></a>
<a href="?do=changetag&fromtag={$key|urlencode}" class="rename-tag">
<i class="fa fa-pencil-square-o {$key}"></i>
{$value}
{/loop}
</div>
- {if="isLoggedIn()===true"}
+ {if="$is_logged_in===true"}
<div class="rename-tag-form pure-u-1">
<input type="text" name="{$key}" value="{$key}" class="rename-tag-input" />
<a href="#" class="validate-rename-tag"><i class="fa fa-check"></i></a>
</div>
</div>
-{if="isLoggedIn()===true"}
+{if="$is_logged_in===true"}
<input type="hidden" name="taglist" value="{loop="$tags"}{$key} {/loop}"
{/if}
</div>
<div class="tools-item">
<a href="https://play.google.com/store/apps/details?id=com.dimtion.shaarlier&hl=fr"
- title="Android">
- <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Android</span>
+ title="Android Shaarlier">
+ <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Android Shaarlier</span>
+ </a>
+ </div>
+ <div class="tools-item">
+ <a href="https://stakali.toneiv.eu/"
+ title="Android Stakali">
+ <span class="pure-button pure-u-lg-2-3 pure-u-3-4">Android Stakali</span>
</a>
</div>
<div class="tools-item">
<img src="img/squiggle.png" width="25" height="26" title="permalink" alt="permalink">
</a>
</div>
- {if="!$hide_timestamps || isLoggedIn()"}
+ {if="!$hide_timestamps || $is_logged_in"}
<div class="dailyEntryLinkdate">
<a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a>
</div>
<a id="{$value.shorturl}"></a>
<div class="thumbnail">{$value.url|thumbnail}</div>
<div class="linkcontainer">
- {if="isLoggedIn()"}
+ {if="$is_logged_in"}
<div class="linkeditbuttons">
<form method="GET" class="buttoneditform">
<input type="hidden" name="edit_link" value="{$value.id}">
</span>
<br>
{if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
- {if="!$hide_timestamps || isLoggedIn()"}
+ {if="!$hide_timestamps || $is_logged_in"}
{$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'}
<span class="linkdate" title="Permalink">
<a href="?{$value.shorturl}">
<div class="paging">
-{if="isLoggedIn()"}
+{if="$is_logged_in"}
<div class="paging_privatelinks">
<a href="?visibility=private">
{if="$visibility=='private'"}
<script src="js/shaarli.min.js"></script>
-{if="isLoggedIn()"}
+{if="$is_logged_in"}
<script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script>
{/if}
{ignore} When called as a popup from bookmarklet, do not display menu. {/ignore}
{else}
<li><a href="{$titleLink}" class="nomobile">Home</a></li>
- {if="isLoggedIn()"}
+ {if="$is_logged_in"}
<li><a href="?do=logout">Logout</a></li>
<li><a href="?do=tools">Tools</a></li>
<li><a href="?do=addlink">Add link</a></li>
</ul>
</div>
-{if="!empty($plugin_errors) && isLoggedIn()"}
+{if="!empty($plugin_errors) && $is_logged_in"}
<ul class="errors">
{loop="$plugin_errors"}
<li>{$value}</li>
version "5.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102"
+acorn@^5.5.0:
+ version "5.5.3"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
+
+ajv-keywords@^1.0.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
+
ajv-keywords@^2.0.0, ajv-keywords@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
-ajv@^4.9.1:
+ajv@^4.7.0, ajv@^4.9.1:
version "4.11.8"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
dependencies:
version "1.0.1"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
+ansi-escapes@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
+
ansi-escapes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
caniuse-lite "^1.0.30000792"
electron-to-chromium "^1.3.30"
+buffer-from@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
+
buffer-xor@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
align-text "^0.1.3"
lazy-cache "^1.0.3"
-chalk@^1.1.1, chalk@^1.1.3:
+chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
dependencies:
chalk "^1.1.3"
+cli-cursor@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
+ dependencies:
+ restore-cursor "^1.0.1"
+
cli-cursor@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
dependencies:
delayed-stream "~1.0.0"
+commander@^2.8.1:
+ version "2.15.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
+
commander@^2.9.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+concat-stream@^1.4.6:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
+ dependencies:
+ buffer-from "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
concat-stream@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
-debug@^2.2.0, debug@^2.6.8, debug@^2.6.9:
+debug@^2.1.1, debug@^2.2.0, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
-doctrine@1.5.0:
+doctrine@1.5.0, doctrine@^1.2.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
dependencies:
version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
+eslint@^2.7.0:
+ version "2.13.1"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11"
+ dependencies:
+ chalk "^1.1.3"
+ concat-stream "^1.4.6"
+ debug "^2.1.1"
+ doctrine "^1.2.2"
+ es6-map "^0.1.3"
+ escope "^3.6.0"
+ espree "^3.1.6"
+ estraverse "^4.2.0"
+ esutils "^2.0.2"
+ file-entry-cache "^1.1.1"
+ glob "^7.0.3"
+ globals "^9.2.0"
+ ignore "^3.1.2"
+ imurmurhash "^0.1.4"
+ inquirer "^0.12.0"
+ is-my-json-valid "^2.10.0"
+ is-resolvable "^1.0.0"
+ js-yaml "^3.5.1"
+ json-stable-stringify "^1.0.0"
+ levn "^0.3.0"
+ lodash "^4.0.0"
+ mkdirp "^0.5.0"
+ optionator "^0.8.1"
+ path-is-absolute "^1.0.0"
+ path-is-inside "^1.0.1"
+ pluralize "^1.2.1"
+ progress "^1.1.8"
+ require-uncached "^1.0.2"
+ shelljs "^0.6.0"
+ strip-json-comments "~1.0.1"
+ table "^3.7.8"
+ text-table "~0.2.0"
+ user-home "^2.0.0"
+
eslint@^4.16.0:
version "4.17.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.17.0.tgz#dc24bb51ede48df629be7031c71d9dc0ee4f3ddf"
table "^4.0.1"
text-table "~0.2.0"
+espree@^3.1.6:
+ version "3.5.4"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
+ dependencies:
+ acorn "^5.5.0"
+ acorn-jsx "^3.0.0"
+
espree@^3.5.2:
version "3.5.3"
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.3.tgz#931e0af64e7fbbed26b050a29daad1fc64799fa6"
estraverse "^4.1.0"
object-assign "^4.0.1"
-estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1:
+estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
+exit-hook@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
+
expand-brackets@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
+figures@^1.3.5:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
+ dependencies:
+ escape-string-regexp "^1.0.5"
+ object-assign "^4.1.0"
+
figures@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
dependencies:
escape-string-regexp "^1.0.5"
+file-entry-cache@^1.1.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-1.3.1.tgz#44c61ea607ae4be9c1402f41f44270cbfe334ff8"
+ dependencies:
+ flat-cache "^1.2.1"
+ object-assign "^4.0.1"
+
file-entry-cache@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
combined-stream "^1.0.5"
mime-types "^2.1.12"
+front-matter@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-2.1.2.tgz#f75983b9f2f413be658c93dfd7bd8ce4078f5cdb"
+ dependencies:
+ js-yaml "^3.4.6"
+
+fs-extra@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291"
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^3.0.0"
+ universalify "^0.1.0"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
version "11.3.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.3.0.tgz#e04fdb7b9796d8adac9c8f64c14837b2313378b0"
-globals@^9.18.0:
+globals@^9.18.0, globals@^9.2.0:
version "9.18.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
lodash "~4.17.4"
minimatch "~3.0.2"
-graceful-fs@^4.1.2:
+gonzales-pe-sl@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/gonzales-pe-sl/-/gonzales-pe-sl-4.2.3.tgz#6a868bc380645f141feeb042c6f97fcc71b59fe6"
+ dependencies:
+ minimist "1.1.x"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
-ignore@^3.3.3:
+ignore@^3.1.2, ignore@^3.3.3:
version "3.3.7"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
+inquirer@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
+ dependencies:
+ ansi-escapes "^1.1.0"
+ ansi-regex "^2.0.0"
+ chalk "^1.0.0"
+ cli-cursor "^1.0.1"
+ cli-width "^2.0.0"
+ figures "^1.3.5"
+ lodash "^4.3.0"
+ readline2 "^1.0.1"
+ run-async "^0.1.0"
+ rx-lite "^3.1.2"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.0"
+ through "^2.3.6"
+
inquirer@^3.0.6:
version "3.3.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
dependencies:
is-extglob "^1.0.0"
+is-my-ip-valid@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
+
+is-my-json-valid@^2.10.0:
+ version "2.17.2"
+ resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c"
+ dependencies:
+ generate-function "^2.0.0"
+ generate-object-property "^1.1.0"
+ is-my-ip-valid "^1.0.0"
+ jsonpointer "^4.0.0"
+ xtend "^4.0.0"
+
is-my-json-valid@^2.12.4:
version "2.17.1"
resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471"
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4:
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
js-yaml@^3.9.1:
version "3.10.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
-json-stable-stringify@^1.0.1:
+json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
dependencies:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+jsonfile@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
dependencies:
is-buffer "^1.1.5"
+known-css-properties@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.3.0.tgz#a3d135bbfc60ee8c6eacf2f7e7e6f2d4755e49a4"
+
lazy-cache@^0.2.3:
version "0.2.7"
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65"
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+lodash.capitalize@^4.1.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
+
lodash.clonedeep@^4.3.2:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+lodash.kebabcase@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
+
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
redent "^1.0.0"
trim-newlines "^1.0.0"
+merge@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
+
micromatch@^2.1.5:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+minimist@1.1.x:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8"
+
minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+mute-stream@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
+
mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
dependencies:
wrappy "1"
+onetime@^1.0.0:
+ version "1.1.0"
+ resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
+
onetime@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
dependencies:
mimic-fn "^1.0.0"
-optionator@^0.8.2:
+optionator@^0.8.1, optionator@^0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
dependencies:
dependencies:
find-up "^2.1.0"
+pluralize@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
+
pluralize@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
+progress@^1.1.8:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
+
progress@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
readable-stream "^2.0.2"
set-immediate-shim "^1.0.1"
+readline2@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35"
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ mute-stream "0.0.5"
+
redent@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
version "1.0.1"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
-require-uncached@^1.0.3:
+require-uncached@^1.0.2, require-uncached@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
dependencies:
dependencies:
path-parse "^1.0.5"
+restore-cursor@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
+ dependencies:
+ exit-hook "^1.0.0"
+ onetime "^1.0.0"
+
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
hash-base "^2.0.0"
inherits "^2.0.1"
+run-async@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
+ dependencies:
+ once "^1.3.0"
+
run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
version "4.0.8"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
+rx-lite@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
+
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
scss-tokenizer "^0.2.3"
yargs "^7.0.0"
+sass-lint@^1.12.1:
+ version "1.12.1"
+ resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.12.1.tgz#630f69c216aa206b8232fb2aa907bdf3336b6d83"
+ dependencies:
+ commander "^2.8.1"
+ eslint "^2.7.0"
+ front-matter "2.1.2"
+ fs-extra "^3.0.1"
+ glob "^7.0.0"
+ globule "^1.0.0"
+ gonzales-pe-sl "^4.2.3"
+ js-yaml "^3.5.4"
+ known-css-properties "^0.3.0"
+ lodash.capitalize "^4.1.0"
+ lodash.kebabcase "^4.0.0"
+ merge "^1.2.0"
+ path-is-absolute "^1.0.0"
+ util "^0.10.3"
+
sass-loader@^6.0.6:
version "6.0.6"
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9"
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+shelljs@^0.6.0:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8"
+
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+slice-ansi@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
+
slice-ansi@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d"
dependencies:
get-stdin "^4.0.1"
+strip-json-comments@~1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
+
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
sax "~1.2.1"
whet.extend "~0.9.9"
+table@^3.7.8:
+ version "3.8.3"
+ resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
+ dependencies:
+ ajv "^4.7.0"
+ ajv-keywords "^1.0.0"
+ chalk "^1.1.1"
+ lodash "^4.0.0"
+ slice-ansi "0.0.4"
+ string-width "^2.0.0"
+
table@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
version "2.0.0"
resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
+universalify@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
+
url-loader@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7"
punycode "1.3.2"
querystring "0.2.0"
+user-home@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
+ dependencies:
+ os-homedir "^1.0.0"
+
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"