Merge pull request #687 from ArthurHoaro/web-thumb

Use web-thumbnailer to retrieve thumbnails
This commit is contained in:
ArthurHoaro 2018-07-28 09:41:29 +02:00 committed by GitHub
commit ad5f47adba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1495 additions and 769 deletions

View File

@ -1,6 +1,7 @@
<?php <?php
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Thumbnailer;
/** /**
* This class is in charge of building the final page. * This class is in charge of building the final page.
@ -21,11 +22,21 @@ class PageBuilder
*/ */
protected $conf; protected $conf;
/**
* @var array $_SESSION
*/
protected $session;
/** /**
* @var LinkDB $linkDB instance. * @var LinkDB $linkDB instance.
*/ */
protected $linkDB; protected $linkDB;
/**
* @var null|string XSRF token
*/
protected $token;
/** @var bool $isLoggedIn Whether the user is logged in **/ /** @var bool $isLoggedIn Whether the user is logged in **/
protected $isLoggedIn = false; protected $isLoggedIn = false;
@ -33,14 +44,17 @@ class PageBuilder
* PageBuilder constructor. * PageBuilder constructor.
* $tpl is initialized at false for lazy loading. * $tpl is initialized at false for lazy loading.
* *
* @param ConfigManager $conf Configuration Manager instance (reference). * @param ConfigManager $conf Configuration Manager instance (reference).
* @param LinkDB $linkDB instance. * @param array $session $_SESSION array
* @param string $token Session token * @param LinkDB $linkDB instance.
* @param string $token Session token
* @param bool $isLoggedIn
*/ */
public function __construct(&$conf, $linkDB = null, $token = null, $isLoggedIn = false) public function __construct(&$conf, $session, $linkDB = null, $token = null, $isLoggedIn = false)
{ {
$this->tpl = false; $this->tpl = false;
$this->conf = $conf; $this->conf = $conf;
$this->session = $session;
$this->linkDB = $linkDB; $this->linkDB = $linkDB;
$this->token = $token; $this->token = $token;
$this->isLoggedIn = $isLoggedIn; $this->isLoggedIn = $isLoggedIn;
@ -105,6 +119,19 @@ class PageBuilder
if ($this->linkDB !== null) { if ($this->linkDB !== null) {
$this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); $this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
} }
$this->tpl->assign(
'thumbnails_enabled',
$this->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
);
$this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width'));
$this->tpl->assign('thumbnails_height', $this->conf->get('thumbnails.height'));
if (! empty($_SESSION['warnings'])) {
$this->tpl->assign('global_warnings', $_SESSION['warnings']);
unset($_SESSION['warnings']);
}
// To be removed with a proper theme configuration. // To be removed with a proper theme configuration.
$this->tpl->assign('conf', $this->conf); $this->tpl->assign('conf', $this->conf);
} }

View File

@ -7,6 +7,8 @@
*/ */
class Router class Router
{ {
public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update';
public static $PAGE_LOGIN = 'login'; public static $PAGE_LOGIN = 'login';
public static $PAGE_PICWALL = 'picwall'; public static $PAGE_PICWALL = 'picwall';
@ -47,6 +49,8 @@ class Router
public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin'; public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin';
public static $PAGE_THUMBS_UPDATE = 'thumbs_update';
public static $GET_TOKEN = 'token'; public static $GET_TOKEN = 'token';
/** /**
@ -101,6 +105,14 @@ class Router
return self::$PAGE_FEED_RSS; return self::$PAGE_FEED_RSS;
} }
if (startsWith($query, 'do='. self::$PAGE_THUMBS_UPDATE)) {
return self::$PAGE_THUMBS_UPDATE;
}
if (startsWith($query, 'do='. self::$AJAX_THUMB_UPDATE)) {
return self::$AJAX_THUMB_UPDATE;
}
// At this point, only loggedin pages. // At this point, only loggedin pages.
if (!$loggedIn) { if (!$loggedIn) {
return self::$PAGE_LINKLIST; return self::$PAGE_LINKLIST;

127
application/Thumbnailer.php Normal file
View File

@ -0,0 +1,127 @@
<?php
namespace Shaarli;
use Shaarli\Config\ConfigManager;
use WebThumbnailer\Exception\WebThumbnailerException;
use WebThumbnailer\WebThumbnailer;
use WebThumbnailer\Application\ConfigManager as WTConfigManager;
/**
* Class Thumbnailer
*
* Utility class used to retrieve thumbnails using web-thumbnailer dependency.
*/
class Thumbnailer
{
const COMMON_MEDIA_DOMAINS = [
'imgur.com',
'flickr.com',
'youtube.com',
'wikimedia.org',
'redd.it',
'gfycat.com',
'media.giphy.com',
'twitter.com',
'twimg.com',
'instagram.com',
'pinterest.com',
'pinterest.fr',
'tumblr.com',
'deviantart.com',
];
const MODE_ALL = 'all';
const MODE_COMMON = 'common';
const MODE_NONE = 'none';
/**
* @var WebThumbnailer instance.
*/
protected $wt;
/**
* @var ConfigManager instance.
*/
protected $conf;
/**
* Thumbnailer constructor.
*
* @param ConfigManager $conf instance.
*/
public function __construct($conf)
{
$this->conf = $conf;
if (! $this->checkRequirements()) {
$this->conf->set('thumbnails.enabled', false);
$this->conf->write(true);
// TODO: create a proper error handling system able to catch exceptions...
die(t('php-gd extension must be loaded to use thumbnails. Thumbnails are now disabled. Please reload the page.'));
}
$this->wt = new WebThumbnailer();
WTConfigManager::addFile('inc/web-thumbnailer.json');
$this->wt->maxWidth($this->conf->get('thumbnails.width'))
->maxHeight($this->conf->get('thumbnails.height'))
->crop(true)
->debug($this->conf->get('dev.debug', false));
}
/**
* Retrieve a thumbnail for given URL
*
* @param string $url where to look for a thumbnail.
*
* @return bool|string The thumbnail relative cache file path, or false if none has been found.
*/
public function get($url)
{
if ($this->conf->get('thumbnails.mode') === self::MODE_COMMON
&& ! $this->isCommonMediaOrImage($url)
) {
return false;
}
try {
return $this->wt->thumbnail($url);
} catch (WebThumbnailerException $e) {
// Exceptions are only thrown in debug mode.
error_log(get_class($e) . ': ' . $e->getMessage());
}
return false;
}
/**
* We check weather the given URL is from a common media domain,
* or if the file extension is an image.
*
* @param string $url to check
*
* @return bool true if it's an image or from a common media domain, false otherwise.
*/
public function isCommonMediaOrImage($url)
{
foreach (self::COMMON_MEDIA_DOMAINS as $domain) {
if (strpos($url, $domain) !== false) {
return true;
}
}
if (endsWith($url, '.jpg') || endsWith($url, '.png') || endsWith($url, '.jpeg')) {
return true;
}
return false;
}
/**
* Make sure that requirements are match to use thumbnails:
* - php-gd is loaded
*/
protected function checkRequirements()
{
return extension_loaded('gd');
}
}

View File

@ -2,6 +2,7 @@
use Shaarli\Config\ConfigJson; use Shaarli\Config\ConfigJson;
use Shaarli\Config\ConfigPhp; use Shaarli\Config\ConfigPhp;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Thumbnailer;
/** /**
* Class Updater. * Class Updater.
@ -30,6 +31,11 @@ class Updater
*/ */
protected $isLoggedIn; protected $isLoggedIn;
/**
* @var array $_SESSION
*/
protected $session;
/** /**
* @var ReflectionMethod[] List of current class methods. * @var ReflectionMethod[] List of current class methods.
*/ */
@ -42,13 +48,17 @@ class Updater
* @param LinkDB $linkDB LinkDB instance. * @param LinkDB $linkDB LinkDB instance.
* @param ConfigManager $conf Configuration Manager instance. * @param ConfigManager $conf Configuration Manager instance.
* @param boolean $isLoggedIn True if the user is logged in. * @param boolean $isLoggedIn True if the user is logged in.
* @param array $session $_SESSION (by reference)
*
* @throws ReflectionException
*/ */
public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn, &$session = [])
{ {
$this->doneUpdates = $doneUpdates; $this->doneUpdates = $doneUpdates;
$this->linkDB = $linkDB; $this->linkDB = $linkDB;
$this->conf = $conf; $this->conf = $conf;
$this->isLoggedIn = $isLoggedIn; $this->isLoggedIn = $isLoggedIn;
$this->session = &$session;
// Retrieve all update methods. // Retrieve all update methods.
$class = new ReflectionClass($this); $class = new ReflectionClass($this);
@ -480,6 +490,30 @@ class Updater
} }
$this->conf->write($this->isLoggedIn); $this->conf->write($this->isLoggedIn);
return true;
}
/**
* * Move thumbnails management to WebThumbnailer, coming with new settings.
*/
public function updateMethodWebThumbnailer()
{
if ($this->conf->exists('thumbnails.mode')) {
return true;
}
$thumbnailsEnabled = $this->conf->get('thumbnail.enable_thumbnails', true);
$this->conf->set('thumbnails.mode', $thumbnailsEnabled ? Thumbnailer::MODE_ALL : Thumbnailer::MODE_NONE);
$this->conf->set('thumbnails.width', 125);
$this->conf->set('thumbnails.height', 90);
$this->conf->remove('thumbnail');
$this->conf->write(true);
if ($thumbnailsEnabled) {
$this->session['warnings'][] = t(
'You have enabled or changed thumbnails mode. <a href="?do=thumbs_update">Please synchronize them</a>.'
);
}
return true; return true;
} }

View File

@ -147,6 +147,33 @@ class ConfigManager
} }
} }
/**
* Remove a config element from the config file.
*
* @param string $setting Asked setting, keys separated with dots.
* @param bool $write Write the new setting in the config file, default false.
* @param bool $isLoggedIn User login state, default false.
*
* @throws \Exception Invalid
*/
public function remove($setting, $write = false, $isLoggedIn = false)
{
if (empty($setting) || ! is_string($setting)) {
throw new \Exception(t('Invalid setting key parameter. String expected, got: '). gettype($setting));
}
// During the ConfigIO transition, map legacy settings to the new ones.
if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
$setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
}
$settings = explode('.', $setting);
self::removeConfig($settings, $this->loadedConfig);
if ($write) {
$this->write($isLoggedIn);
}
}
/** /**
* Check if a settings exists. * Check if a settings exists.
* *
@ -272,7 +299,7 @@ class ConfigManager
* *
* @param array $settings Ordered array which contains keys to find. * @param array $settings Ordered array which contains keys to find.
* @param mixed $value * @param mixed $value
* @param array $conf Loaded settings, then sub-array. * @param array $conf Loaded settings, then sub-array.
* *
* @return mixed Found setting or NOT_FOUND flag. * @return mixed Found setting or NOT_FOUND flag.
*/ */
@ -289,6 +316,27 @@ class ConfigManager
$conf[$setting] = $value; $conf[$setting] = $value;
} }
/**
* Recursive function which find asked setting in the loaded config and deletes it.
*
* @param array $settings Ordered array which contains keys to find.
* @param array $conf Loaded settings, then sub-array.
*
* @return mixed Found setting or NOT_FOUND flag.
*/
protected static function removeConfig($settings, &$conf)
{
if (!is_array($settings) || count($settings) == 0) {
return self::$NOT_FOUND;
}
$setting = array_shift($settings);
if (count($settings) > 0) {
return self::removeConfig($settings, $conf[$setting]);
}
unset($conf[$setting]);
}
/** /**
* Set a bunch of default values allowing Shaarli to start without a config file. * Set a bunch of default values allowing Shaarli to start without a config file.
*/ */
@ -333,12 +381,12 @@ class ConfigManager
// default state of the 'remember me' checkbox of the login form // default state of the 'remember me' checkbox of the login form
$this->setEmpty('privacy.remember_user_default', true); $this->setEmpty('privacy.remember_user_default', true);
$this->setEmpty('thumbnail.enable_thumbnails', true);
$this->setEmpty('thumbnail.enable_localcache', true);
$this->setEmpty('redirector.url', ''); $this->setEmpty('redirector.url', '');
$this->setEmpty('redirector.encode_url', true); $this->setEmpty('redirector.encode_url', true);
$this->setEmpty('thumbnails.width', '125');
$this->setEmpty('thumbnails.height', '90');
$this->setEmpty('translation.language', 'auto'); $this->setEmpty('translation.language', 'auto');
$this->setEmpty('translation.mode', 'php'); $this->setEmpty('translation.mode', 'php');
$this->setEmpty('translation.extensions', []); $this->setEmpty('translation.extensions', []);

View File

@ -1,10 +0,0 @@
import Blazy from 'blazy';
(() => {
const picwall = document.getElementById('picwall_container');
if (picwall != null) {
// Suppress ESLint error because that's how bLazy works
/* eslint-disable no-new */
new Blazy();
}
})();

View File

@ -0,0 +1,51 @@
/**
* Script used in the thumbnails update page.
*
* It retrieves the list of link IDs to update, and execute AJAX requests
* to update their thumbnails, while updating the progress bar.
*/
/**
* Update the thumbnail of the link with the current i index in ids.
* It contains a recursive call to retrieve the thumb of the next link when it succeed.
* It also update the progress bar and other visual feedback elements.
*
* @param {array} ids List of LinkID to update
* @param {int} i Current index in ids
* @param {object} elements List of DOM element to avoid retrieving them at each iteration
*/
function updateThumb(ids, i, elements) {
const xhr = new XMLHttpRequest();
xhr.open('POST', '?do=ajax_thumb_update');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.responseType = 'json';
xhr.onload = () => {
if (xhr.status !== 200) {
alert(`An error occurred. Return code: ${xhr.status}`);
} else {
const { response } = xhr;
i += 1;
elements.progressBar.style.width = `${(i * 100) / ids.length}%`;
elements.current.innerHTML = i;
elements.title.innerHTML = response.title;
if (response.thumbnail !== false) {
elements.thumbnail.innerHTML = `<img src="${response.thumbnail}">`;
}
if (i < ids.length) {
updateThumb(ids, i, elements);
}
}
};
xhr.send(`id=${ids[i]}`);
}
(() => {
const ids = document.getElementsByName('ids')[0].value.split(',');
const elements = {
progressBar: document.querySelector('.progressbar > div'),
current: document.querySelector('.progress-current'),
thumbnail: document.querySelector('.thumbnail-placeholder'),
title: document.querySelector('.thumbnail-link-title'),
};
updateThumb(ids, 0, elements);
})();

