Merge remote-tracking branch 'github/latest' into myShaarli_commu

This commit is contained in:
Knah Tsaeb 2018-02-09 16:10:09 +01:00
commit d923d1db2f
28 changed files with 730 additions and 290 deletions

2
.github/mailmap vendored
View file

@ -1,6 +1,8 @@
ArthurHoaro <arthur@hoa.ro> ArthurHoaro <arthur@hoa.ro>
Florian Eula <eula.florian@gmail.com> feula Florian Eula <eula.florian@gmail.com> feula
Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com> Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com>
Immánuel Fodor <immanuelfactor+github@gmail.com>
kalvn <kalvnthereal@gmail.com> <kalvn@users.noreply.github.com>
Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm
Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar> Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar>
Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com> Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com>

13
AUTHORS
View file

@ -1,6 +1,6 @@
537 ArthurHoaro <arthur@hoa.ro> 588 ArthurHoaro <arthur@hoa.ro>
252 VirtualTam <virtualtam@flibidi.net> 283 VirtualTam <virtualtam@flibidi.net>
148 nodiscc <nodiscc@gmail.com> 179 nodiscc <nodiscc@gmail.com>
56 Sébastien Sauvage <sebsauvage@sebsauvage.net> 56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
15 Florian Eula <eula.florian@gmail.com> 15 Florian Eula <eula.florian@gmail.com>
13 Emilien Klein <emilien@klein.st> 13 Emilien Klein <emilien@klein.st>
@ -11,8 +11,9 @@
5 Lucas Cimon <lucas.cimon@gmail.com> 5 Lucas Cimon <lucas.cimon@gmail.com>
4 Alexandre Alapetite <alexandre@alapetite.fr> 4 Alexandre Alapetite <alexandre@alapetite.fr>
4 David Sferruzza <david.sferruzza@gmail.com> 4 David Sferruzza <david.sferruzza@gmail.com>
4 Immánuel Fodor <immanuelfactor+github@gmail.com>
4 kalvn <kalvnthereal@gmail.com>
3 Teromene <teromene@teromene.fr> 3 Teromene <teromene@teromene.fr>
3 kalvn <kalvnthereal@gmail.com>
2 Chris Kuethe <chris.kuethe@gmail.com> 2 Chris Kuethe <chris.kuethe@gmail.com>
2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org> 2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org>
2 Mathieu Chabanon <git@matchab.fr> 2 Mathieu Chabanon <git@matchab.fr>
@ -27,11 +28,13 @@
1 BoboTiG <bobotig@gmail.com> 1 BoboTiG <bobotig@gmail.com>
1 Bronco <bronco@warriordudimanche.net> 1 Bronco <bronco@warriordudimanche.net>
1 D Low <daniellowtw@gmail.com> 1 D Low <daniellowtw@gmail.com>
1 Daniel Jakots <vigdis@chown.me>
1 Dimtion <zizou.xena@gmail.com> 1 Dimtion <zizou.xena@gmail.com>
1 Fanch <fanch-github@qth.fr> 1 Fanch <fanch-github@qth.fr>
1 Felix Bartels <felix@host-consultants.de> 1 Felix Bartels <felix@host-consultants.de>
1 Felix Kästner <github.com-fpunktk@fpunktk.de> 1 Felix Kästner <github.com-fpunktk@fpunktk.de>
1 Florian Voigt <flvoigt@me.com> 1 Florian Voigt <flvoigt@me.com>
1 Franck Kerbiriou <FranckKe@users.noreply.github.com>
1 Gary Marigliano <gmarigliano93@gmail.com> 1 Gary Marigliano <gmarigliano93@gmail.com>
1 Guillaume Virlet <github@virlet.org> 1 Guillaume Virlet <github@virlet.org>
1 Jonathan Druart <jonathan.druart@gmail.com> 1 Jonathan Druart <jonathan.druart@gmail.com>
@ -41,6 +44,8 @@
1 Lionel Martin <renarddesmers@gmail.com> 1 Lionel Martin <renarddesmers@gmail.com>
1 Mark Gerarts <mark.gerarts@gmail.com> 1 Mark Gerarts <mark.gerarts@gmail.com>
1 Marsup <marsup@gmail.com> 1 Marsup <marsup@gmail.com>
1 Neros <contact@neros.fr>
1 Sbgodin <Sbgodin@users.noreply.github.com> 1 Sbgodin <Sbgodin@users.noreply.github.com>
1 TsT <tst2005@gmail.com> 1 TsT <tst2005@gmail.com>
1 dimtion <zizou.xena@gmail.com> 1 dimtion <zizou.xena@gmail.com>
1 durcheinandr <jochen@durcheinandr.de>

View file

@ -4,6 +4,49 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
## [v0.10.0](https://github.com/shaarli/Shaarli/releases/tag/v0.10.0) - UNPUBLISHED
## [v0.9.5](https://github.com/shaarli/Shaarli/releases/tag/v0.9.5) - 2018-02-02
### Fixed
- Fix a warning happening when `php-intl` is not installed on the system
- Fix warnings happening when updating from legacy SebSauvage version
## [v0.9.4](https://github.com/shaarli/Shaarli/releases/tag/v0.9.4) - 2018-01-30
### Added
- Enable translations: Shaarli is now also available in French. Other language translations are welcome!
- Add EditorConfig configuration
- Add favicons for mobile devices
- Add Alpine Linux arm32v7 Dockerfiles (master, latest)
### Changed
- Do not write bookmark edition history during file imports (performance)
- Migrate Docker images (master, latest) to Alpine Linux
- Improve unitary tests and code coverage
- Improve thumbnail display
- Improve theme ergonomics
- Improve messages if there is no plugin or parameter available in the admin page
- Increase buffer size for cURL download
- Force HTTPS if the original port is 443 behind a reverse proxy (workaround)
- Improve page title retrieval performances
### Removed
- Remove redirector setting from Configure page
### Fixed
- Fix broken links in the documentation
- Enable access to `data/user.css` (Apache 2.2 & 2.4)
- Don't URL encode description links if parameter `redirector.encode_url` is set to false
- Fix an issue preventing the Save button to appear for plugin parameters
## [v0.9.3](https://github.com/shaarli/Shaarli/releases/tag/v0.9.3) - 2018-01-04
**XSS vulnerability fixed. Please update.**
## Security
- Fix an XSS (cross-site-scripting) vulnerability in `index.php` -
[CVE-2018-5249](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-5249)
## [v0.9.2](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) - 2017-10-07 ## [v0.9.2](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) - 2017-10-07
**Major security issue fixed. Please update.** **Major security issue fixed. Please update.**
@ -42,6 +85,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed reflected XSS vulnerability introduced in v0.9.1, discovered by @chb9 ([CVE-2017-15215](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15215)). - Fixed reflected XSS vulnerability introduced in v0.9.1, discovered by @chb9 ([CVE-2017-15215](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15215)).
## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23 ## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23
The documentation has been migrated to ReadTheDocs: The documentation has been migrated to ReadTheDocs:
@ -187,6 +231,13 @@ Theming:
- Editing a link created before the new ID system would change its permalink. - Editing a link created before the new ID system would change its permalink.
## [v0.8.5](https://github.com/shaarli/Shaarli/releases/tag/v0.8.5) - 2018-01-04
**XSS vulnerability fixed. Please update.**
## Security
- Fix an XSS (cross-site-scripting) vulnerability in `index.php` -
[CVE-2018-5249](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-5249)
## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04 ## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04
### Security ### Security
- Markdown plugin: escape HTML entities by default - Markdown plugin: escape HTML entities by default

View file

