Merge branch 'master' of https://github.com/shaarli/Shaarli into myShaarli_commu
This commit is contained in:
commit
22ee4c71a3
108 changed files with 3695 additions and 686 deletions
23
.editorconfig
Normal file
23
.editorconfig
Normal file
|
@ -0,0 +1,23 @@
|
|||
# EditorConfig: http://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{htaccess,html,xml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.php]
|
||||
max_line_length = 100
|
||||
|
||||
[Dockerfile]
|
||||
max_line_length = 80
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -22,8 +22,10 @@ Dockerfile text
|
|||
*.ttf binary
|
||||
*.min.css binary
|
||||
*.min.js binary
|
||||
*.mo binary
|
||||
|
||||
# Exclude from Git archives
|
||||
.editorconfig export-ignore
|
||||
.gitattributes export-ignore
|
||||
.github export-ignore
|
||||
.gitignore export-ignore
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -18,6 +18,7 @@ vendor/
|
|||
# Release archives
|
||||
*.tar.gz
|
||||
*.zip
|
||||
inc/languages/*/LC_MESSAGES/shaarli.mo
|
||||
|
||||
# Development and test resources
|
||||
coverage
|
||||
|
|
|
@ -13,6 +13,8 @@ install:
|
|||
- composer self-update
|
||||
- composer install --prefer-dist
|
||||
- locale -a
|
||||
before_script:
|
||||
- PATH=${PATH//:\.\/node_modules\/\.bin/}
|
||||
script:
|
||||
- make clean
|
||||
- make check_permissions
|
||||
|
|
|
@ -40,7 +40,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||
|
||||
### Security
|
||||
|
||||
- Vulnerability introduced in v0.9.1 fixed.
|
||||
- Fixed reflected XSS vulnerability introduced in v0.9.1, discovered by @chb9 ([CVE-2017-15215](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15215)).
|
||||
|
||||
## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23
|
||||
|
||||
|
|
30
Makefile
30
Makefile
|
@ -1,17 +1,6 @@
|
|||
# The personal, minimalist, super-fast, database free, bookmarking service.
|
||||
# Makefile for PHP code analysis & testing, documentation and release generation
|
||||
|
||||
# Prerequisites:
|
||||
# - install Composer, either:
|
||||
# - from your distro's package manager;
|
||||
# - from the official website (https://getcomposer.org/download/);
|
||||
# - install/update test dependencies:
|
||||
# $ composer install # 1st setup
|
||||
# $ composer update
|
||||
# - install Xdebug for PHPUnit code coverage reports:
|
||||
# - see http://xdebug.org/docs/install
|
||||
# - enable in php.ini
|
||||
|
||||
BIN = vendor/bin
|
||||
PHP_SOURCE = index.php application tests plugins
|
||||
PHP_COMMA_SOURCE = index.php,application,tests,plugins
|
||||
|
@ -115,7 +104,7 @@ check_permissions:
|
|||
@echo "----------------------"
|
||||
@echo "Check file permissions"
|
||||
@echo "----------------------"
|
||||
@for file in `git ls-files`; do \
|
||||
@for file in `git ls-files | grep -v docker`; do \
|
||||
if [ -x $$file ]; then \
|
||||
errors=true; \
|
||||
echo "$${file} is executable"; \
|
||||
|
@ -130,12 +119,12 @@ check_permissions:
|
|||
# See phpunit.xml for configuration
|
||||
# https://phpunit.de/manual/current/en/appendixes.configuration.html
|
||||
##
|
||||
test:
|
||||
test: translate
|
||||
@echo "-------"
|
||||
@echo "PHPUNIT"
|
||||
@echo "-------"
|
||||
@mkdir -p sandbox coverage
|
||||
@$(BIN)/phpunit --coverage-php coverage/main.cov --testsuite unit-tests
|
||||
@$(BIN)/phpunit --coverage-php coverage/main.cov --bootstrap tests/bootstrap.php --testsuite unit-tests
|
||||
|
||||
locale_test_%:
|
||||
@UT_LOCALE=$*.utf8 \
|
||||
|
@ -168,15 +157,15 @@ composer_dependencies: clean
|
|||
composer install --no-dev --prefer-dist
|
||||
find vendor/ -name ".git" -type d -exec rm -rf {} +
|
||||
|
||||
### generate a release tarball and include 3rd-party dependencies
|
||||
release_tar: composer_dependencies htmldoc
|
||||
### generate a release tarball and include 3rd-party dependencies and translations
|
||||
release_tar: composer_dependencies htmldoc translate
|
||||
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD
|
||||
tar rvf $(ARCHIVE_VERSION).tar --transform "s|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/
|
||||
tar rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/
|
||||
gzip $(ARCHIVE_VERSION).tar
|
||||
|
||||
### generate a release zip and include 3rd-party dependencies
|
||||
release_zip: composer_dependencies htmldoc
|
||||
### generate a release zip and include 3rd-party dependencies and translations
|
||||
release_zip: composer_dependencies htmldoc translate
|
||||
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD
|
||||
mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor}
|
||||
rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/
|
||||
|
@ -213,3 +202,8 @@ htmldoc:
|
|||
mkdocs build'
|
||||
find doc/html/ -type f -exec chmod a-x '{}' \;
|
||||
rm -r venv
|
||||
|
||||
|
||||
### Generate Shaarli's translation compiled file (.mo)
|
||||
translate:
|
||||
@find inc/languages/ -name shaarli.po -execdir msgfmt shaarli.po -o shaarli.mo \;
|
|
@ -9,7 +9,7 @@ _It is designed to be personal (single-user), fast and handy._
|
|||
[![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4)
|
||||
[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
|
||||
•
|
||||
[![](https://img.shields.io/badge/latest-v0.9.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1)
|
||||
[![](https://img.shields.io/badge/latest-v0.9.2-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2)
|
||||
[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
|
||||
•
|
||||
[![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli)
|
||||
|
|
|
@ -149,12 +149,13 @@ public static function checkUpdate($currentVersion,
|
|||
public static function checkPHPVersion($minVersion, $curVersion)
|
||||
{
|
||||
if (version_compare($curVersion, $minVersion) < 0) {
|
||||
throw new Exception(
|
||||
$msg = t(
|
||||
'Your PHP version is obsolete!'
|
||||
.' Shaarli requires at least PHP '.$minVersion.', and thus cannot run.'
|
||||
.' Your PHP version has known security vulnerabilities and should be'
|
||||
.' updated as soon as possible.'
|
||||
. ' Shaarli requires at least PHP %s, and thus cannot run.'
|
||||
. ' Your PHP version has known security vulnerabilities and should be'
|
||||
. ' updated as soon as possible.'
|
||||
);
|
||||
throw new Exception(sprintf($msg, $minVersion));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +180,7 @@ public static function checkResourcePermissions($conf)
|
|||
$rainTplDir.'/'.$conf->get('resource.theme'),
|
||||
) as $path) {
|
||||
if (! is_readable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" directory is not readable';
|
||||
$errors[] = '"'.$path.'" '. t('directory is not readable');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,10 +192,10 @@ public static function checkResourcePermissions($conf)
|
|||
$conf->get('resource.raintpl_tmp'),
|
||||
) as $path) {
|
||||
if (! is_readable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" directory is not readable';
|
||||
$errors[] = '"'.$path.'" '. t('directory is not readable');
|
||||
}
|
||||
if (! is_writable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" directory is not writable';
|
||||
$errors[] = '"'.$path.'" '. t('directory is not writable');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,10 +213,10 @@ public static function checkResourcePermissions($conf)
|
|||
}
|
||||
|
||||
if (! is_readable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" file is not readable';
|
||||
$errors[] = '"'.$path.'" '. t('file is not readable');
|
||||
}
|
||||
if (! is_writable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" file is not writable';
|
||||
$errors[] = '"'.$path.'" '. t('file is not writable');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
function purgeCachedPages($pageCacheDir)
|
||||
{
|
||||
if (! is_dir($pageCacheDir)) {
|
||||
$error = 'Cannot purge '.$pageCacheDir.': no directory';
|
||||
$error = sprintf(t('Cannot purge %s: no directory'), $pageCacheDir);
|
||||
error_log($error);
|
||||
return $error;
|
||||
}
|
||||
|
|
|
@ -148,11 +148,11 @@ protected function buildItem($link, $pageaddr)
|
|||
$link['url'] = $pageaddr . $link['url'];
|
||||
}
|
||||
if ($this->usePermalinks === true) {
|
||||
$permalink = '<a href="'. $link['url'] .'" title="Direct link">Direct link</a>';
|
||||
$permalink = '<a href="'. $link['url'] .'" title="'. t('Direct link') .'">'. t('Direct link') .'</a>';
|
||||
} else {
|
||||
$permalink = '<a href="'. $link['guid'] .'" title="Permalink">Permalink</a>';
|
||||
$permalink = '<a href="'. $link['guid'] .'" title="'. t('Permalink') .'">'. t('Permalink') .'</a>';
|
||||
}
|
||||
$link['description'] = format_description($link['description'], '', $pageaddr);
|
||||
$link['description'] = format_description($link['description'], '', false, $pageaddr);
|
||||
$link['description'] .= PHP_EOL .'<br>— '. $permalink;
|
||||
|
||||
$pubDate = $link['created'];
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* - UPDATED: link updated
|
||||
* - DELETED: link deleted
|
||||
* - SETTINGS: the settings have been updated through the UI.
|
||||
* - IMPORT: bulk links import
|
||||
*
|
||||
* Note: new events are put at the beginning of the file and history array.
|
||||
*/
|
||||
|
@ -41,6 +42,11 @@ class History
|
|||
*/
|
||||
const SETTINGS = 'SETTINGS';
|
||||
|
||||
/**
|
||||
* @var string Action key: a bulk import has been processed.
|
||||
*/
|
||||
const IMPORT = 'IMPORT';
|
||||
|
||||
/**
|
||||
* @var string History file path.
|
||||
*/
|
||||
|
@ -121,6 +127,16 @@ public function updateSettings()
|
|||
$this->addEvent(self::SETTINGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Event: bulk import.
|
||||
*
|
||||
* Note: we don't store links add/update one by one since it can have a huge impact on performances.
|
||||
*/
|
||||
public function importLinks()
|
||||
{
|
||||
$this->addEvent(self::IMPORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new event and write it in the history file.
|
||||
*
|
||||
|
@ -155,7 +171,7 @@ protected function check()
|
|||
}
|
||||
|
||||
if (! is_writable($this->historyFilePath)) {
|
||||
throw new Exception('History file isn\'t readable or writable');
|
||||
throw new Exception(t('History file isn\'t readable or writable'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +182,7 @@ protected function read()
|
|||
{
|
||||
$this->history = FileUtils::readFlatDB($this->historyFilePath, []);
|
||||
if ($this->history === false) {
|
||||
throw new Exception('Could not parse history file');
|
||||
throw new Exception(t('Could not parse history file'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304)
|
|||
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
|
||||
|
||||
// Max download size management
|
||||
curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024);
|
||||
curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16);
|
||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION,
|
||||
function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes)
|
||||
|
@ -302,6 +302,13 @@ function server_url($server)
|
|||
$port = $server['HTTP_X_FORWARDED_PORT'];
|
||||
}
|
||||
|
||||
// This is a workaround for proxies that don't forward the scheme properly.
|
||||
// Connecting over port 443 has to be in HTTPS.
|
||||
// See https://github.com/shaarli/Shaarli/issues/1022
|
||||
if ($port == '443') {
|
||||
$scheme = 'https';
|
||||
}
|
||||
|
||||
if (($scheme == 'http' && $port != '80')
|
||||
|| ($scheme == 'https' && $port != '443')
|
||||
) {
|
||||
|
|
|
@ -1,21 +1,164 @@
|
|||
<?php
|
||||
|
||||
namespace Shaarli;
|
||||
|
||||
use Gettext\GettextTranslator;
|
||||
use Gettext\Merge;
|
||||
use Gettext\Translations;
|
||||
use Gettext\Translator;
|
||||
use Gettext\TranslatorInterface;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
|
||||
/**
|
||||
* Wrapper function for translation which match the API
|
||||
* of gettext()/_() and ngettext().
|
||||
* Class Languages
|
||||
*
|
||||
* Not doing translation for now.
|
||||
* Load Shaarli translations using 'gettext/gettext'.
|
||||
* This class allows to either use PHP gettext extension, or a PHP implementation of gettext,
|
||||
* with a fixed language, or dynamically using autoLocale().
|
||||
*
|
||||
* @param string $text Text to translate.
|
||||
* @param string $nText The plural message ID.
|
||||
* @param int $nb The number of items for plural forms.
|
||||
* Translation files PO/MO files follow gettext standard and must be placed under:
|
||||
* <translation path>/<language>/LC_MESSAGES/<domain>.[po|mo]
|
||||
*
|
||||
* @return String Text translated.
|
||||
* Pros/cons:
|
||||
* - gettext extension is faster
|
||||
* - gettext is very system dependent (PHP extension, the locale must be installed, and web server reloaded)
|
||||
*
|
||||
* Settings:
|
||||
* - translation.mode:
|
||||
* - auto: use default setting (PHP implementation)
|
||||
* - php: use PHP implementation
|
||||
* - gettext: use gettext wrapper
|
||||
* - translation.language:
|
||||
* - auto: use autoLocale() and the language change according to user HTTP headers
|
||||
* - fixed language: e.g. 'fr'
|
||||
* - translation.extensions:
|
||||
* - domain => translation_path: allow plugins and themes to extend the defaut extension
|
||||
* The domain must be unique, and translation path must be relative, and contains the tree mentioned above.
|
||||
*
|
||||
* @package Shaarli
|
||||
*/
|
||||
function t($text, $nText = '', $nb = 0) {
|
||||
if (empty($nText)) {
|
||||
return $text;
|
||||
class Languages
|
||||
{
|
||||
/**
|
||||
* Core translations domain
|
||||
*/
|
||||
const DEFAULT_DOMAIN = 'shaarli';
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $language;
|
||||
|
||||
/**
|
||||
* @var ConfigManager
|
||||
*/
|
||||
protected $conf;
|
||||
|
||||
/**
|
||||
* Languages constructor.
|
||||
*
|
||||
* @param string $language lang determined by autoLocale(), can be overridden.
|
||||
* @param ConfigManager $conf instance.
|
||||
*/
|
||||
public function __construct($language, $conf)
|
||||
{
|
||||
$this->conf = $conf;
|
||||
$confLanguage = $this->conf->get('translation.language', 'auto');
|
||||
if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) {
|
||||
$this->language = substr($language, 0, 5);
|
||||
} else {
|
||||
$this->language = $confLanguage;
|
||||
}
|
||||
|
||||
if (! extension_loaded('gettext')
|
||||
|| in_array($this->conf->get('translation.mode', 'auto'), ['auto', 'php'])
|
||||
) {
|
||||
$this->initPhpTranslator();
|
||||
} else {
|
||||
$this->initGettextTranslator();
|
||||
}
|
||||
|
||||
// Register default functions (e.g. '__()') to use our Translator
|
||||
$this->translator->register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the translator using php gettext extension (gettext dependency act as a wrapper).
|
||||
*/
|
||||
protected function initGettextTranslator ()
|
||||
{
|
||||
$this->translator = new GettextTranslator();
|
||||
$this->translator->setLanguage($this->language);
|
||||
$this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages');
|
||||
|
||||
foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
|
||||
if ($domain !== self::DEFAULT_DOMAIN) {
|
||||
$this->translator->loadDomain($domain, $translationPath, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the translator using a PHP implementation of gettext.
|
||||
*
|
||||
* Note that if language po file doesn't exist, errors are ignored (e.g. not installed language).
|
||||
*/
|
||||
protected function initPhpTranslator()
|
||||
{
|
||||
$this->translator = new Translator();
|
||||
$translations = new Translations();
|
||||
// Core translations
|
||||
try {
|
||||
/** @var Translations $translations */
|
||||
$translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po');
|
||||
$translations->setDomain('shaarli');
|
||||
$this->translator->loadTranslations($translations);
|
||||
} catch (\InvalidArgumentException $e) {}
|
||||
|
||||
|
||||
// Extension translations (plugins, themes, etc.).
|
||||
foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
|
||||
if ($domain === self::DEFAULT_DOMAIN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var Translations $extension */
|
||||
$extension = Translations::fromPoFile($translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po');
|
||||
$extension->setDomain($domain);
|
||||
$this->translator->loadTranslations($extension);
|
||||
} catch (\InvalidArgumentException $e) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a language string is valid.
|
||||
*
|
||||
* @param string $language e.g. 'fr' or 'en_US'
|
||||
*
|
||||
* @return bool true if valid, false otherwise
|
||||
*/
|
||||
protected function isValidLanguage($language)
|
||||
{
|
||||
return preg_match('/^[a-z]{2}(_[A-Z]{2})?/', $language) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of available languages for Shaarli.
|
||||
*
|
||||
* @return array List of available languages, with their label.
|
||||
*/
|
||||
public static function getAvailableLanguages()
|
||||
{
|
||||
return [
|
||||
'auto' => t('Automatic'),
|
||||
'en' => t('English'),
|
||||
'fr' => t('French'),
|
||||
];
|
||||
}
|
||||
$actualForm = $nb > 1 ? $nText : $text;
|
||||
return sprintf($actualForm, $nb);
|
||||
}
|
||||
|
|
|
@ -133,16 +133,16 @@ public function offsetSet($offset, $value)
|
|||
{
|
||||
// TODO: use exceptions instead of "die"
|
||||
if (!$this->loggedIn) {
|
||||
die('You are not authorized to add a link.');
|
||||
die(t('You are not authorized to add a link.'));
|
||||
}
|
||||
if (!isset($value['id']) || empty($value['url'])) {
|
||||
die('Internal Error: A link should always have an id and URL.');
|
||||
die(t('Internal Error: A link should always have an id and URL.'));
|
||||
}
|
||||
if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) {
|
||||
die('You must specify an integer as a key.');
|
||||
die(t('You must specify an integer as a key.'));
|
||||
}
|
||||
if ($offset !== null && $offset !== $value['id']) {
|
||||
die('Array offset and link ID must be equal.');
|
||||
die(t('Array offset and link ID must be equal.'));
|
||||
}
|
||||
|
||||
// If the link exists, we reuse the real offset, otherwise new entry
|
||||
|
@ -248,13 +248,13 @@ private function check()
|
|||
$this->links = array();
|
||||
$link = array(
|
||||
'id' => 1,
|
||||
'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
|
||||
'title'=> t('The personal, minimalist, super-fast, database free, bookmarking service'),
|
||||
'url'=>'https://shaarli.readthedocs.io',
|
||||
'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
|
||||
'description'=>t('Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
|
||||
|
||||
To learn how to use Shaarli, consult the link "Help/documentation" at the bottom of this page.
|
||||
To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page.
|
||||
|
||||
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.',
|
||||
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'),
|
||||
'private'=>0,
|
||||
'created'=> new DateTime(),
|
||||
'tags'=>'opensource software'
|
||||
|
@ -264,9 +264,9 @@ private function check()
|
|||
|
||||
$link = array(
|
||||
'id' => 0,
|
||||
'title'=>'My secret stuff... - Pastebin.com',
|
||||
'title'=> t('My secret stuff... - Pastebin.com'),
|
||||
'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
|
||||
'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.',
|
||||
'description'=> t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'),
|
||||
'private'=>1,
|
||||
'created'=> new DateTime('1 minute ago'),
|
||||
'tags'=>'secretstuff',
|
||||
|
@ -289,13 +289,15 @@ private function read()
|
|||
return;
|
||||
}
|
||||
|
||||
$this->urls = [];
|
||||
$this->ids = [];
|
||||
$this->links = FileUtils::readFlatDB($this->datastore, []);
|
||||
|
||||
$toremove = array();
|
||||
foreach ($this->links as $key => &$link) {
|
||||
if (! $this->loggedIn && $link['private'] != 0) {
|
||||
// Transition for not upgraded databases.
|
||||
$toremove[] = $key;
|
||||
unset($this->links[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -329,14 +331,10 @@ private function read()
|
|||
}
|
||||
$link['shorturl'] = smallHash($link['linkdate']);
|
||||
}
|
||||
}
|
||||
|
||||
// If user is not logged in, filter private links.
|
||||
foreach ($toremove as $offset) {
|
||||
unset($this->links[$offset]);
|
||||
$this->urls[$link['url']] = $key;
|
||||
$this->ids[$link['id']] = $key;
|
||||
}
|
||||
|
||||
$this->reorder();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -346,6 +344,7 @@ private function read()
|
|||
*/
|
||||
private function write()
|
||||
{
|
||||
$this->reorder();
|
||||
FileUtils::writeFlatDB($this->datastore, $this->links);
|
||||
}
|
||||
|
||||
|
@ -528,8 +527,8 @@ public function reorder($order = 'DESC')
|
|||
return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
|
||||
});
|
||||
|
||||
$this->urls = array();
|
||||
$this->ids = array();
|
||||
$this->urls = [];
|
||||
$this->ids = [];
|
||||
foreach ($this->links as $key => $link) {
|
||||
$this->urls[$link['url']] = $key;
|
||||
$this->ids[$link['id']] = $key;
|
||||
|
|
|
@ -444,5 +444,11 @@ public static function tagsStrToArray($tags, $casesensitive)
|
|||
|
||||
class LinkNotFoundException extends Exception
|
||||
{
|
||||
protected $message = 'The link you are trying to reach does not exist or has been deleted.';
|
||||
/**
|
||||
* LinkNotFoundException constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = t('The link you are trying to reach does not exist or has been deleted.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,12 +102,13 @@ function count_private($links)
|
|||
*
|
||||
* @param string $text input string.
|
||||
* @param string $redirector if a redirector is set, use it to gerenate links.
|
||||
* @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not.
|
||||
*
|
||||
* @return string returns $text with all links converted to HTML links.
|
||||
*
|
||||
* @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
|
||||
*/
|
||||
function text2clickable($text, $redirector = '')
|
||||
function text2clickable($text, $redirector = '', $urlEncode = true)
|
||||
{
|
||||
$regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si';
|
||||
|
||||
|
@ -117,8 +118,9 @@ function text2clickable($text, $redirector = '')
|
|||
// Redirector is set, urlencode the final URL.
|
||||
return preg_replace_callback(
|
||||
$regex,
|
||||
function ($matches) use ($redirector) {
|
||||
return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>';
|
||||
function ($matches) use ($redirector, $urlEncode) {
|
||||
$url = $urlEncode ? urlencode($matches[1]) : $matches[1];
|
||||
return '<a href="' . $redirector . $url .'">'. $matches[1] .'</a>';
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
@ -164,12 +166,13 @@ function space2nbsp($text)
|
|||
*
|
||||
* @param string $description shaare's description.
|
||||
* @param string $redirector if a redirector is set, use it to gerenate links.
|
||||
* @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not.
|
||||
* @param string $indexUrl URL to Shaarli's index.
|
||||
*
|
||||
|
||||
* @return string formatted description.
|
||||
*/
|
||||
function format_description($description, $redirector = '', $indexUrl = '') {
|
||||
return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl)));
|
||||
function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '') {
|
||||
return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector, $urlEncode), $indexUrl)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,11 +32,10 @@ public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $in
|
|||
{
|
||||
// see tpl/export.html for possible values
|
||||
if (! in_array($selection, array('all', 'public', 'private'))) {
|
||||
throw new Exception('Invalid export selection: "'.$selection.'"');
|
||||
throw new Exception(t('Invalid export selection:') .' "'.$selection.'"');
|
||||
}
|
||||
|
||||
$bookmarkLinks = array();
|
||||
|
||||
foreach ($linkDb as $link) {
|
||||
if ($link['private'] != 0 && $selection == 'public') {
|
||||
continue;
|
||||
|
@ -66,6 +65,7 @@ public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $in
|
|||
* @param int $importCount how many links were imported
|
||||
* @param int $overwriteCount how many links were overwritten
|
||||
* @param int $skipCount how many links were skipped
|
||||
* @param int $duration how many seconds did the import take
|
||||
*
|
||||
* @return string Summary of the bookmark import status
|
||||
*/
|
||||
|
@ -74,16 +74,18 @@ private static function importStatus(
|
|||
$filesize,
|
||||
$importCount=0,
|
||||
$overwriteCount=0,
|
||||
$skipCount=0
|
||||
$skipCount=0,
|
||||
$duration=0
|
||||
)
|
||||
{
|
||||
$status = 'File '.$filename.' ('.$filesize.' bytes) ';
|
||||
$status = sprintf(t('File %s (%d bytes) '), $filename, $filesize);
|
||||
if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
|
||||
$status .= 'has an unknown file format. Nothing was imported.';
|
||||
$status .= t('has an unknown file format. Nothing was imported.');
|
||||
} else {
|
||||
$status .= 'was successfully processed: '.$importCount.' links imported, ';
|
||||
$status .= $overwriteCount.' links overwritten, ';
|
||||
$status .= $skipCount.' links skipped.';
|
||||
$status .= vsprintf(
|
||||
t('was successfully processed in %d seconds: %d links imported, %d links overwritten, %d links skipped.'),
|
||||
[$duration, $importCount, $overwriteCount, $skipCount]
|
||||
);
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
@ -101,6 +103,7 @@ private static function importStatus(
|
|||
*/
|
||||
public static function import($post, $files, $linkDb, $conf, $history)
|
||||
{
|
||||
$start = time();
|
||||
$filename = $files['filetoupload']['name'];
|
||||
$filesize = $files['filetoupload']['size'];
|
||||
$data = file_get_contents($files['filetoupload']['tmp_name']);
|
||||
|
@ -184,7 +187,6 @@ public static function import($post, $files, $linkDb, $conf, $history)
|
|||
$linkDb[$existingLink['id']] = $newLink;
|
||||
$importCount++;
|
||||
$overwriteCount++;
|
||||
$history->updateLink($newLink);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -196,16 +198,19 @@ public static function import($post, $files, $linkDb, $conf, $history)
|
|||
$newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
|
||||
$linkDb[$newLink['id']] = $newLink;
|
||||
$importCount++;
|
||||
$history->addLink($newLink);
|
||||
}
|
||||
|
||||
$linkDb->save($conf->get('resource.page_cache'));
|
||||
$history->importLinks();
|
||||
|
||||
$duration = time() - $start;
|
||||
return self::importStatus(
|
||||
$filename,
|
||||
$filesize,
|
||||
$importCount,
|
||||
$overwriteCount,
|
||||
$skipCount
|
||||
$skipCount,
|
||||
$duration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,12 +32,14 @@ class PageBuilder
|
|||
*
|
||||
* @param ConfigManager $conf Configuration Manager instance (reference).
|
||||
* @param LinkDB $linkDB instance.
|
||||
* @param string $token Session token
|
||||
*/
|
||||
public function __construct(&$conf, $linkDB = null)
|
||||
public function __construct(&$conf, $linkDB = null, $token = null)
|
||||
{
|
||||
$this->tpl = false;
|
||||
$this->conf = $conf;
|
||||
$this->linkDB = $linkDB;
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,7 +94,7 @@ private function initialize()
|
|||
$this->tpl->assign('showatom', $this->conf->get('feed.show_atom', true));
|
||||
$this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss');
|
||||
$this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
|
||||
$this->tpl->assign('token', getToken($this->conf));
|
||||
$this->tpl->assign('token', $this->token);
|
||||
|
||||
if ($this->linkDB !== null) {
|
||||
$this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
|
||||
|
@ -159,9 +161,12 @@ public function renderPage($page)
|
|||
*
|
||||
* @param string $message A messate to display what is not found
|
||||
*/
|
||||
public function render404($message = 'The page you are trying to reach does not exist or has been deleted.')
|
||||
public function render404($message = '')
|
||||
{
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
|
||||
if (empty($message)) {
|
||||
$message = t('The page you are trying to reach does not exist or has been deleted.');
|
||||
}
|
||||
header($_SERVER['SERVER_PROTOCOL'] .' '. t('404 Not Found'));
|
||||
$this->tpl->assign('error_message', $message);
|
||||
$this->renderPage('404');
|
||||
}
|
||||
|
|
|
@ -188,6 +188,9 @@ public function getPluginsMeta()
|
|||
$metaData[$plugin] = parse_ini_file($metaFile);
|
||||
$metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins);
|
||||
|
||||
if (isset($metaData[$plugin]['description'])) {
|
||||
$metaData[$plugin]['description'] = t($metaData[$plugin]['description']);
|
||||
}
|
||||
// Read parameters and format them into an array.
|
||||
if (isset($metaData[$plugin]['parameters'])) {
|
||||
$params = explode(';', $metaData[$plugin]['parameters']);
|
||||
|
@ -203,7 +206,7 @@ public function getPluginsMeta()
|
|||
$metaData[$plugin]['parameters'][$param]['value'] = '';
|
||||
// Optional parameter description in parameter.PARAM_NAME=
|
||||
if (isset($metaData[$plugin]['parameter.'. $param])) {
|
||||
$metaData[$plugin]['parameters'][$param]['desc'] = $metaData[$plugin]['parameter.'. $param];
|
||||
$metaData[$plugin]['parameters'][$param]['desc'] = t($metaData[$plugin]['parameter.'. $param]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,6 +240,6 @@ class PluginFileNotFoundException extends Exception
|
|||
*/
|
||||
public function __construct($pluginName)
|
||||
{
|
||||
$this->message = 'Plugin "'. $pluginName .'" files not found.';
|
||||
$this->message = sprintf(t('Plugin "%s" files not found.'), $pluginName);
|
||||
}
|
||||
}
|
||||
|
|
83
application/SessionManager.php
Normal file
83
application/SessionManager.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
namespace Shaarli;
|
||||
|
||||
/**
|
||||
* Manages the server-side session
|
||||
*/
|
||||
class SessionManager
|
||||
{
|
||||
protected $session = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $session The $_SESSION array (reference)
|
||||
* @param ConfigManager $conf ConfigManager instance
|
||||
*/
|
||||
public function __construct(& $session, $conf)
|
||||
{
|
||||
$this->session = &$session;
|
||||
$this->conf = $conf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a session token
|
||||
*
|
||||
* @return string token
|
||||
*/
|
||||
public function generateToken()
|
||||
{
|
||||
$token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt'));
|
||||
$this->session['tokens'][$token] = 1;
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the validity of a session token, and destroys it afterwards
|
||||
*
|
||||
* @param string $token The token to check
|
||||
*
|
||||
* @return bool true if the token is valid, else false
|
||||
*/
|
||||
public function checkToken($token)
|
||||
{
|
||||
if (! isset($this->session['tokens'][$token])) {
|
||||
// the token is wrong, or has already been used
|
||||
return false;
|
||||
}
|
||||
|
||||
// destroy the token to prevent future use
|
||||
unset($this->session['tokens'][$token]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate session ID to prevent Full Path Disclosure.
|
||||
*
|
||||
* See #298.
|
||||
* The session ID's format depends on the hash algorithm set in PHP settings
|
||||
*
|
||||
* @param string $sessionId Session ID
|
||||
*
|
||||
* @return true if valid, false otherwise.
|
||||
*
|
||||
* @see http://php.net/manual/en/function.hash-algos.php
|
||||
* @see http://php.net/manual/en/session.configuration.php
|
||||
*/
|
||||
public static function checkId($sessionId)
|
||||
{
|
||||
if (empty($sessionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$sessionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -73,7 +73,7 @@ public function update()
|
|||
}
|
||||
|
||||
if ($this->methods === null) {
|
||||
throw new UpdaterException('Couldn\'t retrieve Updater class methods.');
|
||||
throw new UpdaterException(t('Couldn\'t retrieve Updater class methods.'));
|
||||
}
|
||||
|
||||
foreach ($this->methods as $method) {
|
||||
|
@ -436,6 +436,15 @@ public function updateMethodResetHistoryFile()
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the datastore -> the link order is now applied when links are saved.
|
||||
*/
|
||||
public function updateMethodReorderDatastore()
|
||||
{
|
||||
$this->linkDB->save($this->conf->get('resource.page_cache'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -482,7 +491,7 @@ private function buildMessage($message)
|
|||
}
|
||||
|
||||
if (! empty($this->method)) {
|
||||
$out .= 'An error occurred while running the update '. $this->method . PHP_EOL;
|
||||
$out .= t('An error occurred while running the update ') . $this->method . PHP_EOL;
|
||||
}
|
||||
|
||||
if (! empty($this->previous)) {
|
||||
|
@ -522,11 +531,11 @@ function read_updates_file($updatesFilepath)
|
|||
function write_updates_file($updatesFilepath, $updates)
|
||||
{
|
||||
if (empty($updatesFilepath)) {
|
||||
throw new Exception('Updates file path is not set, can\'t write updates.');
|
||||
throw new Exception(t('Updates file path is not set, can\'t write updates.'));
|
||||
}
|
||||
|
||||
$res = file_put_contents($updatesFilepath, implode(';', $updates));
|
||||
if ($res === false) {
|
||||
throw new Exception('Unable to write updates in '. $updatesFilepath . '.');
|
||||
throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,36 +181,6 @@ function generateLocation($referer, $host, $loopTerms = array())
|
|||
return $finalReferer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate session ID to prevent Full Path Disclosure.
|
||||
*
|
||||
* See #298.
|
||||
* The session ID's format depends on the hash algorithm set in PHP settings
|
||||
*
|
||||
* @param string $sessionId Session ID
|
||||
*
|
||||
* @return true if valid, false otherwise.
|
||||
*
|
||||
* @see http://php.net/manual/en/function.hash-algos.php
|
||||
* @see http://php.net/manual/en/session.configuration.php
|
||||
*/
|
||||
function is_session_id_valid($sessionId)
|
||||
{
|
||||
if (empty($sessionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$sessionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sniff browser language to set the locale automatically.
|
||||
* Note that is may not work on your server if the corresponding locale is not installed.
|
||||
|
@ -452,7 +422,7 @@ function get_max_upload_size($limitPost, $limitUpload, $format = true)
|
|||
*/
|
||||
function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
|
||||
{
|
||||
$callback = function($a, $b) use ($reverse) {
|
||||
$callback = function ($a, $b) use ($reverse) {
|
||||
// Collator is part of PHP intl.
|
||||
if (class_exists('Collator')) {
|
||||
$collator = new Collator(setlocale(LC_COLLATE, 0));
|
||||
|
@ -470,3 +440,18 @@ function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
|
|||
usort($data, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper function for translation which match the API
|
||||
* of gettext()/_() and ngettext().
|
||||
*
|
||||
* @param string $text Text to translate.
|
||||
* @param string $nText The plural message ID.
|
||||
* @param int $nb The number of items for plural forms.
|
||||
* @param string $domain The domain where the translation is stored (default: shaarli).
|
||||
*
|
||||
* @return string Text translated.
|
||||
*/
|
||||
function t($text, $nText = '', $nb = 1, $domain = 'shaarli') {
|
||||
return dn__($domain, $text, $nText, $nb);
|
||||
}
|
||||
|
|
|
@ -22,10 +22,15 @@ public function read($filepath)
|
|||
$data = json_decode($data, true);
|
||||
if ($data === null) {
|
||||
$errorCode = json_last_error();
|
||||
$error = 'An error occurred while parsing JSON configuration file ('. $filepath .'): error code #';
|
||||
$error .= $errorCode. '<br>➜ <code>' . json_last_error_msg() .'</code>';
|
||||
$error = sprintf(
|
||||
'An error occurred while parsing JSON configuration file (%s): error code #%d',
|
||||
$filepath,
|
||||
$errorCode
|
||||
);
|
||||
$error .= '<br>➜ <code>' . json_last_error_msg() .'</code>';
|
||||
if ($errorCode === JSON_ERROR_SYNTAX) {
|
||||
$error .= '<br>Please check your JSON syntax (without PHP comment tags) using a JSON lint tool such as ';
|
||||
$error .= '<br>';
|
||||
$error .= 'Please check your JSON syntax (without PHP comment tags) using a JSON lint tool such as ';
|
||||
$error .= '<a href="http://jsonlint.com/">jsonlint.com</a>.';
|
||||
}
|
||||
throw new \Exception($error);
|
||||
|
@ -44,8 +49,8 @@ public function write($filepath, $conf)
|
|||
if (!file_put_contents($filepath, $data)) {
|
||||
throw new \IOException(
|
||||
$filepath,
|
||||
'Shaarli could not create the config file.
|
||||
Please make sure Shaarli has the right to write in the folder is it installed in.'
|
||||
t('Shaarli could not create the config file. '.
|
||||
'Please make sure Shaarli has the right to write in the folder is it installed in.')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ public function get($setting, $default = '')
|
|||
public function set($setting, $value, $write = false, $isLoggedIn = false)
|
||||
{
|
||||
if (empty($setting) || ! is_string($setting)) {
|
||||
throw new \Exception('Invalid setting key parameter. String expected, got: '. gettype($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.
|
||||
|
@ -339,6 +339,10 @@ protected function setDefaultValues()
|
|||
$this->setEmpty('redirector.url', '');
|
||||
$this->setEmpty('redirector.encode_url', true);
|
||||
|
||||
$this->setEmpty('translation.language', 'auto');
|
||||
$this->setEmpty('translation.mode', 'php');
|
||||
$this->setEmpty('translation.extensions', []);
|
||||
|
||||
$this->setEmpty('plugins', array());
|
||||
}
|
||||
|
||||
|
|
|
@ -118,8 +118,8 @@ public function write($filepath, $conf)
|
|||
) {
|
||||
throw new \IOException(
|
||||
$filepath,
|
||||
'Shaarli could not create the config file.
|
||||
Please make sure Shaarli has the right to write in the folder is it installed in.'
|
||||
t('Shaarli could not create the config file. '.
|
||||
'Please make sure Shaarli has the right to write in the folder is it installed in.')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,6 @@ class MissingFieldConfigException extends \Exception
|
|||
public function __construct($field)
|
||||
{
|
||||
$this->field = $field;
|
||||
$this->message = 'Configuration value is required for '. $this->field;
|
||||
$this->message = sprintf(t('Configuration value is required for %s'), $this->field);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,6 @@ class PluginConfigOrderException extends \Exception
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'An error occurred while trying to save plugins loading order.';
|
||||
$this->message = t('An error occurred while trying to save plugins loading order.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,6 @@ class UnauthorizedConfigException extends \Exception
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'You are not authorized to alter config.';
|
||||
$this->message = t('You are not authorized to alter config.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ class IOException extends Exception
|
|||
public function __construct($path, $message = '')
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->message = empty($message) ? 'Error accessing' : $message;
|
||||
$this->message = empty($message) ? t('Error accessing') : $message;
|
||||
$this->message .= ' "' . $this->path .'"';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
"shaarli/netscape-bookmark-parser": "^2.0",
|
||||
"erusev/parsedown": "1.6",
|
||||
"slim/slim": "^3.0",
|
||||
"pubsubhubbub/publisher": "dev-master"
|
||||
"pubsubhubbub/publisher": "dev-master",
|
||||
"gettext/gettext": "^4.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpmd/phpmd" : "@stable",
|
||||
|
|
249
composer.lock
generated
249
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "68beedbfa104c788029b079800cfd6e8",
|
||||
"content-hash": "13b7e1e474fe9264b098ba86face0feb",
|
||||
"packages": [
|
||||
{
|
||||
"name": "container-interop/container-interop",
|
||||
|
@ -76,6 +76,129 @@
|
|||
],
|
||||
"time": "2015-10-04T16:44:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "gettext/gettext",
|
||||
"version": "v4.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/oscarotero/Gettext.git",
|
||||
"reference": "4f57f004635cc6311a20815ebfdc0757cb337113"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/oscarotero/Gettext/zipball/4f57f004635cc6311a20815ebfdc0757cb337113",
|
||||
"reference": "4f57f004635cc6311a20815ebfdc0757cb337113",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"gettext/languages": "^2.3",
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"illuminate/view": "*",
|
||||
"phpunit/phpunit": "^4.8|^5.7",
|
||||
"squizlabs/php_codesniffer": "^3.0",
|
||||
"symfony/yaml": "~2",
|
||||
"twig/extensions": "*",
|
||||
"twig/twig": "^1.31|^2.0"
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/view": "Is necessary if you want to use the Blade extractor",
|
||||
"symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator",
|
||||
"twig/extensions": "Is necessary if you want to use the Twig extractor",
|
||||
"twig/twig": "Is necessary if you want to use the Twig extractor"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Gettext\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oscar Otero",
|
||||
"email": "oom@oscarotero.com",
|
||||
"homepage": "http://oscarotero.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP gettext manager",
|
||||
"homepage": "https://github.com/oscarotero/Gettext",
|
||||
"keywords": [
|
||||
"JS",
|
||||
"gettext",
|
||||
"i18n",
|
||||
"mo",
|
||||
"po",
|
||||
"translation"
|
||||
],
|
||||
"time": "2017-08-09T16:59:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "gettext/languages",
|
||||
"version": "2.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mlocati/cldr-to-gettext-plural-rules.git",
|
||||
"reference": "49c39e51569963cc917a924b489e7025bfb9d8c7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/49c39e51569963cc917a924b489e7025bfb9d8c7",
|
||||
"reference": "49c39e51569963cc917a924b489e7025bfb9d8c7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4"
|
||||
},
|
||||
"bin": [
|
||||
"bin/export-plural-rules",
|
||||
"bin/export-plural-rules.php"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Gettext\\Languages\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||