View File

@ -0,0 +1,7 @@
import Blazy from 'blazy';
(() => {
// Suppress ESLint error because that's how bLazy works
/* eslint-disable no-new */
new Blazy();
})();

View File

@ -146,6 +146,17 @@ body,
background-color: $main-green; background-color: $main-green;
} }
.pure-alert-warning {
a {
color: $warning-text;
font-weight: bold;
}
}
.page-single-alert {
margin-top: 100px;
}
.anchor { .anchor {
&:target { &:target {
padding-top: 40px; padding-top: 40px;
@ -625,23 +636,22 @@ body,
} }
.linklist-item { .linklist-item {
position: relative;
margin: 0 0 10px; margin: 0 0 10px;
box-shadow: 1px 1px 3px $light-grey; box-shadow: 1px 1px 3px $light-grey;
background: $almost-white; background: $almost-white;
&.private { &.private {
.linklist-item-title { &::before {
&::before { display: block;
@extend %private-border; position: absolute;
margin-top: 3px; top: 0;
} left: 0;
} z-index: 1;
background: $orange;
.linklist-item-description { width: 2px;
&::before { height: 100%;
@extend %private-border; content: '';
height: 100%;
}
} }
} }
} }
@ -1543,3 +1553,40 @@ form {
.pure-button-shaarli { .pure-button-shaarli {
background-color: $main-green; background-color: $main-green;
} }
.progressbar {
border-radius: 6px;
background-color: $main-green;
padding: 1px;
> div {
border-radius: 10px;
background: repeating-linear-gradient(
-45deg,
$almost-white,
$almost-white 6px,
$background-color 6px,
$background-color 12px
);
width: 0%;
height: 10px;
}
}
.thumbnails-page-container {
.progress-counter {
padding: 10px 0 20px;
}
.thumbnail-placeholder {
margin: 10px auto;
background-color: $light-grey;
}
.thumbnail-link-title {
padding-bottom: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

View File

@ -701,8 +701,8 @@ a.bigbutton, #pageheader a.bigbutton {
position: relative; position: relative;
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
width: 90px; width: 120px;
height: 90px; height: 120px;
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
float: left; float: left;
@ -739,9 +739,9 @@ a.bigbutton, #pageheader a.bigbutton {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 90px; width: 120px;
font-weight: bold; font-weight: bold;
font-size: 8pt; font-size: 9pt;
color: #fff; color: #fff;
text-align: left; text-align: left;
background-color: transparent; background-color: transparent;
@ -1210,3 +1210,43 @@ ul.errors {
width: 13px; width: 13px;
height: 13px; height: 13px;
} }
.thumbnails-update-container {
padding: 20px 0;
width: 50%;
margin: auto;
}
.thumbnails-update-container .thumbnail-placeholder {
background: grey;
margin: auto;
}
.thumbnails-update-container .thumbnail-link-title {
width: 75%;
margin: auto;
padding-bottom: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.progressbar {
border-radius: 6px;
background-color: #111;
padding: 1px;
}
.progressbar > div {
border-radius: 10px;
background: repeating-linear-gradient(
-45deg,
#f5f5f5,
#f5f5f5 6px,
#d0d0d0 6px,
#d0d0d0 12px
);
width: 0%;
height: 10px;
}

View File

@ -19,6 +19,7 @@
"shaarli/netscape-bookmark-parser": "^2.0", "shaarli/netscape-bookmark-parser": "^2.0",
"erusev/parsedown": "^1.6", "erusev/parsedown": "^1.6",
"slim/slim": "^3.0", "slim/slim": "^3.0",
"arthurhoaro/web-thumbnailer": "^1.1",
"pubsubhubbub/publisher": "dev-master", "pubsubhubbub/publisher": "dev-master",
"gettext/gettext": "^4.4" "gettext/gettext": "^4.4"
}, },

338
composer.lock generated
View File

@ -1,11 +1,59 @@
{ {
"_readme": [ "_readme": [
"This file locks the dependencies of your project to a known state", "This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "308a35eab91602fbb449f2c669c445ed", "content-hash": "da7a0c081b61d949154c5d2e5370cbab",
"packages": [ "packages": [
{
"name": "arthurhoaro/web-thumbnailer",
"version": "v1.2.1",
"source": {
"type": "git",
"url": "https://github.com/ArthurHoaro/web-thumbnailer.git",
"reference": "a5a52f69e8e8f3c71fab9649e2a927e2d3f418f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ArthurHoaro/web-thumbnailer/zipball/a5a52f69e8e8f3c71fab9649e2a927e2d3f418f1",
"reference": "a5a52f69e8e8f3c71fab9649e2a927e2d3f418f1",
"shasum": ""
},
"require": {
"php": ">=5.6",
"phpunit/php-text-template": "^1.2"
},
"conflict": {
"phpunit/php-timer": ">=2"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.0",
"phpunit/phpunit": "5.2.*",
"squizlabs/php_codesniffer": "^3.2"
},
"type": "library",
"autoload": {
"psr-0": {
"WebThumbnailer\\": [
"src/",
"tests/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Arthur Hoaro",
"homepage": "http://hoa.ro"
}
],
"description": "PHP library which will retrieve a thumbnail for any given URL",
"time": "2018-07-17T10:21:14+00:00"
},
{ {
"name": "container-interop/container-interop", "name": "container-interop/container-interop",
"version": "1.2.0", "version": "1.2.0",
@ -85,16 +133,16 @@
}, },
{ {
"name": "gettext/gettext", "name": "gettext/gettext",
"version": "v4.4.4", "version": "v4.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/oscarotero/Gettext.git", "url": "https://github.com/oscarotero/Gettext.git",
"reference": "ab5e863de2f60806d02e6e6081e21efd45249168" "reference": "cae84aff39a87e07bd6e5cddb5adb720a0ffa357"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/oscarotero/Gettext/zipball/ab5e863de2f60806d02e6e6081e21efd45249168", "url": "https://api.github.com/repos/oscarotero/Gettext/zipball/cae84aff39a87e07bd6e5cddb5adb720a0ffa357",
"reference": "ab5e863de2f60806d02e6e6081e21efd45249168", "reference": "cae84aff39a87e07bd6e5cddb5adb720a0ffa357",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -103,7 +151,7 @@
}, },
"require-dev": { "require-dev": {
"illuminate/view": "*", "illuminate/view": "*",
"phpunit/phpunit": "^4.8|^5.7", "phpunit/phpunit": "^4.8|^5.7|^6.5",
"squizlabs/php_codesniffer": "^3.0", "squizlabs/php_codesniffer": "^3.0",
"symfony/yaml": "~2", "symfony/yaml": "~2",
"twig/extensions": "*", "twig/extensions": "*",
@ -143,20 +191,20 @@
"po", "po",
"translation" "translation"
], ],
"time": "2018-02-21T18:49:59+00:00" "time": "2018-06-26T16:51:09+00:00"
}, },
{ {
"name": "gettext/languages", "name": "gettext/languages",
"version": "2.3.0", "version": "2.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/mlocati/cldr-to-gettext-plural-rules.git", "url": "https://github.com/mlocati/cldr-to-gettext-plural-rules.git",
"reference": "49c39e51569963cc917a924b489e7025bfb9d8c7" "reference": "1b74377bd0c4cd87e8d72b948f5d8867e23505a5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/49c39e51569963cc917a924b489e7025bfb9d8c7", "url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/1b74377bd0c4cd87e8d72b948f5d8867e23505a5",
"reference": "49c39e51569963cc917a924b489e7025bfb9d8c7", "reference": "1b74377bd0c4cd87e8d72b948f5d8867e23505a5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -204,7 +252,7 @@
"translations", "translations",
"unicode" "unicode"
], ],
"time": "2017-03-23T17:02:28+00:00" "time": "2018-06-21T15:58:36+00:00"
}, },
{ {
"name": "katzgrau/klogger", "name": "katzgrau/klogger",
@ -302,6 +350,47 @@
], ],
"time": "2018-02-13T20:26:39+00:00" "time": "2018-02-13T20:26:39+00:00"
}, },
{
"name": "phpunit/php-text-template",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"time": "2015-06-21T13:50:34+00:00"
},
{ {
"name": "pimple/pimple", "name": "pimple/pimple",
"version": "v3.2.3", "version": "v3.2.3",
@ -504,12 +593,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pubsubhubbub/php-publisher.git", "url": "https://github.com/pubsubhubbub/php-publisher.git",
"reference": "0d224daebd504ab61c22fee4db58f8d1fc18945f" "reference": "5008fc529b057251b48f4d17a10fdb20047ea8f5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/0d224daebd504ab61c22fee4db58f8d1fc18945f", "url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/5008fc529b057251b48f4d17a10fdb20047ea8f5",
"reference": "0d224daebd504ab61c22fee4db58f8d1fc18945f", "reference": "5008fc529b057251b48f4d17a10fdb20047ea8f5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -539,7 +628,7 @@
"publishers", "publishers",
"pubsubhubbub" "pubsubhubbub"
], ],
"time": "2017-10-08T10:59:41+00:00" "time": "2018-05-22T11:56:26+00:00"
}, },
{ {
"name": "shaarli/netscape-bookmark-parser", "name": "shaarli/netscape-bookmark-parser",
@ -598,16 +687,16 @@
}, },
{ {
"name": "slim/slim", "name": "slim/slim",
"version": "3.9.2", "version": "3.10.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/slimphp/Slim.git", "url": "https://github.com/slimphp/Slim.git",
"reference": "4086d0106cf5a7135c69fce4161fe355a8feb118" "reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/4086d0106cf5a7135c69fce4161fe355a8feb118", "url": "https://api.github.com/repos/slimphp/Slim/zipball/d8aabeacc3688b25e2f2dd2db91df91ec6fdd748",
"reference": "4086d0106cf5a7135c69fce4161fe355a8feb118", "reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -665,7 +754,7 @@
"micro", "micro",
"router" "router"
], ],
"time": "2017-11-26T19:13:09+00:00" "time": "2018-04-19T19:29:08+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@ -1022,23 +1111,23 @@
}, },
{ {
"name": "phpspec/prophecy", "name": "phpspec/prophecy",
"version": "1.7.5", "version": "1.7.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpspec/prophecy.git", "url": "https://github.com/phpspec/prophecy.git",
"reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
"reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/instantiator": "^1.0.2", "doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0", "php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
"sebastian/comparator": "^1.1|^2.0", "sebastian/comparator": "^1.1|^2.0|^3.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0" "sebastian/recursion-context": "^1.0|^2.0|^3.0"
}, },
"require-dev": { "require-dev": {
@ -1081,7 +1170,7 @@
"spy", "spy",
"stub" "stub"
], ],
"time": "2018-02-19T10:16:54+00:00" "time": "2018-04-18T13:57:24+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@ -1193,47 +1282,6 @@
], ],
"time": "2017-11-27T13:52:08+00:00" "time": "2017-11-27T13:52:08+00:00"
}, },
{
"name": "phpunit/php-text-template",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"time": "2015-06-21T13:50:34+00:00"
},
{ {
"name": "phpunit/php-timer", "name": "phpunit/php-timer",
"version": "1.0.9", "version": "1.0.9",
@ -2207,21 +2255,22 @@
}, },
{ {
"name": "symfony/config", "name": "symfony/config",
"version": "v3.4.6", "version": "v3.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/config.git", "url": "https://github.com/symfony/config.git",
"reference": "05e10567b529476a006b00746c5f538f1636810e" "reference": "1fffdeb349ff36a25184e5564c25289b1dbfc402"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/05e10567b529476a006b00746c5f538f1636810e", "url": "https://api.github.com/repos/symfony/config/zipball/1fffdeb349ff36a25184e5564c25289b1dbfc402",
"reference": "05e10567b529476a006b00746c5f538f1636810e", "reference": "1fffdeb349ff36a25184e5564c25289b1dbfc402",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.5.9|>=7.0.8", "php": "^5.5.9|>=7.0.8",
"symfony/filesystem": "~2.8|~3.0|~4.0" "symfony/filesystem": "~2.8|~3.0|~4.0",
"symfony/polyfill-ctype": "~1.8"
}, },
"conflict": { "conflict": {
"symfony/dependency-injection": "<3.3", "symfony/dependency-injection": "<3.3",
@ -2266,20 +2315,20 @@
], ],
"description": "Symfony Config Component", "description": "Symfony Config Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-02-14T10:03:57+00:00" "time": "2018-06-19T14:02:58+00:00"
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v3.4.6", "version": "v3.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "067339e9b8ec30d5f19f5950208893ff026b94f7" "reference": "1b97071a26d028c9bd4588264e101e14f6e7cd00"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/067339e9b8ec30d5f19f5950208893ff026b94f7", "url": "https://api.github.com/repos/symfony/console/zipball/1b97071a26d028c9bd4588264e101e14f6e7cd00",
"reference": "067339e9b8ec30d5f19f5950208893ff026b94f7", "reference": "1b97071a26d028c9bd4588264e101e14f6e7cd00",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2300,7 +2349,7 @@
"symfony/process": "~3.3|~4.0" "symfony/process": "~3.3|~4.0"
}, },
"suggest": { "suggest": {
"psr/log": "For using the console logger", "psr/log-implementation": "For using the console logger",
"symfony/event-dispatcher": "", "symfony/event-dispatcher": "",
"symfony/lock": "", "symfony/lock": "",
"symfony/process": "" "symfony/process": ""
@ -2335,20 +2384,20 @@
], ],
"description": "Symfony Console Component", "description": "Symfony Console Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-02-26T15:46:28+00:00" "time": "2018-05-23T05:02:55+00:00"
}, },
{ {
"name": "symfony/debug", "name": "symfony/debug",
"version": "v3.4.6", "version": "v3.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/debug.git", "url": "https://github.com/symfony/debug.git",
"reference": "9b1071f86e79e1999b3d3675d2e0e7684268b9bc" "reference": "47e6788c5b151cf0cfdf3329116bf33800632d75"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/9b1071f86e79e1999b3d3675d2e0e7684268b9bc", "url": "https://api.github.com/repos/symfony/debug/zipball/47e6788c5b151cf0cfdf3329116bf33800632d75",
"reference": "9b1071f86e79e1999b3d3675d2e0e7684268b9bc", "reference": "47e6788c5b151cf0cfdf3329116bf33800632d75",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2391,20 +2440,20 @@
], ],
"description": "Symfony Debug Component", "description": "Symfony Debug Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-02-28T21:49:22+00:00" "time": "2018-06-25T11:10:40+00:00"
}, },
{ {
"name": "symfony/dependency-injection", "name": "symfony/dependency-injection",
"version": "v3.4.6", "version": "v3.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/dependency-injection.git", "url": "https://github.com/symfony/dependency-injection.git",
"reference": "12e901abc1cb0d637a0e5abe9923471361d96b07" "reference": "a0be80e3f8c11aca506e250c00bb100c04c35d10"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/12e901abc1cb0d637a0e5abe9923471361d96b07", "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a0be80e3f8c11aca506e250c00bb100c04c35d10",
"reference": "12e901abc1cb0d637a0e5abe9923471361d96b07", "reference": "a0be80e3f8c11aca506e250c00bb100c04c35d10",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2462,24 +2511,25 @@
], ],
"description": "Symfony DependencyInjection Component", "description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-03-04T03:54:53+00:00" "time": "2018-06-25T08:36:56+00:00"
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v3.4.6", "version": "v3.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "253a4490b528597aa14d2bf5aeded6f5e5e4a541" "reference": "8a721a5f2553c6c3482b1c5b22ed60fe94dd63ed"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/253a4490b528597aa14d2bf5aeded6f5e5e4a541", "url": "https://api.github.com/repos/symfony/filesystem/zipball/8a721a5f2553c6c3482b1c5b22ed60fe94dd63ed",
"reference": "253a4490b528597aa14d2bf5aeded6f5e5e4a541", "reference": "8a721a5f2553c6c3482b1c5b22ed60fe94dd63ed",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.5.9|>=7.0.8" "php": "^5.5.9|>=7.0.8",
"symfony/polyfill-ctype": "~1.8"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -2511,20 +2561,20 @@
], ],
"description": "Symfony Filesystem Component", "description": "Symfony Filesystem Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-02-22T10:48:49+00:00" "time": "2018-06-21T11:10:19+00:00"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v3.4.6", "version": "v3.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "a479817ce0a9e4adfd7d39c6407c95d97c254625" "reference": "3a8c3de91d2b2c68cd2d665cf9d00f7ef9eaa394"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/a479817ce0a9e4adfd7d39c6407c95d97c254625", "url": "https://api.github.com/repos/symfony/finder/zipball/3a8c3de91d2b2c68cd2d665cf9d00f7ef9eaa394",
"reference": "a479817ce0a9e4adfd7d39c6407c95d97c254625", "reference": "3a8c3de91d2b2c68cd2d665cf9d00f7ef9eaa394",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2560,20 +2610,75 @@
], ],
"description": "Symfony Finder Component", "description": "Symfony Finder Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-03-05T18:28:11+00:00" "time": "2018-06-19T20:52:10+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-ctype",
"version": "v1.7.0", "version": "v1.8.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae",
"reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
},
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"time": "2018-04-30T19:57:29+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "3296adf6a6454a050679cde90f95350ad604b171"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171",
"reference": "3296adf6a6454a050679cde90f95350ad604b171",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2585,7 +2690,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.7-dev" "dev-master": "1.8-dev"
} }
}, },
"autoload": { "autoload": {
@ -2619,24 +2724,25 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2018-01-30T19:27:44+00:00" "time": "2018-04-26T10:06:28+00:00"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v3.4.6", "version": "v3.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "6af42631dcf89e9c616242c900d6c52bd53bd1bb" "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/6af42631dcf89e9c616242c900d6c52bd53bd1bb", "url": "https://api.github.com/repos/symfony/yaml/zipball/c5010cc1692ce1fa328b1fb666961eb3d4a85bb0",
"reference": "6af42631dcf89e9c616242c900d6c52bd53bd1bb", "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.5.9|>=7.0.8" "php": "^5.5.9|>=7.0.8",
"symfony/polyfill-ctype": "~1.8"
}, },
"conflict": { "conflict": {
"symfony/console": "<3.4" "symfony/console": "<3.4"
@ -2677,7 +2783,7 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-02-16T09:50:28+00:00" "time": "2018-05-03T23:18:14+00:00"
}, },
{ {
"name": "theseer/fdomdocument", "name": "theseer/fdomdocument",

18
doc/md/Link-structure.md Normal file
View File

@ -0,0 +1,18 @@
## Link structure
Every link available through the `LinkDB` object is represented as an array
containing the following fields:
* `id` (integer): Unique identifier.
* `title` (string): Title of the link.
* `url` (string): URL of the link. Used for displayable links (without redirector, url encoding, etc.).
Can be absolute or relative for Notes.
* `real_url` (string): Real destination URL, can be redirected, encoded, etc.
* `shorturl` (string): Permalink small hash.
* `description` (string): Link text description.
* `private` (boolean): whether the link is private or not.
* `tags` (string): all link tags separated by a single space
* `thumbnail` (string|boolean): relative path of the thumbnail cache file, or false if there isn't any.
* `created` (DateTime): link creation date time.
* `updated` (DateTime): last modification date time.

View File

@ -29,7 +29,7 @@ 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, some hosting providers | 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 | required to use thumbnails
[`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
[`php-gettext`](http://php.net/manual/en/book.gettext.php) | optional | Use the translation system in gettext mode (faster) [`php-gettext`](http://php.net/manual/en/book.gettext.php) | optional | Use the translation system in gettext mode (faster)

View File

@ -1,15 +1,15 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Shaarli\n" "Project-Id-Version: Shaarli\n"
"POT-Creation-Date: 2018-01-24 18:43+0100\n" "POT-Creation-Date: 2018-07-17 13:04+0200\n"
"PO-Revision-Date: 2018-03-06 18:44+0100\n" "PO-Revision-Date: 2018-07-17 13:07+0200\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Shaarli\n" "Language-Team: Shaarli\n"
"Language: fr_FR\n" "Language: fr_FR\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.6\n" "X-Generator: Poedit 2.0.9\n"
"X-Poedit-Basepath: ../../../..\n" "X-Poedit-Basepath: ../../../..\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SourceCharset: UTF-8\n"
@ -56,7 +56,7 @@ msgstr "Liens directs"
#: application/FeedBuilder.php:153 #: application/FeedBuilder.php:153
#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:178 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:177
msgid "Permalink" msgid "Permalink"
msgstr "Permalien" msgstr "Permalien"
@ -68,18 +68,22 @@ msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture"
msgid "Could not parse history file" msgid "Could not parse history file"
msgstr "Format incorrect pour le fichier d'historique" msgstr "Format incorrect pour le fichier d'historique"
#: application/Languages.php:161 #: application/Languages.php:177
msgid "Automatic" msgid "Automatic"
msgstr "Automatique" msgstr "Automatique"
#: application/Languages.php:162 #: application/Languages.php:178
msgid "English" msgid "English"
msgstr "Anglais" msgstr "Anglais"
#: application/Languages.php:163 #: application/Languages.php:179
msgid "French" msgid "French"
msgstr "Français" msgstr "Français"
#: application/Languages.php:180
msgid "German"
msgstr "Allemand"
#: application/LinkDB.php:136 #: application/LinkDB.php:136
msgid "You are not authorized to add a link." msgid "You are not authorized to add a link."
msgstr "Vous n'êtes pas autorisé à ajouter un lien." msgstr "Vous n'êtes pas autorisé à ajouter un lien."
@ -163,11 +167,11 @@ msgstr ""
"a été importé avec succès en %d secondes : %d liens importés, %d liens " "a été importé avec succès en %d secondes : %d liens importés, %d liens "
"écrasés, %d liens ignorés." "écrasés, %d liens ignorés."
#: application/PageBuilder.php:168 #: application/PageBuilder.php:200
msgid "The page you are trying to reach does not exist or has been deleted." msgid "The page you are trying to reach does not exist or has been deleted."
msgstr "La page que vous essayez de consulter n'existe pas ou a été supprimée." msgstr "La page que vous essayez de consulter n'existe pas ou a été supprimée."
#: application/PageBuilder.php:170 #: application/PageBuilder.php:202
msgid "404 Not Found" msgid "404 Not Found"
msgstr "404 Introuvable" msgstr "404 Introuvable"
@ -176,21 +180,37 @@ msgstr "404 Introuvable"
msgid "Plugin \"%s\" files not found." msgid "Plugin \"%s\" files not found."
msgstr "Les fichiers de l'extension \"%s\" sont introuvables." msgstr "Les fichiers de l'extension \"%s\" sont introuvables."
#: application/Updater.php:76 #: application/Thumbnailer.php:61
msgid ""
"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
"disabled. Please reload the page."
msgstr ""
"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
"disabled. Please reload the page."
#: application/Updater.php:86
msgid "Couldn't retrieve Updater class methods." msgid "Couldn't retrieve Updater class methods."
msgstr "Impossible de récupérer les méthodes de la classe Updater." msgstr "Impossible de récupérer les méthodes de la classe Updater."
#: application/Updater.php:506 #: application/Updater.php:514 index.php:1023
msgid ""
"You have enabled or changed thumbnails mode. <a href=\"?do=thumbs_update"
"\">Please synchronize them</a>."
msgstr ""
"Vous avez activé ou changé le mode de miniatures. <a href=\"?do=thumbs_update"
"\">Merci de les synchroniser</a>."
#: application/Updater.php:566
msgid "An error occurred while running the update " msgid "An error occurred while running the update "
msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour "
#: application/Updater.php:546 #: application/Updater.php:606
msgid "Updates file path is not set, can't write updates." msgid "Updates file path is not set, can't write updates."
msgstr "" msgstr ""
"Le chemin vers le fichier de mise à jour n'est pas défini, impossible " "Le chemin vers le fichier de mise à jour n'est pas défini, impossible "
"d'écrire les mises à jour." "d'écrire les mises à jour."
#: application/Updater.php:551 #: application/Updater.php:611
msgid "Unable to write updates in " msgid "Unable to write updates in "
msgstr "Impossible d'écrire les mises à jour dans " msgstr "Impossible d'écrire les mises à jour dans "
@ -230,6 +250,7 @@ msgstr ""
"Shaarli a les droits d'écriture dans le dossier dans lequel il est installé." "Shaarli a les droits d'écriture dans le dossier dans lequel il est installé."
#: application/config/ConfigManager.php:135 #: application/config/ConfigManager.php:135
#: application/config/ConfigManager.php:162
msgid "Invalid setting key parameter. String expected, got: " msgid "Invalid setting key parameter. String expected, got: "
msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : " msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : "
@ -251,135 +272,133 @@ msgstr "Vous n'êtes pas autorisé à modifier la configuration."
msgid "Error accessing" msgid "Error accessing"
msgstr "Une erreur s'est produite en accédant à" msgstr "Une erreur s'est produite en accédant à"
#: index.php:142 #: index.php:143
msgid "Shared links on " msgid "Shared links on "
msgstr "Liens partagés sur " msgstr "Liens partagés sur "
#: index.php:164 #: index.php:165
msgid "Insufficient permissions:" msgid "Insufficient permissions:"
msgstr "Permissions insuffisantes :" msgstr "Permissions insuffisantes :"
#: index.php:303 #: index.php:201
msgid "I said: NO. You are banned for the moment. Go away." msgid "I said: NO. You are banned for the moment. Go away."
msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard." msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard."
#: index.php:368 #: index.php:273
msgid "Wrong login/password." msgid "Wrong login/password."
msgstr "Nom d'utilisateur ou mot de passe incorrects." msgstr "Nom d'utilisateur ou mot de passe incorrects."
#: index.php:576 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 #: index.php:483 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:42 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:46
msgid "Daily" msgid "Daily"
msgstr "Quotidien" msgstr "Quotidien"
#: index.php:681 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 #: index.php:589 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:95 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:71 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:75
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:95 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:99
msgid "Login" msgid "Login"
msgstr "Connexion" msgstr "Connexion"
#: index.php:722 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39 #: index.php:606 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:39 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:41
msgid "Picture wall" msgid "Picture wall"
msgstr "Mur d'images" msgstr "Mur d'images"
#: index.php:770 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 #: index.php:683 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36
#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
msgid "Tag cloud" msgid "Tag cloud"
msgstr "Nuage de tags" msgstr "Nuage de tags"
#: index.php:803 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 #: index.php:716 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19
msgid "Tag list" msgid "Tag list"
msgstr "Liste des tags" msgstr "Liste des tags"
#: index.php:1028 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 #: index.php:941 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31
msgid "Tools" msgid "Tools"
msgstr "Outils" msgstr "Outils"
#: index.php:1037 #: index.php:950
msgid "You are not supposed to change a password on an Open Shaarli." msgid "You are not supposed to change a password on an Open Shaarli."
msgstr "" msgstr ""
"Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert." "Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert."
#: index.php:1042 index.php:1084 index.php:1162 index.php:1193 index.php:1293 #: index.php:955 index.php:997 index.php:1085 index.php:1116 index.php:1221
msgid "Wrong token." msgid "Wrong token."
msgstr "Jeton invalide." msgstr "Jeton invalide."
#: index.php:1047 #: index.php:960
msgid "The old password is not correct." msgid "The old password is not correct."
msgstr "L'ancien mot de passe est incorrect." msgstr "L'ancien mot de passe est incorrect."
#: index.php:1067 #: index.php:980
msgid "Your password has been changed" msgid "Your password has been changed"
msgstr "Votre mot de passe a été modifié" msgstr "Votre mot de passe a été modifié"
#: index.php:1072 #: index.php:985
#: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
msgid "Change password" msgid "Change password"
msgstr "Modification du mot de passe" msgstr "Modification du mot de passe"
#: index.php:1121 #: index.php:1043
msgid "Configuration was saved." msgid "Configuration was saved."
msgstr "La configuration a été sauvegardé." msgstr "La configuration a été sauvegardé."
#: index.php:1145 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 #: index.php:1068 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
msgid "Configure" msgid "Configure"
msgstr "Configurer" msgstr "Configurer"
#: index.php:1156 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 #: index.php:1079 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
msgid "Manage tags" msgid "Manage tags"
msgstr "Gérer les tags" msgstr "Gérer les tags"
#: index.php:1174 #: index.php:1097
#, php-format #, php-format
msgid "The tag was removed from %d link." msgid "The tag was removed from %d link."
msgid_plural "The tag was removed from %d links." msgid_plural "The tag was removed from %d links."
msgstr[0] "Le tag a été supprimé de %d lien." msgstr[0] "Le tag a été supprimé de %d lien."
msgstr[1] "Le tag a été supprimé de %d liens." msgstr[1] "Le tag a été supprimé de %d liens."
#: index.php:1175 #: index.php:1098
#, php-format #, php-format
msgid "The tag was renamed in %d link." msgid "The tag was renamed in %d link."
msgid_plural "The tag was renamed in %d links." msgid_plural "The tag was renamed in %d links."
msgstr[0] "Le tag a été renommé dans %d lien." msgstr[0] "Le tag a été renommé dans %d lien."
msgstr[1] "Le tag a été renommé dans %d liens." msgstr[1] "Le tag a été renommé dans %d liens."
#: index.php:1183 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 #: index.php:1106 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
msgid "Shaare a new link" msgid "Shaare a new link"
msgstr "Partager un nouveau lien" msgstr "Partager un nouveau lien"
#: index.php:1353 tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 #: index.php:1281 tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
msgid "Edit" msgid "Edit"
msgstr "Modifier" msgstr "Modifier"
#: index.php:1353 index.php:1418 #: index.php:1281 index.php:1351
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26
msgid "Shaare" msgid "Shaare"
msgstr "Shaare" msgstr "Shaare"
#: index.php:1387 #: index.php:1320
msgid "Note: " msgid "Note: "
msgstr "Note : " msgstr "Note : "
#: index.php:1427 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 #: index.php:1360 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
msgid "Export" msgid "Export"
msgstr "Exporter" msgstr "Exporter"
#: index.php:1489 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 #: index.php:1422 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
msgid "Import" msgid "Import"
msgstr "Importer" msgstr "Importer"
#: index.php:1499 #: index.php:1432
#, php-format #, php-format
msgid "" msgid ""
"The file you are trying to upload is probably bigger than what this " "The file you are trying to upload is probably bigger than what this "
@ -389,16 +408,20 @@ msgstr ""
"le serveur web peut accepter (%s). Merci de l'envoyer en parties plus " "le serveur web peut accepter (%s). Merci de l'envoyer en parties plus "
"légères." "légères."
#: index.php:1538 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 #: index.php:1471 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
msgid "Plugin administration" msgid "Plugin administration"
msgstr "Administration des extensions" msgstr "Administration des extensions"
#: index.php:1703 #: index.php:1523 tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
msgid "Thumbnails update"
msgstr "Mise à jour des miniatures"
#: index.php:1695
msgid "Search: " msgid "Search: "
msgstr "Recherche : " msgstr "Recherche : "
#: index.php:1930 #: index.php:1735
#, php-format #, php-format
msgid "" msgid ""
"<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the "
@ -417,7 +440,7 @@ msgstr ""
"cookies. Nous vous recommandons d'accéder à votre serveur depuis son adresse " "cookies. Nous vous recommandons d'accéder à votre serveur depuis son adresse "
"IP ou un <em>Fully Qualified Domain Name</em>.<br>" "IP ou un <em>Fully Qualified Domain Name</em>.<br>"
#: index.php:1940 #: index.php:1745
msgid "Click to try again." msgid "Click to try again."
msgstr "Cliquer ici pour réessayer." msgstr "Cliquer ici pour réessayer."
@ -467,19 +490,19 @@ msgstr ""
msgid "Isso server URL (without 'http://')" msgid "Isso server URL (without 'http://')"
msgstr "URL du serveur Isso (sans 'http://')" msgstr "URL du serveur Isso (sans 'http://')"
#: plugins/markdown/markdown.php:158 #: plugins/markdown/markdown.php:161
msgid "Description will be rendered with" msgid "Description will be rendered with"
msgstr "La description sera générée avec" msgstr "La description sera générée avec"
#: plugins/markdown/markdown.php:159 #: plugins/markdown/markdown.php:162
msgid "Markdown syntax documentation" msgid "Markdown syntax documentation"
msgstr "Documentation sur la syntaxe Markdown" msgstr "Documentation sur la syntaxe Markdown"
#: plugins/markdown/markdown.php:160 #: plugins/markdown/markdown.php:163
msgid "Markdown syntax" msgid "Markdown syntax"
msgstr "la syntaxe Markdown" msgstr "la syntaxe Markdown"
#: plugins/markdown/markdown.php:339 #: plugins/markdown/markdown.php:347
msgid "" msgid ""
"Render shaare description with Markdown syntax.<br><strong>Warning</" "Render shaare description with Markdown syntax.<br><strong>Warning</"
"strong>:\n" "strong>:\n"
@ -577,11 +600,11 @@ msgstr "URL de l'API Wallabag"
msgid "Wallabag API version (1 or 2)" msgid "Wallabag API version (1 or 2)"
msgstr "Version de l'API Wallabag (1 ou 2)" msgstr "Version de l'API Wallabag (1 ou 2)"
#: tests/LanguagesTest.php:188 tests/LanguagesTest.php:201 #: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227
#: tests/languages/fr/LanguagesFrTest.php:160 #: tests/languages/fr/LanguagesFrTest.php:160
#: tests/languages/fr/LanguagesFrTest.php:173 #: tests/languages/fr/LanguagesFrTest.php:173
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:81 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:85
msgid "Search" msgid "Search"
msgid_plural "Search" msgid_plural "Search"
msgstr[0] "Rechercher" msgstr[0] "Rechercher"
@ -625,8 +648,8 @@ msgid "Rename"
msgstr "Renommer" msgstr "Renommer"
#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79 #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:172 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
msgid "Delete" msgid "Delete"
msgstr "Supprimer" msgstr "Supprimer"
@ -736,8 +759,36 @@ msgstr ""
msgid "API secret" msgid "API secret"
msgstr "Clé d'API secrète" msgstr "Clé d'API secrète"
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274 #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:277
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 msgid "Enable thumbnails"
msgstr "Activer les miniatures"
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:281
msgid "You need to enable the extension <code>php-gd</code> to use thumbnails."
msgstr ""
"Vous devez activer l'extension <code>php-gd</code> pour utiliser les "
"miniatures."
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:285
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
msgid "Synchronize thumbnails"
msgstr "Synchroniser les miniatures"
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:296
#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
msgid "All"
msgstr "Tous"
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:300
msgid "Only common media hosts"
msgstr "Seulement les hébergeurs de média connus"
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:304
msgid "None"
msgstr "Aucune"
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:312
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
msgid "Save" msgid "Save"
@ -763,25 +814,27 @@ msgstr "Tous les liens d'un jour sur une page."
msgid "Next day" msgid "Next day"
msgstr "Jour suivant" msgstr "Jour suivant"
#: tpl/editlink.html #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
msgid "Edit Shaare" msgid "Edit Shaare"
msgstr "Modifier le Shaare" msgstr "Modifier le Shaare"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
msgid "New Shaare" msgid "New Shaare"
msgstr "Nouveau Shaare" msgstr "Nouveau Shaare"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23
msgid "Created:" msgid "Created:"
msgstr "Création :" msgstr "Création :"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
msgid "URL" msgid "URL"
msgstr "URL" msgstr "URL"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
msgid "Title" msgid "Title"
msgstr "Titre" msgstr "Titre"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40 #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99
@ -789,17 +842,17 @@ msgstr "Titre"
msgid "Description" msgid "Description"
msgstr "Description" msgstr "Description"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44
msgid "Tags" msgid "Tags"
msgstr "Tags" msgstr "Tags"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59 #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57
#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167
msgid "Private" msgid "Private"
msgstr "Privé" msgstr "Privé"
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
msgid "Apply Changes" msgid "Apply Changes"
msgstr "Appliquer" msgstr "Appliquer"
@ -811,10 +864,6 @@ msgstr "Exporter les données"
msgid "Selection" msgid "Selection"
msgstr "Choisir" msgstr "Choisir"
#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
msgid "All"
msgstr "Tous"
#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
msgid "Public" msgid "Public"
msgstr "Publics" msgstr "Publics"
@ -876,15 +925,15 @@ msgstr ""
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33 #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:151
msgid "Username" msgid "Username"
msgstr "Nom d'utilisateur" msgstr "Nom d'utilisateur"
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:148 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:152
msgid "Password" msgid "Password"
msgstr "Mot de passe" msgstr "Mot de passe"
@ -901,28 +950,28 @@ msgid "Install"
msgstr "Installer" msgstr "Installer"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79
msgid "shaare" msgid "shaare"
msgid_plural "shaares" msgid_plural "shaares"
msgstr[0] "shaare" msgstr[0] "shaare"
msgstr[1] "shaares" msgstr[1] "shaares"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
msgid "private link" msgid "private link"
msgid_plural "private links" msgid_plural "private links"
msgstr[0] "lien privé" msgstr[0] "lien privé"
msgstr[1] "liens privés" msgstr[1] "liens privés"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:117 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:121
msgid "Search text" msgid "Search text"
msgstr "Recherche texte" msgstr "Recherche texte"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:128
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:124 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:128
#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
#: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64 #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64
#: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
@ -930,52 +979,52 @@ msgstr "Recherche texte"
msgid "Filter by tag" msgid "Filter by tag"
msgstr "Filtrer par tag" msgstr "Filtrer par tag"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
msgid "Nothing found." msgid "Nothing found."
msgstr "Aucun résultat." msgstr "Aucun résultat."
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:119 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:118
#, php-format #, php-format
msgid "%s result" msgid "%s result"
msgid_plural "%s results" msgid_plural "%s results"
msgstr[0] "%s résultat" msgstr[0] "%s résultat"
msgstr[1] "%s résultats" msgstr[1] "%s résultats"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
msgid "for" msgid "for"
msgstr "pour" msgstr "pour"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:129
msgid "tagged" msgid "tagged"
msgstr "taggé" msgstr "taggé"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:133
msgid "Remove tag" msgid "Remove tag"
msgstr "Retirer le tag" msgstr "Retirer le tag"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:142
msgid "with status" msgid "with status"
msgstr "avec le statut" msgstr "avec le statut"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153
msgid "without any tag" msgid "without any tag"
msgstr "sans tag" msgstr "sans tag"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:174 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173
#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42 #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42
msgid "Fold" msgid "Fold"
msgstr "Replier" msgstr "Replier"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175
msgid "Edited: " msgid "Edited: "
msgstr "Modifié : " msgstr "Modifié : "
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:180 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179
msgid "permalink" msgid "permalink"
msgstr "permalien" msgstr "permalien"
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181
msgid "Add tag" msgid "Add tag"
msgstr "Ajouter un tag" msgstr "Ajouter un tag"
@ -1021,8 +1070,8 @@ msgstr ""
"réessayer plus tard." "réessayer plus tard."
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:151 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:155
msgid "Remember me" msgid "Remember me"
msgstr "Rester connecté" msgstr "Rester connecté"
@ -1053,35 +1102,52 @@ msgstr "Déplier tout"
msgid "Are you sure you want to delete this link?" msgid "Are you sure you want to delete this link?"
msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?" msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?"
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:90
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:61 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:65
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:86 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:90
msgid "RSS Feed" msgid "RSS Feed"
msgstr "Flux RSS" msgstr "Flux RSS"
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:70
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:66 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:70
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:102 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:106
msgid "Logout" msgid "Logout"
msgstr "Déconnexion" msgstr "Déconnexion"
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:169 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:173
msgid "is available" msgid "is available"
msgstr "est disponible" msgstr "est disponible"
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:180
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:176 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:180
msgid "Error" msgid "Error"
msgstr "Erreur" msgstr "Erreur"
#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
msgid "Picture wall unavailable (thumbnails are disabled)."
msgstr ""
"Le mur d'images n'est pas disponible (les miniatures sont désactivées)."
#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
#, fuzzy
#| msgid ""
#| "You don't have any cached thumbnail. Try to <a href=\"?do=thumbs_update"
#| "\">synchronize them</a>."
msgid ""
"There is no cached thumbnail. Try to <a href=\"?do=thumbs_update"
"\">synchronize them</a>."
msgstr ""
"Il n'y a aucune miniature en cache. Essayer de <a href=\"?do=thumbs_update"
"\">les synchroniser</a>."
#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
msgid "Picture Wall" msgid "Picture Wall"
msgstr "Mur d'images" msgstr "Mur d'images"
#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
msgid "pics" msgid "pics"
msgstr "images" msgstr "images"
@ -1223,7 +1289,11 @@ msgstr ""
msgid "Export database" msgid "Export database"
msgstr "Exporter les données" msgstr "Exporter les données"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:55
msgid "Synchronize all link thumbnails"
msgstr "Synchroniser toutes les miniatures"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81
msgid "" msgid ""
"Drag one of these button to your bookmarks toolbar or right-click it and " "Drag one of these button to your bookmarks toolbar or right-click it and "
"\"Bookmark This Link\"" "\"Bookmark This Link\""
@ -1231,13 +1301,13 @@ msgstr ""
"Glisser un de ces bouttons dans votre barre de favoris ou cliquer droit " "Glisser un de ces bouttons dans votre barre de favoris ou cliquer droit "
"dessus et « Ajouter aux favoris »" "dessus et « Ajouter aux favoris »"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82
msgid "then click on the bookmarklet in any page you want to share." msgid "then click on the bookmarklet in any page you want to share."
msgstr "" msgstr ""
"puis cliquer sur le marque page depuis un site que vous souhaitez partager." "puis cliquer sur le marque page depuis un site que vous souhaitez partager."
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:100 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
msgid "" msgid ""
"Drag this link to your bookmarks toolbar or right-click it and Bookmark This " "Drag this link to your bookmarks toolbar or right-click it and Bookmark This "
"Link" "Link"
@ -1245,31 +1315,31 @@ msgstr ""
"Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « "
"Ajouter aux favoris »" "Ajouter aux favoris »"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
msgid "then click ✚Shaare link button in any page you want to share" msgid "then click ✚Shaare link button in any page you want to share"
msgstr "puis cliquer sur ✚Shaare depuis un site que vous souhaitez partager" msgstr "puis cliquer sur ✚Shaare depuis un site que vous souhaitez partager"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:118
msgid "The selected text is too long, it will be truncated." msgid "The selected text is too long, it will be truncated."
msgstr "Le texte sélectionné est trop long, il sera tronqué." msgstr "Le texte sélectionné est trop long, il sera tronqué."
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
msgid "Shaare link" msgid "Shaare link"
msgstr "Shaare" msgstr "Shaare"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111
msgid "" msgid ""
"Then click ✚Add Note button anytime to start composing a private Note (text " "Then click ✚Add Note button anytime to start composing a private Note (text "
"post) to your Shaarli" "post) to your Shaarli"
msgstr "" msgstr ""
"Puis cliquer sur ✚Add Note pour commencer à rédiger une Note sur Shaarli" "Puis cliquer sur ✚Add Note pour commencer à rédiger une Note sur Shaarli"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:127
msgid "Add Note" msgid "Add Note"
msgstr "Ajouter une Note" msgstr "Ajouter une Note"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:129 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
msgid "" msgid ""
"You need to browse your Shaarli over <strong>HTTPS</strong> to use this " "You need to browse your Shaarli over <strong>HTTPS</strong> to use this "
"functionality." "functionality."
@ -1277,25 +1347,25 @@ msgstr ""
"Vous devez utiliser Shaarli en <strong>HTTPS</strong> pour utiliser cette " "Vous devez utiliser Shaarli en <strong>HTTPS</strong> pour utiliser cette "
"fonctionalité." "fonctionalité."
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
msgid "Add to" msgid "Add to"
msgstr "Ajouter à" msgstr "Ajouter à"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:145 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
msgid "3rd party" msgid "3rd party"
msgstr "Applications tierces" msgstr "Applications tierces"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:157
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:163
msgid "Plugin" msgid "Plugin"
msgstr "Extension" msgstr "Extension"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:158
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164
msgid "plugin" msgid "plugin"
msgstr "extension" msgstr "extension"
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175 #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:191
msgid "" msgid ""
"Drag this link to your bookmarks toolbar, or right-click it and choose " "Drag this link to your bookmarks toolbar, or right-click it and choose "
"Bookmark This Link" "Bookmark This Link"
@ -1303,6 +1373,26 @@ msgstr ""
"Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « "
"Ajouter aux favoris »" "Ajouter aux favoris »"
#, fuzzy
#~| msgid "Enable thumbnails"
#~ msgid "Synchonize thumbnails"
#~ msgstr "Activer les miniatures"
#~ msgid "Warning: "
#~ msgstr "Attention : "
#~ msgid ""
#~ "It's recommended to visit the picture wall after enabling this feature."
#~ msgstr ""
#~ "Il est recommandé de visiter le Mur d'images après avoir activé cette "
#~ "fonctionnalité."
#~ msgid ""
#~ "If you have a large database, the first retrieval may take a few minutes."
#~ msgstr ""
#~ "Si vous avez beaucoup de liens, la première récupération peut prendre "
#~ "plusieurs minutes."
#, fuzzy #, fuzzy
#~| msgid "Change" #~| msgid "Change"
#~ msgid "range" #~ msgid "range"

13
inc/web-thumbnailer.json Normal file
View File

@ -0,0 +1,13 @@
{
"settings": {
"default": {
"download_mode": "DOWNLOAD",
"_comment": "infinite cache",
"cache_duration": -1,
"timeout": 10
},
"path": {
"cache": "cache/"
}
}
}

527
index.php
View File

@ -75,11 +75,12 @@ require_once 'application/Utils.php';
require_once 'application/PluginManager.php'; require_once 'application/PluginManager.php';
require_once 'application/Router.php'; require_once 'application/Router.php';
require_once 'application/Updater.php'; require_once 'application/Updater.php';
use \Shaarli\Languages;
use \Shaarli\ThemeUtils;
use \Shaarli\Config\ConfigManager; use \Shaarli\Config\ConfigManager;
use \Shaarli\Languages;
use \Shaarli\Security\LoginManager; use \Shaarli\Security\LoginManager;
use \Shaarli\Security\SessionManager; use \Shaarli\Security\SessionManager;
use \Shaarli\ThemeUtils;
use \Shaarli\Thumbnailer;
// Ensure the PHP version is supported // Ensure the PHP version is supported
try { try {
@ -513,7 +514,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
read_updates_file($conf->get('resource.updates')), read_updates_file($conf->get('resource.updates')),
$LINKSDB, $LINKSDB,
$conf, $conf,
$loginManager->isLoggedIn() $loginManager->isLoggedIn(),
$_SESSION
); );
try { try {
$newUpdates = $updater->update(); $newUpdates = $updater->update();
@ -528,7 +530,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
die($e->getMessage()); die($e->getMessage());
} }
$PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken(), $loginManager->isLoggedIn()); $PAGE = new PageBuilder($conf, $_SESSION, $LINKSDB, $sessionManager->generateToken(), $loginManager->isLoggedIn());
$PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('linkcount', count($LINKSDB));
$PAGE->assign('privateLinkcount', count_private($LINKSDB)); $PAGE->assign('privateLinkcount', count_private($LINKSDB));
$PAGE->assign('plugin_errors', $pluginManager->getErrors()); $PAGE->assign('plugin_errors', $pluginManager->getErrors());
@ -601,19 +603,23 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
// -------- Picture wall // -------- Picture wall
if ($targetPage == Router::$PAGE_PICWALL) if ($targetPage == Router::$PAGE_PICWALL)
{ {
$PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli'));
if (! $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) {
$PAGE->assign('linksToDisplay', []);
$PAGE->renderPage('picwall');
exit;
}
// Optionally filter the results: // Optionally filter the results:
$links = $LINKSDB->filterSearch($_GET); $links = $LINKSDB->filterSearch($_GET);
$linksToDisplay = array(); $linksToDisplay = array();
// Get only links which have a thumbnail. // Get only links which have a thumbnail.
foreach($links as $link) // Note: we do not retrieve thumbnails here, the request is too heavy.
foreach($links as $key => $link)
{ {
$permalink='?'.$link['shorturl']; if (isset($link['thumbnail']) && $link['thumbnail'] !== false) {
$thumb=lazyThumbnail($conf, $link['url'],$permalink); $linksToDisplay[] = $link; // Add to array.
if ($thumb!='') // Only output links which have a thumbnail.
{
$link['thumbnail']=$thumb; // Thumbnail HTML code.
$linksToDisplay[]=$link; // Add to array.
} }
} }
@ -626,7 +632,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$PAGE->assign($key, $value); $PAGE->assign($key, $value);
} }
$PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli'));
$PAGE->renderPage('picwall'); $PAGE->renderPage('picwall');
exit; exit;
} }
@ -1009,6 +1015,16 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$conf->set('api.secret', escape($_POST['apiSecret'])); $conf->set('api.secret', escape($_POST['apiSecret']));
$conf->set('translation.language', escape($_POST['language'])); $conf->set('translation.language', escape($_POST['language']));
$thumbnailsMode = extension_loaded('gd') ? $_POST['enableThumbnails'] : Thumbnailer::MODE_NONE;
if ($thumbnailsMode !== Thumbnailer::MODE_NONE
&& $thumbnailsMode !== $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)
) {
$_SESSION['warnings'][] = t(
'You have enabled or changed thumbnails mode. <a href="?do=thumbs_update">Please synchronize them</a>.'
);
}
$conf->set('thumbnails.mode', $thumbnailsMode);
try { try {
$conf->write($loginManager->isLoggedIn()); $conf->write($loginManager->isLoggedIn());
$history->updateSettings(); $history->updateSettings();
@ -1047,6 +1063,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$PAGE->assign('api_secret', $conf->get('api.secret')); $PAGE->assign('api_secret', $conf->get('api.secret'));
$PAGE->assign('languages', Languages::getAvailableLanguages()); $PAGE->assign('languages', Languages::getAvailableLanguages());
$PAGE->assign('language', $conf->get('translation.language')); $PAGE->assign('language', $conf->get('translation.language'));
$PAGE->assign('gd_enabled', extension_loaded('gd'));
$PAGE->assign('thumbnails_mode', $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE));
$PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli')); $PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli'));
$PAGE->renderPage('configure'); $PAGE->renderPage('configure');
exit; exit;
@ -1148,6 +1166,11 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$link['title'] = $link['url']; $link['title'] = $link['url'];
} }
if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE) {
$thumbnailer = new Thumbnailer($conf);
$link['thumbnail'] = $thumbnailer->get($url);
}
$pluginManager->executeHooks('save_link', $link); $pluginManager->executeHooks('save_link', $link);
$LINKSDB[$id] = $link; $LINKSDB[$id] = $link;
@ -1486,6 +1509,43 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
exit; exit;
} }
// -------- Thumbnails Update
if ($targetPage == Router::$PAGE_THUMBS_UPDATE) {
$ids = [];
foreach ($LINKSDB as $link) {
// A note or not HTTP(S)
if ($link['url'][0] === '?' || ! startsWith(strtolower($link['url']), 'http')) {
continue;
}
$ids[] = $link['id'];
}
$PAGE->assign('ids', $ids);
$PAGE->assign('pagetitle', t('Thumbnails update') .' - '. $conf->get('general.title', 'Shaarli'));
$PAGE->renderPage('thumbnails');
exit;
}
// -------- Single Thumbnail Update
if ($targetPage == Router::$AJAX_THUMB_UPDATE) {
if (! isset($_POST['id']) || ! ctype_digit($_POST['id'])) {
http_response_code(400);
exit;
}
$id = (int) $_POST['id'];
if (empty($LINKSDB[$id])) {
http_response_code(404);
exit;
}
$thumbnailer = new Thumbnailer($conf);
$link = $LINKSDB[$id];
$link['thumbnail'] = $thumbnailer->get($link['url']);
$LINKSDB[$id] = $link;
$LINKSDB->save($conf->get('resource.page_cache'));
echo json_encode($link);
exit;
}
// -------- Otherwise, simply display search form and links: // -------- Otherwise, simply display search form and links:
showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager); showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
exit; exit;
@ -1549,6 +1609,12 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
// Start index. // Start index.
$i = ($page-1) * $_SESSION['LINKS_PER_PAGE']; $i = ($page-1) * $_SESSION['LINKS_PER_PAGE'];
$end = $i + $_SESSION['LINKS_PER_PAGE']; $end = $i + $_SESSION['LINKS_PER_PAGE'];
$thumbnailsEnabled = $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE;
if ($thumbnailsEnabled) {
$thumbnailer = new Thumbnailer($conf);
}
$linkDisp = array(); $linkDisp = array();
while ($i<$end && $i<count($keys)) while ($i<$end && $i<count($keys))
{ {
@ -1569,9 +1635,21 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
$taglist = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY); $taglist = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY);
uasort($taglist, 'strcasecmp'); uasort($taglist, 'strcasecmp');
$link['taglist'] = $taglist; $link['taglist'] = $taglist;
// Thumbnails enabled, not a note,
// and (never retrieved yet or no valid cache file)
if ($thumbnailsEnabled && $link['url'][0] != '?'
&& (! isset($link['thumbnail']) || ($link['thumbnail'] !== false && ! is_file($link['thumbnail'])))
) {
$elem = $LINKSDB[$keys[$i]];
$elem['thumbnail'] = $thumbnailer->get($link['url']);
$LINKSDB[$keys[$i]] = $elem;
$updateDB = true;
$link['thumbnail'] = $elem['thumbnail'];
}
// Check for both signs of a note: starting with ? and 7 chars long. // Check for both signs of a note: starting with ? and 7 chars long.
if ($link['url'][0] === '?' && if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
strlen($link['url']) === 7) {
$link['url'] = index_url($_SERVER) . $link['url']; $link['url'] = index_url($_SERVER) . $link['url'];
} }
@ -1579,6 +1657,11 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
$i++; $i++;
} }
// If we retrieved new thumbnails, we update the database.
if (!empty($updateDB)) {
$LINKSDB->save($conf->get('resource.page_cache'));
}
// Compute paging navigation // Compute paging navigation
$searchtagsUrl = $searchtags === '' ? '' : '&searchtags=' . urlencode($searchtags); $searchtagsUrl = $searchtags === '' ? '' : '&searchtags=' . urlencode($searchtags);
$searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm); $searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm);
@ -1629,194 +1712,6 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
return; return;
} }
/**
* Compute the thumbnail for a link.
*
* With a link to the original URL.
* Understands various services (youtube.com...)
* Input: $url = URL for which the thumbnail must be found.
* $href = if provided, this URL will be followed instead of $url
* Returns an associative array with thumbnail attributes (src,href,width,height,style,alt)
* Some of them may be missing.
* Return an empty array if no thumbnail available.
*
* @param ConfigManager $conf Configuration Manager instance.
* @param string $url
* @param string|bool $href
*
* @return array
*/
function computeThumbnail($conf, $url, $href = false)
{
if (!$conf->get('thumbnail.enable_thumbnails')) return array();
if ($href==false) $href=$url;
// For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link.
// (e.g. http://www.youtube.com/watch?v=spVypYk4kto ---> http://img.youtube.com/vi/spVypYk4kto/default.jpg )
// ^^^^^^^^^^^ ^^^^^^^^^^^
$domain = parse_url($url,PHP_URL_HOST);
if ($domain=='youtube.com' || $domain=='www.youtube.com')
{
parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract video ID and get thumbnail
if (!empty($params['v'])) return array('src'=>'https://img.youtube.com/vi/'.$params['v'].'/default.jpg',
'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail');
}
if ($domain=='youtu.be') // Youtube short links
{
$path = parse_url($url,PHP_URL_PATH);
return array('src'=>'https://img.youtube.com/vi'.$path.'/default.jpg',
'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail');
}
if ($domain=='pix.toile-libre.org') // pix.toile-libre.org image hosting
{
parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract image filename.
if (!empty($params) && !empty($params['img'])) return array('src'=>'http://pix.toile-libre.org/upload/thumb/'.urlencode($params['img']),
'href'=>$href,'style'=>'max-width:120px; max-height:150px','alt'=>'pix.toile-libre.org thumbnail');
}
if ($domain=='imgur.com')
{
$path = parse_url($url,PHP_URL_PATH);
if (startsWith($path,'/a/')) return array(); // Thumbnails for albums are not available.
if (startsWith($path,'/r/')) return array('src'=>'https://i.imgur.com/'.basename($path).'s.jpg',
'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
if (startsWith($path,'/gallery/')) return array('src'=>'https://i.imgur.com'.substr($path,8).'s.jpg',
'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
if (substr_count($path,'/')==1) return array('src'=>'https://i.imgur.com/'.substr($path,1).'s.jpg',
'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
}
if ($domain=='i.imgur.com')
{
$pi = pathinfo(parse_url($url,PHP_URL_PATH));
if (!empty($pi['filename'])) return array('src'=>'https://i.imgur.com/'.$pi['filename'].'s.jpg',
'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
}
if ($domain=='dailymotion.com' || $domain=='www.dailymotion.com')
{
if (strpos($url,'dailymotion.com/video/')!==false)
{
$thumburl=str_replace('dailymotion.com/video/','dailymotion.com/thumbnail/video/',$url);
return array('src'=>$thumburl,
'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'DailyMotion thumbnail');
}
}
if (endsWith($domain,'.imageshack.us'))
{
$ext=strtolower(pathinfo($url,PATHINFO_EXTENSION));
if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
{
$thumburl = substr($url,0,strlen($url)-strlen($ext)).'th.'.$ext;
return array('src'=>$thumburl,
'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'imageshack.us thumbnail');
}
}
// Some other hosts are SLOW AS HELL and usually require an extra HTTP request to get the thumbnail URL.
// So we deport the thumbnail generation in order not to slow down page generation
// (and we also cache the thumbnail)
if (! $conf->get('thumbnail.enable_localcache')) return array(); // If local cache is disabled, no thumbnails for services which require the use a local cache.
if ($domain=='flickr.com' || endsWith($domain,'.flickr.com')
|| $domain=='vimeo.com'
|| $domain=='ted.com' || endsWith($domain,'.ted.com')
|| $domain=='xkcd.com' || endsWith($domain,'.xkcd.com')
)
{
if ($domain=='vimeo.com')
{ // Make sure this vimeo URL points to a video (/xxx... where xxx is numeric)
$path = parse_url($url,PHP_URL_PATH);
if (!preg_match('!/\d+.+?!',$path)) return array(); // This is not a single video URL.
}
if ($domain=='xkcd.com' || endsWith($domain,'.xkcd.com'))
{ // Make sure this URL points to a single comic (/xxx... where xxx is numeric)
$path = parse_url($url,PHP_URL_PATH);
if (!preg_match('!/\d+.+?!',$path)) return array();
}
if ($domain=='ted.com' || endsWith($domain,'.ted.com'))
{ // Make sure this TED URL points to a video (/talks/...)
$path = parse_url($url,PHP_URL_PATH);
if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL.
}
$sign = hash_hmac('sha256', $url, $conf->get('credentials.salt')); // We use the salt to sign data (it's random, secret, and specific to each installation)
return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
}
// For all other, we try to make a thumbnail of links ending with .jpg/jpeg/png/gif
// Technically speaking, we should download ALL links and check their Content-Type to see if they are images.
// But using the extension will do.
$ext=strtolower(pathinfo($url,PATHINFO_EXTENSION));
if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
{
$sign = hash_hmac('sha256', $url, $conf->get('credentials.salt')); // We use the salt to sign data (it's random, secret, and specific to each installation)
return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
}
return array(); // No thumbnail.
}
// Returns the HTML code to display a thumbnail for a link
// with a link to the original URL.
// Understands various services (youtube.com...)
// Input: $url = URL for which the thumbnail must be found.
// $href = if provided, this URL will be followed instead of $url
// Returns '' if no thumbnail available.
function thumbnail($url,$href=false)
{
// FIXME!
global $conf;
$t = computeThumbnail($conf, $url,$href);
if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
$html='<a href="'.escape($t['href']).'"><img src="'.escape($t['src']).'"';
if (!empty($t['width'])) $html.=' width="'.escape($t['width']).'"';
if (!empty($t['height'])) $html.=' height="'.escape($t['height']).'"';
if (!empty($t['style'])) $html.=' style="'.escape($t['style']).'"';
if (!empty($t['alt'])) $html.=' alt="'.escape($t['alt']).'"';
$html.='></a>';
return $html;
}
// Returns the HTML code to display a thumbnail for a link
// for the picture wall (using lazy image loading)
// Understands various services (youtube.com...)
// Input: $url = URL for which the thumbnail must be found.
// $href = if provided, this URL will be followed instead of $url
// Returns '' if no thumbnail available.
function lazyThumbnail($conf, $url,$href=false)
{
// FIXME!
global $conf;
$t = computeThumbnail($conf, $url,$href);
if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
$html='<a href="'.escape($t['href']).'">';
// Lazy image
$html.='<img class="b-lazy" src="#" data-src="'.escape($t['src']).'"';
if (!empty($t['width'])) $html.=' width="'.escape($t['width']).'"';
if (!empty($t['height'])) $html.=' height="'.escape($t['height']).'"';
if (!empty($t['style'])) $html.=' style="'.escape($t['style']).'"';
if (!empty($t['alt'])) $html.=' alt="'.escape($t['alt']).'"';
$html.='>';
// No-JavaScript fallback.
$html.='<noscript><img src="'.escape($t['src']).'"';
if (!empty($t['width'])) $html.=' width="'.escape($t['width']).'"';
if (!empty($t['height'])) $html.=' height="'.escape($t['height']).'"';
if (!empty($t['style'])) $html.=' style="'.escape($t['style']).'"';
if (!empty($t['alt'])) $html.=' alt="'.escape($t['alt']).'"';
$html.='></noscript></a>';
return $html;
}
/** /**
* Installation * Installation
* This function should NEVER be called if the file data/config.php exists. * This function should NEVER be called if the file data/config.php exists.
@ -1908,7 +1803,7 @@ function install($conf, $sessionManager, $loginManager) {
exit; exit;
} }
$PAGE = new PageBuilder($conf, null, $sessionManager->generateToken()); $PAGE = new PageBuilder($conf, $_SESSION, null, $sessionManager->generateToken());
list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
$PAGE->assign('continents', $continents); $PAGE->assign('continents', $continents);
$PAGE->assign('cities', $cities); $PAGE->assign('cities', $cities);
@ -1917,232 +1812,6 @@ function install($conf, $sessionManager, $loginManager) {
exit; exit;
} }
/**
* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL,
* I have deported the thumbnail URL code generation here, otherwise this would slow down page generation.
* The following function takes the URL a link (e.g. a flickr page) and return the proper thumbnail.
* This function is called by passing the URL:
* http://mywebsite.com/shaarli/?do=genthumbnail&hmac=[HMAC]&url=[URL]
* [URL] is the URL of the link (e.g. a flickr page)
* [HMAC] is the signature for the [URL] (so that these URL cannot be forged).
* The function below will fetch the image from the webservice and store it in the cache.
*
* @param ConfigManager $conf Configuration Manager instance,
*/
function genThumbnail($conf)
{
// Make sure the parameters in the URL were generated by us.
$sign = hash_hmac('sha256', $_GET['url'], $conf->get('credentials.salt'));
if ($sign!=$_GET['hmac']) die('Naughty boy!');
$cacheDir = $conf->get('resource.thumbnails_cache', 'cache');
// Let's see if we don't already have the image for this URL in the cache.
$thumbname=hash('sha1',$_GET['url']).'.jpg';
if (is_file($cacheDir .'/'. $thumbname))
{ // We have the thumbnail, just serve it:
header('Content-Type: image/jpeg');
echo file_get_contents($cacheDir .'/'. $thumbname);
return;
}
// We may also serve a blank image (if service did not respond)
$blankname=hash('sha1',$_GET['url']).'.gif';
if (is_file($cacheDir .'/'. $blankname))
{
header('Content-Type: image/gif');
echo file_get_contents($cacheDir .'/'. $blankname);
return;
}
// Otherwise, generate the thumbnail.
$url = $_GET['url'];
$domain = parse_url($url,PHP_URL_HOST);
if ($domain=='flickr.com' || endsWith($domain,'.flickr.com'))
{
// Crude replacement to handle new flickr domain policy (They prefer www. now)
$url = str_replace('http://flickr.com/','http://www.flickr.com/',$url);
// Is this a link to an image, or to a flickr page ?
$imageurl='';
if (endsWith(parse_url($url, PHP_URL_PATH), '.jpg'))
{ // This is a direct link to an image. e.g. http://farm1.staticflickr.com/5/5921913_ac83ed27bd_o.jpg
preg_match('!(http://farm\d+\.staticflickr\.com/\d+/\d+_\w+_)\w.jpg!',$url,$matches);
if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg';
}
else // This is a flickr page (html)
{
// Get the flickr html page.
list($headers, $content) = get_http_response($url, 20);
if (strpos($headers[0], '200 OK') !== false)
{
// flickr now nicely provides the URL of the thumbnail in each flickr page.
preg_match('!<link rel=\"image_src\" href=\"(.+?)\"!', $content, $matches);
if (!empty($matches[1])) $imageurl=$matches[1];
// In albums (and some other pages), the link rel="image_src" is not provided,
// but flickr provides:
// <meta property="og:image" content="http://farm4.staticflickr.com/3398/3239339068_25d13535ff_z.jpg" />
if ($imageurl=='')
{
preg_match('!<meta property=\"og:image\" content=\"(.+?)\"!', $content, $matches);
if (!empty($matches[1])) $imageurl=$matches[1];
}
}
}
if ($imageurl!='')
{ // Let's download the image.
// Image is 240x120, so 10 seconds to download should be enough.
list($headers, $content) = get_http_response($imageurl, 10);
if (strpos($headers[0], '200 OK') !== false) {
// Save image to cache.
file_put_contents($cacheDir .'/'. $thumbname, $content);
header('Content-Type: image/jpeg');
echo $content;
return;
}
}
}
elseif ($domain=='vimeo.com' )
{
// This is more complex: we have to perform a HTTP request, then parse the result.
// Maybe we should deport this to JavaScript ? Example: http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo/4285098#4285098
$vid = substr(parse_url($url,PHP_URL_PATH),1);
list($headers, $content) = get_http_response('https://vimeo.com/api/v2/video/'.escape($vid).'.php', 5);
if (strpos($headers[0], '200 OK') !== false) {
$t = unserialize($content);
$imageurl = $t[0]['thumbnail_medium'];
// Then we download the image and serve it to our client.
list($headers, $content) = get_http_response($imageurl, 10);
if (strpos($headers[0], '200 OK') !== false) {
// Save image to cache.
file_put_contents($cacheDir .'/'. $thumbname, $content);
header('Content-Type: image/jpeg');
echo $content;
return;
}
}
}
elseif ($domain=='ted.com' || endsWith($domain,'.ted.com'))
{
// The thumbnail for TED talks is located in the <link rel="image_src" [...]> tag on that page
// http://www.ted.com/talks/mikko_hypponen_fighting_viruses_defending_the_net.html
// <link rel="image_src" href="http://images.ted.com/images/ted/28bced335898ba54d4441809c5b1112ffaf36781_389x292.jpg" />
list($headers, $content) = get_http_response($url, 5);
if (strpos($headers[0], '200 OK') !== false) {
// Extract the link to the thumbnail
preg_match('!link rel="image_src" href="(http://images.ted.com/images/ted/.+_\d+x\d+\.jpg)"!', $content, $matches);
if (!empty($matches[1]))
{ // Let's download the image.
$imageurl=$matches[1];
// No control on image size, so wait long enough
list($headers, $content) = get_http_response($imageurl, 20);
if (strpos($headers[0], '200 OK') !== false) {
$filepath = $cacheDir .'/'. $thumbname;
file_put_contents($filepath, $content); // Save image to cache.
if (resizeImage($filepath))
{
header('Content-Type: image/jpeg');
echo file_get_contents($filepath);
return;
}
}
}
}
}
elseif ($domain=='xkcd.com' || endsWith($domain,'.xkcd.com'))
{
// There is no thumbnail available for xkcd comics, so download the whole image and resize it.
// http://xkcd.com/327/
// <img src="http://imgs.xkcd.com/comics/exploits_of_a_mom.png" title="<BLABLA>" alt="<BLABLA>" />
list($headers, $content) = get_http_response($url, 5);
if (strpos($headers[0], '200 OK') !== false) {
// Extract the link to the thumbnail
preg_match('!<img src="(http://imgs.xkcd.com/comics/.*)" title="[^s]!', $content, $matches);
if (!empty($matches[1]))
{ // Let's download the image.
$imageurl=$matches[1];
// No control on image size, so wait long enough
list($headers, $content) = get_http_response($imageurl, 20);
if (strpos($headers[0], '200 OK') !== false) {
$filepath = $cacheDir.'/'.$thumbname;
// Save image to cache.
file_put_contents($filepath, $content);
if (resizeImage($filepath))
{
header('Content-Type: image/jpeg');
echo file_get_contents($filepath);
return;
}
}
}
}
}
else
{
// For all other domains, we try to download the image and make a thumbnail.
// We allow 30 seconds max to download (and downloads are limited to 4 Mb)
list($headers, $content) = get_http_response($url, 30);
if (strpos($headers[0], '200 OK') !== false) {
$filepath = $cacheDir .'/'.$thumbname;
// Save image to cache.
file_put_contents($filepath, $content);
if (resizeImage($filepath))
{
header('Content-Type: image/jpeg');
echo file_get_contents($filepath);
return;
}
}
}
// Otherwise, return an empty image (8x8 transparent gif)
$blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7');
// Also put something in cache so that this URL is not requested twice.
file_put_contents($cacheDir .'/'. $blankname, $blankgif);
header('Content-Type: image/gif');
echo $blankgif;
}
// Make a thumbnail of the image (to width: 120 pixels)
// Returns true if success, false otherwise.
function resizeImage($filepath)
{
if (!function_exists('imagecreatefromjpeg')) return false; // GD not present: no thumbnail possible.
// Trick: some stupid people rename GIF as JPEG... or else.
// So we really try to open each image type whatever the extension is.
$header=file_get_contents($filepath,false,NULL,0,256); // Read first 256 bytes and try to sniff file type.
$im=false;
$i=strpos($header,'GIF8'); if (($i!==false) && ($i==0)) $im = imagecreatefromgif($filepath); // Well this is crude, but it should be enough.
$i=strpos($header,'PNG'); if (($i!==false) && ($i==1)) $im = imagecreatefrompng($filepath);
$i=strpos($header,'JFIF'); if ($i!==false) $im = imagecreatefromjpeg($filepath);
if (!$im) return false; // Unable to open image (corrupted or not an image)
$w = imagesx($im);
$h = imagesy($im);
$ystart = 0; $yheight=$h;
if ($h>$w) { $ystart= ($h/2)-($w/2); $yheight=$w/2; }
$nw = 120; // Desired width
$nh = min(floor(($h*$nw)/$w),120); // Compute new width/height, but maximum 120 pixels height.
// Resize image:
$im2 = imagecreatetruecolor($nw,$nh);
imagecopyresampled($im2, $im, 0, 0, 0, $ystart, $nw, $nh, $w, $yheight);
imageinterlace($im2,true); // For progressive JPEG.
$tempname=$filepath.'_TEMP.jpg';
imagejpeg($im2, $tempname, 90);
imagedestroy($im);
imagedestroy($im2);
unlink($filepath);
rename($tempname,$filepath); // Overwrite original picture with thumbnail.
return true;
}
if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=genthumbnail')) { genThumbnail($conf); exit; } // Thumbnail generation/cache does not need the link database.
if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { showDailyRSS($conf); exit; } if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { showDailyRSS($conf); exit; }
if (!isset($_SESSION['LINKS_PER_PAGE'])) { if (!isset($_SESSION['LINKS_PER_PAGE'])) {
$_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20); $_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20);

View File

@ -39,6 +39,7 @@ pages:
- Continuous integration tools: Continuous-integration-tools.md - Continuous integration tools: Continuous-integration-tools.md
- GnuPG signature: GnuPG-signature.md - GnuPG signature: GnuPG-signature.md
- Directory structure: Directory-structure.md - Directory structure: Directory-structure.md
- Link Structure: Link-structure.md
- 3rd party libraries: 3rd-party-libraries.md - 3rd party libraries: 3rd-party-libraries.md
- Plugin System: Plugin-System.md - Plugin System: Plugin-System.md
- Release Shaarli: Release-Shaarli.md - Release Shaarli: Release-Shaarli.md

114
tests/ThumbnailerTest.php Normal file
View File

@ -0,0 +1,114 @@
<?php
namespace Shaarli;
use PHPUnit\Framework\TestCase;
use Shaarli\Config\ConfigManager;
use WebThumbnailer\Application\ConfigManager as WTConfigManager;
/**
* Class ThumbnailerTest
*
* We only make 1 thumb test because:
*
* 1. the thumbnailer library is itself tested
* 2. we don't want to make too many external requests during the tests
*/
class ThumbnailerTest extends TestCase
{
const WIDTH = 190;
const HEIGHT = 210;
/**
* @var Thumbnailer;
*/
protected $thumbnailer;
/**
* @var ConfigManager
*/
protected $conf;
public function setUp()
{
$this->conf = new ConfigManager('tests/utils/config/configJson');
$this->conf->set('thumbnails.mode', Thumbnailer::MODE_ALL);
$this->conf->set('thumbnails.width', self::WIDTH);
$this->conf->set('thumbnails.height', self::HEIGHT);
$this->conf->set('dev.debug', true);
$this->thumbnailer = new Thumbnailer($this->conf);
// cache files in the sandbox
WTConfigManager::addFile('tests/utils/config/wt.json');
}
public function tearDown()
{
$this->rrmdirContent('sandbox/');
}
/**
* Test a thumbnail with a custom size in 'all' mode.
*/
public function testThumbnailAllValid()
{
$thumb = $this->thumbnailer->get('https://github.com/shaarli/Shaarli/');
$this->assertNotFalse($thumb);
$image = imagecreatefromstring(file_get_contents($thumb));
$this->assertEquals(self::WIDTH, imagesx($image));
$this->assertEquals(self::HEIGHT, imagesy($image));
}
/**
* Test a thumbnail with a custom size in 'common' mode.
*/
public function testThumbnailCommonValid()
{
$this->conf->set('thumbnails.mode', Thumbnailer::MODE_COMMON);
$thumb = $this->thumbnailer->get('https://imgur.com/jlFgGpe');
$this->assertNotFalse($thumb);
$image = imagecreatefromstring(file_get_contents($thumb));
$this->assertEquals(self::WIDTH, imagesx($image));
$this->assertEquals(self::HEIGHT, imagesy($image));
}
/**
* Test a thumbnail in 'common' mode which isn't include in common websites.
*/
public function testThumbnailCommonInvalid()
{
$this->conf->set('thumbnails.mode', Thumbnailer::MODE_COMMON);
$thumb = $this->thumbnailer->get('https://github.com/shaarli/Shaarli/');
$this->assertFalse($thumb);
}
/**
* Test a thumbnail that can't be retrieved.
*/
public function testThumbnailNotValid()
{
$oldlog = ini_get('error_log');
ini_set('error_log', '/dev/null');
$thumbnailer = new Thumbnailer(new ConfigManager());
$thumb = $thumbnailer->get('nope');
$this->assertFalse($thumb);
ini_set('error_log', $oldlog);
}
protected function rrmdirContent($dir) {
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (is_dir($dir."/".$object))
$this->rrmdirContent($dir."/".$object);
else
unlink($dir."/".$object);
}
}
}
}
}

View File

@ -2,6 +2,7 @@
use Shaarli\Config\ConfigJson; use Shaarli\Config\ConfigJson;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Config\ConfigPhp; use Shaarli\Config\ConfigPhp;
use Shaarli\Thumbnailer;
require_once 'tests/Updater/DummyUpdater.php'; require_once 'tests/Updater/DummyUpdater.php';
require_once 'inc/rain.tpl.class.php'; require_once 'inc/rain.tpl.class.php';
@ -20,7 +21,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
/** /**
* @var string Config file path (without extension). * @var string Config file path (without extension).
*/ */
protected static $configFile = 'tests/utils/config/configJson'; protected static $configFile = 'sandbox/config';
/** /**
* @var ConfigManager * @var ConfigManager
@ -32,6 +33,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
*/ */
public function setUp() public function setUp()
{ {
copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php');
$this->conf = new ConfigManager(self::$configFile); $this->conf = new ConfigManager(self::$configFile);
} }
@ -684,4 +686,50 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
$this->assertEquals(4194304, $this->conf->get('general.download_max_size')); $this->assertEquals(4194304, $this->conf->get('general.download_max_size'));
$this->assertEquals(3, $this->conf->get('general.download_timeout')); $this->assertEquals(3, $this->conf->get('general.download_timeout'));
} }
/**
* Test updateMethodWebThumbnailer with thumbnails enabled.
*/
public function testUpdateMethodWebThumbnailerEnabled()
{
$this->conf->remove('thumbnails');
$this->conf->set('thumbnail.enable_thumbnails', true);
$updater = new Updater([], [], $this->conf, true, $_SESSION);
$this->assertTrue($updater->updateMethodWebThumbnailer());
$this->assertFalse($this->conf->exists('thumbnail'));
$this->assertEquals(\Shaarli\Thumbnailer::MODE_ALL, $this->conf->get('thumbnails.mode'));
$this->assertEquals(125, $this->conf->get('thumbnails.width'));
$this->assertEquals(90, $this->conf->get('thumbnails.height'));
$this->assertContains('You have enabled or changed thumbnails', $_SESSION['warnings'][0]);
}
/**
* Test updateMethodWebThumbnailer with thumbnails disabled.
*/
public function testUpdateMethodWebThumbnailerDisabled()
{
$this->conf->remove('thumbnails');
$this->conf->set('thumbnail.enable_thumbnails', false);
$updater = new Updater([], [], $this->conf, true, $_SESSION);
$this->assertTrue($updater->updateMethodWebThumbnailer());
$this->assertFalse($this->conf->exists('thumbnail'));
$this->assertEquals(Thumbnailer::MODE_NONE, $this->conf->get('thumbnails.mode'));
$this->assertEquals(125, $this->conf->get('thumbnails.width'));
$this->assertEquals(90, $this->conf->get('thumbnails.height'));
$this->assertTrue(empty($_SESSION['warnings']));
}
/**
* Test updateMethodWebThumbnailer with thumbnails disabled.
*/
public function testUpdateMethodWebThumbnailerNothingToDo()
{
$updater = new Updater([], [], $this->conf, true, $_SESSION);
$this->assertTrue($updater->updateMethodWebThumbnailer());
$this->assertFalse($this->conf->exists('thumbnail'));
$this->assertEquals(Thumbnailer::MODE_COMMON, $this->conf->get('thumbnails.mode'));
$this->assertEquals(90, $this->conf->get('thumbnails.width'));
$this->assertEquals(53, $this->conf->get('thumbnails.height'));
$this->assertTrue(empty($_SESSION['warnings']));
}
} }

View File

@ -81,6 +81,18 @@ class ConfigManagerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('testSetWriteGetNested', $this->conf->get('foo.bar.key.stuff')); $this->assertEquals('testSetWriteGetNested', $this->conf->get('foo.bar.key.stuff'));
} }
public function testSetDeleteNested()
{
$this->conf->set('foo.bar.key.stuff', 'testSetDeleteNested');
$this->assertTrue($this->conf->exists('foo.bar'));
$this->assertTrue($this->conf->exists('foo.bar.key.stuff'));
$this->assertEquals('testSetDeleteNested', $this->conf->get('foo.bar.key.stuff'));
$this->conf->remove('foo.bar');
$this->assertFalse($this->conf->exists('foo.bar.key.stuff'));
$this->assertFalse($this->conf->exists('foo.bar'));
}
/** /**
* Set with an empty key. * Set with an empty key.
* *
@ -103,6 +115,17 @@ class ConfigManagerTest extends \PHPUnit_Framework_TestCase
$this->conf->set(array('foo' => 'bar'), 'stuff'); $this->conf->set(array('foo' => 'bar'), 'stuff');
} }
/**
* Remove with an empty key.
*
* @expectedException \Exception
* @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*#
*/
public function testRmoveEmptyKey()
{
$this->conf->remove('');
}
/** /**
* Try to write the config without mandatory parameter (e.g. 'login'). * Try to write the config without mandatory parameter (e.g. 'login').
* *

View File

@ -1,35 +1,84 @@
<?php /* <?php /*
{ {
"credentials": { "credentials": {
"login":"root", "login": "root",
"hash":"hash", "hash": "hash",
"salt":"salt" "salt": "salt"
}, },
"security": { "security": {
"session_protection_disabled":false "session_protection_disabled": false,
"ban_after": 4,
"ban_duration": 1800,
"open_shaarli": false,
"allowed_protocols": [
"ftp",
"ftps",
"magnet"
]
}, },
"general": { "general": {
"timezone":"Europe\/Paris", "timezone": "Europe\/Paris",
"title": "Shaarli", "title": "Shaarli",
"header_link": "?" "header_link": "?",
"links_per_page": 20,
"enabled_plugins": [
"qrcode"
],
"default_note_title": "Note: "
}, },
"privacy": { "privacy": {
"default_private_links":true "default_private_links": true,
"hide_public_links": false,
"force_login": false,
"hide_timestamps": false,
"remember_user_default": true
}, },
"redirector": { "redirector": {
"url":"lala" "url": "lala",
"encode_url": true
}, },
"config": { "config": {
"foo": "bar" "foo": "bar"
}, },
"resource": { "resource": {
"datastore": "tests\/utils\/config\/datastore.php", "datastore": "tests\/utils\/config\/datastore.php",
"data_dir": "sandbox/", "data_dir": "sandbox\/",
"raintpl_tpl": "tpl/" "raintpl_tpl": "tpl\/",
"config": "data\/config.php",
"ban_file": "data\/ipbans.php",
"updates": "data\/updates.txt",
"log": "data\/log.txt",
"update_check": "data\/lastupdatecheck.txt",
"history": "data\/history.php",
"theme": "default",
"raintpl_tmp": "tmp\/",
"thumbnails_cache": "cache",
"page_cache": "pagecache"
}, },
"plugins": { "plugins": {
"WALLABAG_VERSION": 1 "WALLABAG_VERSION": 1
},
"dev": {
"debug": true
},
"updates": {
"check_updates": false,
"check_updates_branch": "stable",
"check_updates_interval": 86400
},
"feed": {
"rss_permalinks": true,
"show_atom": true
},
"translation": {
"language": "auto",
"mode": "php",
"extensions": []
},
"thumbnails": {
"mode": "common",
"width": 90,
"height": 53
} }
} }
*/ ?> */ ?>

View File

@ -0,0 +1,12 @@
{
"settings": {
"default": {
"_comment": "infinite cache",
"cache_duration": -1,
"timeout": 10
},
"path": {
"cache": "sandbox/"
}
}
}

View File

@ -242,6 +242,37 @@
</div> </div>
</div> </div>
</div> </div>
<div class="pure-g">
<div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}">
<div class="form-label">
<label for="enableThumbnails">
<span class="label-name">{'Enable thumbnails'|t}</span><br>
<span class="label-desc">
{if="! $gd_enabled"}
{'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t}
{elseif="$thumbnails_enabled"}
<a href="?do=thumbs_update">{'Synchronize thumbnails'|t}</a>
{/if}
</span>
</label>
</div>
</div>
<div class="pure-u-lg-{$ratioInput} pure-u-{$ratioInputMobile}">
<div class="form-input">
<select name="enableThumbnails" id="enableThumbnails" class="align">
<option value="all" {if="$thumbnails_mode=='all'"}selected{/if}>
{'All'|t}
</option>
<option value="common" {if="$thumbnails_mode=='common'"}selected{/if}>
{'Only common media hosts'|t}
</option>
<option value="none" {if="$thumbnails_mode=='none'"}selected{/if}>
{'None'|t}
</option>
</select>
</div>
</div>
</div>
<div class="center"> <div class="center">
<input type="submit" value="{'Save'|t}" name="save"> <input type="submit" value="{'Save'|t}" name="save">
</div> </div>

View File

@ -131,9 +131,17 @@
<div class="linklist-item linklist-item{if="$value.class"} {$value.class}{/if}" data-id="{$value.id}"> <div class="linklist-item linklist-item{if="$value.class"} {$value.class}{/if}" data-id="{$value.id}">
<div class="linklist-item-title"> <div class="linklist-item-title">
{$thumb=thumbnail($value.url)} {if="$thumbnails_enabled && !empty($value.thumbnail)"}
{if="$thumb!=false"} <div class="linklist-item-thumbnail" style="width:{$thumbnails_width}px;height:{$thumbnails_height}px;">
<div class="linklist-item-thumbnail">{$thumb}</div> <div class="thumbnail">
<a href="{$value.real_url}">
{ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
<img data-src="{$value.thumbnail}#" class="b-lazy"
src="#"
alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
</a>
</div>
</div>
{/if} {/if}
{if="$is_logged_in"} {if="$is_logged_in"}
@ -268,5 +276,6 @@
</div> </div>
{include="page.footer"} {include="page.footer"}
<script src="js/thumbnails.min.js?v={$version_hash}"></script>
</body> </body>
</html> </html>

View File

@ -30,9 +30,11 @@
<li class="pure-menu-item" id="shaarli-menu-tags"> <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" id="shaarli-menu-picwall"> {if="$thumbnails_enabled"}
<a href="?do=picwall{$searchcrits}" class="pure-menu-link">{'Picture wall'|t}</a> <li class="pure-menu-item" id="shaarli-menu-picwall">
</li> <a href="?do=picwall{$searchcrits}" class="pure-menu-link">{'Picture wall'|t}</a>
</li>
{/if}
<li class="pure-menu-item" id="shaarli-menu-daily"> <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>
@ -169,4 +171,18 @@
</div> </div>
{/if} {/if}
{if="!empty($global_warnings) && $is_logged_in"}
<div class="pure-g pure-alert pure-alert-warning pure-alert-closable" id="shaarli-warnings-alert">
<div class="pure-u-2-24"></div>
<div class="pure-u-20-24">
{loop="global_warnings"}
<p>{$value}</p>
{/loop}
</div>
<div class="pure-u-2-24">
<i class="fa fa-times pure-alert-close"></i>
</div>
</div>
{/if}
<div class="clear"></div> <div class="clear"></div>

View File

@ -5,41 +5,61 @@
</head> </head>
<body> <body>
{include="page.header"} {include="page.header"}
{if="!$thumbnails_enabled"}
<div class="pure-g"> <div class="pure-g pure-alert pure-alert-warning page-single-alert">
<div class="pure-u-lg-1-6 pure-u-1-24"></div> <div class="pure-u-1 center">
<div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor"> {'Picture wall unavailable (thumbnails are disabled).'|t}
{$countPics=count($linksToDisplay)}
<h2 class="window-title">{'Picture Wall'|t} - {$countPics} {'pics'|t}</h2>
<div id="plugin_zone_start_picwall" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div id="picwall_container" class="picwall-container">
{loop="$linksToDisplay"}
<div class="picwall-pictureframe">
{$value.thumbnail}<a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
{loop="$value.picwall_plugin"}
{$value}
{/loop}
</div>
{/loop}
<div class="clear"></div>
</div>
<div id="plugin_zone_end_picwall" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div> </div>
</div> </div>
{else}
{if="count($linksToDisplay)===0 && $is_logged_in"}
<div class="pure-g pure-alert pure-alert-warning page-single-alert">
<div class="pure-u-1 center">
{'There is no cached thumbnail. Try to <a href="?do=thumbs_update">synchronize them</a>.'|t}
</div>
</div>
{/if}
<div class="pure-g">
<div class="pure-u-lg-1-6 pure-u-1-24"></div>
<div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor">
{$countPics=count($linksToDisplay)}
<h2 class="window-title">{'Picture Wall'|t} - {$countPics} {'pics'|t}</h2>
<div id="plugin_zone_start_picwall" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div id="picwall-container" class="picwall-container">
{loop="$linksToDisplay"}
<div class="picwall-pictureframe">
{ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
<img data-src="{$value.thumbnail}#" class="b-lazy"
src="#"
alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
<a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
{loop="$value.picwall_plugin"}
{$value}
{/loop}
</div>
{/loop}
<div class="clear"></div>
</div>
<div id="plugin_zone_end_picwall" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div>
<div class="pure-u-lg-1-6 pure-u-1-24"></div>
</div>
{/if}
{include="page.footer"} {include="page.footer"}
<script src="js/picwall.min.js?v={$version_hash}"></script> <script src="js/thumbnails.min.js?v={$version_hash}"></script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="pure-g thumbnails-page-container">
<div class="pure-u-lg-1-3 pure-u-1-24"></div>
<div class="pure-u-lg-1-3 pure-u-22-24 page-form page-form-light">
<h2 class="window-title">{'Thumbnails update'|t}</h2>
<div class="pure-g">
<div class="pure-u-lg-1-3 pure-u-1-24"></div>
<div class="pure-u-lg-1-3 pure-u-22-24">
<div class="thumbnail-placeholder" style="width: {$thumbnails_width}px; height: {$thumbnails_height}px;"></div>
</div>
</div>
<div class="pure-g">
<div class="pure-u-1-12"></div>
<div class="pure-u-5-6">
<div class="thumbnail-link-title"></div>
<div class="progressbar">
<div></div>
</div>
</div>
</div>
<div class="pure-g">
<div class="pure-u-lg-1-3 pure-u-1-24"></div>
<div class="pure-u-lg-1-3 pure-u-22-24">
<div class="progress-counter">
<span class="progress-current">0</span> / <span class="progress-total">{$ids|count}</span>
</div>
</div>
</div>
<input type="hidden" name="ids" value="{function="implode($ids, ',')"}" />
</div>
</div>
{include="page.footer"}
<script src="js/thumbnails_update.min.js?v={$version_hash}"></script>
</body>
</html>

View File

@ -45,6 +45,14 @@
</a> </a>
</div> </div>
{if="$thumbnails_enabled"}
<div class="tools-item">
<a href="?do=thumbs_update" title="{'Synchronize all link thumbnails'|t}">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Synchronize thumbnails'|t}</span>
</a>
</div>
{/if}
{loop="$tools_plugin"} {loop="$tools_plugin"}
<div class="tools-item"> <div class="tools-item">
{$value} {$value}

View File

@ -128,6 +128,29 @@
<input type="text" name="apiSecret" id="apiSecret" size="50" value="{$api_secret}" /> <input type="text" name="apiSecret" id="apiSecret" size="50" value="{$api_secret}" />
</td> </td>
</tr> </tr>
<tr>
<td valign="top"><b>Enable thumbnails</b></td>
<td>
<select name="enableThumbnails" id="enableThumbnails" class="align">
<option value="all" {if="$thumbnails_mode=='all'"}selected{/if}>
{'All'|t}
</option>
<option value="common" {if="$thumbnails_mode=='common'"}selected{/if}>
{'Only common media hosts'|t}
</option>
<option value="none" {if="$thumbnails_mode=='none'"}selected{/if}>
{'None'|t}
</option>
</select>
<label for="enableThumbnails">
{if="! $gd_enabled"}
{'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t}
{elseif="$thumbnails_enabled"}
<a href="?do=thumbs_update">{'Synchonize thumbnails'|t}</a>
{/if}
</label>
</td>
</tr>
<tr> <tr>
<td></td> <td></td>

View File

@ -80,7 +80,16 @@
{loop="$links"} {loop="$links"}
<li{if="$value.class"} class="{$value.class}"{/if}> <li{if="$value.class"} class="{$value.class}"{/if}>
<a id="{$value.shorturl}"></a> <a id="{$value.shorturl}"></a>
<div class="thumbnail">{$value.url|thumbnail}</div> {if="$thumbnails_enabled && !empty($value.thumbnail)"}
<div class="thumbnail">
<a href="{$value.real_url}">
{ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
<img data-src="{$value.thumbnail}#" class="b-lazy"
src="#"
alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
</a>
</div>
{/if}
<div class="linkcontainer"> <div class="linkcontainer">
{if="$is_logged_in"} {if="$is_logged_in"}
<div class="linkeditbuttons"> <div class="linkeditbuttons">
@ -145,6 +154,7 @@
</div> </div>
{include="page.footer"} {include="page.footer"}
<script src="js/thumbnails.min.js"></script>
</body> </body>
</html> </html>

View File

@ -15,7 +15,11 @@
<div id="picwall_container"> <div id="picwall_container">
{loop="$linksToDisplay"} {loop="$linksToDisplay"}
<div class="picwall_pictureframe"> <div class="picwall_pictureframe">
{$value.thumbnail}<a href="{$value.real_url}"><span class="info">{$value.title}</span></a> {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
<img data-src="{$value.thumbnail}#" class="b-lazy"
src="#"
alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
<a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
{loop="$value.picwall_plugin"} {loop="$value.picwall_plugin"}
{$value} {$value}
{/loop} {/loop}
@ -34,6 +38,6 @@
{include="page.footer"} {include="page.footer"}
<script src="js/picwall.min.js"></script> <script src="js/thumbnails.min.js"></script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>{include="includes"}</head>
<body>
<div id="pageheader">
{include="page.header"}
</div>
<div class="center thumbnails-update-container">
<div class="thumbnail-placeholder" style="width: {$thumbnails_width}px; height: {$thumbnails_height}px;"></div>
<div class="thumbnail-link-title"></div>
<div class="progressbar">
<div></div>
</div>
<div class="progress-counter">
<span class="progress-current">0</span> / <span class="progress-total">{$ids|count}</span>
</div>
</div>
<input type="hidden" name="ids" value="{function="implode($ids, ',')"}" />
{include="page.footer"}
<script src="js/thumbnails_update.min.js?v={$version_hash}"></script>
</body>
</html>

View File

@ -23,7 +23,8 @@ const extractCssVintage = new ExtractTextPlugin({
module.exports = [ module.exports = [
{ {
entry: { entry: {
picwall: './assets/common/js/picwall.js', thumbnails: './assets/common/js/thumbnails.js',
thumbnails_update: './assets/common/js/thumbnails-update.js',
pluginsadmin: './assets/default/js/plugins-admin.js', pluginsadmin: './assets/default/js/plugins-admin.js',
shaarli: [ shaarli: [
'./assets/default/js/base.js', './assets/default/js/base.js',
@ -96,7 +97,8 @@ module.exports = [
'./assets/vintage/css/reset.css', './assets/vintage/css/reset.css',
'./assets/vintage/css/shaarli.css', './assets/vintage/css/shaarli.css',
].concat(glob.sync('./assets/vintage/img/*')), ].concat(glob.sync('./assets/vintage/img/*')),
picwall: './assets/common/js/picwall.js', thumbnails: './assets/common/js/thumbnails.js',
thumbnails_update: './assets/common/js/thumbnails-update.js',
}, },
output: { output: {
filename: '[name].min.js', filename: '[name].min.js',