@ -6,13 +6,13 @@ _Do you want to share the links you discover?_
_Shaarli is a minimalist link sharing service that you can install on your own server._ _Shaarli is a minimalist link sharing service that you can install on your own server._
_It is designed to be personal (single-user), fast and handy._ _It is designed to be personal (single-user), fast and handy._
[![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) [![](https://img.shields.io/badge/stable-v0.8.5-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.5)
[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli) [![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
&bull; &bull;
[![](https://img.shields.io/badge/latest-v0.9.2-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) [![](https://img.shields.io/badge/latest-v0.9.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.4)
[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) [![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
&bull; &bull;
[![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli) [![](https://img.shields.io/badge/master-v0.10.x-blue.svg)](https://github.com/shaarli/Shaarli)
[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli) [![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli)
[![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli) [![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli)

View file

@ -6,6 +6,8 @@
* @param string $url URL to get (http://...) * @param string $url URL to get (http://...)
* @param int $timeout network timeout (in seconds) * @param int $timeout network timeout (in seconds)
* @param int $maxBytes maximum downloaded bytes (default: 4 MiB) * @param int $maxBytes maximum downloaded bytes (default: 4 MiB)
* @param callable|string $curlWriteFunction Optional callback called during the download (cURL CURLOPT_WRITEFUNCTION).
* Can be used to add download conditions on the headers (response code, content type, etc.).
* *
* @return array HTTP response headers, downloaded content * @return array HTTP response headers, downloaded content
* *
@ -29,7 +31,7 @@
* @see http://stackoverflow.com/q/9183178 * @see http://stackoverflow.com/q/9183178
* @see http://stackoverflow.com/q/1462720 * @see http://stackoverflow.com/q/1462720
*/ */
function get_http_response($url, $timeout = 30, $maxBytes = 4194304) function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteFunction = null)
{ {
$urlObj = new Url($url); $urlObj = new Url($url);
$cleanUrl = $urlObj->idnToAscii(); $cleanUrl = $urlObj->idnToAscii();
@ -75,6 +77,10 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304)
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent); curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
if (is_callable($curlWriteFunction)) {
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $curlWriteFunction);
}
// Max download size management // Max download size management
curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16); curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16);
curl_setopt($ch, CURLOPT_NOPROGRESS, false); curl_setopt($ch, CURLOPT_NOPROGRESS, false);

View file

@ -69,6 +69,8 @@ public function __construct($language, $conf)
{ {
$this->conf = $conf; $this->conf = $conf;
$confLanguage = $this->conf->get('translation.language', 'auto'); $confLanguage = $this->conf->get('translation.language', 'auto');
// Auto mode or invalid parameter, use the detected language.
// If the detected language is invalid, it doesn't matter, it will use English.
if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) { if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) {
$this->language = substr($language, 0, 5); $this->language = substr($language, 0, 5);
} else { } else {

View file

@ -1,5 +1,54 @@
<?php <?php
/**
* Get cURL callback function for CURLOPT_WRITEFUNCTION
*
* @param string $charset to extract from the downloaded page (reference)
* @param string $title to extract from the downloaded page (reference)
* @param string $curlGetInfo Optionnaly overrides curl_getinfo function
*
* @return Closure
*/
function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_getinfo')
{
/**
* cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
*
* While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text'
* Then we extract the title and the charset and stop the download when it's done.
*
* @param resource $ch cURL resource
* @param string $data chunk of data being downloaded
*
* @return int|bool length of $data or false if we need to stop the download
*/
return function(&$ch, $data) use ($curlGetInfo, &$charset, &$title) {
$responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
if (!empty($responseCode) && $responseCode != 200) {
return false;
}
$contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE);
if (!empty($contentType) && strpos($contentType, 'text/html') === false) {
return false;
}
if (empty($charset)) {
$charset = header_extract_charset($contentType);
}
if (empty($charset)) {
$charset = html_extract_charset($data);
}
if (empty($title)) {
$title = html_extract_title($data);
}
// We got everything we want, stop the download.
if (!empty($responseCode) && !empty($contentType) && !empty($charset) && !empty($title)) {
return false;
}
return strlen($data);
};
}
/** /**
* Extract title from an HTML document. * Extract title from an HTML document.
* *
@ -16,46 +65,18 @@ function html_extract_title($html)
} }
/** /**
* Determine charset from downloaded page. * Extract charset from HTTP header if it's defined.
* Priority:
* 1. HTTP headers (Content type).
* 2. HTML content page (tag <meta charset>).
* 3. Use a default charset (default: UTF-8).
* *
* @param array $headers HTTP headers array. * @param string $header HTTP header Content-Type line.
* @param string $htmlContent HTML content where to look for charset.
* @param string $defaultCharset Default charset to apply if other methods failed.
*
* @return string Determined charset.
*/
function get_charset($headers, $htmlContent, $defaultCharset = 'utf-8')
{
if ($charset = headers_extract_charset($headers)) {
return $charset;
}
if ($charset = html_extract_charset($htmlContent)) {
return $charset;
}
return $defaultCharset;
}
/**
* Extract charset from HTTP headers if it's defined.
*
* @param array $headers HTTP headers array.
* *
* @return bool|string Charset string if found (lowercase), false otherwise. * @return bool|string Charset string if found (lowercase), false otherwise.
*/ */
function headers_extract_charset($headers) function header_extract_charset($header)
{ {
if (! empty($headers['Content-Type']) && strpos($headers['Content-Type'], 'charset=') !== false) { preg_match('/charset="?([^; ]+)/i', $header, $match);
preg_match('/charset="?([^; ]+)/i', $headers['Content-Type'], $match);
if (! empty($match[1])) { if (! empty($match[1])) {
return strtolower(trim($match[1])); return strtolower(trim($match[1]));
} }
}
return false; return false;
} }

View file

@ -83,10 +83,10 @@ public function read($filepath)
$out = array(); $out = array();
foreach (self::$ROOT_KEYS as $key) { foreach (self::$ROOT_KEYS as $key) {
$out[$key] = $GLOBALS[$key]; $out[$key] = isset($GLOBALS[$key]) ? $GLOBALS[$key] : '';
} }
$out['config'] = $GLOBALS['config']; $out['config'] = isset($GLOBALS['config']) ? $GLOBALS['config'] : [];
$out['plugins'] = !empty($GLOBALS['plugins']) ? $GLOBALS['plugins'] : array(); $out['plugins'] = isset($GLOBALS['plugins']) ? $GLOBALS['plugins'] : [];
return $out; return $out;
} }

178
composer.lock generated
View file

@ -294,16 +294,16 @@
}, },
{ {
"name": "pimple/pimple", "name": "pimple/pimple",
"version": "v3.2.2", "version": "v3.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/silexphp/Pimple.git", "url": "https://github.com/silexphp/Pimple.git",
"reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a" "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/silexphp/Pimple/zipball/4d45fb62d96418396ec58ba76e6f065bca16e10a", "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
"reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a", "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -340,7 +340,7 @@
"container", "container",
"dependency injection" "dependency injection"
], ],
"time": "2017-07-23T07:32:15+00:00" "time": "2018-01-21T07:42:36+00:00"
}, },
{ {
"name": "psr/container", "name": "psr/container",
@ -533,16 +533,16 @@
}, },
{ {
"name": "shaarli/netscape-bookmark-parser", "name": "shaarli/netscape-bookmark-parser",
"version": "v2.0.4", "version": "v2.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/shaarli/netscape-bookmark-parser.git", "url": "https://github.com/shaarli/netscape-bookmark-parser.git",
"reference": "81023979c981514f5dda5582e9c0be2ed6688a6b" "reference": "ea6911a0ea3dd372fa7002593c5aef9c15a49315"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/81023979c981514f5dda5582e9c0be2ed6688a6b", "url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/ea6911a0ea3dd372fa7002593c5aef9c15a49315",
"reference": "81023979c981514f5dda5582e9c0be2ed6688a6b", "reference": "ea6911a0ea3dd372fa7002593c5aef9c15a49315",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -584,20 +584,20 @@
"netscape", "netscape",
"parse" "parse"
], ],
"time": "2017-07-30T21:08:03+00:00" "time": "2018-01-30T17:34:48+00:00"
}, },
{ {
"name": "slim/slim", "name": "slim/slim",
"version": "3.8.1", "version": "3.9.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/slimphp/Slim.git", "url": "https://github.com/slimphp/Slim.git",
"reference": "5385302707530b2bccee1769613ad769859b826d" "reference": "4086d0106cf5a7135c69fce4161fe355a8feb118"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/5385302707530b2bccee1769613ad769859b826d", "url": "https://api.github.com/repos/slimphp/Slim/zipball/4086d0106cf5a7135c69fce4161fe355a8feb118",
"reference": "5385302707530b2bccee1769613ad769859b826d", "reference": "4086d0106cf5a7135c69fce4161fe355a8feb118",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -655,7 +655,7 @@
"micro", "micro",
"router" "router"
], ],
"time": "2017-03-19T17:55:20+00:00" "time": "2017-11-26T19:13:09+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@ -715,26 +715,26 @@
}, },
{ {
"name": "pdepend/pdepend", "name": "pdepend/pdepend",
"version": "2.5.0", "version": "2.5.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pdepend/pdepend.git", "url": "https://github.com/pdepend/pdepend.git",
"reference": "0c50874333149c0dad5a2877801aed148f2767ff" "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pdepend/pdepend/zipball/0c50874333149c0dad5a2877801aed148f2767ff", "url": "https://api.github.com/repos/pdepend/pdepend/zipball/9daf26d0368d4a12bed1cacae1a9f3a6f0adf239",
"reference": "0c50874333149c0dad5a2877801aed148f2767ff", "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.7", "php": ">=5.3.7",
"symfony/config": "^2.3.0|^3", "symfony/config": "^2.3.0|^3|^4",
"symfony/dependency-injection": "^2.3.0|^3", "symfony/dependency-injection": "^2.3.0|^3|^4",
"symfony/filesystem": "^2.3.0|^3" "symfony/filesystem": "^2.3.0|^3|^4"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.4.0,<4.8", "phpunit/phpunit": "^4.8|^5.7",
"squizlabs/php_codesniffer": "^2.0.0" "squizlabs/php_codesniffer": "^2.0.0"
}, },
"bin": [ "bin": [
@ -751,7 +751,7 @@
"BSD-3-Clause" "BSD-3-Clause"
], ],
"description": "Official version of pdepend to be handled with Composer", "description": "Official version of pdepend to be handled with Composer",
"time": "2017-01-19T14:23:36+00:00" "time": "2017-12-13T13:21:38+00:00"
}, },
{ {
"name": "phpdocumentor/reflection-common", "name": "phpdocumentor/reflection-common",
@ -967,16 +967,16 @@
}, },
{ {
"name": "phpspec/prophecy", "name": "phpspec/prophecy",
"version": "v1.7.2", "version": "1.7.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpspec/prophecy.git", "url": "https://github.com/phpspec/prophecy.git",
"reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf",
"reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -988,7 +988,7 @@
}, },
"require-dev": { "require-dev": {
"phpspec/phpspec": "^2.5|^3.2", "phpspec/phpspec": "^2.5|^3.2",
"phpunit/phpunit": "^4.8 || ^5.6.5" "phpunit/phpunit": "^4.8.35 || ^5.7"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -1026,7 +1026,7 @@
"spy", "spy",
"stub" "stub"
], ],
"time": "2017-09-04T11:05:03+00:00" "time": "2017-11-24T13:59:53+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@ -1092,16 +1092,16 @@
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
"version": "1.4.2", "version": "1.4.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
"reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1135,7 +1135,7 @@
"filesystem", "filesystem",
"iterator" "iterator"
], ],
"time": "2016-10-03T07:40:28+00:00" "time": "2017-11-27T13:52:08+00:00"
}, },
{ {
"name": "phpunit/php-text-template", "name": "phpunit/php-text-template",
@ -1229,16 +1229,16 @@
}, },
{ {
"name": "phpunit/php-token-stream", "name": "phpunit/php-token-stream",
"version": "1.4.11", "version": "1.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git", "url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
"reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1274,7 +1274,7 @@
"keywords": [ "keywords": [
"tokenizer" "tokenizer"
], ],
"time": "2017-02-27T10:12:30+00:00" "time": "2017-12-04T08:55:13+00:00"
}, },
{ {
"name": "phpunit/phpcov", "name": "phpunit/phpcov",
@ -1691,20 +1691,20 @@
}, },
{ {
"name": "sebastian/finder-facade", "name": "sebastian/finder-facade",
"version": "1.2.1", "version": "1.2.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/finder-facade.git", "url": "https://github.com/sebastianbergmann/finder-facade.git",
"reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9" "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9", "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f",
"reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9", "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"symfony/finder": "~2.3|~3.0", "symfony/finder": "~2.3|~3.0|~4.0",
"theseer/fdomdocument": "~1.3" "theseer/fdomdocument": "~1.3"
}, },
"type": "library", "type": "library",
@ -1726,7 +1726,7 @@
], ],
"description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.",
"homepage": "https://github.com/sebastianbergmann/finder-facade", "homepage": "https://github.com/sebastianbergmann/finder-facade",
"time": "2016-02-17T07:02:23+00:00" "time": "2017-11-18T17:31:49+00:00"
}, },
{ {
"name": "sebastian/global-state", "name": "sebastian/global-state",
@ -1998,30 +1998,30 @@
}, },
{ {
"name": "symfony/config", "name": "symfony/config",
"version": "v3.3.10", "version": "v3.4.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/config.git", "url": "https://github.com/symfony/config.git",
"reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd" "reference": "72689b934d6c6ecf73eca874e98933bf055313c9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/4ab62407bff9cd97c410a7feaef04c375aaa5cfd", "url": "https://api.github.com/repos/symfony/config/zipball/72689b934d6c6ecf73eca874e98933bf055313c9",
"reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd", "reference": "72689b934d6c6ecf73eca874e98933bf055313c9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.5.9|>=7.0.8", "php": "^5.5.9|>=7.0.8",
"symfony/filesystem": "~2.8|~3.0" "symfony/filesystem": "~2.8|~3.0|~4.0"
}, },
"conflict": { "conflict": {
"symfony/dependency-injection": "<3.3", "symfony/dependency-injection": "<3.3",
"symfony/finder": "<3.3" "symfony/finder": "<3.3"
}, },
"require-dev": { "require-dev": {
"symfony/dependency-injection": "~3.3", "symfony/dependency-injection": "~3.3|~4.0",
"symfony/finder": "~3.3", "symfony/finder": "~3.3|~4.0",
"symfony/yaml": "~3.0" "symfony/yaml": "~3.0|~4.0"
}, },
"suggest": { "suggest": {
"symfony/yaml": "To use the yaml reference dumper" "symfony/yaml": "To use the yaml reference dumper"
@ -2029,7 +2029,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.3-dev" "dev-master": "3.4-dev"
} }
}, },
"autoload": { "autoload": {
@ -2056,20 +2056,20 @@
], ],
"description": "Symfony Config Component", "description": "Symfony Config Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-10-04T18:56:58+00:00" "time": "2018-01-21T19:05:02+00:00"
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v2.8.28", "version": "v2.8.34",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853" "reference": "162ca7d0ea597599967aa63b23418e747da0896b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/f81549d2c5fdee8d711c9ab3c7e7362353ea5853", "url": "https://api.github.com/repos/symfony/console/zipball/162ca7d0ea597599967aa63b23418e747da0896b",
"reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853", "reference": "162ca7d0ea597599967aa63b23418e747da0896b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2117,7 +2117,7 @@
], ],
"description": "Symfony Console Component", "description": "Symfony Console Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-10-01T21:00:16+00:00" "time": "2018-01-29T08:54:45+00:00"
}, },
{ {
"name": "symfony/debug", "name": "symfony/debug",
@ -2178,16 +2178,16 @@
}, },
{ {
"name": "symfony/dependency-injection", "name": "symfony/dependency-injection",
"version": "v3.3.10", "version": "v3.3.16",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/dependency-injection.git", "url": "https://github.com/symfony/dependency-injection.git",
"reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1" "reference": "54243abc4e1a1a15e274e391bd6f7090b44711f1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8ebad929aee3ca185b05f55d9cc5521670821ad1", "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/54243abc4e1a1a15e274e391bd6f7090b44711f1",
"reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1", "reference": "54243abc4e1a1a15e274e391bd6f7090b44711f1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2195,7 +2195,7 @@
"psr/container": "^1.0" "psr/container": "^1.0"
}, },
"conflict": { "conflict": {
"symfony/config": "<3.3.1", "symfony/config": "<3.3.7",
"symfony/finder": "<3.3", "symfony/finder": "<3.3",
"symfony/yaml": "<3.3" "symfony/yaml": "<3.3"
}, },
@ -2244,20 +2244,20 @@
], ],
"description": "Symfony DependencyInjection Component", "description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-10-04T17:15:30+00:00" "time": "2018-01-29T09:02:23+00:00"
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v3.3.10", "version": "v3.4.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1" "reference": "e078773ad6354af38169faf31c21df0f18ace03d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/90bc45abf02ae6b7deb43895c1052cb0038506f1", "url": "https://api.github.com/repos/symfony/filesystem/zipball/e078773ad6354af38169faf31c21df0f18ace03d",
"reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1", "reference": "e078773ad6354af38169faf31c21df0f18ace03d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2266,7 +2266,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.3-dev" "dev-master": "3.4-dev"
} }
}, },
"autoload": { "autoload": {
@ -2293,20 +2293,20 @@
], ],
"description": "Symfony Filesystem Component", "description": "Symfony Filesystem Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-10-03T13:33:10+00:00" "time": "2018-01-03T07:37:34+00:00"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v3.3.10", "version": "v3.4.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "773e19a491d97926f236942484cb541560ce862d" "reference": "613e26310776f49a1773b6737c6bd554b8bc8c6f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/773e19a491d97926f236942484cb541560ce862d", "url": "https://api.github.com/repos/symfony/finder/zipball/613e26310776f49a1773b6737c6bd554b8bc8c6f",
"reference": "773e19a491d97926f236942484cb541560ce862d", "reference": "613e26310776f49a1773b6737c6bd554b8bc8c6f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2315,7 +2315,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.3-dev" "dev-master": "3.4-dev"
} }
}, },
"autoload": { "autoload": {
@ -2342,7 +2342,7 @@
], ],
"description": "Symfony Finder Component", "description": "Symfony Finder Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-10-02T06:42:24+00:00" "time": "2018-01-03T07:37:34+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
@ -2405,16 +2405,16 @@
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v3.3.10", "version": "v3.3.16",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46" "reference": "af615970e265543a26ee712c958404eb9b7ac93d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", "url": "https://api.github.com/repos/symfony/yaml/zipball/af615970e265543a26ee712c958404eb9b7ac93d",
"reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", "reference": "af615970e265543a26ee712c958404eb9b7ac93d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2456,7 +2456,7 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-10-05T14:43:42+00:00" "time": "2018-01-20T15:04:53+00:00"
}, },
{ {
"name": "theseer/fdomdocument", "name": "theseer/fdomdocument",
@ -2500,16 +2500,16 @@
}, },
{ {
"name": "webmozart/assert", "name": "webmozart/assert",
"version": "1.2.0", "version": "1.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/webmozart/assert.git", "url": "https://github.com/webmozart/assert.git",
"reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" "reference": "0df1908962e7a3071564e857d86874dad1ef204a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a",
"reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", "reference": "0df1908962e7a3071564e857d86874dad1ef204a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2546,7 +2546,7 @@
"check", "check",
"validate" "validate"
], ],
"time": "2016-11-23T20:04:58+00:00" "time": "2018-01-29T19:49:41+00:00"
} }
], ],
"aliases": [], "aliases": [],

View file

@ -45,6 +45,10 @@ Shaarli cannot import data directly from [Scuttle](https://github.com/scronide/s
However, you can use the third-party [scuttle-to-shaarli](https://github.com/q2apro/scuttle-to-shaarli) However, you can use the third-party [scuttle-to-shaarli](https://github.com/q2apro/scuttle-to-shaarli)
tool to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer. tool to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer.
### Refind
You can use the third-party tool [Derefind](https://github.com/ShawnPConroy/Derefind) to convert refind.com bookmark exports to a format that can be imported into Shaarli.
## Import Shaarli links to Firefox ## Import Shaarli links to Firefox
- Export your Shaarli links as described above. - Export your Shaarli links as described above.

View file

@ -21,7 +21,7 @@ _This bookmarklet button is compatible with Firefox, Opera, Chrome and Safari. U
Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunatly, there is nothing Shaarli can do about it. 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#196). See [#196](https://github.com/shaarli/Shaarli/issues/196).
There is an open bug for both Firefox and Chromium: There is an open bug for both Firefox and Chromium:

View file

@ -14,10 +14,24 @@ Use the `Filter by tags` field to restrict displayed links to entries tagged wit
**Hidden tags:** Tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in. **Hidden tags:** Tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in.
Alternatively you can use the `Tag cloud` to discover all tags and click on any of them to display related links. ### Tag cloud
To search for links that are not tagged, enter `""` in the tag search field. The `Tag cloud` page diplays a "cloud" view of all tags in your Shaarli.
* The most frequently used tags are displayed with a bigger font size.
* When sorting by `Most used` or `Alphabetical`, tags are displayed as a _list_, along with counters and edit/delete buttons for each tag.
* Clicking on any tag will display a list of all Shaares matching this tag.
* Clicking on the counter next to a tag `example`, will filter the tag cloud to only display tags found in Shaares tagged `example`. Repeat this any number of times to further filter the tag cloud. Click `List all links with those tags` to display Shaares matching your current tag filter.
## Filtering RSS feeds/Picture wall ## Filtering RSS feeds/Picture wall
RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS-feeds). RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS-feeds).
## Filter buttons
Filter buttons can be found at the top left of the link list. They allow you to apply different filters to the list:
* **Private links:** When this toggle button is enabled, only shaares set to `private` will be shown.
* **Untagged links:** When the this toggle button is enabled (top left of the link list), only shaares _without any tags_ will be shown in the link list.
Filter buttons are only available when logged in.

View file

@ -1,6 +1,59 @@
_Unofficial but related work on Shaarli. If you maintain one of these, _Unofficial but related work on Shaarli. If you maintain one of these,
please get in touch with us to help us find a way to adapt your work to our fork._ please get in touch with us to help us find a way to adapt your work to our fork._
## Related software
### REST API clients
See [REST API](REST-API) for a list of official and community clients.
### Third party plugins
- [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.
- [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.
- [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.
- [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.
- [twemoji](https://github.com/NerosTie/twemoji) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli (Twemoji version)
- [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support
- [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.
- [markdown-toolbar](https://github.com/immanuelfodor/shaarli-markdown-toolbar) by [@immanuelfodor](https://github.com/immanuelfodor) - Easily insert markdown syntax into the Description field when editing a link.
- [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related links based on the number of identical tags.
- [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.
- [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli
- [shaarli2mastodon](https://github.com/kalvn/shaarli2mastodon) by [@kalvn](https://github.com/kalvn) - This Shaarli plugin allows you to automatically publish links you post on your Mastodon timeline.
- [shaarli-descriptor](https://github.com/immanuelfodor/shaarli-descriptor) by [@immanuelfodor](https://github.com/immanuelfodor) - Customize the default height/number of rows of the Description field when editing a link.
### Third-party themes
See [Theming](Theming) for a list of community-contributed themes, and an installation guide.
### Integration with other platforms
- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [Tiny-Tiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli
- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar
- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle
### Mobile Apps
- [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
### Browser addons
* [Shaarli Web Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli.
### Server apps
- [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content
- [shaarli-river](https://github.com/mknexen/shaarli-river) - An aggregator for shaarlis with many features
- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among French shaarliers: [shaarli.fr](http://shaarli.fr/))
- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis
- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli
- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).
- [Bookmark Archiver](https://github.com/pirate/bookmark-archiver) - Save an archived copy of all websites starred using browser bookmarks/Shaarli/Delicious/Instapaper/Unmark.it/Pocket/Pinboard. Outputs browseable html.
## Alternatives to Shaarli
See [awesome-selfhosted: bookmarks & link sharing](https://github.com/Kickball/awesome-selfhosted/#bookmarks--link-sharing).
## Community ## Community
- [Liens en vrac de sebsauvage](http://sebsauvage.net/links/) - the original Shaarli - [Liens en vrac de sebsauvage](http://sebsauvage.net/links/) - the original Shaarli
- [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html) - [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html)
@ -12,57 +65,8 @@ please get in touch with us to help us find a way to adapt your work to our fork
- [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
- [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni) - [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni)
### Articles and social media discussions ### Articles and social media discussions
- 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176 - 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176
- 2015-08-15 - Reddit - [Question about migrating from WordPress to Shaarli.](https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/) - 2015-08-15 - Reddit - [Question about migrating from WordPress to Shaarli.](https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/)
- 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366 - 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366
- 2015-05-12 - Reddit - [shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)](https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/) - 2015-05-12 - Reddit - [shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)](https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/)
### REST API clients
See [REST API](REST-API) for a list of official and community clients.
### Third party plugins
- [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.
- [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.
- [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.
- [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.
- [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support
- [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.
- [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related links based on the number of identical tags.
- [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.
- [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli
- [shaarli2mastodon](https://github.com/kalvn/shaarli2mastodon) by [@kalvn](https://github.com/kalvn) - This Shaarli plugin allows you to automatically publish links you post on your Mastodon timeline.
### Third-party themes
See [Theming](Theming) for a list of community-contributed themes, and an installation guide.
## Integration with other platforms
- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [Tiny-Tiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli
- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar
- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle
### Mobile Apps
- [ShaarliOS](https://github.com/mro/ShaarliOS) iOS share extension - see [#308](https://github.com/shaarli/Shaarli/issues/308#issuecomment-184592070) for some promo codes,
- [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
### Server apps
- [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content
- [shaarli-river](https://github.com/mknexen/shaarli-river) - An aggregator for shaarlis with many features
- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among French shaarliers: [shaarli.fr](http://shaarli.fr/))
- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis
- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli
- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).
- [Bookmark Archiver](https://github.com/pirate/bookmark-archiver) - Save an archived copy of all websites starred using browser bookmarks/Shaarli/Delicious/Instapaper/Unmark.it/Pocket/Pinboard. Outputs browseable html.
## Alternatives to Shaarli
See the [bookmarks & link sharing](https://github.com/Kickball/awesome-selfhosted/#bookmarks--link-sharing)
section on [awesome-selfhosted](https://github.com/Kickball/awesome-selfhosted/).

View file

@ -15,7 +15,7 @@ Using one of the following methods:
- by downloading full release archives including all dependencies - by downloading full release archives including all dependencies
- by downloading Github archives - by downloading Github archives
- by cloning the Git repository - by cloning the Git repository
- using Docker: [see the documentation](docker/shaarli-images) - using Docker: [see the documentation](docker/shaarli-images.md)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -25,11 +25,11 @@ Using one of the following methods:
In most cases, you should download the latest Shaarli release from the [releases](https://github.com/shaarli/Shaarli/releases) page. **Download our *shaarli-full* archive** to include dependencies. In most cases, you should download the latest Shaarli release from the [releases](https://github.com/shaarli/Shaarli/releases) page. **Download our *shaarli-full* archive** to include dependencies.
The current latest released version is `v0.9.1` The current latest released version is `v0.9.3`
```bash ```bash
$ wget https://github.com/shaarli/Shaarli/releases/download/v0.9.1/shaarli-v0.9.1-full.zip $ wget https://github.com/shaarli/Shaarli/releases/download/v0.9.3/shaarli-v0.9.3-full.zip
$ unzip shaarli-v0.9.1-full.zip $ unzip shaarli-v0.9.3-full.zip
$ mv Shaarli /path/to/shaarli/ $ mv Shaarli /path/to/shaarli/
``` ```

View file

@ -1,3 +1,6 @@
| Note | Firefox Share is no longer available for Firefox 57 and later versions. |
|---------|---------|
### Add Shaarli as a sharing service to Firefox ### Add Shaarli as a sharing service to Firefox
- Open your Shaarli and `Login` - Open your Shaarli and `Login`

View file

@ -35,7 +35,7 @@ Library | Required? | Usage
Extension | Required? | Usage Extension | Required? | Usage
---|:---:|--- ---|:---:|---
[`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS [`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS
[`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows | multibyte (Unicode) string support [`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows, some hosting providers | multibyte (Unicode) string support
[`php-gd`](http://php.net/manual/en/book.image.php) | optional | thumbnail resizing [`php-gd`](http://php.net/manual/en/book.image.php) | optional | thumbnail resizing
[`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`) [`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`)
[`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way [`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way

View file

@ -1,3 +1,6 @@
A brief guide on getting starting using docker is given in [Docker 101](docker-101.md).
To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](../Upgrade-and-migration.md).
## Get and run a Shaarli image ## Get and run a Shaarli image
### DockerHub repository ### DockerHub repository
@ -21,6 +24,7 @@ The `stable` image relies on:
- [PHP5-FPM](http://php-fpm.org/) - [PHP5-FPM](http://php-fpm.org/)
- [Nginx](http://nginx.org/) - [Nginx](http://nginx.org/)
Additional [Dockerfiles](https://github.com/shaarli/Shaarli/tree/master/docker) are provided for the `arm32v7` platform, relying on [Linuxserver.io Alpine armhf images](https://hub.docker.com/r/lsiobase/alpine.armhf/). These images must be built using [`docker build`](https://docs.docker.com/engine/reference/commandline/build/) on an `arm32v7` machine or using an emulator such as [qemu](https://resin.io/blog/building-arm-containers-on-any-x86-machine-even-dockerhub/).
### Download from DockerHub ### Download from DockerHub
```bash ```bash
@ -78,3 +82,14 @@ backstabbing_galileo
$ docker ps -a $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
``` ```
### Automatic builds
Docker users can start a personal instance from an [autobuild image](https://hub.docker.com/r/shaarli/shaarli/). For example to start a temporary Shaarli at ``localhost:8000``, and keep session data (config, storage):
```
MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P)
docker run -ti --rm \
-p 8000:80 \
-v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \
shaarli/shaarli
```

View file

@ -22,20 +22,25 @@ It runs the latest development version of Shaarli and is updated/reset daily.
Login: `demo`; Password: `demo` Login: `demo`; Password: `demo`
Docker users can start a personal instance from an [autobuild image](https://hub.docker.com/r/shaarli/shaarli/). For example to start a temporary Shaarli at ``localhost:8000``, and keep session data (config, storage):
```
MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P)
docker run -ti --rm \
-p 8000:80 \
-v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \
shaarli/shaarli
```
A brief guide on getting starting using docker is given in [Docker 101](docker/docker-101).
To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](Upgrade-and-migration) documentation.
## Features ## Features
Shaarli can be used:
- to share, comment and save interesting links and news.
- to bookmark useful/frequent personal links (as private links) and share them between computers.
- as a minimal blog/microblog/writing platform (no character limit).
- as a read-it-later list (for example items tagged `readlater`).
- to draft and save articles/posts/ideas.
- to keep code snippets.
- to keep notes and documentation.
- as a shared clipboard/notepad/pastebin between machines.
- as a todo list.
- to store playlists (e.g. with the `music` or `video` tags).
- to keep extracts/comments from webpages that may disappear.
- to keep track of ongoing discussions (for example items tagged `discussion`).
- [to feed RSS aggregators](http://shaarli.chassegnouf.net/?9Efeiw) (planets) with specific tags.
- to feed other social networks, blogs... using RSS feeds and external services (dlvr.it, ifttt.com ...).
### Interface ### Interface
- minimalist design (simple is beautiful) - minimalist design (simple is beautiful)
- FAST - FAST
@ -89,14 +94,12 @@ Easily extensible by any client using the REST API exposed by Shaarli.
See the [API documentation](http://shaarli.github.io/api-documentation/). See the [API documentation](http://shaarli.github.io/api-documentation/).
### Other usages ### Using Shaarli as a blog, notepad, pastebin...
Though Shaarli is primarily a bookmarking application, it can serve other purposes - Go to your Shaarli setup and log in
(see [Features](Features)): - Click the `Add Link` button
- To share text only, do not enter any URL in the corresponding input field and click `Add Link`
- micro-blogging - Pick a title and enter your article, or note, in the description field; add a few tags; optionally check `Private` then click `Save`
- pastebin - Voilà! Your article is now published (privately if you selected that option) and accessible using its permalink.
- online notepad
- snippet archive
## About ## About
### Shaarli community fork ### Shaarli community fork

View file

@ -0,0 +1,47 @@
FROM lsiobase/alpine.armhf:3.6
MAINTAINER Shaarli Community
RUN apk --update --no-cache add \
ca-certificates \
curl \
nginx \
php7 \
php7-ctype \
php7-curl \
php7-fpm \
php7-gd \
php7-iconv \
php7-intl \
php7-json \
php7-mbstring \
php7-openssl \
php7-phar \
php7-session \
php7-xml \
php7-zlib \
s6
COPY nginx.conf /etc/nginx/nginx.conf
COPY php-fpm.conf /etc/php7/php-fpm.conf
COPY services.d /etc/services.d
RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \
&& rm -rf /etc/php7/php-fpm.d/www.conf \
&& sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \
&& sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini
WORKDIR /var/www
RUN curl -L https://github.com/shaarli/Shaarli/archive/latest.tar.gz | tar xzf - \
&& mv Shaarli-latest shaarli \
&& cd shaarli \
&& composer --prefer-dist --no-dev install \
&& rm -rf ~/.composer \
&& chown -R nginx:nginx .
VOLUME /var/www/shaarli/data
EXPOSE 80
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
CMD []

View file

@ -0,0 +1,47 @@
FROM lsiobase/alpine.armhf:3.6
MAINTAINER Shaarli Community
RUN apk --update --no-cache add \
ca-certificates \
curl \
nginx \
php7 \
php7-ctype \
php7-curl \
php7-fpm \
php7-gd \
php7-iconv \
php7-intl \
php7-json \
php7-mbstring \
php7-openssl \
php7-phar \
php7-session \
php7-xml \
php7-zlib \
s6
COPY nginx.conf /etc/nginx/nginx.conf
COPY php-fpm.conf /etc/php7/php-fpm.conf
COPY services.d /etc/services.d
RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \
&& rm -rf /etc/php7/php-fpm.d/www.conf \
&& sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \
&& sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini
WORKDIR /var/www
RUN curl -L https://github.com/shaarli/Shaarli/archive/master.tar.gz | tar xzf - \
&& mv Shaarli-master shaarli \
&& cd shaarli \
&& composer --prefer-dist --no-dev install \
&& rm -rf ~/.composer \
&& chown -R nginx:nginx .
VOLUME /var/www/shaarli/data
EXPOSE 80
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
CMD []

View file

@ -124,6 +124,11 @@
$conf = new ConfigManager(); $conf = new ConfigManager();
$sessionManager = new SessionManager($_SESSION, $conf); $sessionManager = new SessionManager($_SESSION, $conf);
// LC_MESSAGES isn't defined without php-intl, in this case use LC_COLLATE locale instead.
if (! defined('LC_MESSAGES')) {
define('LC_MESSAGES', LC_COLLATE);
}
// Sniff browser language and set date format accordingly. // Sniff browser language and set date format accordingly.
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
autoLocale($_SERVER['HTTP_ACCEPT_LANGUAGE']); autoLocale($_SERVER['HTTP_ACCEPT_LANGUAGE']);
@ -436,7 +441,7 @@ function ban_canLogin($conf)
else else
{ {
ban_loginFailed($conf); ban_loginFailed($conf);
$redir = '&username='. $_POST['login']; $redir = '&username='. urlencode($_POST['login']);
if (isset($_GET['post'])) { if (isset($_GET['post'])) {
$redir .= '&post=' . urlencode($_GET['post']); $redir .= '&post=' . urlencode($_GET['post']);
foreach (array('description', 'source', 'title', 'tags') as $param) { foreach (array('description', 'source', 'title', 'tags') as $param) {
@ -1443,18 +1448,12 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager)
// If this is an HTTP(S) link, we try go get the page to extract the title (otherwise we will to straight to the edit form.) // If this is an HTTP(S) link, we try go get the page to extract the title (otherwise we will to straight to the edit form.)
if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) {
// Short timeout to keep the application responsive // Short timeout to keep the application responsive
list($headers, $content) = get_http_response($url, 4); // The callback will fill $charset and $title with data from the downloaded page.
if (strpos($headers[0], '200 OK') !== false) { get_http_response($url, 25, 4194304, get_curl_download_callback($charset, $title));
// Retrieve charset.
$charset = get_charset($headers, $content);
// Extract title.
$title = html_extract_title($content);
// Re-encode title in utf-8 if necessary.
if (! empty($title) && strtolower($charset) != 'utf-8') { if (! empty($title) && strtolower($charset) != 'utf-8') {
$title = mb_convert_encoding($title, 'utf-8', $charset); $title = mb_convert_encoding($title, 'utf-8', $charset);
} }
} }
}
if ($url == '') { if ($url == '') {
$url = '?' . smallHash($linkdate . $LINKSDB->getNextId()); $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());

View file

@ -22,16 +22,15 @@ pages:
- Reverse proxy configuration: docker/reverse-proxy-configuration.md - Reverse proxy configuration: docker/reverse-proxy-configuration.md
- Docker resources: docker/resources.md - Docker resources: docker/resources.md
- Usage: - Usage:
- Features: Features.md
- Bookmarklet: Bookmarklet.md - Bookmarklet: Bookmarklet.md
- Browsing and searching: Browsing-and-searching.md - Browsing and searching: Browsing-and-searching.md
- Firefox share: Firefox-share.md - Firefox share: Firefox-share.md
- RSS feeds: RSS-feeds.md - RSS feeds: RSS-feeds.md
- REST API: REST-API.md - REST API: REST-API.md
- Community & Related software: Community-&-Related-software.md
- How To: - How To:
- Backup, restore, import and export: Backup,-restore,-import-and-export.md - Backup, restore, import and export: Backup,-restore,-import-and-export.md
- Various hacks: Various-hacks.md - Various hacks: Various-hacks.md
- Troubleshooting: Troubleshooting.md
- Development: - Development:
- Development guidelines: Development-guidelines.md - Development guidelines: Development-guidelines.md
- Continuous integration tools: Continuous-integration-tools.md - Continuous integration tools: Continuous-integration-tools.md
@ -47,6 +46,5 @@ pages:
- Theming: Theming.md - Theming: Theming.md
- Unit tests: Unit-tests.md - Unit tests: Unit-tests.md
- Unit tests inside Docker: Unit-tests-Docker.md - Unit tests inside Docker: Unit-tests-Docker.md
- About:
- FAQ: FAQ.md - FAQ: FAQ.md
- Community & Related software: Community-&-Related-software.md - Troubleshooting: Troubleshooting.md

View file

@ -1 +1 @@
<?php /* 0.9.2 */ ?> <?php /* 0.9.5 */ ?>

View file

@ -28,28 +28,14 @@ public function testHtmlExtractNonExistentTitle()
$this->assertFalse(html_extract_title($html)); $this->assertFalse(html_extract_title($html));
} }
/**
* Test get_charset() with all priorities.
*/
public function testGetCharset()
{
$headers = array('Content-Type' => 'text/html; charset=Headers');
$html = '<html><meta>stuff</meta><meta charset="Html"/></html>';
$default = 'default';
$this->assertEquals('headers', get_charset($headers, $html, $default));
$this->assertEquals('html', get_charset(array(), $html, $default));
$this->assertEquals($default, get_charset(array(), '', $default));
$this->assertEquals('utf-8', get_charset(array(), ''));
}
/** /**
* Test headers_extract_charset() when the charset is found. * Test headers_extract_charset() when the charset is found.
*/ */
public function testHeadersExtractExistentCharset() public function testHeadersExtractExistentCharset()
{ {
$charset = 'x-MacCroatian'; $charset = 'x-MacCroatian';
$headers = array('Content-Type' => 'text/html; charset='. $charset); $headers = 'text/html; charset='. $charset;
$this->assertEquals(strtolower($charset), headers_extract_charset($headers)); $this->assertEquals(strtolower($charset), header_extract_charset($headers));
} }
/** /**
@ -57,11 +43,11 @@ public function testHeadersExtractExistentCharset()
*/ */
public function testHeadersExtractNonExistentCharset() public function testHeadersExtractNonExistentCharset()
{ {
$headers = array(); $headers = '';
$this->assertFalse(headers_extract_charset($headers)); $this->assertFalse(header_extract_charset($headers));
$headers = array('Content-Type' => 'text/html'); $headers = 'text/html';
$this->assertFalse(headers_extract_charset($headers)); $this->assertFalse(header_extract_charset($headers));
} }
/** /**
@ -85,6 +71,131 @@ public function testHtmlExtractNonExistentCharset()
$this->assertFalse(html_extract_charset($html)); $this->assertFalse(html_extract_charset($html));
} }
/**
* Test the download callback with valid value
*/
public function testCurlDownloadCallbackOk()
{
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok');
$data = [
'HTTP/1.1 200 OK',
'Server: GitHub.com',
'Date: Sat, 28 Oct 2017 12:01:33 GMT',
'Content-Type: text/html; charset=utf-8',
'Status: 200 OK',
'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea',
'<title>ignored</title>',
];
foreach ($data as $key => $line) {
$ignore = null;
$expected = $key !== 'end' ? strlen($line) : false;
$this->assertEquals($expected, $callback($ignore, $line));
if ($expected === false) {
break;
}
}
$this->assertEquals('utf-8', $charset);
$this->assertEquals('Refactoring · GitHub', $title);
}
/**
* Test the download callback with valid values and no charset
*/
public function testCurlDownloadCallbackOkNoCharset()
{
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset');
$data = [
'HTTP/1.1 200 OK',
'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea',
'<title>ignored</title>',
];
foreach ($data as $key => $line) {
$ignore = null;
$this->assertEquals(strlen($line), $callback($ignore, $line));
}
$this->assertEmpty($charset);
$this->assertEquals('Refactoring · GitHub', $title);
}
/**
* Test the download callback with valid values and no charset
*/
public function testCurlDownloadCallbackOkHtmlCharset()
{
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset');
$data = [
'HTTP/1.1 200 OK',
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />',
'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea',
'<title>ignored</title>',
];
foreach ($data as $key => $line) {
$ignore = null;
$expected = $key !== 'end' ? strlen($line) : false;
$this->assertEquals($expected, $callback($ignore, $line));
if ($expected === false) {
break;
}
}
$this->assertEquals('utf-8', $charset);
$this->assertEquals('Refactoring · GitHub', $title);
}
/**
* Test the download callback with valid values and no title
*/
public function testCurlDownloadCallbackOkNoTitle()
{
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok');
$data = [
'HTTP/1.1 200 OK',
'end' => 'th=device-width">Refactoring · GitHub<link rel="search" type="application/opensea',
'ignored',
];
foreach ($data as $key => $line) {
$ignore = null;
$this->assertEquals(strlen($line), $callback($ignore, $line));
}
$this->assertEquals('utf-8', $charset);
$this->assertEmpty($title);
}
/**
* Test the download callback with an invalid content type.
*/
public function testCurlDownloadCallbackInvalidContentType()
{
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ct_ko');
$ignore = null;
$this->assertFalse($callback($ignore, ''));
$this->assertEmpty($charset);
$this->assertEmpty($title);
}
/**
* Test the download callback with an invalid response code.
*/
public function testCurlDownloadCallbackInvalidResponseCode()
{
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rc_ko');
$ignore = null;
$this->assertFalse($callback($ignore, ''));
$this->assertEmpty($charset);
$this->assertEmpty($title);
}
/**
* Test the download callback with an invalid content type and response code.
*/
public function testCurlDownloadCallbackInvalidContentTypeAndResponseCode()
{
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rs_ct_ko');
$ignore = null;
$this->assertFalse($callback($ignore, ''));
$this->assertEmpty($charset);
$this->assertEmpty($title);
}
/** /**
* Test count_private. * Test count_private.
*/ */
@ -207,3 +318,96 @@ private function getHashtagLink($hashtag, $index = '')
return str_replace('$1', $hashtag, $hashtagLink); return str_replace('$1', $hashtag, $hashtagLink);
} }
} }
// old style mock: PHPUnit doesn't allow function mock
/**
* Returns code 200 or html content type.
*
* @param resource $ch cURL resource
* @param int $type cURL info type
*
* @return int|string 200 or 'text/html'
*/
function ut_curl_getinfo_ok($ch, $type)
{
switch ($type) {
case CURLINFO_RESPONSE_CODE:
return 200;
case CURLINFO_CONTENT_TYPE:
return 'text/html; charset=utf-8';
}
}
/**
* Returns code 200 or html content type without charset.
*
* @param resource $ch cURL resource
* @param int $type cURL info type
*
* @return int|string 200 or 'text/html'
*/
function ut_curl_getinfo_no_charset($ch, $type)
{
switch ($type) {
case CURLINFO_RESPONSE_CODE:
return 200;
case CURLINFO_CONTENT_TYPE:
return 'text/html';
}
}
/**
* Invalid response code.
*
* @param resource $ch cURL resource
* @param int $type cURL info type
*
* @return int|string 404 or 'text/html'
*/
function ut_curl_getinfo_rc_ko($ch, $type)
{
switch ($type) {
case CURLINFO_RESPONSE_CODE:
return 404;
case CURLINFO_CONTENT_TYPE:
return 'text/html; charset=utf-8';
}
}
/**
* Invalid content type.
*
* @param resource $ch cURL resource
* @param int $type cURL info type
*
* @return int|string 200 or 'text/plain'
*/
function ut_curl_getinfo_ct_ko($ch, $type)
{
switch ($type) {
case CURLINFO_RESPONSE_CODE:
return 200;
case CURLINFO_CONTENT_TYPE:
return 'text/plain';
}
}
/**
* Invalid response code and content type.
*
* @param resource $ch cURL resource
* @param int $type cURL info type
*
* @return int|string 404 or 'text/plain'
*/
function ut_curl_getinfo_rs_ct_ko($ch, $type)
{
switch ($type) {
case CURLINFO_RESPONSE_CODE:
return 404;
case CURLINFO_CONTENT_TYPE:
return 'text/plain';
}
}

View file

@ -36,6 +36,20 @@ public function testReadNonExistent()
$this->assertEquals(array(), $this->configIO->read('nope')); $this->assertEquals(array(), $this->configIO->read('nope'));
} }
/**
* Read an empty existent config file -> array with blank default values.
*/
public function testReadEmpty()
{
$dataFile = 'tests/utils/config/emptyConfigPhp.php';
$conf = $this->configIO->read($dataFile);
$this->assertEmpty($conf['login']);
$this->assertEmpty($conf['title']);
$this->assertEmpty($conf['titleLink']);
$this->assertEmpty($conf['config']);
$this->assertEmpty($conf['plugins']);
}
/** /**
* Write a new config file. * Write a new config file.
*/ */

View file

@ -0,0 +1 @@
<?php

View file

@ -1,7 +1,7 @@
<div class="shaarli-menu pure-g" id="shaarli-menu"> <div class="shaarli-menu pure-g" id="shaarli-menu">
<div class="pure-u-lg-0 pure-u-1"> <div class="pure-u-lg-0 pure-u-1">
<div class="pure-menu"> <div class="pure-menu">
<a href="{$titleLink}" class="pure-menu-link"> <a href="{$titleLink}" class="pure-menu-link shaarli-title" id="shaarli-title-mobile">
<img src="img/icon.png" width="16" height="16" class="head-logo" alt="logo" /> <img src="img/icon.png" width="16" height="16" class="head-logo" alt="logo" />
{$shaarlititle} {$shaarlititle}
</a> </a>
@ -12,32 +12,32 @@
<div class="pure-menu menu-transform pure-menu-horizontal pure-g"> <div class="pure-menu menu-transform pure-menu-horizontal pure-g">
<ul class="pure-menu-list pure-u-lg-5-6 pure-u-1"> <ul class="pure-menu-list pure-u-lg-5-6 pure-u-1">
<li class="pure-menu-item pure-u-0 pure-u-lg-visible"> <li class="pure-menu-item pure-u-0 pure-u-lg-visible">
<a href="{$titleLink}" class="pure-menu-link"> <a href="{$titleLink}" class="pure-menu-link shaarli-title" id="shaarli-title-desktop">
<img src="img/icon.png" width="16" height="16" class="head-logo" alt="logo" /> <img src="img/icon.png" width="16" height="16" class="head-logo" alt="logo" />
{$shaarlititle} {$shaarlititle}
</a> </a>
</li> </li>
{if="isLoggedIn() || $openshaarli"} {if="isLoggedIn() || $openshaarli"}
<li class="pure-menu-item"> <li class="pure-menu-item">
<a href="?do=addlink" class="pure-menu-link"> <a href="?do=addlink" class="pure-menu-link" id="shaarli-menu-shaare">
<i class="fa fa-plus" ></i> {'Shaare'|t} <i class="fa fa-plus" ></i> {'Shaare'|t}
</a> </a>
</li> </li>
<li class="pure-menu-item"> <li class="pure-menu-item" id="shaarli-menu-tools">
<a href="?do=tools" class="pure-menu-link">{'Tools'|t}</a> <a href="?do=tools" class="pure-menu-link">{'Tools'|t}</a>
</li> </li>
{/if} {/if}
<li class="pure-menu-item"> <li class="pure-menu-item" id="shaarli-menu-tags">
<a href="?do=tagcloud" class="pure-menu-link">{'Tag cloud'|t}</a> <a href="?do=tagcloud" class="pure-menu-link">{'Tag cloud'|t}</a>
</li> </li>
<li class="pure-menu-item"> <li class="pure-menu-item" id="shaarli-menu-picwall">
<a href="?do=picwall{$searchcrits}" class="pure-menu-link">{'Picture wall'|t}</a> <a href="?do=picwall{$searchcrits}" class="pure-menu-link">{'Picture wall'|t}</a>
</li> </li>
<li class="pure-menu-item"> <li class="pure-menu-item" id="shaarli-menu-daily">
<a href="?do=daily" class="pure-menu-link">{'Daily'|t}</a> <a href="?do=daily" class="pure-menu-link">{'Daily'|t}</a>
</li> </li>
{loop="$plugins_header.buttons_toolbar"} {loop="$plugins_header.buttons_toolbar"}
<li class="pure-menu-item"> <li class="pure-menu-item shaarli-menu-plugin">
<a <a
{$value.attr.class=isset($value.class) ? $value.attr.class . ' pure-menu-link' : 'pure-menu-link'} {$value.attr.class=isset($value.class) ? $value.attr.class . ' pure-menu-link' : 'pure-menu-link'}
{loop="$value.attr"} {loop="$value.attr"}
@ -47,35 +47,35 @@
</a> </a>
</li> </li>
{/loop} {/loop}
<li class="pure-menu-item pure-u-lg-0"> <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> <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a>
</li> </li>
{if="isLoggedIn()"} {if="isLoggedIn()"}
<li class="pure-menu-item pure-u-lg-0"> <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> <a href="?do=logout" class="pure-menu-link">{'Logout'|t}</a>
</li> </li>
{else} {else}
<li class="pure-menu-item pure-u-lg-0"> <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-login">
<a href="?do=login" class="pure-menu-link">{'Login'|t}</a> <a href="?do=login" class="pure-menu-link">{'Login'|t}</a>
</li> </li>
{/if} {/if}
</ul> </ul>
<div class="header-buttons pure-u-lg-1-6 pure-u-0 pure-u-lg-visible"> <div class="header-buttons pure-u-lg-1-6 pure-u-0 pure-u-lg-visible">
<ul class="pure-menu-list"> <ul class="pure-menu-list">
<li class="pure-menu-item"> <li class="pure-menu-item" id="shaarli-menu-desktop-search">
<a href="#" class="pure-menu-link subheader-opener" <a href="#" class="pure-menu-link subheader-opener"
data-open-id="search" data-open-id="search"
id="search-button" title="{'Search'|t}"> id="search-button" title="{'Search'|t}">
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
</a> </a>
</li> </li>
<li class="pure-menu-item"> <li class="pure-menu-item" id="shaarli-menu-desktop-rss">
<a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}"> <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}">
<i class="fa fa-rss"></i> <i class="fa fa-rss"></i>
</a> </a>
</li> </li>
{if="!isLoggedIn()"} {if="!isLoggedIn()"}
<li class="pure-menu-item"> <li class="pure-menu-item" id="shaarli-menu-desktop-login">
<a href="?do=login" class="pure-menu-link" <a href="?do=login" class="pure-menu-link"
data-open-id="header-login-form" data-open-id="header-login-form"
id="login-button" title="{'Login'|t}"> id="login-button" title="{'Login'|t}">
@ -83,7 +83,7 @@
</a> </a>
</li> </li>
{else} {else}
<li class="pure-menu-item"> <li class="pure-menu-item" id="shaarli-menu-desktop-logout">
<a href="?do=logout" class="pure-menu-link" title="{'Logout'|t}"> <a href="?do=logout" class="pure-menu-link" title="{'Logout'|t}">
<i class="fa fa-sign-out"></i> <i class="fa fa-sign-out"></i>
</a> </a>
@ -156,7 +156,7 @@
{/if} {/if}
{if="!empty($plugin_errors) && isLoggedIn()"} {if="!empty($plugin_errors) && isLoggedIn()"}
<div class="pure-g new-version-message pure-alert pure-alert-error pure-alert-closable"> <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-2-24"></div>
<div class="pure-u-20-24"> <div class="pure-u-20-24">
{loop="plugin_errors"} {loop="plugin_errors"}

View file

@ -137,9 +137,9 @@ <h2 class="window-title">{'Plugin configuration'|t}</h2>
{if="count($enabledPlugins)==0"} {if="count($enabledPlugins)==0"}
<p class="center">{'No plugin enabled.'|t}</p> <p class="center">{'No plugin enabled.'|t}</p>
{else} {else}
{$counter=0} {$nbParameters=0}
{loop="$enabledPlugins"} {loop="$enabledPlugins"}
{$counter=$counter+count($value.parameters)} {$nbParameters=$nbParameters+count($value.parameters)}
{if="count($value.parameters) > 0"} {if="count($value.parameters) > 0"}
<div class="plugin_parameters"> <div class="plugin_parameters">
<h3 class="window-subtitle">{function="str_replace('_', ' ', $key)"}</h3> <h3 class="window-subtitle">{function="str_replace('_', ' ', $key)"}</h3>
@ -161,7 +161,7 @@ <h3 class="window-subtitle">{function="str_replace('_', ' ', $key)"}</h3>
</div> </div>
{/if} {/if}
{/loop} {/loop}
{if="$counter===0"} {if="$nbParameters===0"}
<p class="center">{'No parameter available.'|t}</p> <p class="center">{'No parameter available.'|t}</p>
{else} {else}
<div class="center"> <div class="center">