Release v0.9.4
-----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEWe5LuNiFNDXAgI8BOzJIyqqwgW4FAlpws1AACgkQOzJIyqqw gW5FRw//YU1dW5CUwKjL9LxvQWWZmgm+iwuJP4sohCrySAG/2ZKxCRlJtdD1WGU3 jF1HufmdDdx0fHiAAKSz5GK+9XVnI1MuGYzTWSTS+pZ1XO5v0nJMskSd+PSkHrs1 5DaTzFnvwKflN7mKKbFOi9aBo7fIOYp8hmPHOHyDC458MJw7vraSiFjWXih10UW4 3m3442UQ14Hfwe7uN6kOfxYrNmkyisa1VJshBYs5gs1qP0L4IGMoDIAuDzVCxbcA u/olrxfSaScrV9+yFUmUlcBHGq8ejQl20MsfK7QhErbZu6Y3FlcucySGWdzVV5Nr 39sLFTjgoMhIk8oPt0N0szKH1uaqcNGbgOoo16unVFM/Kkd7kbLRoltTZIaNKyOs akqRczDkh8sd6RITsE7JwPEYloJPOLnNUPhTPqLTq9kFlCB8uGzy1VFnVUfSrqHU j6b/6xaoZUZ3hynBRLzwaN0wYQXH0jXWBHVbn2aZPSp0tTxhsnudCpPZ0STFu9As fv8NwGNejPr4I9hjoiys6ICu0NV+v88SdA347lUoXa2233Wg3EdIv8eAnZeANpkr ij0KfFhg7qiHQB8TftZjY9S9ehomw1jxShUkf2xwk7PQUngaKce/1xZAizn10jqj kLNTzPRUyVFUhEwYIeSCSOFJ22g7p8GvU+HxCIjystsxGDH3Q8s= =N7I1 -----END PGP SIGNATURE----- Merge tag 'v0.9.4' into latest Release v0.9.4
This commit is contained in:
commit
a74184e1b0
118 changed files with 4292 additions and 878 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
|
*.ttf binary
|
||||||
*.min.css binary
|
*.min.css binary
|
||||||
*.min.js binary
|
*.min.js binary
|
||||||
|
*.mo binary
|
||||||
|
|
||||||
# Exclude from Git archives
|
# Exclude from Git archives
|
||||||
|
.editorconfig export-ignore
|
||||||
.gitattributes export-ignore
|
.gitattributes export-ignore
|
||||||
.github export-ignore
|
.github export-ignore
|
||||||
.gitignore export-ignore
|
.gitignore export-ignore
|
||||||
|
|
2
.github/mailmap
vendored
2
.github/mailmap
vendored
|
@ -1,6 +1,8 @@
|
||||||
ArthurHoaro <arthur@hoa.ro>
|
ArthurHoaro <arthur@hoa.ro>
|
||||||
Florian Eula <eula.florian@gmail.com> feula
|
Florian Eula <eula.florian@gmail.com> feula
|
||||||
Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com>
|
Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com>
|
||||||
|
Immánuel Fodor <immanuelfactor+github@gmail.com>
|
||||||
|
kalvn <kalvnthereal@gmail.com> <kalvn@users.noreply.github.com>
|
||||||
Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm
|
Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm
|
||||||
Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar>
|
Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar>
|
||||||
Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com>
|
Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com>
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -18,6 +18,7 @@ vendor/
|
||||||
# Release archives
|
# Release archives
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.zip
|
*.zip
|
||||||
|
inc/languages/*/LC_MESSAGES/shaarli.mo
|
||||||
|
|
||||||
# Development and test resources
|
# Development and test resources
|
||||||
coverage
|
coverage
|
||||||
|
|
|
@ -13,6 +13,8 @@ install:
|
||||||
- composer self-update
|
- composer self-update
|
||||||
- composer install --prefer-dist
|
- composer install --prefer-dist
|
||||||
- locale -a
|
- locale -a
|
||||||
|
before_script:
|
||||||
|
- PATH=${PATH//:\.\/node_modules\/\.bin/}
|
||||||
script:
|
script:
|
||||||
- make clean
|
- make clean
|
||||||
- make check_permissions
|
- make check_permissions
|
||||||
|
|
13
AUTHORS
13
AUTHORS
|
@ -1,6 +1,6 @@
|
||||||
542 ArthurHoaro <arthur@hoa.ro>
|
577 ArthurHoaro <arthur@hoa.ro>
|
||||||
255 VirtualTam <virtualtam@flibidi.net>
|
283 VirtualTam <virtualtam@flibidi.net>
|
||||||
148 nodiscc <nodiscc@gmail.com>
|
179 nodiscc <nodiscc@gmail.com>
|
||||||
56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
|
56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
|
||||||
15 Florian Eula <eula.florian@gmail.com>
|
15 Florian Eula <eula.florian@gmail.com>
|
||||||
13 Emilien Klein <emilien@klein.st>
|
13 Emilien Klein <emilien@klein.st>
|
||||||
|
@ -11,8 +11,9 @@
|
||||||
5 Lucas Cimon <lucas.cimon@gmail.com>
|
5 Lucas Cimon <lucas.cimon@gmail.com>
|
||||||
4 Alexandre Alapetite <alexandre@alapetite.fr>
|
4 Alexandre Alapetite <alexandre@alapetite.fr>
|
||||||
4 David Sferruzza <david.sferruzza@gmail.com>
|
4 David Sferruzza <david.sferruzza@gmail.com>
|
||||||
|
4 Immánuel Fodor <immanuelfactor+github@gmail.com>
|
||||||
|
4 kalvn <kalvnthereal@gmail.com>
|
||||||
3 Teromene <teromene@teromene.fr>
|
3 Teromene <teromene@teromene.fr>
|
||||||
3 kalvn <kalvnthereal@gmail.com>
|
|
||||||
2 Chris Kuethe <chris.kuethe@gmail.com>
|
2 Chris Kuethe <chris.kuethe@gmail.com>
|
||||||
2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org>
|
2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org>
|
||||||
2 Mathieu Chabanon <git@matchab.fr>
|
2 Mathieu Chabanon <git@matchab.fr>
|
||||||
|
@ -27,11 +28,13 @@
|
||||||
1 BoboTiG <bobotig@gmail.com>
|
1 BoboTiG <bobotig@gmail.com>
|
||||||
1 Bronco <bronco@warriordudimanche.net>
|
1 Bronco <bronco@warriordudimanche.net>
|
||||||
1 D Low <daniellowtw@gmail.com>
|
1 D Low <daniellowtw@gmail.com>
|
||||||
|
1 Daniel Jakots <vigdis@chown.me>
|
||||||
1 Dimtion <zizou.xena@gmail.com>
|
1 Dimtion <zizou.xena@gmail.com>
|
||||||
1 Fanch <fanch-github@qth.fr>
|
1 Fanch <fanch-github@qth.fr>
|
||||||
1 Felix Bartels <felix@host-consultants.de>
|
1 Felix Bartels <felix@host-consultants.de>
|
||||||
1 Felix Kästner <github.com-fpunktk@fpunktk.de>
|
1 Felix Kästner <github.com-fpunktk@fpunktk.de>
|
||||||
1 Florian Voigt <flvoigt@me.com>
|
1 Florian Voigt <flvoigt@me.com>
|
||||||
|
1 Franck Kerbiriou <FranckKe@users.noreply.github.com>
|
||||||
1 Gary Marigliano <gmarigliano93@gmail.com>
|
1 Gary Marigliano <gmarigliano93@gmail.com>
|
||||||
1 Guillaume Virlet <github@virlet.org>
|
1 Guillaume Virlet <github@virlet.org>
|
||||||
1 Jonathan Druart <jonathan.druart@gmail.com>
|
1 Jonathan Druart <jonathan.druart@gmail.com>
|
||||||
|
@ -41,6 +44,8 @@
|
||||||
1 Lionel Martin <renarddesmers@gmail.com>
|
1 Lionel Martin <renarddesmers@gmail.com>
|
||||||
1 Mark Gerarts <mark.gerarts@gmail.com>
|
1 Mark Gerarts <mark.gerarts@gmail.com>
|
||||||
1 Marsup <marsup@gmail.com>
|
1 Marsup <marsup@gmail.com>
|
||||||
|
1 Neros <contact@neros.fr>
|
||||||
1 Sbgodin <Sbgodin@users.noreply.github.com>
|
1 Sbgodin <Sbgodin@users.noreply.github.com>
|
||||||
1 TsT <tst2005@gmail.com>
|
1 TsT <tst2005@gmail.com>
|
||||||
1 dimtion <zizou.xena@gmail.com>
|
1 dimtion <zizou.xena@gmail.com>
|
||||||
|
1 durcheinandr <jochen@durcheinandr.de>
|
||||||
|
|
46
CHANGELOG.md
46
CHANGELOG.md
|
@ -4,12 +4,42 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
## [v0.9.3](https://github.com/shaarli/Shaarli/releases/tag/v0.9.3) - 2018-01-04
|
## [v0.10.0](https://github.com/shaarli/Shaarli/releases/tag/v0.10.0) - UNPUBLISHED
|
||||||
|
|
||||||
|
## [v0.9.4](https://github.com/shaarli/Shaarli/releases/tag/v0.9.4) - 2018-01-30
|
||||||
|
### Added
|
||||||
|
- Enable translations: Shaarli is now also available in French. Other language translations are welcome!
|
||||||
|
- Add EditorConfig configuration
|
||||||
|
- Add favicons for mobile devices
|
||||||
|
- Add Alpine Linux arm32v7 Dockerfiles (master, latest)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Do not write bookmark edition history during file imports (performance)
|
||||||
|
- Migrate Docker images (master, latest) to Alpine Linux
|
||||||
|
- Improve unitary tests and code coverage
|
||||||
|
- Improve thumbnail display
|
||||||
|
- Improve theme ergonomics
|
||||||
|
- Improve messages if there is no plugin or parameter available in the admin page
|
||||||
|
- Increase buffer size for cURL download
|
||||||
|
- Force HTTPS if the original port is 443 behind a reverse proxy (workaround)
|
||||||
|
- Improve page title retrieval performances
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Remove redirector setting from Configure page
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix broken links in the documentation
|
||||||
|
- Enable access to `data/user.css` (Apache 2.2 & 2.4)
|
||||||
|
- Don't URL encode description links if parameter `redirector.encode_url` is set to false
|
||||||
|
- Fix an issue preventing the Save button to appear for plugin parameters
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.9.3](https://github.com/shaarli/Shaarli/releases/tag/v0.9.3) - 2018-01-04
|
||||||
**XSS vulnerability fixed. Please update.**
|
**XSS vulnerability fixed. Please update.**
|
||||||
|
|
||||||
### Security
|
## Security
|
||||||
- Fix an XSS (cross-site-scripting) vulnerability in `index.php`
|
- Fix an XSS (cross-site-scripting) vulnerability in `index.php` -
|
||||||
|
[CVE-2018-5249](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-5249)
|
||||||
|
|
||||||
|
|
||||||
## [v0.9.2](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) - 2017-10-07
|
## [v0.9.2](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) - 2017-10-07
|
||||||
|
@ -48,7 +78,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
### Security
|
### 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
|
## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23
|
||||||
|
|
||||||
|
@ -195,6 +226,13 @@ Theming:
|
||||||
|
|
||||||
- Editing a link created before the new ID system would change its permalink.
|
- Editing a link created before the new ID system would change its permalink.
|
||||||
|
|
||||||
|
## [v0.8.5](https://github.com/shaarli/Shaarli/releases/tag/v0.8.5) - 2018-01-04
|
||||||
|
**XSS vulnerability fixed. Please update.**
|
||||||
|
|
||||||
|
## Security
|
||||||
|
- Fix an XSS (cross-site-scripting) vulnerability in `index.php` -
|
||||||
|
[CVE-2018-5249](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-5249)
|
||||||
|
|
||||||
## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04
|
## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04
|
||||||
### Security
|
### Security
|
||||||
- Markdown plugin: escape HTML entities by default
|
- Markdown plugin: escape HTML entities by default
|
||||||
|
|
30
Makefile
30
Makefile
|
@ -1,17 +1,6 @@
|
||||||
# The personal, minimalist, super-fast, database free, bookmarking service.
|
# The personal, minimalist, super-fast, database free, bookmarking service.
|
||||||
# Makefile for PHP code analysis & testing, documentation and release generation
|
# 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
|
BIN = vendor/bin
|
||||||
PHP_SOURCE = index.php application tests plugins
|
PHP_SOURCE = index.php application tests plugins
|
||||||
PHP_COMMA_SOURCE = index.php,application,tests,plugins
|
PHP_COMMA_SOURCE = index.php,application,tests,plugins
|
||||||
|
@ -115,7 +104,7 @@ check_permissions:
|
||||||
@echo "----------------------"
|
@echo "----------------------"
|
||||||
@echo "Check file permissions"
|
@echo "Check file permissions"
|
||||||
@echo "----------------------"
|
@echo "----------------------"
|
||||||
@for file in `git ls-files`; do \
|
@for file in `git ls-files | grep -v docker`; do \
|
||||||
if [ -x $$file ]; then \
|
if [ -x $$file ]; then \
|
||||||
errors=true; \
|
errors=true; \
|
||||||
echo "$${file} is executable"; \
|
echo "$${file} is executable"; \
|
||||||
|
@ -130,12 +119,12 @@ check_permissions:
|
||||||
# See phpunit.xml for configuration
|
# See phpunit.xml for configuration
|
||||||
# https://phpunit.de/manual/current/en/appendixes.configuration.html
|
# https://phpunit.de/manual/current/en/appendixes.configuration.html
|
||||||
##
|
##
|
||||||
test:
|
test: translate
|
||||||
@echo "-------"
|
@echo "-------"
|
||||||
@echo "PHPUNIT"
|
@echo "PHPUNIT"
|
||||||
@echo "-------"
|
@echo "-------"
|
||||||
@mkdir -p sandbox coverage
|
@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_%:
|
locale_test_%:
|
||||||
@UT_LOCALE=$*.utf8 \
|
@UT_LOCALE=$*.utf8 \
|
||||||
|
@ -168,15 +157,15 @@ composer_dependencies: clean
|
||||||
composer install --no-dev --prefer-dist
|
composer install --no-dev --prefer-dist
|
||||||
find vendor/ -name ".git" -type d -exec rm -rf {} +
|
find vendor/ -name ".git" -type d -exec rm -rf {} +
|
||||||
|
|
||||||
### generate a release tarball and include 3rd-party dependencies
|
### generate a release tarball and include 3rd-party dependencies and translations
|
||||||
release_tar: composer_dependencies htmldoc
|
release_tar: composer_dependencies htmldoc translate
|
||||||
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD
|
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|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/
|
||||||
tar rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/
|
tar rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/
|
||||||
gzip $(ARCHIVE_VERSION).tar
|
gzip $(ARCHIVE_VERSION).tar
|
||||||
|
|
||||||
### generate a release zip and include 3rd-party dependencies
|
### generate a release zip and include 3rd-party dependencies and translations
|
||||||
release_zip: composer_dependencies htmldoc
|
release_zip: composer_dependencies htmldoc translate
|
||||||
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD
|
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD
|
||||||
mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor}
|
mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor}
|
||||||
rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/
|
rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/
|
||||||
|
@ -213,3 +202,8 @@ htmldoc:
|
||||||
mkdocs build'
|
mkdocs build'
|
||||||
find doc/html/ -type f -exec chmod a-x '{}' \;
|
find doc/html/ -type f -exec chmod a-x '{}' \;
|
||||||
rm -r venv
|
rm -r venv
|
||||||
|
|
||||||
|
|
||||||
|
### Generate Shaarli's translation compiled file (.mo)
|
||||||
|
translate:
|
||||||
|
@find inc/languages/ -name shaarli.po -execdir msgfmt shaarli.po -o shaarli.mo \;
|
|
@ -6,10 +6,10 @@ _Do you want to share the links you discover?_
|
||||||
_Shaarli is a minimalist link sharing service that you can install on your own server._
|
_Shaarli is a minimalist link sharing service that you can install on your own server._
|
||||||
_It is designed to be personal (single-user), fast and handy._
|
_It is designed to be personal (single-user), fast and handy._
|
||||||
|
|
||||||
[![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4)
|
[![](https://img.shields.io/badge/stable-v0.8.5-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.5)
|
||||||
[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
|
[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
|
||||||
•
|
•
|
||||||
[![](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.3-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.3)
|
||||||
[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
|
[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
|
||||||
•
|
•
|
||||||
[![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/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)
|
public static function checkPHPVersion($minVersion, $curVersion)
|
||||||
{
|
{
|
||||||
if (version_compare($curVersion, $minVersion) < 0) {
|
if (version_compare($curVersion, $minVersion) < 0) {
|
||||||
throw new Exception(
|
$msg = t(
|
||||||
'Your PHP version is obsolete!'
|
'Your PHP version is obsolete!'
|
||||||
.' Shaarli requires at least PHP '.$minVersion.', and thus cannot run.'
|
. ' Shaarli requires at least PHP %s, and thus cannot run.'
|
||||||
. ' Your PHP version has known security vulnerabilities and should be'
|
. ' Your PHP version has known security vulnerabilities and should be'
|
||||||
. ' updated as soon as possible.'
|
. ' 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'),
|
$rainTplDir.'/'.$conf->get('resource.theme'),
|
||||||
) as $path) {
|
) as $path) {
|
||||||
if (! is_readable(realpath($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'),
|
$conf->get('resource.raintpl_tmp'),
|
||||||
) as $path) {
|
) as $path) {
|
||||||
if (! is_readable(realpath($path))) {
|
if (! is_readable(realpath($path))) {
|
||||||
$errors[] = '"'.$path.'" directory is not readable';
|
$errors[] = '"'.$path.'" '. t('directory is not readable');
|
||||||
}
|
}
|
||||||
if (! is_writable(realpath($path))) {
|
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))) {
|
if (! is_readable(realpath($path))) {
|
||||||
$errors[] = '"'.$path.'" file is not readable';
|
$errors[] = '"'.$path.'" '. t('file is not readable');
|
||||||
}
|
}
|
||||||
if (! is_writable(realpath($path))) {
|
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)
|
function purgeCachedPages($pageCacheDir)
|
||||||
{
|
{
|
||||||
if (! is_dir($pageCacheDir)) {
|
if (! is_dir($pageCacheDir)) {
|
||||||
$error = 'Cannot purge '.$pageCacheDir.': no directory';
|
$error = sprintf(t('Cannot purge %s: no directory'), $pageCacheDir);
|
||||||
error_log($error);
|
error_log($error);
|
||||||
return $error;
|
return $error;
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,11 +148,11 @@ protected function buildItem($link, $pageaddr)
|
||||||
$link['url'] = $pageaddr . $link['url'];
|
$link['url'] = $pageaddr . $link['url'];
|
||||||
}
|
}
|
||||||
if ($this->usePermalinks === true) {
|
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 {
|
} 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;
|
$link['description'] .= PHP_EOL .'<br>— '. $permalink;
|
||||||
|
|
||||||
$pubDate = $link['created'];
|
$pubDate = $link['created'];
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* - UPDATED: link updated
|
* - UPDATED: link updated
|
||||||
* - DELETED: link deleted
|
* - DELETED: link deleted
|
||||||
* - SETTINGS: the settings have been updated through the UI.
|
* - 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.
|
* Note: new events are put at the beginning of the file and history array.
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +42,11 @@ class History
|
||||||
*/
|
*/
|
||||||
const SETTINGS = 'SETTINGS';
|
const SETTINGS = 'SETTINGS';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Action key: a bulk import has been processed.
|
||||||
|
*/
|
||||||
|
const IMPORT = 'IMPORT';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string History file path.
|
* @var string History file path.
|
||||||
*/
|
*/
|
||||||
|
@ -121,6 +127,16 @@ public function updateSettings()
|
||||||
$this->addEvent(self::SETTINGS);
|
$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.
|
* Save a new event and write it in the history file.
|
||||||
*
|
*
|
||||||
|
@ -155,7 +171,7 @@ protected function check()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! is_writable($this->historyFilePath)) {
|
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, []);
|
$this->history = FileUtils::readFlatDB($this->historyFilePath, []);
|
||||||
if ($this->history === false) {
|
if ($this->history === false) {
|
||||||
throw new Exception('Could not parse history file');
|
throw new Exception(t('Could not parse history file'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
* @param string $url URL to get (http://...)
|
* @param string $url URL to get (http://...)
|
||||||
* @param int $timeout network timeout (in seconds)
|
* @param int $timeout network timeout (in seconds)
|
||||||
* @param int $maxBytes maximum downloaded bytes (default: 4 MiB)
|
* @param int $maxBytes maximum downloaded bytes (default: 4 MiB)
|
||||||
|
* @param callable|string $curlWriteFunction Optional callback called during the download (cURL CURLOPT_WRITEFUNCTION).
|
||||||
|
* Can be used to add download conditions on the headers (response code, content type, etc.).
|
||||||
*
|
*
|
||||||
* @return array HTTP response headers, downloaded content
|
* @return array HTTP response headers, downloaded content
|
||||||
*
|
*
|
||||||
|
@ -29,7 +31,7 @@
|
||||||
* @see http://stackoverflow.com/q/9183178
|
* @see http://stackoverflow.com/q/9183178
|
||||||
* @see http://stackoverflow.com/q/1462720
|
* @see http://stackoverflow.com/q/1462720
|
||||||
*/
|
*/
|
||||||
function get_http_response($url, $timeout = 30, $maxBytes = 4194304)
|
function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteFunction = null)
|
||||||
{
|
{
|
||||||
$urlObj = new Url($url);
|
$urlObj = new Url($url);
|
||||||
$cleanUrl = $urlObj->idnToAscii();
|
$cleanUrl = $urlObj->idnToAscii();
|
||||||
|
@ -75,8 +77,12 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304)
|
||||||
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
|
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
|
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
|
||||||
|
|
||||||
|
if (is_callable($curlWriteFunction)) {
|
||||||
|
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $curlWriteFunction);
|
||||||
|
}
|
||||||
|
|
||||||
// Max download size management
|
// Max download size management
|
||||||
curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024);
|
curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16);
|
||||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
||||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION,
|
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION,
|
||||||
function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes)
|
function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes)
|
||||||
|
@ -302,6 +308,13 @@ function server_url($server)
|
||||||
$port = $server['HTTP_X_FORWARDED_PORT'];
|
$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')
|
if (($scheme == 'http' && $port != '80')
|
||||||
|| ($scheme == 'https' && $port != '443')
|
|| ($scheme == 'https' && $port != '443')
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,21 +1,164 @@
|
||||||
<?php
|
<?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
|
* Class Languages
|
||||||
* of gettext()/_() and ngettext().
|
|
||||||
*
|
*
|
||||||
* 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.
|
* Translation files PO/MO files follow gettext standard and must be placed under:
|
||||||
* @param string $nText The plural message ID.
|
* <translation path>/<language>/LC_MESSAGES/<domain>.[po|mo]
|
||||||
* @param int $nb The number of items for plural forms.
|
|
||||||
*
|
*
|
||||||
* @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) {
|
class Languages
|
||||||
if (empty($nText)) {
|
{
|
||||||
return $text;
|
/**
|
||||||
|
* 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"
|
// TODO: use exceptions instead of "die"
|
||||||
if (!$this->loggedIn) {
|
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'])) {
|
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'])) {
|
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']) {
|
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
|
// If the link exists, we reuse the real offset, otherwise new entry
|
||||||
|
@ -248,13 +248,13 @@ private function check()
|
||||||
$this->links = array();
|
$this->links = array();
|
||||||
$link = array(
|
$link = array(
|
||||||
'id' => 1,
|
'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',
|
'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,
|
'private'=>0,
|
||||||
'created'=> new DateTime(),
|
'created'=> new DateTime(),
|
||||||
'tags'=>'opensource software'
|
'tags'=>'opensource software'
|
||||||
|
@ -264,9 +264,9 @@ private function check()
|
||||||
|
|
||||||
$link = array(
|
$link = array(
|
||||||
'id' => 0,
|
'id' => 0,
|
||||||
'title'=>'My secret stuff... - Pastebin.com',
|
'title'=> t('My secret stuff... - Pastebin.com'),
|
||||||
'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
|
'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,
|
'private'=>1,
|
||||||
'created'=> new DateTime('1 minute ago'),
|
'created'=> new DateTime('1 minute ago'),
|
||||||
'tags'=>'secretstuff',
|
'tags'=>'secretstuff',
|
||||||
|
@ -289,13 +289,15 @@ private function read()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->urls = [];
|
||||||
|
$this->ids = [];
|
||||||
$this->links = FileUtils::readFlatDB($this->datastore, []);
|
$this->links = FileUtils::readFlatDB($this->datastore, []);
|
||||||
|
|
||||||
$toremove = array();
|
$toremove = array();
|
||||||
foreach ($this->links as $key => &$link) {
|
foreach ($this->links as $key => &$link) {
|
||||||
if (! $this->loggedIn && $link['private'] != 0) {
|
if (! $this->loggedIn && $link['private'] != 0) {
|
||||||
// Transition for not upgraded databases.
|
// Transition for not upgraded databases.
|
||||||
$toremove[] = $key;
|
unset($this->links[$key]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,14 +331,10 @@ private function read()
|
||||||
}
|
}
|
||||||
$link['shorturl'] = smallHash($link['linkdate']);
|
$link['shorturl'] = smallHash($link['linkdate']);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If user is not logged in, filter private links.
|
$this->urls[$link['url']] = $key;
|
||||||
foreach ($toremove as $offset) {
|
$this->ids[$link['id']] = $key;
|
||||||
unset($this->links[$offset]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->reorder();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -346,6 +344,7 @@ private function read()
|
||||||
*/
|
*/
|
||||||
private function write()
|
private function write()
|
||||||
{
|
{
|
||||||
|
$this->reorder();
|
||||||
FileUtils::writeFlatDB($this->datastore, $this->links);
|
FileUtils::writeFlatDB($this->datastore, $this->links);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,8 +527,8 @@ public function reorder($order = 'DESC')
|
||||||
return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
|
return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->urls = array();
|
$this->urls = [];
|
||||||
$this->ids = array();
|
$this->ids = [];
|
||||||
foreach ($this->links as $key => $link) {
|
foreach ($this->links as $key => $link) {
|
||||||
$this->urls[$link['url']] = $key;
|
$this->urls[$link['url']] = $key;
|
||||||
$this->ids[$link['id']] = $key;
|
$this->ids[$link['id']] = $key;
|
||||||
|
|
|
@ -444,5 +444,11 @@ public static function tagsStrToArray($tags, $casesensitive)
|
||||||
|
|
||||||
class LinkNotFoundException extends Exception
|
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.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,54 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cURL callback function for CURLOPT_WRITEFUNCTION
|
||||||
|
*
|
||||||
|
* @param string $charset to extract from the downloaded page (reference)
|
||||||
|
* @param string $title to extract from the downloaded page (reference)
|
||||||
|
* @param string $curlGetInfo Optionnaly overrides curl_getinfo function
|
||||||
|
*
|
||||||
|
* @return Closure
|
||||||
|
*/
|
||||||
|
function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_getinfo')
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
|
||||||
|
*
|
||||||
|
* While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text'
|
||||||
|
* Then we extract the title and the charset and stop the download when it's done.
|
||||||
|
*
|
||||||
|
* @param resource $ch cURL resource
|
||||||
|
* @param string $data chunk of data being downloaded
|
||||||
|
*
|
||||||
|
* @return int|bool length of $data or false if we need to stop the download
|
||||||
|
*/
|
||||||
|
return function(&$ch, $data) use ($curlGetInfo, &$charset, &$title) {
|
||||||
|
$responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
|
||||||
|
if (!empty($responseCode) && $responseCode != 200) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE);
|
||||||
|
if (!empty($contentType) && strpos($contentType, 'text/html') === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (empty($charset)) {
|
||||||
|
$charset = header_extract_charset($contentType);
|
||||||
|
}
|
||||||
|
if (empty($charset)) {
|
||||||
|
$charset = html_extract_charset($data);
|
||||||
|
}
|
||||||
|
if (empty($title)) {
|
||||||
|
$title = html_extract_title($data);
|
||||||
|
}
|
||||||
|
// We got everything we want, stop the download.
|
||||||
|
if (!empty($responseCode) && !empty($contentType) && !empty($charset) && !empty($title)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strlen($data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract title from an HTML document.
|
* Extract title from an HTML document.
|
||||||
*
|
*
|
||||||
|
@ -16,46 +65,18 @@ function html_extract_title($html)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine charset from downloaded page.
|
* Extract charset from HTTP header if it's defined.
|
||||||
* Priority:
|
|
||||||
* 1. HTTP headers (Content type).
|
|
||||||
* 2. HTML content page (tag <meta charset>).
|
|
||||||
* 3. Use a default charset (default: UTF-8).
|
|
||||||
*
|
*
|
||||||
* @param array $headers HTTP headers array.
|
* @param string $header HTTP header Content-Type line.
|
||||||
* @param string $htmlContent HTML content where to look for charset.
|
|
||||||
* @param string $defaultCharset Default charset to apply if other methods failed.
|
|
||||||
*
|
|
||||||
* @return string Determined charset.
|
|
||||||
*/
|
|
||||||
function get_charset($headers, $htmlContent, $defaultCharset = 'utf-8')
|
|
||||||
{
|
|
||||||
if ($charset = headers_extract_charset($headers)) {
|
|
||||||
return $charset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($charset = html_extract_charset($htmlContent)) {
|
|
||||||
return $charset;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $defaultCharset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract charset from HTTP headers if it's defined.
|
|
||||||
*
|
|
||||||
* @param array $headers HTTP headers array.
|
|
||||||
*
|
*
|
||||||
* @return bool|string Charset string if found (lowercase), false otherwise.
|
* @return bool|string Charset string if found (lowercase), false otherwise.
|
||||||
*/
|
*/
|
||||||
function headers_extract_charset($headers)
|
function header_extract_charset($header)
|
||||||
{
|
{
|
||||||
if (! empty($headers['Content-Type']) && strpos($headers['Content-Type'], 'charset=') !== false) {
|
preg_match('/charset="?([^; ]+)/i', $header, $match);
|
||||||
preg_match('/charset="?([^; ]+)/i', $headers['Content-Type'], $match);
|
|
||||||
if (! empty($match[1])) {
|
if (! empty($match[1])) {
|
||||||
return strtolower(trim($match[1]));
|
return strtolower(trim($match[1]));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -102,12 +123,13 @@ function count_private($links)
|
||||||
*
|
*
|
||||||
* @param string $text input string.
|
* @param string $text input string.
|
||||||
* @param string $redirector if a redirector is set, use it to gerenate links.
|
* @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.
|
* @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
|
* @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';
|
$regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si';
|
||||||
|
|
||||||
|
@ -117,8 +139,9 @@ function text2clickable($text, $redirector = '')
|
||||||
// Redirector is set, urlencode the final URL.
|
// Redirector is set, urlencode the final URL.
|
||||||
return preg_replace_callback(
|
return preg_replace_callback(
|
||||||
$regex,
|
$regex,
|
||||||
function ($matches) use ($redirector) {
|
function ($matches) use ($redirector, $urlEncode) {
|
||||||
return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>';
|
$url = $urlEncode ? urlencode($matches[1]) : $matches[1];
|
||||||
|
return '<a href="' . $redirector . $url .'">'. $matches[1] .'</a>';
|
||||||
},
|
},
|
||||||
$text
|
$text
|
||||||
);
|
);
|
||||||
|
@ -164,12 +187,13 @@ function space2nbsp($text)
|
||||||
*
|
*
|
||||||
* @param string $description shaare's description.
|
* @param string $description shaare's description.
|
||||||
* @param string $redirector if a redirector is set, use it to gerenate links.
|
* @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.
|
* @param string $indexUrl URL to Shaarli's index.
|
||||||
*
|
|
||||||
* @return string formatted description.
|
* @return string formatted description.
|
||||||
*/
|
*/
|
||||||
function format_description($description, $redirector = '', $indexUrl = '') {
|
function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '') {
|
||||||
return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $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
|
// see tpl/export.html for possible values
|
||||||
if (! in_array($selection, array('all', 'public', 'private'))) {
|
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();
|
$bookmarkLinks = array();
|
||||||
|
|
||||||
foreach ($linkDb as $link) {
|
foreach ($linkDb as $link) {
|
||||||
if ($link['private'] != 0 && $selection == 'public') {
|
if ($link['private'] != 0 && $selection == 'public') {
|
||||||
continue;
|
continue;
|
||||||
|
@ -66,6 +65,7 @@ public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $in
|
||||||
* @param int $importCount how many links were imported
|
* @param int $importCount how many links were imported
|
||||||
* @param int $overwriteCount how many links were overwritten
|
* @param int $overwriteCount how many links were overwritten
|
||||||
* @param int $skipCount how many links were skipped
|
* @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
|
* @return string Summary of the bookmark import status
|
||||||
*/
|
*/
|
||||||
|
@ -74,16 +74,18 @@ private static function importStatus(
|
||||||
$filesize,
|
$filesize,
|
||||||
$importCount=0,
|
$importCount=0,
|
||||||
$overwriteCount=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) {
|
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 {
|
} else {
|
||||||
$status .= 'was successfully processed: '.$importCount.' links imported, ';
|
$status .= vsprintf(
|
||||||
$status .= $overwriteCount.' links overwritten, ';
|
t('was successfully processed in %d seconds: %d links imported, %d links overwritten, %d links skipped.'),
|
||||||
$status .= $skipCount.' links skipped.';
|
[$duration, $importCount, $overwriteCount, $skipCount]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return $status;
|
return $status;
|
||||||
}
|
}
|
||||||
|
@ -101,6 +103,7 @@ private static function importStatus(
|
||||||
*/
|
*/
|
||||||
public static function import($post, $files, $linkDb, $conf, $history)
|
public static function import($post, $files, $linkDb, $conf, $history)
|
||||||
{
|
{
|
||||||
|
$start = time();
|
||||||
$filename = $files['filetoupload']['name'];
|
$filename = $files['filetoupload']['name'];
|
||||||
$filesize = $files['filetoupload']['size'];
|
$filesize = $files['filetoupload']['size'];
|
||||||
$data = file_get_contents($files['filetoupload']['tmp_name']);
|
$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;
|
$linkDb[$existingLink['id']] = $newLink;
|
||||||
$importCount++;
|
$importCount++;
|
||||||
$overwriteCount++;
|
$overwriteCount++;
|
||||||
$history->updateLink($newLink);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,16 +198,19 @@ public static function import($post, $files, $linkDb, $conf, $history)
|
||||||
$newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
|
$newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
|
||||||
$linkDb[$newLink['id']] = $newLink;
|
$linkDb[$newLink['id']] = $newLink;
|
||||||
$importCount++;
|
$importCount++;
|
||||||
$history->addLink($newLink);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$linkDb->save($conf->get('resource.page_cache'));
|
$linkDb->save($conf->get('resource.page_cache'));
|
||||||
|
$history->importLinks();
|
||||||
|
|
||||||
|
$duration = time() - $start;
|
||||||
return self::importStatus(
|
return self::importStatus(
|
||||||
$filename,
|
$filename,
|
||||||
$filesize,
|
$filesize,
|
||||||
$importCount,
|
$importCount,
|
||||||
$overwriteCount,
|
$overwriteCount,
|
||||||
$skipCount
|
$skipCount,
|
||||||
|
$duration
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,12 +32,14 @@ class PageBuilder
|
||||||
*
|
*
|
||||||
* @param ConfigManager $conf Configuration Manager instance (reference).
|
* @param ConfigManager $conf Configuration Manager instance (reference).
|
||||||
* @param LinkDB $linkDB instance.
|
* @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->tpl = false;
|
||||||
$this->conf = $conf;
|
$this->conf = $conf;
|
||||||
$this->linkDB = $linkDB;
|
$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('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('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('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) {
|
if ($this->linkDB !== null) {
|
||||||
$this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
|
$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
|
* @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->tpl->assign('error_message', $message);
|
||||||
$this->renderPage('404');
|
$this->renderPage('404');
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,6 +188,9 @@ public function getPluginsMeta()
|
||||||
$metaData[$plugin] = parse_ini_file($metaFile);
|
$metaData[$plugin] = parse_ini_file($metaFile);
|
||||||
$metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins);
|
$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.
|
// Read parameters and format them into an array.
|
||||||
if (isset($metaData[$plugin]['parameters'])) {
|
if (isset($metaData[$plugin]['parameters'])) {
|
||||||
$params = explode(';', $metaData[$plugin]['parameters']);
|
$params = explode(';', $metaData[$plugin]['parameters']);
|
||||||
|
@ -203,7 +206,7 @@ public function getPluginsMeta()
|
||||||
$metaData[$plugin]['parameters'][$param]['value'] = '';
|
$metaData[$plugin]['parameters'][$param]['value'] = '';
|
||||||
// Optional parameter description in parameter.PARAM_NAME=
|
// Optional parameter description in parameter.PARAM_NAME=
|
||||||
if (isset($metaData[$plugin]['parameter.'. $param])) {
|
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)
|
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) {
|
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) {
|
foreach ($this->methods as $method) {
|
||||||
|
@ -436,6 +436,15 @@ public function updateMethodResetHistoryFile()
|
||||||
}
|
}
|
||||||
return true;
|
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)) {
|
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)) {
|
if (! empty($this->previous)) {
|
||||||
|
@ -522,11 +531,11 @@ function read_updates_file($updatesFilepath)
|
||||||
function write_updates_file($updatesFilepath, $updates)
|
function write_updates_file($updatesFilepath, $updates)
|
||||||
{
|
{
|
||||||
if (empty($updatesFilepath)) {
|
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));
|
$res = file_put_contents($updatesFilepath, implode(';', $updates));
|
||||||
if ($res === false) {
|
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;
|
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.
|
* Sniff browser language to set the locale automatically.
|
||||||
* Note that is may not work on your server if the corresponding locale is not installed.
|
* Note that is may not work on your server if the corresponding locale is not installed.
|
||||||
|
@ -470,3 +440,18 @@ function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
|
||||||
usort($data, $callback);
|
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);
|
$data = json_decode($data, true);
|
||||||
if ($data === null) {
|
if ($data === null) {
|
||||||
$errorCode = json_last_error();
|
$errorCode = json_last_error();
|
||||||
$error = 'An error occurred while parsing JSON configuration file ('. $filepath .'): error code #';
|
$error = sprintf(
|
||||||
$error .= $errorCode. '<br>➜ <code>' . json_last_error_msg() .'</code>';
|
'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) {
|
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>.';
|
$error .= '<a href="http://jsonlint.com/">jsonlint.com</a>.';
|
||||||
}
|
}
|
||||||
throw new \Exception($error);
|
throw new \Exception($error);
|
||||||
|
@ -44,8 +49,8 @@ public function write($filepath, $conf)
|
||||||
if (!file_put_contents($filepath, $data)) {
|
if (!file_put_contents($filepath, $data)) {
|
||||||
throw new \IOException(
|
throw new \IOException(
|
||||||
$filepath,
|
$filepath,
|
||||||
'Shaarli could not create the config file.
|
t('Shaarli could not create the config file. '.
|
||||||
Please make sure Shaarli has the right to write in the folder is it installed in.'
|
'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)
|
public function set($setting, $value, $write = false, $isLoggedIn = false)
|
||||||
{
|
{
|
||||||
if (empty($setting) || ! is_string($setting)) {
|
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.
|
// 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.url', '');
|
||||||
$this->setEmpty('redirector.encode_url', true);
|
$this->setEmpty('redirector.encode_url', true);
|
||||||
|
|
||||||
|
$this->setEmpty('translation.language', 'auto');
|
||||||
|
$this->setEmpty('translation.mode', 'php');
|
||||||
|
$this->setEmpty('translation.extensions', []);
|
||||||
|
|
||||||
$this->setEmpty('plugins', array());
|
$this->setEmpty('plugins', array());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,8 +118,8 @@ public function write($filepath, $conf)
|
||||||
) {
|
) {
|
||||||
throw new \IOException(
|
throw new \IOException(
|
||||||
$filepath,
|
$filepath,
|
||||||
'Shaarli could not create the config file.
|
t('Shaarli could not create the config file. '.
|
||||||
Please make sure Shaarli has the right to write in the folder is it installed in.'
|
'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)
|
public function __construct($field)
|
||||||
{
|
{
|
||||||
$this->field = $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()
|
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()
|
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 = '')
|
public function __construct($path, $message = '')
|
||||||
{
|
{
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
$this->message = empty($message) ? 'Error accessing' : $message;
|
$this->message = empty($message) ? t('Error accessing') : $message;
|
||||||
$this->message .= ' "' . $this->path .'"';
|
$this->message .= ' "' . $this->path .'"';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
"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",
|
||||||
"pubsubhubbub/publisher": "dev-master"
|
"pubsubhubbub/publisher": "dev-master",
|
||||||
|
"gettext/gettext": "^4.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpmd/phpmd" : "@stable",
|
"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",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "68beedbfa104c788029b079800cfd6e8",
|
"content-hash": "13b7e1e474fe9264b098ba86face0feb",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "container-interop/container-interop",
|
"name": "container-interop/container-interop",
|
||||||
|
@ -76,6 +76,129 @@
|
||||||
],
|
],
|
||||||
"time": "2015-10-04T16:44:32+00:00"
|
"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/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Michele Locati",
|
||||||
|
"email": "mlocati@gmail.com",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "gettext languages with plural rules",
|
||||||
|
"homepage": "https://github.com/mlocati/cldr-to-gettext-plural-rules",
|
||||||
|
"keywords": [
|
||||||
|
"cldr",
|
||||||
|
"i18n",
|
||||||
|
"internationalization",
|
||||||
|
"l10n",
|
||||||
|
"language",
|
||||||
|
"languages",
|
||||||
|
"localization",
|
||||||
|
"php",
|
||||||
|
"plural",
|
||||||
|
"plural rules",
|
||||||
|
"plurals",
|
||||||
|
"translate",
|
||||||
|
"translations",
|
||||||
|
"unicode"
|
||||||
|
],
|
||||||
|
"time": "2017-03-23T17:02:28+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "katzgrau/klogger",
|
"name": "katzgrau/klogger",
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
|
@ -371,12 +494,12 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/pubsubhubbub/php-publisher.git",
|
"url": "https://github.com/pubsubhubbub/php-publisher.git",
|
||||||
"reference": "a5d6a0e1cc9d49101c3904480e5b06cbb8addba7"
|
"reference": "0d224daebd504ab61c22fee4db58f8d1fc18945f"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/a5d6a0e1cc9d49101c3904480e5b06cbb8addba7",
|
"url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/0d224daebd504ab61c22fee4db58f8d1fc18945f",
|
||||||
"reference": "a5d6a0e1cc9d49101c3904480e5b06cbb8addba7",
|
"reference": "0d224daebd504ab61c22fee4db58f8d1fc18945f",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -406,7 +529,7 @@
|
||||||
"publishers",
|
"publishers",
|
||||||
"pubsubhubbub"
|
"pubsubhubbub"
|
||||||
],
|
],
|
||||||
"time": "2016-11-15T06:24:01+00:00"
|
"time": "2017-10-08T10:59:41+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "shaarli/netscape-bookmark-parser",
|
"name": "shaarli/netscape-bookmark-parser",
|
||||||
|
@ -632,16 +755,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpdocumentor/reflection-common",
|
"name": "phpdocumentor/reflection-common",
|
||||||
"version": "1.0",
|
"version": "1.0.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
|
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
|
||||||
"reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
|
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
|
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
|
||||||
"reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
|
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -682,20 +805,20 @@
|
||||||
"reflection",
|
"reflection",
|
||||||
"static analysis"
|
"static analysis"
|
||||||
],
|
],
|
||||||
"time": "2015-12-27T11:43:31+00:00"
|
"time": "2017-09-11T18:02:19+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpdocumentor/reflection-docblock",
|
"name": "phpdocumentor/reflection-docblock",
|
||||||
"version": "3.2.1",
|
"version": "3.2.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
|
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
|
||||||
"reference": "183824db76118b9dddffc7e522b91fa175f75119"
|
"reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/183824db76118b9dddffc7e522b91fa175f75119",
|
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/4aada1f93c72c35e22fb1383b47fee43b8f1d157",
|
||||||
"reference": "183824db76118b9dddffc7e522b91fa175f75119",
|
"reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -727,7 +850,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
|
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
|
||||||
"time": "2017-08-04T20:55:59+00:00"
|
"time": "2017-08-08T06:39:58+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpdocumentor/type-resolver",
|
"name": "phpdocumentor/type-resolver",
|
||||||
|
@ -844,22 +967,22 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpspec/prophecy",
|
"name": "phpspec/prophecy",
|
||||||
"version": "v1.7.0",
|
"version": "v1.7.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpspec/prophecy.git",
|
"url": "https://github.com/phpspec/prophecy.git",
|
||||||
"reference": "93d39f1f7f9326d746203c7c056f300f7f126073"
|
"reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073",
|
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
|
||||||
"reference": "93d39f1f7f9326d746203c7c056f300f7f126073",
|
"reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
|
||||||
"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",
|
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
|
||||||
"sebastian/comparator": "^1.1|^2.0",
|
"sebastian/comparator": "^1.1|^2.0",
|
||||||
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
|
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
|
||||||
},
|
},
|
||||||
|
@ -870,7 +993,7 @@
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.6.x-dev"
|
"dev-master": "1.7.x-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -903,7 +1026,7 @@
|
||||||
"spy",
|
"spy",
|
||||||
"stub"
|
"stub"
|
||||||
],
|
],
|
||||||
"time": "2017-03-02T20:05:34+00:00"
|
"time": "2017-09-04T11:05:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
|
@ -1875,20 +1998,20 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/config",
|
"name": "symfony/config",
|
||||||
"version": "v3.3.6",
|
"version": "v3.3.10",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/config.git",
|
"url": "https://github.com/symfony/config.git",
|
||||||
"reference": "54ee12b0dd60f294132cabae6f5da9573d2e5297"
|
"reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/config/zipball/54ee12b0dd60f294132cabae6f5da9573d2e5297",
|
"url": "https://api.github.com/repos/symfony/config/zipball/4ab62407bff9cd97c410a7feaef04c375aaa5cfd",
|
||||||
"reference": "54ee12b0dd60f294132cabae6f5da9573d2e5297",
|
"reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.5.9",
|
"php": "^5.5.9|>=7.0.8",
|
||||||
"symfony/filesystem": "~2.8|~3.0"
|
"symfony/filesystem": "~2.8|~3.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
|
@ -1933,20 +2056,20 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony Config Component",
|
"description": "Symfony Config Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2017-07-19T07:37:29+00:00"
|
"time": "2017-10-04T18:56:58+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/console",
|
"name": "symfony/console",
|
||||||
"version": "v2.8.26",
|
"version": "v2.8.28",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/console.git",
|
"url": "https://github.com/symfony/console.git",
|
||||||
"reference": "32a3c6b3398de5db8ed381f4ef92970c59c2fcdd"
|
"reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/console/zipball/32a3c6b3398de5db8ed381f4ef92970c59c2fcdd",
|
"url": "https://api.github.com/repos/symfony/console/zipball/f81549d2c5fdee8d711c9ab3c7e7362353ea5853",
|
||||||
"reference": "32a3c6b3398de5db8ed381f4ef92970c59c2fcdd",
|
"reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1994,7 +2117,7 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony Console Component",
|
"description": "Symfony Console Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2017-07-29T21:26:04+00:00"
|
"time": "2017-10-01T21:00:16+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/debug",
|
"name": "symfony/debug",
|
||||||
|
@ -2055,20 +2178,20 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/dependency-injection",
|
"name": "symfony/dependency-injection",
|
||||||
"version": "v3.3.6",
|
"version": "v3.3.10",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/dependency-injection.git",
|
"url": "https://github.com/symfony/dependency-injection.git",
|
||||||
"reference": "8d70987f991481e809c63681ffe8ce3f3fde68a0"
|
"reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8d70987f991481e809c63681ffe8ce3f3fde68a0",
|
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8ebad929aee3ca185b05f55d9cc5521670821ad1",
|
||||||
"reference": "8d70987f991481e809c63681ffe8ce3f3fde68a0",
|
"reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.5.9",
|
"php": "^5.5.9|>=7.0.8",
|
||||||
"psr/container": "^1.0"
|
"psr/container": "^1.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
|
@ -2121,24 +2244,24 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony DependencyInjection Component",
|
"description": "Symfony DependencyInjection Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2017-07-28T15:27:31+00:00"
|
"time": "2017-10-04T17:15:30+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/filesystem",
|
"name": "symfony/filesystem",
|
||||||
"version": "v3.3.6",
|
"version": "v3.3.10",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/filesystem.git",
|
"url": "https://github.com/symfony/filesystem.git",
|
||||||
"reference": "427987eb4eed764c3b6e38d52a0f87989e010676"
|
"reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676",
|
"url": "https://api.github.com/repos/symfony/filesystem/zipball/90bc45abf02ae6b7deb43895c1052cb0038506f1",
|
||||||
"reference": "427987eb4eed764c3b6e38d52a0f87989e010676",
|
"reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.5.9"
|
"php": "^5.5.9|>=7.0.8"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
|
@ -2170,24 +2293,24 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony Filesystem Component",
|
"description": "Symfony Filesystem Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2017-07-11T07:17:58+00:00"
|
"time": "2017-10-03T13:33:10+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/finder",
|
"name": "symfony/finder",
|
||||||
"version": "v3.3.6",
|
"version": "v3.3.10",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/finder.git",
|
"url": "https://github.com/symfony/finder.git",
|
||||||
"reference": "baea7f66d30854ad32988c11a09d7ffd485810c4"
|
"reference": "773e19a491d97926f236942484cb541560ce862d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4",
|
"url": "https://api.github.com/repos/symfony/finder/zipball/773e19a491d97926f236942484cb541560ce862d",
|
||||||
"reference": "baea7f66d30854ad32988c11a09d7ffd485810c4",
|
"reference": "773e19a491d97926f236942484cb541560ce862d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.5.9"
|
"php": "^5.5.9|>=7.0.8"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
|
@ -2219,20 +2342,20 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony Finder Component",
|
"description": "Symfony Finder Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2017-06-01T21:01:25+00:00"
|
"time": "2017-10-02T06:42:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.4.0",
|
"version": "v1.6.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
"reference": "f29dca382a6485c3cbe6379f0c61230167681937"
|
"reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937",
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
|
||||||
"reference": "f29dca382a6485c3cbe6379f0c61230167681937",
|
"reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2244,7 +2367,7 @@
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.4-dev"
|
"dev-master": "1.6-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -2278,24 +2401,24 @@
|
||||||
"portable",
|
"portable",
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"time": "2017-06-09T14:24:12+00:00"
|
"time": "2017-10-11T12:05:26+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/yaml",
|
"name": "symfony/yaml",
|
||||||
"version": "v3.3.6",
|
"version": "v3.3.10",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/yaml.git",
|
"url": "https://github.com/symfony/yaml.git",
|
||||||
"reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed"
|
"reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/ddc23324e6cfe066f3dd34a37ff494fa80b617ed",
|
"url": "https://api.github.com/repos/symfony/yaml/zipball/8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46",
|
||||||
"reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed",
|
"reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.5.9"
|
"php": "^5.5.9|>=7.0.8"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/console": "~2.8|~3.0"
|
"symfony/console": "~2.8|~3.0"
|
||||||
|
@ -2333,7 +2456,7 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony Yaml Component",
|
"description": "Symfony Yaml Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2017-07-23T12:43:26+00:00"
|
"time": "2017-10-05T14:43:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "theseer/fdomdocument",
|
"name": "theseer/fdomdocument",
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
<IfModule version_module>
|
<IfModule version_module>
|
||||||
<IfVersion >= 2.4>
|
<IfVersion >= 2.4>
|
||||||
Require all denied
|
Require all denied
|
||||||
|
<Files "user.css">
|
||||||
|
Require all granted
|
||||||
|
</Files>
|
||||||
</IfVersion>
|
</IfVersion>
|
||||||
<IfVersion < 2.4>
|
<IfVersion < 2.4>
|
||||||
Allow from none
|
Allow from none
|
||||||
Deny from all
|
Deny from all
|
||||||
|
<Files "user.css">
|
||||||
|
Allow from all
|
||||||
|
</Files>
|
||||||
</IfVersion>
|
</IfVersion>
|
||||||
</IfModule>
|
</IfModule>
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,10 @@ Shaarli cannot import data directly from [Scuttle](https://github.com/scronide/s
|
||||||
However, you can use the third-party [scuttle-to-shaarli](https://github.com/q2apro/scuttle-to-shaarli)
|
However, you can use the third-party [scuttle-to-shaarli](https://github.com/q2apro/scuttle-to-shaarli)
|
||||||
tool to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer.
|
tool to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer.
|
||||||
|
|
||||||
|
### Refind
|
||||||
|
|
||||||
|
You can use the third-party tool [Derefind](https://github.com/ShawnPConroy/Derefind) to convert refind.com bookmark exports to a format that can be imported into Shaarli.
|
||||||
|
|
||||||
## Import Shaarli links to Firefox
|
## Import Shaarli links to Firefox
|
||||||
|
|
||||||
- Export your Shaarli links as described above.
|
- Export your Shaarli links as described above.
|
||||||
|
|
|
@ -21,7 +21,7 @@ _This bookmarklet button is compatible with Firefox, Opera, Chrome and Safari. U
|
||||||
|
|
||||||
Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunatly, there is nothing Shaarli can do about it.
|
Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunatly, there is nothing Shaarli can do about it.
|
||||||
|
|
||||||
See [#196](https://github.com/shaarli/Shaarli#196).
|
See [#196](https://github.com/shaarli/Shaarli/issues/196).
|
||||||
|
|
||||||
There is an open bug for both Firefox and Chromium:
|
There is an open bug for both Firefox and Chromium:
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,24 @@ Use the `Filter by tags` field to restrict displayed links to entries tagged wit
|
||||||
|
|
||||||
**Hidden tags:** Tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in.
|
**Hidden tags:** Tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in.
|
||||||
|
|
||||||
Alternatively you can use the `Tag cloud` to discover all tags and click on any of them to display related links.
|
### Tag cloud
|
||||||
|
|
||||||
To search for links that are not tagged, enter `""` in the tag search field.
|
The `Tag cloud` page diplays a "cloud" view of all tags in your Shaarli.
|
||||||
|
|
||||||
|
* The most frequently used tags are displayed with a bigger font size.
|
||||||
|
* When sorting by `Most used` or `Alphabetical`, tags are displayed as a _list_, along with counters and edit/delete buttons for each tag.
|
||||||
|
* Clicking on any tag will display a list of all Shaares matching this tag.
|
||||||
|
* Clicking on the counter next to a tag `example`, will filter the tag cloud to only display tags found in Shaares tagged `example`. Repeat this any number of times to further filter the tag cloud. Click `List all links with those tags` to display Shaares matching your current tag filter.
|
||||||
|
|
||||||
## Filtering RSS feeds/Picture wall
|
## Filtering RSS feeds/Picture wall
|
||||||
|
|
||||||
RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS feeds).
|
RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS-feeds).
|
||||||
|
|
||||||
|
## Filter buttons
|
||||||
|
|
||||||
|
Filter buttons can be found at the top left of the link list. They allow you to apply different filters to the list:
|
||||||
|
|
||||||
|
* **Private links:** When this toggle button is enabled, only shaares set to `private` will be shown.
|
||||||
|
* **Untagged links:** When the this toggle button is enabled (top left of the link list), only shaares _without any tags_ will be shown in the link list.
|
||||||
|
|
||||||
|
Filter buttons are only available when logged in.
|
||||||
|
|
|
@ -1,6 +1,59 @@
|
||||||
_Unofficial but related work on Shaarli. If you maintain one of these,
|
_Unofficial but related work on Shaarli. If you maintain one of these,
|
||||||
please get in touch with us to help us find a way to adapt your work to our fork._
|
please get in touch with us to help us find a way to adapt your work to our fork._
|
||||||
|
|
||||||
|
## Related software
|
||||||
|
|
||||||
|
|
||||||
|
### REST API clients
|
||||||
|
See [REST API](REST-API) for a list of official and community clients.
|
||||||
|
|
||||||
|
|
||||||
|
### Third party plugins
|
||||||
|
- [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.
|
||||||
|
- [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.
|
||||||
|
- [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.
|
||||||
|
- [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.
|
||||||
|
- [twemoji](https://github.com/NerosTie/twemoji) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli (Twemoji version)
|
||||||
|
- [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support
|
||||||
|
- [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.
|
||||||
|
- [markdown-toolbar](https://github.com/immanuelfodor/shaarli-markdown-toolbar) by [@immanuelfodor](https://github.com/immanuelfodor) - Easily insert markdown syntax into the Description field when editing a link.
|
||||||
|
- [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related links based on the number of identical tags.
|
||||||
|
- [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.
|
||||||
|
- [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli
|
||||||
|
- [shaarli2mastodon](https://github.com/kalvn/shaarli2mastodon) by [@kalvn](https://github.com/kalvn) - This Shaarli plugin allows you to automatically publish links you post on your Mastodon timeline.
|
||||||
|
- [shaarli-descriptor](https://github.com/immanuelfodor/shaarli-descriptor) by [@immanuelfodor](https://github.com/immanuelfodor) - Customize the default height/number of rows of the Description field when editing a link.
|
||||||
|
|
||||||
|
|
||||||
|
### Third-party themes
|
||||||
|
See [Theming](Theming) for a list of community-contributed themes, and an installation guide.
|
||||||
|
|
||||||
|
|
||||||
|
### Integration with other platforms
|
||||||
|
- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [Tiny-Tiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli
|
||||||
|
- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar
|
||||||
|
- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle
|
||||||
|
|
||||||
|
|
||||||
|
### Mobile Apps
|
||||||
|
- [ShaarliOS](https://github.com/mro/ShaarliOS) - Apple iOS share extension.
|
||||||
|
- [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider
|
||||||
|
- [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add links directly into your Shaarli
|
||||||
|
|
||||||
|
### Browser addons
|
||||||
|
* [Shaarli Web Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli.
|
||||||
|
|
||||||
|
### Server apps
|
||||||
|
- [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content
|
||||||
|
- [shaarli-river](https://github.com/mknexen/shaarli-river) - An aggregator for shaarlis with many features
|
||||||
|
- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among French shaarliers: [shaarli.fr](http://shaarli.fr/))
|
||||||
|
- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis
|
||||||
|
- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli
|
||||||
|
- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).
|
||||||
|
- [Bookmark Archiver](https://github.com/pirate/bookmark-archiver) - Save an archived copy of all websites starred using browser bookmarks/Shaarli/Delicious/Instapaper/Unmark.it/Pocket/Pinboard. Outputs browseable html.
|
||||||
|
|
||||||
|
## Alternatives to Shaarli
|
||||||
|
See [awesome-selfhosted: bookmarks & link sharing](https://github.com/Kickball/awesome-selfhosted/#bookmarks--link-sharing).
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
- [Liens en vrac de sebsauvage](http://sebsauvage.net/links/) - the original Shaarli
|
- [Liens en vrac de sebsauvage](http://sebsauvage.net/links/) - the original Shaarli
|
||||||
- [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html)
|
- [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html)
|
||||||
|
@ -12,56 +65,8 @@ please get in touch with us to help us find a way to adapt your work to our fork
|
||||||
- [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
|
- [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
|
||||||
- [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni)
|
- [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni)
|
||||||
|
|
||||||
|
|
||||||
### Articles and social media discussions
|
### Articles and social media discussions
|
||||||
- 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176
|
- 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176
|
||||||
- 2015-08-15 - Reddit - [Question about migrating from WordPress to Shaarli.](https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/)
|
- 2015-08-15 - Reddit - [Question about migrating from WordPress to Shaarli.](https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/)
|
||||||
- 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366
|
- 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366
|
||||||
- 2015-05-12 - Reddit - [shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)](https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/)
|
- 2015-05-12 - Reddit - [shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)](https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/)
|
||||||
|
|
||||||
|
|
||||||
### REST API clients
|
|
||||||
See [REST API](REST-API) for a list of official and community clients.
|
|
||||||
|
|
||||||
|
|
||||||
### Third party plugins
|
|
||||||
- [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.
|
|
||||||
- [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.
|
|
||||||
- [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.
|
|
||||||
- [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.
|
|
||||||
- [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support
|
|
||||||
- [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.
|
|
||||||
- [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related links based on the number of identical tags.
|
|
||||||
- [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.
|
|
||||||
- [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli
|
|
||||||
|
|
||||||
|
|
||||||
### Third-party themes
|
|
||||||
See [Theming](Theming) for a list of community-contributed themes, and an installation guide.
|
|
||||||
|
|
||||||
|
|
||||||
## Integration with other platforms
|
|
||||||
- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [Tiny-Tiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli
|
|
||||||
- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar
|
|
||||||
- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle
|
|
||||||
|
|
||||||
|
|
||||||
### Mobile Apps
|
|
||||||
- [ShaarliOS](https://github.com/mro/ShaarliOS) iOS share extension - see [#308](https://github.com/shaarli/Shaarli/issues/308#issuecomment-184592070) for some promo codes,
|
|
||||||
- [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider
|
|
||||||
- [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add links directly into your Shaarli
|
|
||||||
|
|
||||||
|
|
||||||
### Server apps
|
|
||||||
- [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content
|
|
||||||
- [shaarli-river](https://github.com/mknexen/shaarli-river) - An aggregator for shaarlis with many features
|
|
||||||
- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among French shaarliers: [shaarli.fr](http://shaarli.fr/))
|
|
||||||
- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis
|
|
||||||
- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli
|
|
||||||
- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).
|
|
||||||
- [Bookmark Archiver](https://github.com/pirate/bookmark-archiver) - Save an archived copy of all websites starred using browser bookmarks/Shaarli/Delicious/Instapaper/Unmark.it/Pocket/Pinboard. Outputs browseable html.
|
|
||||||
|
|
||||||
|
|
||||||
## Alternatives to Shaarli
|
|
||||||
See the [bookmarks & link sharing](https://github.com/Kickball/awesome-selfhosted/#bookmarks--link-sharing)
|
|
||||||
section on [awesome-selfhosted](https://github.com/Kickball/awesome-selfhosted/).
|
|
||||||
|
|
|
@ -4,44 +4,57 @@ Document Root (or directly at the document root).
|
||||||
Also, please make sure your server meets the [requirements](Server-requirements)
|
Also, please make sure your server meets the [requirements](Server-requirements)
|
||||||
and is properly [configured](Server-configuration).
|
and is properly [configured](Server-configuration).
|
||||||
|
|
||||||
Several releases are available:
|
Multiple releases branches are available:
|
||||||
|
|
||||||
|
- latest (last release)
|
||||||
|
- stable (previous major release)
|
||||||
|
- master (development)
|
||||||
|
|
||||||
|
Using one of the following methods:
|
||||||
|
|
||||||
- by downloading full release archives including all dependencies
|
- by downloading full release archives including all dependencies
|
||||||
- by downloading Github archives
|
- by downloading Github archives
|
||||||
- by cloning the Git repository
|
- by cloning the Git repository
|
||||||
|
- using Docker: [see the documentation](docker/shaarli-images.md)
|
||||||
|
|
||||||
---
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
## Latest release (recommended)
|
## Latest release (recommended)
|
||||||
|
|
||||||
### Download as an archive
|
### Download as an archive
|
||||||
Get the latest released version from the [releases](https://github.com/shaarli/Shaarli/releases) page.
|
|
||||||
|
|
||||||
**Download our *shaarli-full* archive** to include dependencies.
|
In most cases, you should download the latest Shaarli release from the [releases](https://github.com/shaarli/Shaarli/releases) page. **Download our *shaarli-full* archive** to include dependencies.
|
||||||
|
|
||||||
The current latest released version is `v0.9.1`
|
The current latest released version is `v0.9.3`
|
||||||
|
|
||||||
Or in command lines:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ wget https://github.com/shaarli/Shaarli/releases/download/v0.9.1/shaarli-v0.9.1-full.zip
|
$ wget https://github.com/shaarli/Shaarli/releases/download/v0.9.3/shaarli-v0.9.3-full.zip
|
||||||
$ unzip shaarli-v0.9.1-full.zip
|
$ unzip shaarli-v0.9.3-full.zip
|
||||||
$ mv Shaarli /path/to/shaarli/
|
$ mv Shaarli /path/to/shaarli/
|
||||||
```
|
```
|
||||||
|
|
||||||
In most cases, download Shaarli from the [releases](https://github.com/shaarli/Shaarli/releases) page. Cloning using `git` or downloading Github branches as zip files requires additional steps (see below).|
|
|
||||||
|
|
||||||
### Using git
|
### Using git
|
||||||
|
|
||||||
|
Cloning using `git` or downloading Github branches as zip files requires additional steps:
|
||||||
|
|
||||||
|
* Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies.
|
||||||
|
* Install [python3-virtualenv](https://pypi.python.org/pypi/virtualenv) to build the local HTML documentation.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ mkdir -p /path/to/shaarli && cd /path/to/shaarli/
|
$ mkdir -p /path/to/shaarli && cd /path/to/shaarli/
|
||||||
$ git clone -b v0.9 https://github.com/shaarli/Shaarli.git .
|
$ git clone -b latest https://github.com/shaarli/Shaarli.git .
|
||||||
$ composer install --no-dev --prefer-dist
|
$ composer install --no-dev --prefer-dist
|
||||||
|
$ make translate
|
||||||
|
$ make htmldoc
|
||||||
```
|
```
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
## Stable version
|
## Stable version
|
||||||
|
|
||||||
The stable version has been experienced by Shaarli users, and will receive security updates.
|
The stable version has been experienced by Shaarli users, and will receive security updates.
|
||||||
|
|
||||||
|
|
||||||
### Download as an archive
|
### Download as an archive
|
||||||
|
|
||||||
As a .zip archive:
|
As a .zip archive:
|
||||||
|
@ -60,9 +73,9 @@ $ tar xvf stable.tar.gz
|
||||||
$ mv Shaarli-stable /path/to/shaarli/
|
$ mv Shaarli-stable /path/to/shaarli/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clone with Git
|
### Using git
|
||||||
|
|
||||||
[Composer](https://getcomposer.org/) is required to build a functional Shaarli installation when pulling from git.
|
Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/
|
$ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/
|
||||||
|
@ -71,10 +84,15 @@ $ cd /path/to/shaarli/
|
||||||
$ composer install --no-dev --prefer-dist
|
$ composer install --no-dev --prefer-dist
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
## Development version (mainline)
|
## Development version (mainline)
|
||||||
|
|
||||||
_Use at your own risk!_
|
_Use at your own risk!_
|
||||||
|
|
||||||
|
Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies.
|
||||||
|
|
||||||
To get the latest changes from the `master` branch:
|
To get the latest changes from the `master` branch:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -83,13 +101,17 @@ $ git clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/
|
||||||
# install/update third-party dependencies
|
# install/update third-party dependencies
|
||||||
$ cd /path/to/shaarli
|
$ cd /path/to/shaarli
|
||||||
$ composer install --no-dev --prefer-dist
|
$ composer install --no-dev --prefer-dist
|
||||||
|
$ make translate
|
||||||
|
$ make htmldoc
|
||||||
```
|
```
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
## Finish Installation
|
## Finish Installation
|
||||||
|
|
||||||
Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.
|
Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.
|
||||||
|
|
||||||
![install screenshot](http://i.imgur.com/wuMpDSN.png)
|
![install screenshot](images/install-shaarli.png)
|
||||||
|
|
||||||
Setup your Shaarli installation, and it's ready to use!
|
Setup your Shaarli installation, and it's ready to use!
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
### Main features
|
|
||||||
Shaarli is intended:
|
|
||||||
|
|
||||||
- to share, comment and save interesting links and news
|
|
||||||
- to bookmark useful/frequent personal links (as private links) and share them between computers
|
|
||||||
- as a minimal blog/microblog/writing platform (no character limit)
|
|
||||||
- as a read-it-later list (for example items tagged `readlater`)
|
|
||||||
- to draft and save articles/ideas
|
|
||||||
- to keep code snippets
|
|
||||||
- to keep notes and documentation
|
|
||||||
- as a shared clipboard between machines
|
|
||||||
- as a todo list
|
|
||||||
- to store playlists (e.g. with the `music` or `video` tags)
|
|
||||||
- to keep extracts/comments from webpages that may disappear
|
|
||||||
- to keep track of ongoing discussions (for example items tagged `discussion`)
|
|
||||||
- [to feed RSS aggregators](http://shaarli.chassegnouf.net/?9Efeiw) (planets) with specific tags
|
|
||||||
- to feed other social networks, blogs... using RSS feeds and external services (dlvr.it, ifttt.com ...)
|
|
||||||
|
|
||||||
### Using Shaarli as a blog, notepad, pastebin...
|
|
||||||
|
|
||||||
- Go to your Shaarli setup and log in
|
|
||||||
- Click the `Add Link` button
|
|
||||||
- To share text only, do not enter any URL in the corresponding input field and click `Add Link`
|
|
||||||
- Pick a title and enter your article, or note, in the description field; add a few tags; optionally check `Private` then click `Save`
|
|
||||||
- Voilà! Your article is now published (privately if you selected that option) and accessible using its permalink.
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
| Note | Firefox Share is no longer available for Firefox 57 and later versions. |
|
||||||
|
|---------|---------|
|
||||||
|
|
||||||
### Add Shaarli as a sharing service to Firefox
|
### Add Shaarli as a sharing service to Firefox
|
||||||
|
|
||||||
- Open your Shaarli and `Login`
|
- Open your Shaarli and `Login`
|
||||||
|
|
|
@ -35,7 +35,8 @@ Library | Required? | Usage
|
||||||
Extension | Required? | Usage
|
Extension | Required? | Usage
|
||||||
---|:---:|---
|
---|:---:|---
|
||||||
[`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS
|
[`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS
|
||||||
[`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows | multibyte (Unicode) string support
|
[`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows, some hosting providers | multibyte (Unicode) string support
|
||||||
[`php-gd`](http://php.net/manual/en/book.image.php) | optional | thumbnail resizing
|
[`php-gd`](http://php.net/manual/en/book.image.php) | optional | thumbnail resizing
|
||||||
[`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`)
|
[`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`)
|
||||||
[`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way
|
[`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way
|
||||||
|
[`php-gettext`](http://php.net/manual/en/book.gettext.php) | optional | Use the translation system in gettext mode (faster)
|
||||||
|
|
|
@ -81,6 +81,20 @@ _These settings should not be edited_
|
||||||
- **page_cache**: Shaarli's internal cache directory.
|
- **page_cache**: Shaarli's internal cache directory.
|
||||||
- **ban_file**: Banned IP file path.
|
- **ban_file**: Banned IP file path.
|
||||||
|
|
||||||
|
### Translation
|
||||||
|
|
||||||
|
- **language**: translation language (also see [Translations](Translations))
|
||||||
|
- **auto** (default): The translation language is chosen from the browser locale.
|
||||||
|
It means that the language can be different for 2 different visitors depending on their locale.
|
||||||
|
- **en**: Use the English translation.
|
||||||
|
- **fr**: Use the French translation.
|
||||||
|
- **mode**:
|
||||||
|
- **auto** or **php** (default): Use the PHP implementation of gettext (slower)
|
||||||
|
- **gettext**: Use PHP builtin gettext extension
|
||||||
|
(faster, but requires `php-gettext` to be installed and to reload the web server on update)
|
||||||
|
- **extension**: Translation extensions for custom themes or plugins.
|
||||||
|
Must be an associative array: `translation domain => translation path`.
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
|
||||||
- **check_updates**: Enable or disable update check to the git repository.
|
- **check_updates**: Enable or disable update check to the git repository.
|
||||||
|
@ -211,6 +225,13 @@ _These settings should not be edited_
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"WALLABAG_URL": "http://demo.wallabag.org",
|
"WALLABAG_URL": "http://demo.wallabag.org",
|
||||||
"WALLABAG_VERSION": "1"
|
"WALLABAG_VERSION": "1"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"language": "fr",
|
||||||
|
"mode": "php",
|
||||||
|
"extensions": {
|
||||||
|
"demo": "plugins/demo_plugin/languages/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} ?>
|
} ?>
|
||||||
```
|
```
|
||||||
|
|
152
doc/md/Translations.md
Normal file
152
doc/md/Translations.md
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
Shaarli supports [gettext](https://www.gnu.org/software/gettext/manual/gettext.html) translations
|
||||||
|
since `>= v0.9.2`.
|
||||||
|
|
||||||
|
Note that only the `default` theme supports translations.
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
We encourage the community to contribute to Shaarli's translation either by improving existing
|
||||||
|
translations or submitting a new language.
|
||||||
|
|
||||||
|
Contributing to the translation does not require development skill.
|
||||||
|
|
||||||
|
Please submit a pull request with the `.po` file updated/created. Note that the compiled file (`.mo`)
|
||||||
|
is not stored on the repository, and is generated during the release process.
|
||||||
|
|
||||||
|
### How to
|
||||||
|
|
||||||
|
First, install [Poedit](https://poedit.net/) tool.
|
||||||
|
|
||||||
|
Poedit will extract strings to translate from the PHP source code.
|
||||||
|
|
||||||
|
**Important**: due to the usage of a template engine, it's important to generate PHP cache files to extract
|
||||||
|
every translatable string.
|
||||||
|
|
||||||
|
You can either use [this script](https://gist.github.com/ArthurHoaro/5d0323f758ab2401ef444a53f54e9a07) (recommended)
|
||||||
|
or visit every template page in your browser to generate cache files, while logged in.
|
||||||
|
|
||||||
|
Here is a list :
|
||||||
|
|
||||||
|
```
|
||||||
|
http://<replace_domain>/
|
||||||
|
http://<replace_domain>/?nonope
|
||||||
|
http://<replace_domain>/?do=addlink
|
||||||
|
http://<replace_domain>/?do=changepasswd
|
||||||
|
http://<replace_domain>/?do=changetag
|
||||||
|
http://<replace_domain>/?do=configure
|
||||||
|
http://<replace_domain>/?do=tools
|
||||||
|
http://<replace_domain>/?do=daily
|
||||||
|
http://<replace_domain>/?post
|
||||||
|
http://<replace_domain>/?do=export
|
||||||
|
http://<replace_domain>/?do=import
|
||||||
|
http://<replace_domain>/?do=login
|
||||||
|
http://<replace_domain>/?do=picwall
|
||||||
|
http://<replace_domain>/?do=pluginadmin
|
||||||
|
http://<replace_domain>/?do=tagcloud
|
||||||
|
http://<replace_domain>/?do=taglist
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Improve existing translation
|
||||||
|
|
||||||
|
In Poedit, click on "Edit a Translation", and from Shaarli's directory open
|
||||||
|
`inc/languages/<lang>/LC_MESSAGES/shaarli.po`.
|
||||||
|
|
||||||
|
The existing list of translatable strings should have been loaded, then click on the "Update" button.
|
||||||
|
|
||||||
|
You can start editing the translation.
|
||||||
|
|
||||||
|
![poedit-screenshot](images/poedit-1.jpg)
|
||||||
|
|
||||||
|
Save when you're done, then you can submit a pull request containing the updated `shaarli.po`.
|
||||||
|
|
||||||
|
#### Add a new language
|
||||||
|
|
||||||
|
Open Poedit and select "Create New Translation", then from Shaarli's directory open
|
||||||
|
`inc/languages/<lang>/LC_MESSAGES/shaarli.po`.
|
||||||
|
|
||||||
|
Then select the language you want to create.
|
||||||
|
|
||||||
|
Click on `File > Save as...`, and save your file in `<shaarli directory>/inc/language/<new language>/LC_MESSAGES/shaarli.po`.
|
||||||
|
`<new language>` here should be the language code respecting the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-2)
|
||||||
|
format in lowercase (e.g. `de` for German).
|
||||||
|
|
||||||
|
Then click on the "Update" button, and you can start to translate every available string.
|
||||||
|
|
||||||
|
Save when you're done, then you can submit a pull request containing the new `shaarli.po`.
|
||||||
|
|
||||||
|
### Extend Shaarli's translation
|
||||||
|
|
||||||
|
If you're writing a custom theme, or a non official plugin, you might want to use the translation system,
|
||||||
|
but you won't be able to able to override Shaarli's translation.
|
||||||
|
|
||||||
|
However, you can add your own translation domain which extends the main translation list.
|
||||||
|
|
||||||
|
> Note that you can find a live example of translation extension in the `demo_plugin`.
|
||||||
|
|
||||||
|
First, create your translation files tree directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
<your_module>/languages/<ISO 3166-1 alpha-2 language code>/LC_MESSAGES/
|
||||||
|
```
|
||||||
|
|
||||||
|
Your `.po` files must be named like your domain. E.g. if your translation domain is `my_theme`, then your file will be
|
||||||
|
`my_theme.po`.
|
||||||
|
|
||||||
|
Users have to register your extension in their configuration with the parameter
|
||||||
|
`translation.extensions.<domain>: <translation files path>`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```php
|
||||||
|
if (! $conf->exists('translation.extensions.my_theme')) {
|
||||||
|
$conf->set('translation.extensions.my_theme', '<your_module>/languages/');
|
||||||
|
$conf->write(true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note that the page needs to be reloaded after the registration.
|
||||||
|
|
||||||
|
It is then recommended to create a custom translation function which will call the `t()` function with your domain.
|
||||||
|
For example :
|
||||||
|
|
||||||
|
```php
|
||||||
|
function my_theme_t($text, $nText = '', $nb = 1)
|
||||||
|
{
|
||||||
|
return t($text, $nText, $nb, 'my_theme'); // the last parameter is your translation domain.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All strings which can be translated should be processed through your function:
|
||||||
|
|
||||||
|
```php
|
||||||
|
my_theme_t('Comment');
|
||||||
|
my_theme_t('Comment', 'Comments', 2);
|
||||||
|
```
|
||||||
|
|
||||||
|
Or in templates:
|
||||||
|
|
||||||
|
```php
|
||||||
|
{'Comment'|my_theme_t}
|
||||||
|
{function="my_theme_t('Comment', 'Comments', 2)"}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note than in template, you need to visit your page at least once to generate a cache file.
|
||||||
|
|
||||||
|
When you're done, open Poedit and load translation strings from sources:
|
||||||
|
|
||||||
|
1. `File > New`
|
||||||
|
2. Choose your language
|
||||||
|
3. Save your `PO` file in `<your_module>/languages/<language code>/LC_MESSAGES/my_theme.po`.
|
||||||
|
4. Go to `Catalog > Properties...`
|
||||||
|
5. Fill the `Translation Properties` tab
|
||||||
|
6. Add your source path in the `Sources Paths` tab
|
||||||
|
7. In the `Sources Keywords` tab uncheck "Also use default keywords" and add the following lines:
|
||||||
|
|
||||||
|
```
|
||||||
|
my_theme_t
|
||||||
|
my_theme_t:1,2
|
||||||
|
```
|
||||||
|
|
||||||
|
Click on the "Update" button and you're free to start your translations!
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
The framework used is [PHPUnit](https://phpunit.de/); it can be installed with [Composer](https://getcomposer.org/), which is a dependency management tool.
|
The framework used is [PHPUnit](https://phpunit.de/); it can be installed with [Composer](https://getcomposer.org/), which is a dependency management tool.
|
||||||
|
|
||||||
Regarding Composer, you can either use:
|
### Install composer
|
||||||
|
|
||||||
|
You can either use:
|
||||||
|
|
||||||
- a system-wide version, e.g. installed through your distro's package manager
|
- a system-wide version, e.g. installed through your distro's package manager
|
||||||
- a local version, downloadable [here](https://getcomposer.org/download/)
|
- a local version, downloadable [here](https://getcomposer.org/download/).
|
||||||
|
|
||||||
#### Sample usage
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# system-wide version
|
# system-wide version
|
||||||
|
@ -29,6 +29,8 @@ $ composer update
|
||||||
|
|
||||||
#### Install and enable Xdebug to generate PHPUnit coverage reports
|
#### Install and enable Xdebug to generate PHPUnit coverage reports
|
||||||
|
|
||||||
|
See http://xdebug.org/docs/install
|
||||||
|
|
||||||
For Debian-based distros:
|
For Debian-based distros:
|
||||||
```bash
|
```bash
|
||||||
$ aptitude install php5-xdebug
|
$ aptitude install php5-xdebug
|
||||||
|
|
|
@ -14,7 +14,7 @@ Shaarli stores all user data under the `data` directory:
|
||||||
- `data/ipbans.php` - banned IP addresses
|
- `data/ipbans.php` - banned IP addresses
|
||||||
- `data/updates.txt` - contains all automatic update to the configuration and datastore files already run
|
- `data/updates.txt` - contains all automatic update to the configuration and datastore files already run
|
||||||
|
|
||||||
See [Shaarli configuration](Shaarli configuration) for more information about Shaarli resources.
|
See [Shaarli configuration](Shaarli-configuration) for more information about Shaarli resources.
|
||||||
|
|
||||||
It is recommended to backup this repository _before_ starting updating/upgrading Shaarli:
|
It is recommended to backup this repository _before_ starting updating/upgrading Shaarli:
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ As all user data is kept under `data`, this is the only directory you need to wo
|
||||||
|
|
||||||
- backup the `data` directory
|
- backup the `data` directory
|
||||||
- install or update Shaarli:
|
- install or update Shaarli:
|
||||||
- fresh installation - see [Download and installation](Download and installation)
|
- fresh installation - see [Download and installation](Download-and-installation)
|
||||||
- update - see the following sections
|
- update - see the following sections
|
||||||
- check or restore the `data` directory
|
- check or restore the `data` directory
|
||||||
|
|
||||||
|
@ -35,10 +35,13 @@ As all user data is kept under `data`, this is the only directory you need to wo
|
||||||
|
|
||||||
All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.
|
All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.
|
||||||
|
|
||||||
We recommend that you use the latest release tarball with the `-full` suffix. It contains the dependencies, please read [Download and installation](Download and installation) for `git` complete instructions.
|
We recommend that you use the latest release tarball with the `-full` suffix. It contains the dependencies, please read [Download and installation](Download-and-installation) for `git` complete instructions.
|
||||||
|
|
||||||
Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the `data` directory!
|
Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the `data` directory!
|
||||||
|
|
||||||
|
If you use translations in gettext mode - meaning you manually changed the default mode -,
|
||||||
|
reload your web server.
|
||||||
|
|
||||||
After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli configuration) for more details).
|
After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli configuration) for more details).
|
||||||
|
|
||||||
## Upgrading with Git
|
## Upgrading with Git
|
||||||
|
@ -72,6 +75,14 @@ Updating dependencies
|
||||||
Downloading: 100%
|
Downloading: 100%
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Shaarli >= `v0.9.2` supports translations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ make translate
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use translations in gettext mode, reload your web server.
|
||||||
|
|
||||||
### Migrating and upgrading from Sebsauvage's repository
|
### Migrating and upgrading from Sebsauvage's repository
|
||||||
|
|
||||||
If you have installed Shaarli from [Sebsauvage's original Git repository](https://github.com/sebsauvage/Shaarli), you can use [Git remotes](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) to update your working copy.
|
If you have installed Shaarli from [Sebsauvage's original Git repository](https://github.com/sebsauvage/Shaarli), you can use [Git remotes](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) to update your working copy.
|
||||||
|
@ -151,6 +162,14 @@ Updating dependencies
|
||||||
Downloading: 100%
|
Downloading: 100%
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Shaarli >= `v0.9.2` supports translations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ make translate
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use translations in gettext mode, reload your web server.
|
||||||
|
|
||||||
Optionally, you can delete information related to the legacy version:
|
Optionally, you can delete information related to the legacy version:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -173,7 +192,7 @@ Total 3317 (delta 2050), reused 3301 (delta 2034)to
|
||||||
|
|
||||||
#### Step 3: configuration
|
#### Step 3: configuration
|
||||||
|
|
||||||
After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli configuration) for more details).
|
After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration) for more details).
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,120 @@
|
||||||
|
## Foreword
|
||||||
|
|
||||||
|
This guide assumes that:
|
||||||
|
|
||||||
|
- Shaarli runs in a Docker container
|
||||||
|
- The host's `10080` port is mapped to the container's `80` port
|
||||||
|
- Shaarli's Fully Qualified Domain Name (FQDN) is `shaarli.domain.tld`
|
||||||
|
- HTTP traffic is redirected to HTTPS
|
||||||
|
|
||||||
|
## Apache
|
||||||
|
|
||||||
|
- [Apache 2.4 documentation](https://httpd.apache.org/docs/2.4/)
|
||||||
|
- [mod_proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html)
|
||||||
|
- [Reverse Proxy Request Headers](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#x-headers)
|
||||||
|
|
||||||
|
The following HTTP headers are set by using the `ProxyPass` directive:
|
||||||
|
|
||||||
|
- `X-Forwarded-For`
|
||||||
|
- `X-Forwarded-Host`
|
||||||
|
- `X-Forwarded-Server`
|
||||||
|
|
||||||
|
```apache
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName shaarli.domain.tld
|
||||||
|
Redirect permanent / https://shaarli.domain.tld
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
<VirtualHost *:443>
|
||||||
|
ServerName shaarli.domain.tld
|
||||||
|
|
||||||
|
SSLEngine on
|
||||||
|
SSLCertificateFile /path/to/cert
|
||||||
|
SSLCertificateKeyFile /path/to/certkey
|
||||||
|
|
||||||
|
LogLevel warn
|
||||||
|
ErrorLog /var/log/apache2/shaarli-error.log
|
||||||
|
CustomLog /var/log/apache2/shaarli-access.log combined
|
||||||
|
|
||||||
|
RequestHeader set X-Forwarded-Proto "https"
|
||||||
|
|
||||||
|
ProxyPass / http://127.0.0.1:10080/
|
||||||
|
ProxyPassReverse / http://127.0.0.1:10080/
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
TODO, see https://github.com/shaarli/Shaarli/issues/888
|
|
||||||
|
|
||||||
## HAProxy
|
## HAProxy
|
||||||
|
|
||||||
|
- [HAProxy documentation](https://cbonte.github.io/haproxy-dconv/)
|
||||||
|
|
||||||
|
```conf
|
||||||
|
global
|
||||||
|
[...]
|
||||||
|
|
||||||
|
defaults
|
||||||
|
[...]
|
||||||
|
|
||||||
|
frontend http-in
|
||||||
|
bind :80
|
||||||
|
redirect scheme https code 301 if !{ ssl_fc }
|
||||||
|
|
||||||
|
bind :443 ssl crt /path/to/cert.pem
|
||||||
|
|
||||||
|
default_backend shaarli
|
||||||
|
|
||||||
|
|
||||||
|
backend shaarli
|
||||||
|
mode http
|
||||||
|
option http-server-close
|
||||||
|
option forwardfor
|
||||||
|
reqadd X-Forwarded-Proto: https
|
||||||
|
|
||||||
|
server shaarli1 127.0.0.1:10080
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Nginx
|
## Nginx
|
||||||
|
|
||||||
|
- [Nginx documentation](https://nginx.org/en/docs/)
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
http {
|
||||||
|
[...]
|
||||||
|
|
||||||
|
index index.html index.php;
|
||||||
|
|
||||||
|
root /home/john/web;
|
||||||
|
access_log /var/log/nginx/access.log;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name shaarli.domain.tld;
|
||||||
|
return 301 https://shaarli.domain.tld$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name shaarli.domain.tld;
|
||||||
|
|
||||||
|
ssl_certificate /path/to/cert
|
||||||
|
ssl_certificate_key /path/to/certkey
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
|
||||||
|
proxy_pass http://localhost:10080/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_connect_timeout 30s;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/shaarli.access.log;
|
||||||
|
error_log /var/log/nginx/shaarli.error.log;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
A brief guide on getting starting using docker is given in [Docker 101](docker-101.md).
|
||||||
|
To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](../Upgrade-and-migration.md).
|
||||||
|
|
||||||
## Get and run a Shaarli image
|
## Get and run a Shaarli image
|
||||||
|
|
||||||
### DockerHub repository
|
### DockerHub repository
|
||||||
|
@ -5,14 +8,24 @@ The images can be found in the [`shaarli/shaarli`](https://hub.docker.com/r/shaa
|
||||||
repository.
|
repository.
|
||||||
|
|
||||||
### Available image tags
|
### Available image tags
|
||||||
- `latest`: master branch (tarball release)
|
- `latest`: latest branch (tarball release)
|
||||||
|
- `master`: master branch (tarball release)
|
||||||
- `stable`: stable branch (tarball release)
|
- `stable`: stable branch (tarball release)
|
||||||
|
|
||||||
All images rely on:
|
The `latest` and `master` images rely on:
|
||||||
|
|
||||||
|
- [Alpine Linux](https://www.alpinelinux.org/)
|
||||||
|
- [PHP7-FPM](http://php-fpm.org/)
|
||||||
|
- [Nginx](http://nginx.org/)
|
||||||
|
|
||||||
|
The `stable` image relies on:
|
||||||
|
|
||||||
- [Debian 8 Jessie](https://hub.docker.com/_/debian/)
|
- [Debian 8 Jessie](https://hub.docker.com/_/debian/)
|
||||||
- [PHP5-FPM](http://php-fpm.org/)
|
- [PHP5-FPM](http://php-fpm.org/)
|
||||||
- [Nginx](http://nginx.org/)
|
- [Nginx](http://nginx.org/)
|
||||||
|
|
||||||
|
Additional [Dockerfiles](https://github.com/shaarli/Shaarli/tree/master/docker) are provided for the `arm32v7` platform, relying on [Linuxserver.io Alpine armhf images](https://hub.docker.com/r/lsiobase/alpine.armhf/). These images must be built using [`docker build`](https://docs.docker.com/engine/reference/commandline/build/) on an `arm32v7` machine or using an emulator such as [qemu](https://resin.io/blog/building-arm-containers-on-any-x86-machine-even-dockerhub/).
|
||||||
|
|
||||||
### Download from DockerHub
|
### Download from DockerHub
|
||||||
```bash
|
```bash
|
||||||
$ docker pull shaarli/shaarli
|
$ docker pull shaarli/shaarli
|
||||||
|
@ -69,3 +82,14 @@ backstabbing_galileo
|
||||||
$ docker ps -a
|
$ docker ps -a
|
||||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Automatic builds
|
||||||
|
|
||||||
|
Docker users can start a personal instance from an [autobuild image](https://hub.docker.com/r/shaarli/shaarli/). For example to start a temporary Shaarli at ``localhost:8000``, and keep session data (config, storage):
|
||||||
|
```
|
||||||
|
MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P)
|
||||||
|
docker run -ti --rm \
|
||||||
|
-p 8000:80 \
|
||||||
|
-v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \
|
||||||
|
shaarli/shaarli
|
||||||
|
```
|
||||||
|
|
BIN
doc/md/images/install-shaarli.png
Normal file
BIN
doc/md/images/install-shaarli.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
doc/md/images/poedit-1.jpg
Normal file
BIN
doc/md/images/poedit-1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
|
@ -22,20 +22,25 @@ It runs the latest development version of Shaarli and is updated/reset daily.
|
||||||
|
|
||||||
Login: `demo`; Password: `demo`
|
Login: `demo`; Password: `demo`
|
||||||
|
|
||||||
Docker users can start a personal instance from an [autobuild image](https://hub.docker.com/r/shaarli/shaarli/). For example to start a temporary Shaarli at ``localhost:8000``, and keep session data (config, storage):
|
|
||||||
```
|
|
||||||
MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P)
|
|
||||||
docker run -ti --rm \
|
|
||||||
-p 8000:80 \
|
|
||||||
-v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \
|
|
||||||
shaarli/shaarli
|
|
||||||
```
|
|
||||||
|
|
||||||
A brief guide on getting starting using docker is given in [Docker 101](docker/docker-101).
|
|
||||||
To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](Upgrade-and-migration) documentation.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
Shaarli can be used:
|
||||||
|
|
||||||
|
- to share, comment and save interesting links and news.
|
||||||
|
- to bookmark useful/frequent personal links (as private links) and share them between computers.
|
||||||
|
- as a minimal blog/microblog/writing platform (no character limit).
|
||||||
|
- as a read-it-later list (for example items tagged `readlater`).
|
||||||
|
- to draft and save articles/posts/ideas.
|
||||||
|
- to keep code snippets.
|
||||||
|
- to keep notes and documentation.
|
||||||
|
- as a shared clipboard/notepad/pastebin between machines.
|
||||||
|
- as a todo list.
|
||||||
|
- to store playlists (e.g. with the `music` or `video` tags).
|
||||||
|
- to keep extracts/comments from webpages that may disappear.
|
||||||
|
- to keep track of ongoing discussions (for example items tagged `discussion`).
|
||||||
|
- [to feed RSS aggregators](http://shaarli.chassegnouf.net/?9Efeiw) (planets) with specific tags.
|
||||||
|
- to feed other social networks, blogs... using RSS feeds and external services (dlvr.it, ifttt.com ...).
|
||||||
|
|
||||||
### Interface
|
### Interface
|
||||||
- minimalist design (simple is beautiful)
|
- minimalist design (simple is beautiful)
|
||||||
- FAST
|
- FAST
|
||||||
|
@ -89,14 +94,12 @@ Easily extensible by any client using the REST API exposed by Shaarli.
|
||||||
|
|
||||||
See the [API documentation](http://shaarli.github.io/api-documentation/).
|
See the [API documentation](http://shaarli.github.io/api-documentation/).
|
||||||
|
|
||||||
### Other usages
|
### Using Shaarli as a blog, notepad, pastebin...
|
||||||
Though Shaarli is primarily a bookmarking application, it can serve other purposes
|
- Go to your Shaarli setup and log in
|
||||||
(see [Features](Features)):
|
- Click the `Add Link` button
|
||||||
|
- To share text only, do not enter any URL in the corresponding input field and click `Add Link`
|
||||||
- micro-blogging
|
- Pick a title and enter your article, or note, in the description field; add a few tags; optionally check `Private` then click `Save`
|
||||||
- pastebin
|
- Voilà! Your article is now published (privately if you selected that option) and accessible using its permalink.
|
||||||
- online notepad
|
|
||||||
- snippet archive
|
|
||||||
|
|
||||||
## About
|
## About
|
||||||
### Shaarli community fork
|
### Shaarli community fork
|
||||||
|
|
47
docker/alpine/Dockerfile.armhf.latest
Normal file
47
docker/alpine/Dockerfile.armhf.latest
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
FROM lsiobase/alpine.armhf:3.6
|
||||||
|
MAINTAINER Shaarli Community
|
||||||
|
|
||||||
|
RUN apk --update --no-cache add \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
nginx \
|
||||||
|
php7 \
|
||||||
|
php7-ctype \
|
||||||
|
php7-curl \
|
||||||
|
php7-fpm \
|
||||||
|
php7-gd \
|
||||||
|
php7-iconv \
|
||||||
|
php7-intl \
|
||||||
|
php7-json \
|
||||||
|
php7-mbstring \
|
||||||
|
php7-openssl \
|
||||||
|
php7-phar \
|
||||||
|
php7-session \
|
||||||
|
php7-xml \
|
||||||
|
php7-zlib \
|
||||||
|
s6
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY php-fpm.conf /etc/php7/php-fpm.conf
|
||||||
|
COPY services.d /etc/services.d
|
||||||
|
|
||||||
|
RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \
|
||||||
|
&& rm -rf /etc/php7/php-fpm.d/www.conf \
|
||||||
|
&& sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \
|
||||||
|
&& sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini
|
||||||
|
|
||||||
|
|
||||||
|
WORKDIR /var/www
|
||||||
|
RUN curl -L https://github.com/shaarli/Shaarli/archive/latest.tar.gz | tar xzf - \
|
||||||
|
&& mv Shaarli-latest shaarli \
|
||||||
|
&& cd shaarli \
|
||||||
|
&& composer --prefer-dist --no-dev install \
|
||||||
|
&& rm -rf ~/.composer \
|
||||||
|
&& chown -R nginx:nginx .
|
||||||
|
|
||||||
|
VOLUME /var/www/shaarli/data
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
|
||||||
|
CMD []
|
47
docker/alpine/Dockerfile.armhf.master
Normal file
47
docker/alpine/Dockerfile.armhf.master
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
FROM lsiobase/alpine.armhf:3.6
|
||||||
|
MAINTAINER Shaarli Community
|
||||||
|
|
||||||
|
RUN apk --update --no-cache add \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
nginx \
|
||||||
|
php7 \
|
||||||
|
php7-ctype \
|
||||||
|
php7-curl \
|
||||||
|
php7-fpm \
|
||||||
|
php7-gd \
|
||||||
|
php7-iconv \
|
||||||
|
php7-intl \
|
||||||
|
php7-json \
|
||||||
|
php7-mbstring \
|
||||||
|
php7-openssl \
|
||||||
|
php7-phar \
|
||||||
|
php7-session \
|
||||||
|
php7-xml \
|
||||||
|
php7-zlib \
|
||||||
|
s6
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY php-fpm.conf /etc/php7/php-fpm.conf
|
||||||
|
COPY services.d /etc/services.d
|
||||||
|
|
||||||
|
RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \
|
||||||
|
&& rm -rf /etc/php7/php-fpm.d/www.conf \
|
||||||
|
&& sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \
|
||||||
|
&& sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini
|
||||||
|
|
||||||
|
|
||||||
|
WORKDIR /var/www
|
||||||
|
RUN curl -L https://github.com/shaarli/Shaarli/archive/master.tar.gz | tar xzf - \
|
||||||
|
&& mv Shaarli-master shaarli \
|
||||||
|
&& cd shaarli \
|
||||||
|
&& composer --prefer-dist --no-dev install \
|
||||||
|
&& rm -rf ~/.composer \
|
||||||
|
&& chown -R nginx:nginx .
|
||||||
|
|
||||||
|
VOLUME /var/www/shaarli/data
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
|
||||||
|
CMD []
|
47
docker/alpine/Dockerfile.latest
Normal file
47
docker/alpine/Dockerfile.latest
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
FROM alpine:3.6
|
||||||
|
MAINTAINER Shaarli Community
|
||||||
|
|
||||||
|
RUN apk --update --no-cache add \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
nginx \
|
||||||
|
php7 \
|
||||||
|
php7-ctype \
|
||||||
|
php7-curl \
|
||||||
|
php7-fpm \
|
||||||
|
php7-gd \
|
||||||
|
php7-iconv \
|
||||||
|
php7-intl \
|
||||||
|
php7-json \
|
||||||
|
php7-mbstring \
|
||||||
|
php7-openssl \
|
||||||
|
php7-phar \
|
||||||
|
php7-session \
|
||||||
|
php7-xml \
|
||||||
|
php7-zlib \
|
||||||
|
s6
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY php-fpm.conf /etc/php7/php-fpm.conf
|
||||||
|
COPY services.d /etc/services.d
|
||||||
|
|
||||||
|
RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \
|
||||||
|
&& rm -rf /etc/php7/php-fpm.d/www.conf \
|
||||||
|
&& sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \
|
||||||
|
&& sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini
|
||||||
|
|
||||||
|
|
||||||
|
WORKDIR /var/www
|
||||||
|
RUN curl -L https://github.com/shaarli/Shaarli/archive/latest.tar.gz | tar xzf - \
|
||||||
|
&& mv Shaarli-latest shaarli \
|
||||||
|
&& cd shaarli \
|
||||||
|
&& composer --prefer-dist --no-dev install \
|
||||||
|
&& rm -rf ~/.composer \
|
||||||
|
&& chown -R nginx:nginx .
|
||||||
|
|
||||||
|
VOLUME /var/www/shaarli/data
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
|
||||||
|
CMD []
|
47
docker/alpine/Dockerfile.master
Normal file
47
docker/alpine/Dockerfile.master
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
FROM alpine:3.6
|
||||||
|
MAINTAINER Shaarli Community
|
||||||
|
|
||||||
|
RUN apk --update --no-cache add \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
nginx \
|
||||||
|
php7 \
|
||||||
|
php7-ctype \
|
||||||
|
php7-curl \
|
||||||
|
php7-fpm \
|
||||||
|
php7-gd \
|
||||||
|
php7-iconv \
|
||||||
|
php7-intl \
|
||||||
|
php7-json \
|
||||||
|
php7-mbstring \
|
||||||
|
php7-openssl \
|
||||||
|
php7-phar \
|
||||||
|
php7-session \
|
||||||
|
php7-xml \
|
||||||
|
php7-zlib \
|
||||||
|
s6
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY php-fpm.conf /etc/php7/php-fpm.conf
|
||||||
|
COPY services.d /etc/services.d
|
||||||
|
|
||||||
|
RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \
|
||||||
|
&& rm -rf /etc/php7/php-fpm.d/www.conf \
|
||||||
|
&& sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \
|
||||||
|
&& sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini
|
||||||
|
|
||||||
|
|
||||||
|
WORKDIR /var/www
|
||||||
|
RUN curl -L https://github.com/shaarli/Shaarli/archive/master.tar.gz | tar xzf - \
|
||||||
|
&& mv Shaarli-master shaarli \
|
||||||
|
&& cd shaarli \
|
||||||
|
&& composer --prefer-dist --no-dev install \
|
||||||
|
&& rm -rf ~/.composer \
|
||||||
|
&& chown -R nginx:nginx .
|
||||||
|
|
||||||
|
VOLUME /var/www/shaarli/data
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
|
||||||
|
CMD []
|
10
docker/alpine/IMAGE.md
Normal file
10
docker/alpine/IMAGE.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
## Alpine images
|
||||||
|
- [Alpine Linux](https://www.alpinelinux.org/)
|
||||||
|
- [PHP-FPM](http://php-fpm.org/)
|
||||||
|
- [Nginx](http://nginx.org/)
|
||||||
|
|
||||||
|
### `shaarli/shaarli:latest`
|
||||||
|
- [Shaarli](https://github.com/shaarli/Shaarli), `latest` branch
|
||||||
|
|
||||||
|
### `shaarli/shaarli:master`
|
||||||
|
- [Shaarli](https://github.com/shaarli/Shaarli), `master` branch
|
|
@ -1,6 +1,7 @@
|
||||||
user www-data www-data;
|
user nginx nginx;
|
||||||
daemon off;
|
daemon off;
|
||||||
worker_processes 4;
|
worker_processes 4;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
worker_connections 768;
|
worker_connections 768;
|
||||||
|
@ -59,7 +60,7 @@ http {
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
|
||||||
# filter and proxy PHP requests to PHP-FPM
|
# filter and proxy PHP requests to PHP-FPM
|
||||||
fastcgi_pass unix:/var/run/php5-fpm.sock;
|
fastcgi_pass unix:/var/run/php-fpm.sock;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi.conf;
|
include fastcgi.conf;
|
||||||
}
|
}
|
16
docker/alpine/php-fpm.conf
Normal file
16
docker/alpine/php-fpm.conf
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[global]
|
||||||
|
daemonize = no
|
||||||
|
|
||||||
|
[www]
|
||||||
|
user = nginx
|
||||||
|
group = nginx
|
||||||
|
listen.owner = nginx
|
||||||
|
listen.group = nginx
|
||||||
|
catch_workers_output = yes
|
||||||
|
listen = /var/run/php-fpm.sock
|
||||||
|
pm = dynamic
|
||||||
|
pm.max_children = 20
|
||||||
|
pm.start_servers = 1
|
||||||
|
pm.min_spare_servers = 1
|
||||||
|
pm.max_spare_servers = 3
|
||||||
|
pm.max_requests = 2048
|
2
docker/alpine/services.d/.s6-svscan/finish
Executable file
2
docker/alpine/services.d/.s6-svscan/finish
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
/bin/true
|
2
docker/alpine/services.d/nginx/run
Executable file
2
docker/alpine/services.d/nginx/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/execlineb -P
|
||||||
|
nginx
|
2
docker/alpine/services.d/php-fpm/run
Executable file
2
docker/alpine/services.d/php-fpm/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/execlineb -P
|
||||||
|
php-fpm7 -F
|
|
@ -1,37 +0,0 @@
|
||||||
FROM debian:jessie
|
|
||||||
MAINTAINER Shaarli Community
|
|
||||||
|
|
||||||
ENV TERM dumb
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install --no-install-recommends -y \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
nginx-light \
|
|
||||||
php5-curl \
|
|
||||||
php5-fpm \
|
|
||||||
php5-gd \
|
|
||||||
php5-intl \
|
|
||||||
supervisor \
|
|
||||||
&& apt-get clean
|
|
||||||
|
|
||||||
RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
|
|
||||||
RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
|
||||||
COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
|
|
||||||
|
|
||||||
ADD https://getcomposer.org/composer.phar /usr/local/bin/composer
|
|
||||||
RUN chmod 755 /usr/local/bin/composer
|
|
||||||
|
|
||||||
WORKDIR /var/www
|
|
||||||
RUN curl -L https://github.com/shaarli/Shaarli/archive/master.tar.gz | tar xzf - \
|
|
||||||
&& mv Shaarli-master shaarli \
|
|
||||||
&& cd shaarli \
|
|
||||||
&& composer --prefer-dist --no-dev install
|
|
||||||
RUN rm -rf html \
|
|
||||||
&& chown -R www-data:www-data .
|
|
||||||
|
|
||||||
VOLUME /var/www/shaarli/data
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"]
|
|
|
@ -1,5 +0,0 @@
|
||||||
## shaarli:latest
|
|
||||||
- [Debian 8 Jessie](https://hub.docker.com/_/debian/)
|
|
||||||
- [PHP5-FPM](http://php-fpm.org/)
|
|
||||||
- [Nginx](http://nginx.org/)
|
|
||||||
- [Shaarli](https://github.com/shaarli/Shaarli)
|
|
|
@ -1,13 +0,0 @@
|
||||||
[program:php5-fpm]
|
|
||||||
command=/usr/sbin/php5-fpm -F
|
|
||||||
priority=5
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
|
|
||||||
[program:nginx]
|
|
||||||
command=/usr/sbin/nginx
|
|
||||||
priority=10
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_events_enabled=true
|
|
||||||
stderr_events_enabled=true
|
|
1367
inc/languages/fr/LC_MESSAGES/shaarli.po
Normal file
1367
inc/languages/fr/LC_MESSAGES/shaarli.po
Normal file
File diff suppressed because it is too large
Load diff
178
index.php
178
index.php
|
@ -64,7 +64,6 @@
|
||||||
require_once 'application/FileUtils.php';
|
require_once 'application/FileUtils.php';
|
||||||
require_once 'application/History.php';
|
require_once 'application/History.php';
|
||||||
require_once 'application/HttpUtils.php';
|
require_once 'application/HttpUtils.php';
|
||||||
require_once 'application/Languages.php';
|
|
||||||
require_once 'application/LinkDB.php';
|
require_once 'application/LinkDB.php';
|
||||||
require_once 'application/LinkFilter.php';
|
require_once 'application/LinkFilter.php';
|
||||||
require_once 'application/LinkUtils.php';
|
require_once 'application/LinkUtils.php';
|
||||||
|
@ -76,8 +75,10 @@
|
||||||
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\ThemeUtils;
|
||||||
use \Shaarli\Config\ConfigManager;
|
use \Shaarli\Config\ConfigManager;
|
||||||
|
use \Shaarli\SessionManager;
|
||||||
|
|
||||||
// Ensure the PHP version is supported
|
// Ensure the PHP version is supported
|
||||||
try {
|
try {
|
||||||
|
@ -115,14 +116,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regenerate session ID if invalid or not defined in cookie.
|
// Regenerate session ID if invalid or not defined in cookie.
|
||||||
if (isset($_COOKIE['shaarli']) && !is_session_id_valid($_COOKIE['shaarli'])) {
|
if (isset($_COOKIE['shaarli']) && !SessionManager::checkId($_COOKIE['shaarli'])) {
|
||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
$_COOKIE['shaarli'] = session_id();
|
$_COOKIE['shaarli'] = session_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
$conf = new ConfigManager();
|
$conf = new ConfigManager();
|
||||||
|
$sessionManager = new SessionManager($_SESSION, $conf);
|
||||||
|
|
||||||
|
// Sniff browser language and set date format accordingly.
|
||||||
|
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||||
|
autoLocale($_SERVER['HTTP_ACCEPT_LANGUAGE']);
|
||||||
|
}
|
||||||
|
|
||||||
|
new Languages(setlocale(LC_MESSAGES, 0), $conf);
|
||||||
|
|
||||||
$conf->setEmpty('general.timezone', date_default_timezone_get());
|
$conf->setEmpty('general.timezone', date_default_timezone_get());
|
||||||
$conf->setEmpty('general.title', 'Shared links on '. escape(index_url($_SERVER)));
|
$conf->setEmpty('general.title', t('Shared links on '). escape(index_url($_SERVER)));
|
||||||
RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory
|
RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory
|
||||||
RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory
|
RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory
|
||||||
|
|
||||||
|
@ -144,7 +154,7 @@
|
||||||
$errors = ApplicationUtils::checkResourcePermissions($conf);
|
$errors = ApplicationUtils::checkResourcePermissions($conf);
|
||||||
|
|
||||||
if ($errors != array()) {
|
if ($errors != array()) {
|
||||||
$message = '<p>Insufficient permissions:</p><ul>';
|
$message = '<p>'. t('Insufficient permissions:') .'</p><ul>';
|
||||||
|
|
||||||
foreach ($errors as $error) {
|
foreach ($errors as $error) {
|
||||||
$message .= '<li>'.$error.'</li>';
|
$message .= '<li>'.$error.'</li>';
|
||||||
|
@ -157,17 +167,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display the installation form if no existing config is found
|
// Display the installation form if no existing config is found
|
||||||
install($conf);
|
install($conf, $sessionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
// a token depending of deployment salt, user password, and the current ip
|
// a token depending of deployment salt, user password, and the current ip
|
||||||
define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt')));
|
define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt')));
|
||||||
|
|
||||||
// Sniff browser language and set date format accordingly.
|
|
||||||
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
|
||||||
autoLocale($_SERVER['HTTP_ACCEPT_LANGUAGE']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checking session state (i.e. is the user still logged in)
|
* Checking session state (i.e. is the user still logged in)
|
||||||
*
|
*
|
||||||
|
@ -376,9 +381,9 @@ function ban_canLogin($conf)
|
||||||
// Process login form: Check if login/password is correct.
|
// Process login form: Check if login/password is correct.
|
||||||
if (isset($_POST['login']))
|
if (isset($_POST['login']))
|
||||||
{
|
{
|
||||||
if (!ban_canLogin($conf)) die('I said: NO. You are banned for the moment. Go away.');
|
if (!ban_canLogin($conf)) die(t('I said: NO. You are banned for the moment. Go away.'));
|
||||||
if (isset($_POST['password'])
|
if (isset($_POST['password'])
|
||||||
&& tokenOk($_POST['token'])
|
&& $sessionManager->checkToken($_POST['token'])
|
||||||
&& (check_auth($_POST['login'], $_POST['password'], $conf))
|
&& (check_auth($_POST['login'], $_POST['password'], $conf))
|
||||||
) { // Login/password is OK.
|
) { // Login/password is OK.
|
||||||
ban_loginOk($conf);
|
ban_loginOk($conf);
|
||||||
|
@ -440,7 +445,8 @@ function ban_canLogin($conf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
echo '<script>alert("Wrong login/password.");document.location=\'?do=login'.$redir.'\';</script>'; // Redirect to login screen.
|
// Redirect to login screen.
|
||||||
|
echo '<script>alert("'. t("Wrong login/password.") .'");document.location=\'?do=login'.$redir.'\';</script>';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,32 +456,6 @@ function ban_canLogin($conf)
|
||||||
// Token should be used in any form which acts on data (create,update,delete,import...).
|
// Token should be used in any form which acts on data (create,update,delete,import...).
|
||||||
if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session.
|
if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session.
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a token.
|
|
||||||
*
|
|
||||||
* @param ConfigManager $conf Configuration Manager instance.
|
|
||||||
*
|
|
||||||
* @return string token.
|
|
||||||
*/
|
|
||||||
function getToken($conf)
|
|
||||||
{
|
|
||||||
$rnd = sha1(uniqid('', true) .'_'. mt_rand() . $conf->get('credentials.salt')); // We generate a random string.
|
|
||||||
$_SESSION['tokens'][$rnd]=1; // Store it on the server side.
|
|
||||||
return $rnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tells if a token is OK. Using this function will destroy the token.
|
|
||||||
// true=token is OK.
|
|
||||||
function tokenOk($token)
|
|
||||||
{
|
|
||||||
if (isset($_SESSION['tokens'][$token]))
|
|
||||||
{
|
|
||||||
unset($_SESSION['tokens'][$token]); // Token is used: destroy it.
|
|
||||||
return true; // Token is OK.
|
|
||||||
}
|
|
||||||
return false; // Wrong token, or already used.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Daily RSS feed: 1 RSS entry per day giving all the links on that day.
|
* Daily RSS feed: 1 RSS entry per day giving all the links on that day.
|
||||||
* Gives the last 7 days (which have links).
|
* Gives the last 7 days (which have links).
|
||||||
|
@ -546,7 +526,11 @@ function showDailyRSS($conf) {
|
||||||
|
|
||||||
// We pre-format some fields for proper output.
|
// We pre-format some fields for proper output.
|
||||||
foreach ($links as &$link) {
|
foreach ($links as &$link) {
|
||||||
$link['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
|
$link['formatedDescription'] = format_description(
|
||||||
|
$link['description'],
|
||||||
|
$conf->get('redirector.url'),
|
||||||
|
$conf->get('redirector.encode_url')
|
||||||
|
);
|
||||||
$link['thumbnail'] = thumbnail($conf, $link['url']);
|
$link['thumbnail'] = thumbnail($conf, $link['url']);
|
||||||
$link['timestamp'] = $link['created']->getTimestamp();
|
$link['timestamp'] = $link['created']->getTimestamp();
|
||||||
if (startsWith($link['url'], '?')) {
|
if (startsWith($link['url'], '?')) {
|
||||||
|
@ -618,7 +602,11 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
|
||||||
$taglist = explode(' ',$link['tags']);
|
$taglist = explode(' ',$link['tags']);
|
||||||
uasort($taglist, 'strcasecmp');
|
uasort($taglist, 'strcasecmp');
|
||||||
$linksToDisplay[$key]['taglist']=$taglist;
|
$linksToDisplay[$key]['taglist']=$taglist;
|
||||||
$linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
|
$linksToDisplay[$key]['formatedDescription'] = format_description(
|
||||||
|
$link['description'],
|
||||||
|
$conf->get('redirector.url'),
|
||||||
|
$conf->get('redirector.encode_url')
|
||||||
|
);
|
||||||
$linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
|
$linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
|
||||||
$linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
|
$linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
|
||||||
}
|
}
|
||||||
|
@ -687,8 +675,9 @@ function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager) {
|
||||||
* @param PluginManager $pluginManager Plugin Manager instance,
|
* @param PluginManager $pluginManager Plugin Manager instance,
|
||||||
* @param LinkDB $LINKSDB
|
* @param LinkDB $LINKSDB
|
||||||
* @param History $history instance
|
* @param History $history instance
|
||||||
|
* @param SessionManager $sessionManager SessionManager instance
|
||||||
*/
|
*/
|
||||||
function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager)
|
||||||
{
|
{
|
||||||
$updater = new Updater(
|
$updater = new Updater(
|
||||||
read_updates_file($conf->get('resource.updates')),
|
read_updates_file($conf->get('resource.updates')),
|
||||||
|
@ -709,7 +698,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
die($e->getMessage());
|
die($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
$PAGE = new PageBuilder($conf, $LINKSDB);
|
$PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken());
|
||||||
$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());
|
||||||
|
@ -1100,16 +1089,19 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
if ($targetPage == Router::$PAGE_CHANGEPASSWORD)
|
if ($targetPage == Router::$PAGE_CHANGEPASSWORD)
|
||||||
{
|
{
|
||||||
if ($conf->get('security.open_shaarli')) {
|
if ($conf->get('security.open_shaarli')) {
|
||||||
die('You are not supposed to change a password on an Open Shaarli.');
|
die(t('You are not supposed to change a password on an Open Shaarli.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword']))
|
if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword']))
|
||||||
{
|
{
|
||||||
if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away!
|
if (!$sessionManager->checkToken($_POST['token'])) die(t('Wrong token.')); // Go away!
|
||||||
|
|
||||||
// Make sure old password is correct.
|
// Make sure old password is correct.
|
||||||
$oldhash = sha1($_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt'));
|
$oldhash = sha1($_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt'));
|
||||||
if ($oldhash!= $conf->get('credentials.hash')) { echo '<script>alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; }
|
if ($oldhash!= $conf->get('credentials.hash')) {
|
||||||
|
echo '<script>alert("'. t('The old password is not correct.') .'");document.location=\'?do=changepasswd\';</script>';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
// Save new password
|
// Save new password
|
||||||
// Salt renders rainbow-tables attacks useless.
|
// Salt renders rainbow-tables attacks useless.
|
||||||
$conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
|
$conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
|
||||||
|
@ -1127,7 +1119,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
|
echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>';
|
echo '<script>alert("'. t('Your password has been changed') .'");document.location=\'?do=tools\';</script>';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
else // show the change password form.
|
else // show the change password form.
|
||||||
|
@ -1142,8 +1134,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
{
|
{
|
||||||
if (!empty($_POST['title']) )
|
if (!empty($_POST['title']) )
|
||||||
{
|
{
|
||||||
if (!tokenOk($_POST['token'])) {
|
if (!$sessionManager->checkToken($_POST['token'])) {
|
||||||
die('Wrong token.'); // Go away!
|
die(t('Wrong token.')); // Go away!
|
||||||
}
|
}
|
||||||
$tz = 'UTC';
|
$tz = 'UTC';
|
||||||
if (!empty($_POST['continent']) && !empty($_POST['city'])
|
if (!empty($_POST['continent']) && !empty($_POST['city'])
|
||||||
|
@ -1163,6 +1155,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
$conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks']));
|
$conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks']));
|
||||||
$conf->set('api.enabled', !empty($_POST['enableApi']));
|
$conf->set('api.enabled', !empty($_POST['enableApi']));
|
||||||
$conf->set('api.secret', escape($_POST['apiSecret']));
|
$conf->set('api.secret', escape($_POST['apiSecret']));
|
||||||
|
$conf->set('translation.language', escape($_POST['language']));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$conf->write(isLoggedIn());
|
$conf->write(isLoggedIn());
|
||||||
$history->updateSettings();
|
$history->updateSettings();
|
||||||
|
@ -1178,7 +1172,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=configure\';</script>';
|
echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=configure\';</script>';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
echo '<script>alert("Configuration was saved.");document.location=\'?do=configure\';</script>';
|
echo '<script>alert("'. t('Configuration was saved.') .'");document.location=\'?do=configure\';</script>';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
else // Show the configuration form.
|
else // Show the configuration form.
|
||||||
|
@ -1200,6 +1194,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
$PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false));
|
$PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false));
|
||||||
$PAGE->assign('api_enabled', $conf->get('api.enabled', true));
|
$PAGE->assign('api_enabled', $conf->get('api.enabled', true));
|
||||||
$PAGE->assign('api_secret', $conf->get('api.secret'));
|
$PAGE->assign('api_secret', $conf->get('api.secret'));
|
||||||
|
$PAGE->assign('languages', Languages::getAvailableLanguages());
|
||||||
|
$PAGE->assign('language', $conf->get('translation.language'));
|
||||||
$PAGE->renderPage('configure');
|
$PAGE->renderPage('configure');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
@ -1214,8 +1210,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tokenOk($_POST['token'])) {
|
if (!$sessionManager->checkToken($_POST['token'])) {
|
||||||
die('Wrong token.');
|
die(t('Wrong token.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), escape($_POST['totag']));
|
$alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), escape($_POST['totag']));
|
||||||
|
@ -1225,9 +1221,10 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
}
|
}
|
||||||
$delete = empty($_POST['totag']);
|
$delete = empty($_POST['totag']);
|
||||||
$redirect = $delete ? 'do=changetag' : 'searchtags='. urlencode(escape($_POST['totag']));
|
$redirect = $delete ? 'do=changetag' : 'searchtags='. urlencode(escape($_POST['totag']));
|
||||||
|
$count = count($alteredLinks);
|
||||||
$alert = $delete
|
$alert = $delete
|
||||||
? sprintf(t('The tag was removed from %d links.'), count($alteredLinks))
|
? sprintf(t('The tag was removed from %d link.', 'The tag was removed from %d links.', $count), $count)
|
||||||
: sprintf(t('The tag was renamed in %d links.'), count($alteredLinks));
|
: sprintf(t('The tag was renamed in %d link.', 'The tag was renamed in %d links.', $count), $count);
|
||||||
echo '<script>alert("'. $alert .'");document.location=\'?'. $redirect .'\';</script>';
|
echo '<script>alert("'. $alert .'");document.location=\'?'. $redirect .'\';</script>';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
@ -1243,8 +1240,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
if (isset($_POST['save_edit']))
|
if (isset($_POST['save_edit']))
|
||||||
{
|
{
|
||||||
// Go away!
|
// Go away!
|
||||||
if (! tokenOk($_POST['token'])) {
|
if (! $sessionManager->checkToken($_POST['token'])) {
|
||||||
die('Wrong token.');
|
die(t('Wrong token.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// lf_id should only be present if the link exists.
|
// lf_id should only be present if the link exists.
|
||||||
|
@ -1343,8 +1340,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
// -------- User clicked the "Delete" button when editing a link: Delete link from database.
|
// -------- User clicked the "Delete" button when editing a link: Delete link from database.
|
||||||
if ($targetPage == Router::$PAGE_DELETELINK)
|
if ($targetPage == Router::$PAGE_DELETELINK)
|
||||||
{
|
{
|
||||||
if (! tokenOk($_GET['token'])) {
|
if (! $sessionManager->checkToken($_GET['token'])) {
|
||||||
die('Wrong token.');
|
die(t('Wrong token.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$ids = trim($_GET['lf_linkdate']);
|
$ids = trim($_GET['lf_linkdate']);
|
||||||
|
@ -1428,22 +1425,16 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
// If this is an HTTP(S) link, we try go get the page to extract the title (otherwise we will to straight to the edit form.)
|
// If this is an HTTP(S) link, we try go get the page to extract the title (otherwise we will to straight to the edit form.)
|
||||||
if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) {
|
if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) {
|
||||||
// Short timeout to keep the application responsive
|
// Short timeout to keep the application responsive
|
||||||
list($headers, $content) = get_http_response($url, 4);
|
// The callback will fill $charset and $title with data from the downloaded page.
|
||||||
if (strpos($headers[0], '200 OK') !== false) {
|
get_http_response($url, 25, 4194304, get_curl_download_callback($charset, $title));
|
||||||
// Retrieve charset.
|
|
||||||
$charset = get_charset($headers, $content);
|
|
||||||
// Extract title.
|
|
||||||
$title = html_extract_title($content);
|
|
||||||
// Re-encode title in utf-8 if necessary.
|
|
||||||
if (! empty($title) && strtolower($charset) != 'utf-8') {
|
if (! empty($title) && strtolower($charset) != 'utf-8') {
|
||||||
$title = mb_convert_encoding($title, 'utf-8', $charset);
|
$title = mb_convert_encoding($title, 'utf-8', $charset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ($url == '') {
|
if ($url == '') {
|
||||||
$url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
|
$url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
|
||||||
$title = $conf->get('general.default_note_title', 'Note: ');
|
$title = $conf->get('general.default_note_title', t('Note: '));
|
||||||
}
|
}
|
||||||
$url = escape($url);
|
$url = escape($url);
|
||||||
$title = escape($title);
|
$title = escape($title);
|
||||||
|
@ -1550,14 +1541,17 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
// Import bookmarks from an uploaded file
|
// Import bookmarks from an uploaded file
|
||||||
if (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size'] == 0) {
|
if (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size'] == 0) {
|
||||||
// The file is too big or some form field may be missing.
|
// The file is too big or some form field may be missing.
|
||||||
echo '<script>alert("The file you are trying to upload is probably'
|
$msg = sprintf(
|
||||||
.' bigger than what this webserver can accept ('
|
t(
|
||||||
.get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')).').'
|
'The file you are trying to upload is probably bigger than what this webserver can accept'
|
||||||
.' Please upload in smaller chunks.");document.location=\'?do='
|
.' (%s). Please upload in smaller chunks.'
|
||||||
.Router::$PAGE_IMPORT .'\';</script>';
|
),
|
||||||
|
get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize'))
|
||||||
|
);
|
||||||
|
echo '<script>alert("'. $msg .'");document.location=\'?do='.Router::$PAGE_IMPORT .'\';</script>';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
if (! tokenOk($_POST['token'])) {
|
if (! $sessionManager->checkToken($_POST['token'])) {
|
||||||
die('Wrong token.');
|
die('Wrong token.');
|
||||||
}
|
}
|
||||||
$status = NetscapeBookmarkUtils::import(
|
$status = NetscapeBookmarkUtils::import(
|
||||||
|
@ -1624,7 +1618,7 @@ function($a, $b) { return $a['order'] - $b['order']; }
|
||||||
// Get a fresh token
|
// Get a fresh token
|
||||||
if ($targetPage == Router::$GET_TOKEN) {
|
if ($targetPage == Router::$GET_TOKEN) {
|
||||||
header('Content-Type:text/plain');
|
header('Content-Type:text/plain');
|
||||||
echo getToken($conf);
|
echo $sessionManager->generateToken($conf);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1696,7 +1690,11 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
|
||||||
while ($i<$end && $i<count($keys))
|
while ($i<$end && $i<count($keys))
|
||||||
{
|
{
|
||||||
$link = $linksToDisplay[$keys[$i]];
|
$link = $linksToDisplay[$keys[$i]];
|
||||||
$link['description'] = format_description($link['description'], $conf->get('redirector.url'));
|
$link['description'] = format_description(
|
||||||
|
$link['description'],
|
||||||
|
$conf->get('redirector.url'),
|
||||||
|
$conf->get('redirector.encode_url')
|
||||||
|
);
|
||||||
$classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
|
$classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
|
||||||
$link['class'] = $link['private'] == 0 ? $classLi : 'private';
|
$link['class'] = $link['private'] == 0 ? $classLi : 'private';
|
||||||
$link['timestamp'] = $link['created']->getTimestamp();
|
$link['timestamp'] = $link['created']->getTimestamp();
|
||||||
|
@ -1951,9 +1949,9 @@ function lazyThumbnail($conf, $url,$href=false)
|
||||||
* 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.
|
||||||
*
|
*
|
||||||
* @param ConfigManager $conf Configuration Manager instance.
|
* @param ConfigManager $conf Configuration Manager instance.
|
||||||
|
* @param SessionManager $sessionManager SessionManager instance
|
||||||
*/
|
*/
|
||||||
function install($conf)
|
function install($conf, $sessionManager) {
|
||||||
{
|
|
||||||
// On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
|
// On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
|
||||||
if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705);
|
if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705);
|
||||||
|
|
||||||
|
@ -1962,12 +1960,20 @@ function install($conf)
|
||||||
// (Because on some hosts, session.save_path may not be set correctly,
|
// (Because on some hosts, session.save_path may not be set correctly,
|
||||||
// or we may not have write access to it.)
|
// or we may not have write access to it.)
|
||||||
if (isset($_GET['test_session']) && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working'))
|
if (isset($_GET['test_session']) && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working'))
|
||||||
{ // Step 2: Check if data in session is correct.
|
{
|
||||||
echo '<pre>Sessions do not seem to work correctly on your server.<br>';
|
// Step 2: Check if data in session is correct.
|
||||||
echo 'Make sure the variable session.save_path is set correctly in your php config, and that you have write access to it.<br>';
|
$msg = t(
|
||||||
echo 'It currently points to '.session_save_path().'<br>';
|
'<pre>Sessions do not seem to work correctly on your server.<br>'.
|
||||||
echo 'Check that the hostname used to access Shaarli contains a dot. On some browsers, accessing your server via a hostname like \'localhost\' or any custom hostname without a dot causes cookie storage to fail. We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>';
|
'Make sure the variable "session.save_path" is set correctly in your PHP config, '.
|
||||||
echo '<br><a href="?">Click to try again.</a></pre>';
|
'and that you have write access to it.<br>'.
|
||||||
|
'It currently points to %s.<br>'.
|
||||||
|
'On some browsers, accessing your server via a hostname like \'localhost\' '.
|
||||||
|
'or any custom hostname without a dot causes cookie storage to fail. '.
|
||||||
|
'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>'
|
||||||
|
);
|
||||||
|
$msg = sprintf($msg, session_save_path());
|
||||||
|
echo $msg;
|
||||||
|
echo '<br><a href="?">'. t('Click to try again.') .'</a></pre>';
|
||||||
die;
|
die;
|
||||||
}
|
}
|
||||||
if (!isset($_SESSION['session_tested']))
|
if (!isset($_SESSION['session_tested']))
|
||||||
|
@ -2000,6 +2006,7 @@ function install($conf)
|
||||||
} else {
|
} else {
|
||||||
$conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER)));
|
$conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER)));
|
||||||
}
|
}
|
||||||
|
$conf->set('translation.language', escape($_POST['language']));
|
||||||
$conf->set('updates.check_updates', !empty($_POST['updateCheck']));
|
$conf->set('updates.check_updates', !empty($_POST['updateCheck']));
|
||||||
$conf->set('api.enabled', !empty($_POST['enableApi']));
|
$conf->set('api.enabled', !empty($_POST['enableApi']));
|
||||||
$conf->set(
|
$conf->set(
|
||||||
|
@ -2027,10 +2034,11 @@ function install($conf)
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$PAGE = new PageBuilder($conf);
|
$PAGE = new PageBuilder($conf, 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);
|
||||||
|
$PAGE->assign('languages', Languages::getAvailableLanguages());
|
||||||
$PAGE->renderPage('install');
|
$PAGE->renderPage('install');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
@ -2303,7 +2311,7 @@ function resizeImage($filepath)
|
||||||
if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) {
|
if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) {
|
||||||
// We use UTF-8 for proper international characters handling.
|
// We use UTF-8 for proper international characters handling.
|
||||||
header('Content-Type: text/html; charset=utf-8');
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
renderPage($conf, $pluginManager, $linkDb, $history);
|
renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager);
|
||||||
} else {
|
} else {
|
||||||
$app->respond($response);
|
$app->respond($response);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,16 +22,15 @@ pages:
|
||||||
- Reverse proxy configuration: docker/reverse-proxy-configuration.md
|
- Reverse proxy configuration: docker/reverse-proxy-configuration.md
|
||||||
- Docker resources: docker/resources.md
|
- Docker resources: docker/resources.md
|
||||||
- Usage:
|
- Usage:
|
||||||
- Features: Features.md
|
|
||||||
- Bookmarklet: Bookmarklet.md
|
- Bookmarklet: Bookmarklet.md
|
||||||
- Browsing and searching: Browsing-and-searching.md
|
- Browsing and searching: Browsing-and-searching.md
|
||||||
- Firefox share: Firefox-share.md
|
- Firefox share: Firefox-share.md
|
||||||
- RSS feeds: RSS-feeds.md
|
- RSS feeds: RSS-feeds.md
|
||||||
- REST API: REST-API.md
|
- REST API: REST-API.md
|
||||||
|
- Community & Related software: Community-&-Related-software.md
|
||||||
- How To:
|
- How To:
|
||||||
- Backup, restore, import and export: Backup,-restore,-import-and-export.md
|
- Backup, restore, import and export: Backup,-restore,-import-and-export.md
|
||||||
- Various hacks: Various-hacks.md
|
- Various hacks: Various-hacks.md
|
||||||
- Troubleshooting: Troubleshooting.md
|
|
||||||
- Development:
|
- Development:
|
||||||
- Development guidelines: Development-guidelines.md
|
- Development guidelines: Development-guidelines.md
|
||||||
- Continuous integration tools: Continuous-integration-tools.md
|
- Continuous integration tools: Continuous-integration-tools.md
|
||||||
|
@ -43,9 +42,9 @@ pages:
|
||||||
- Versioning and Branches: Versioning-and-Branches.md
|
- Versioning and Branches: Versioning-and-Branches.md
|
||||||
- Security: Security.md
|
- Security: Security.md
|
||||||
- Static analysis: Static-analysis.md
|
- Static analysis: Static-analysis.md
|
||||||
|
- Translations: Translations.md
|
||||||
- Theming: Theming.md
|
- Theming: Theming.md
|
||||||
- Unit tests: Unit-tests.md
|
- Unit tests: Unit-tests.md
|
||||||
- Unit tests inside Docker: Unit-tests-Docker.md
|
- Unit tests inside Docker: Unit-tests-Docker.md
|
||||||
- About:
|
|
||||||
- FAQ: FAQ.md
|
- FAQ: FAQ.md
|
||||||
- Community & Related software: Community-&-Related-software.md
|
- Troubleshooting: Troubleshooting.md
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
https://github.com/shaarli/Shaarli/issues/181 - Add Disqus or Isso comments box on a permalink page
|
|
||||||
|
|
||||||
* http://posativ.org/isso/
|
|
||||||
* install debian package https://packages.debian.org/sid/isso
|
|
||||||
* configure server http://posativ.org/isso/docs/configuration/server/
|
|
||||||
* configure client http://posativ.org/isso/docs/configuration/client/
|
|
||||||
* http://posativ.org/isso/docs/quickstart/ and add `<script data-isso="//comments.example.tld/" src="//comments.example.tld/js/embed.min.js"></script>` to includes.html template; then add `<section id="isso-thread"></section>` in the linklist template where you want the comments (in the linklist_plugins loop for example)
|
|
||||||
|
|
||||||
|
|
||||||
Problem: by default, Isso thread ID is guessed from the current url (only one thread per page).
|
|
||||||
if we want multiple threads on a single page (shaarli linklist), we must use : the `data-isso-id` client config,
|
|
||||||
with data-isso-id being the permalink of an item.
|
|
||||||
|
|
||||||
`<section data-isso-id="aH7klxW" id="isso-thread"></section>`
|
|
||||||
`data-isso-id: Set a custom thread id, defaults to current URI.`
|
|
||||||
|
|
||||||
Problem: feature is currently broken https://github.com/posativ/isso/issues/27
|
|
||||||
|
|
||||||
Another option, only display isso threads when current URL is a permalink (`\?(A-Z|a-z|0-9|-){7}`) (only show thread
|
|
||||||
when displaying only this link), and just display a "comments" button on each linklist item. Optionally show the comment
|
|
||||||
count on each item using the API (http://posativ.org/isso/docs/extras/api/#get-comment-count). API requests can be done
|
|
||||||
by raintpl `{function` or client-side with js. The former should be faster if isso and shaarli are on ther same server.
|
|
||||||
|
|
||||||
Showing all full isso threads in the linklist would destroy layout
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
http://www.git-attitude.fr/2014/11/04/git-rerere/ for the merge
|
|
|
@ -26,11 +26,11 @@ function hook_addlink_toolbar_render_header($data)
|
||||||
array(
|
array(
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'name' => 'post',
|
'name' => 'post',
|
||||||
'placeholder' => 'URI',
|
'placeholder' => t('URI'),
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'type' => 'submit',
|
'type' => 'submit',
|
||||||
'value' => 'Add link',
|
'value' => t('Add link'),
|
||||||
'class' => 'bigbutton',
|
'class' => 'bigbutton',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -40,3 +40,12 @@ function hook_addlink_toolbar_render_header($data)
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function addlink_toolbar_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('Adds the addlink input on the linklist page.');
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
<span><a href="https://web.archive.org/web/%s"><img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="View on archive.org" alt="archive.org" /></a></span>
|
<span>
|
||||||
|
<a href="https://web.archive.org/web/%s">
|
||||||
|
<img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="%s" alt="archive.org" />
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
|
|
@ -20,9 +20,18 @@ function hook_archiveorg_render_linklist($data)
|
||||||
if($value['private'] && preg_match('/^\?[a-zA-Z0-9-_@]{6}($|&|#)/', $value['real_url'])) {
|
if($value['private'] && preg_match('/^\?[a-zA-Z0-9-_@]{6}($|&|#)/', $value['real_url'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$archive = sprintf($archive_html, $value['url']);
|
$archive = sprintf($archive_html, $value['url'], t('View on archive.org'));
|
||||||
$value['link_plugin'][] = $archive;
|
$value['link_plugin'][] = $archive;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function archiveorg_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('For each link, add an Archive.org icon.');
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,26 @@
|
||||||
* and check user status with _LOGGEDIN_.
|
* and check user status with _LOGGEDIN_.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In the footer hook, there is a working example of a translation extension for Shaarli.
|
||||||
|
*
|
||||||
|
* The extension must be attached to a new translation domain (i.e. NOT 'shaarli').
|
||||||
|
* Use case: any custom theme or non official plugin can use the translation system.
|
||||||
|
*
|
||||||
|
* See the documentation for more information.
|
||||||
|
*/
|
||||||
|
const EXT_TRANSLATION_DOMAIN = 'demo';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is not necessary, but it's easier if you don't want Poedit to mix up your translations.
|
||||||
|
*/
|
||||||
|
function demo_plugin_t($text, $nText = '', $nb = 1)
|
||||||
|
{
|
||||||
|
return t($text, $nText, $nb, EXT_TRANSLATION_DOMAIN);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialization function.
|
* Initialization function.
|
||||||
* It will be called when the plugin is loaded.
|
* It will be called when the plugin is loaded.
|
||||||
|
@ -27,6 +47,12 @@ function demo_plugin_init($conf)
|
||||||
{
|
{
|
||||||
$conf->get('toto', 'nope');
|
$conf->get('toto', 'nope');
|
||||||
|
|
||||||
|
if (! $conf->exists('translation.extensions.demo')) {
|
||||||
|
// Custom translation with the domain 'demo'
|
||||||
|
$conf->set('translation.extensions.demo', 'plugins/demo_plugin/languages/');
|
||||||
|
$conf->write(true);
|
||||||
|
}
|
||||||
|
|
||||||
$errors[] = 'This a demo init error.';
|
$errors[] = 'This a demo init error.';
|
||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +186,7 @@ function hook_demo_plugin_render_includes($data)
|
||||||
function hook_demo_plugin_render_footer($data)
|
function hook_demo_plugin_render_footer($data)
|
||||||
{
|
{
|
||||||
// footer text
|
// footer text
|
||||||
$data['text'][] = 'Shaarli is now enhanced by the awesome demo_plugin.';
|
$data['text'][] = '<br>'. demo_plugin_t('Shaarli is now enhanced by the awesome demo_plugin.');
|
||||||
|
|
||||||
// Free elements at the end of the page.
|
// Free elements at the end of the page.
|
||||||
$data['endofpage'][] = '<marquee id="demo_marquee">' .
|
$data['endofpage'][] = '<marquee id="demo_marquee">' .
|
||||||
|
@ -433,3 +459,12 @@ function hook_demo_plugin_render_feed($data)
|
||||||
}
|
}
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function demo_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('A demo plugin covering all use cases for template designers and plugin developers.');
|
||||||
|
}
|
||||||
|
|
BIN
plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.mo
Normal file
BIN
plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.mo
Normal file
Binary file not shown.
21
plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.po
Normal file
21
plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.po
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Demo plugin\n"
|
||||||
|
"POT-Creation-Date: 2017-08-19 10:45+0200\n"
|
||||||
|
"PO-Revision-Date: 2017-08-19 11:28+0200\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: demo\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Poedit 2.0.2\n"
|
||||||
|
"X-Poedit-Basepath: ../../..\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Poedit-KeywordsList: ;demo_plugin_t:1,2;demo_plugin_t\n"
|
||||||
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
|
"X-Poedit-SearchPath-0: .\n"
|
||||||
|
|
||||||
|
#: demo_plugin.php:173
|
||||||
|
msgid "Shaarli is now enhanced by the awesome demo_plugin."
|
||||||
|
msgstr "Shaarli est maintenant amélioré avec le fantastique demo_plugin."
|
|
@ -4,10 +4,11 @@
|
||||||
* Plugin Isso.
|
* Plugin Isso.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an error everywhere if the plugin is enabled without configuration.
|
* Display an error everywhere if the plugin is enabled without configuration.
|
||||||
*
|
*
|
||||||
* @param $data array List of links
|
|
||||||
* @param $conf ConfigManager instance
|
* @param $conf ConfigManager instance
|
||||||
*
|
*
|
||||||
* @return mixed - linklist data with Isso plugin.
|
* @return mixed - linklist data with Isso plugin.
|
||||||
|
@ -16,8 +17,8 @@ function isso_init($conf)
|
||||||
{
|
{
|
||||||
$issoUrl = $conf->get('plugins.ISSO_SERVER');
|
$issoUrl = $conf->get('plugins.ISSO_SERVER');
|
||||||
if (empty($issoUrl)) {
|
if (empty($issoUrl)) {
|
||||||
$error = 'Isso plugin error: '.
|
$error = t('Isso plugin error: '.
|
||||||
'Please define the "ISSO_SERVER" setting in the plugin administration page.';
|
'Please define the "ISSO_SERVER" setting in the plugin administration page.');
|
||||||
return array($error);
|
return array($error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,3 +53,13 @@ function hook_isso_render_linklist($data, $conf)
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function isso_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('Let visitor comment your shaares on permalinks with Isso.');
|
||||||
|
t('Isso server URL (without \'http://\')');
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="md_help">
|
<div class="md_help">
|
||||||
Description will be rendered with
|
%s
|
||||||
<a href="http://daringfireball.net/projects/markdown/syntax" title="Markdown syntax documentation">
|
<a href="http://daringfireball.net/projects/markdown/syntax" title="%s">
|
||||||
Markdown syntax</a>.
|
%s</a>.
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -154,8 +154,13 @@ function hook_markdown_render_includes($data)
|
||||||
function hook_markdown_render_editlink($data)
|
function hook_markdown_render_editlink($data)
|
||||||
{
|
{
|
||||||
// Load help HTML into a string
|
// Load help HTML into a string
|
||||||
$data['edit_link_plugin'][] = file_get_contents(PluginManager::$PLUGINS_PATH .'/markdown/help.html');
|
$txt = file_get_contents(PluginManager::$PLUGINS_PATH .'/markdown/help.html');
|
||||||
|
$translations = [
|
||||||
|
t('Description will be rendered with'),
|
||||||
|
t('Markdown syntax documentation'),
|
||||||
|
t('Markdown syntax'),
|
||||||
|
];
|
||||||
|
$data['edit_link_plugin'][] = vsprintf($txt, $translations);
|
||||||
// Add no markdown 'meta-tag' in tag list if it was never used, for autocompletion.
|
// Add no markdown 'meta-tag' in tag list if it was never used, for autocompletion.
|
||||||
if (! in_array(NO_MD_TAG, $data['tags'])) {
|
if (! in_array(NO_MD_TAG, $data['tags'])) {
|
||||||
$data['tags'][NO_MD_TAG] = 0;
|
$data['tags'][NO_MD_TAG] = 0;
|
||||||
|
@ -325,3 +330,15 @@ function process_markdown($description, $escape = true, $allowedProtocols = [])
|
||||||
|
|
||||||
return $processedDescription;
|
return $processedDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function markdown_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('Render shaare description with Markdown syntax.<br><strong>Warning</strong>:
|
||||||
|
If your shaared descriptions contained HTML tags before enabling the markdown plugin,
|
||||||
|
enabling it might break your page.
|
||||||
|
See the <a href="https://github.com/shaarli/Shaarli/tree/master/plugins/markdown#html-rendering">README</a>.');
|
||||||
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ function piwik_init($conf)
|
||||||
$piwikUrl = $conf->get('plugins.PIWIK_URL');
|
$piwikUrl = $conf->get('plugins.PIWIK_URL');
|
||||||
$piwikSiteid = $conf->get('plugins.PIWIK_SITEID');
|
$piwikSiteid = $conf->get('plugins.PIWIK_SITEID');
|
||||||
if (empty($piwikUrl) || empty($piwikSiteid)) {
|
if (empty($piwikUrl) || empty($piwikSiteid)) {
|
||||||
$error = 'Piwik plugin error: ' .
|
$error = t('Piwik plugin error: ' .
|
||||||
'Please define PIWIK_URL and PIWIK_SITEID in the plugin administration page.';
|
'Please define PIWIK_URL and PIWIK_SITEID in the plugin administration page.');
|
||||||
return array($error);
|
return array($error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,3 +60,14 @@ function hook_piwik_render_footer($data, $conf)
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function piwik_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('A plugin that adds Piwik tracking code to Shaarli pages.');
|
||||||
|
t('Piwik URL');
|
||||||
|
t('Piwik site ID');
|
||||||
|
}
|
||||||
|
|
|
@ -19,10 +19,10 @@ function hook_playvideos_render_header($data)
|
||||||
$playvideo = array(
|
$playvideo = array(
|
||||||
'attr' => array(
|
'attr' => array(
|
||||||
'href' => '#',
|
'href' => '#',
|
||||||
'title' => 'Video player',
|
'title' => t('Video player'),
|
||||||
'id' => 'playvideos',
|
'id' => 'playvideos',
|
||||||
),
|
),
|
||||||
'html' => '► Play Videos'
|
'html' => '► '. t('Play Videos')
|
||||||
);
|
);
|
||||||
$data['buttons_toolbar'][] = $playvideo;
|
$data['buttons_toolbar'][] = $playvideo;
|
||||||
}
|
}
|
||||||
|
@ -46,3 +46,12 @@ function hook_playvideos_render_footer($data)
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function playvideos_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('Add a button in the toolbar allowing to watch all videos.');
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use pubsubhubbub\publisher\Publisher;
|
use pubsubhubbub\publisher\Publisher;
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin init function - set the hub to the default appspot one.
|
* Plugin init function - set the hub to the default appspot one.
|
||||||
|
@ -65,7 +66,7 @@ function hook_pubsubhubbub_save_link($data, $conf)
|
||||||
$p = new Publisher($conf->get('plugins.PUBSUBHUB_URL'));
|
$p = new Publisher($conf->get('plugins.PUBSUBHUB_URL'));
|
||||||
$p->publish_update($feeds, $httpPost);
|
$p->publish_update($feeds, $httpPost);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log('Could not publish to PubSubHubbub: ' . $e->getMessage());
|
error_log(sprintf(t('Could not publish to PubSubHubbub: %s'), $e->getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
|
@ -91,11 +92,20 @@ function nocurl_http_post($url, $postString) {
|
||||||
$context = stream_context_create($params);
|
$context = stream_context_create($params);
|
||||||
$fp = @fopen($url, 'rb', false, $context);
|
$fp = @fopen($url, 'rb', false, $context);
|
||||||
if (!$fp) {
|
if (!$fp) {
|
||||||
throw new Exception('Could not post to '. $url);
|
throw new Exception(sprintf(t('Could not post to %s'), $url));
|
||||||
}
|
}
|
||||||
$response = @stream_get_contents($fp);
|
$response = @stream_get_contents($fp);
|
||||||
if ($response === false) {
|
if ($response === false) {
|
||||||
throw new Exception('Bad response from the hub '. $url);
|
throw new Exception(sprintf(t('Bad response from the hub %s'), $url));
|
||||||
}
|
}
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function pubsubhubbub_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('Enable PubSubHubbub feed publishing.');
|
||||||
|
}
|
||||||
|
|
|
@ -59,3 +59,12 @@ function hook_qrcode_render_includes($data)
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function qrcode_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('For each link, add a QRCode icon.');
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
<span><a href="%s%s" target="_blank"><img class="linklist-plugin-icon" src="%s/wallabag/wallabag.png" title="Save to wallabag" alt="wallabag" /></a></span>
|
<span>
|
||||||
|
<a href="%s%s" target="_blank">
|
||||||
|
<img class="linklist-plugin-icon" src="%s/wallabag/wallabag.png" title="%s" alt="wallabag" />
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require_once 'WallabagInstance.php';
|
require_once 'WallabagInstance.php';
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init function, return an error if the server is not set.
|
* Init function, return an error if the server is not set.
|
||||||
|
@ -17,8 +18,8 @@ function wallabag_init($conf)
|
||||||
{
|
{
|
||||||
$wallabagUrl = $conf->get('plugins.WALLABAG_URL');
|
$wallabagUrl = $conf->get('plugins.WALLABAG_URL');
|
||||||
if (empty($wallabagUrl)) {
|
if (empty($wallabagUrl)) {
|
||||||
$error = 'Wallabag plugin error: '.
|
$error = t('Wallabag plugin error: '.
|
||||||
'Please define the "WALLABAG_URL" setting in the plugin administration page.';
|
'Please define the "WALLABAG_URL" setting in the plugin administration page.');
|
||||||
return array($error);
|
return array($error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,12 +44,14 @@ function hook_wallabag_render_linklist($data, $conf)
|
||||||
|
|
||||||
$wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html');
|
$wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html');
|
||||||
|
|
||||||
|
$linkTitle = t('Save to wallabag');
|
||||||
foreach ($data['links'] as &$value) {
|
foreach ($data['links'] as &$value) {
|
||||||
$wallabag = sprintf(
|
$wallabag = sprintf(
|
||||||
$wallabagHtml,
|
$wallabagHtml,
|
||||||
$wallabagInstance->getWallabagUrl(),
|
$wallabagInstance->getWallabagUrl(),
|
||||||
urlencode($value['url']),
|
urlencode($value['url']),
|
||||||
PluginManager::$PLUGINS_PATH
|
PluginManager::$PLUGINS_PATH,
|
||||||
|
$linkTitle
|
||||||
);
|
);
|
||||||
$value['link_plugin'][] = $wallabag;
|
$value['link_plugin'][] = $wallabag;
|
||||||
}
|
}
|
||||||
|
@ -56,3 +59,14 @@ function hook_wallabag_render_linklist($data, $conf)
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is never called, but contains translation calls for GNU gettext extraction.
|
||||||
|
*/
|
||||||
|
function wallabag_dummy_translation()
|
||||||
|
{
|
||||||
|
// meta
|
||||||
|
t('For each link, add a QRCode icon.');
|
||||||
|
t('Wallabag API URL');
|
||||||
|
t('Wallabag API version (1 or 2)');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<?php /* 0.9.3 */ ?>
|
<?php /* 0.9.4 */ ?>
|
||||||
|
|
|
@ -186,4 +186,36 @@ public function testStandardHttpsPort()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Misconfigured server (see #1022): Proxy HTTP but 443
|
||||||
|
*/
|
||||||
|
public function testHttpWithPort433()
|
||||||
|
{
|
||||||
|
$this->assertEquals(
|
||||||
|
'https://host.tld',
|
||||||
|
server_url(
|
||||||
|
array(
|
||||||
|
'HTTPS' => 'Off',
|
||||||
|
'SERVER_NAME' => 'host.tld',
|
||||||
|
'SERVER_PORT' => '80',
|
||||||
|
'HTTP_X_FORWARDED_PROTO' => 'http',
|
||||||
|
'HTTP_X_FORWARDED_PORT' => '443'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'https://host.tld',
|
||||||
|
server_url(
|
||||||
|
array(
|
||||||
|
'HTTPS' => 'Off',
|
||||||
|
'SERVER_NAME' => 'host.tld',
|
||||||
|
'SERVER_PORT' => '80',
|
||||||
|
'HTTP_X_FORWARDED_PROTO' => 'https, http',
|
||||||
|
'HTTP_X_FORWARDED_PORT' => '443, 80'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,203 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once 'application/Languages.php';
|
namespace Shaarli;
|
||||||
|
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class LanguagesTest.
|
* Class LanguagesTest.
|
||||||
*/
|
*/
|
||||||
class LanguagesTest extends PHPUnit_Framework_TestCase
|
class LanguagesTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var string Config file path (without extension).
|
||||||
|
*/
|
||||||
|
protected static $configFile = 'tests/utils/config/configJson';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ConfigManager
|
||||||
|
*/
|
||||||
|
protected $conf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->conf = new ConfigManager(self::$configFile);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test t() with a simple non identified value.
|
* Test t() with a simple non identified value.
|
||||||
*/
|
*/
|
||||||
public function testTranslateSingleNotID()
|
public function testTranslateSingleNotIDGettext()
|
||||||
{
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
$text = 'abcdé 564 fgK';
|
$text = 'abcdé 564 fgK';
|
||||||
$this->assertEquals($text, t($text));
|
$this->assertEquals($text, t($text));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test t() with a non identified plural form.
|
* Test t() with a simple identified value in gettext mode.
|
||||||
*/
|
*/
|
||||||
public function testTranslatePluralNotID()
|
public function testTranslateSingleIDGettext()
|
||||||
{
|
{
|
||||||
$text = '%s sandwich';
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
$nText = '%s sandwiches';
|
new Languages('en', $this->conf);
|
||||||
$this->assertEquals('0 sandwich', t($text, $nText));
|
$text = 'permalink';
|
||||||
$this->assertEquals('1 sandwich', t($text, $nText, 1));
|
$this->assertEquals($text, t($text));
|
||||||
$this->assertEquals('2 sandwiches', t($text, $nText, 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test t() with a non identified invalid plural form.
|
* Test t() with a non identified plural form in gettext mode.
|
||||||
*/
|
*/
|
||||||
public function testTranslatePluralNotIDInvalid()
|
public function testTranslatePluralNotIDGettext()
|
||||||
{
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
$text = 'sandwich';
|
$text = 'sandwich';
|
||||||
$nText = 'sandwiches';
|
$nText = 'sandwiches';
|
||||||
|
$this->assertEquals('sandwiches', t($text, $nText, 0));
|
||||||
$this->assertEquals('sandwich', t($text, $nText, 1));
|
$this->assertEquals('sandwich', t($text, $nText, 1));
|
||||||
$this->assertEquals('sandwiches', t($text, $nText, 2));
|
$this->assertEquals('sandwiches', t($text, $nText, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an identified plural form in gettext mode.
|
||||||
|
*/
|
||||||
|
public function testTranslatePluralIDGettext()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'shaare';
|
||||||
|
$nText = 'shaares';
|
||||||
|
// In english, zero is followed by plural form
|
||||||
|
$this->assertEquals('shaares', t($text, $nText, 0));
|
||||||
|
$this->assertEquals('shaare', t($text, $nText, 1));
|
||||||
|
$this->assertEquals('shaares', t($text, $nText, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with a simple non identified value.
|
||||||
|
*/
|
||||||
|
public function testTranslateSingleNotIDPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'abcdé 564 fgK';
|
||||||
|
$this->assertEquals($text, t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with a simple identified value in PHP mode.
|
||||||
|
*/
|
||||||
|
public function testTranslateSingleIDPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'permalink';
|
||||||
|
$this->assertEquals($text, t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with a non identified plural form in PHP mode.
|
||||||
|
*/
|
||||||
|
public function testTranslatePluralNotIDPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'sandwich';
|
||||||
|
$nText = 'sandwiches';
|
||||||
|
$this->assertEquals('sandwiches', t($text, $nText, 0));
|
||||||
|
$this->assertEquals('sandwich', t($text, $nText, 1));
|
||||||
|
$this->assertEquals('sandwiches', t($text, $nText, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an identified plural form in PHP mode.
|
||||||
|
*/
|
||||||
|
public function testTranslatePluralIDPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'shaare';
|
||||||
|
$nText = 'shaares';
|
||||||
|
// In english, zero is followed by plural form
|
||||||
|
$this->assertEquals('shaares', t($text, $nText, 0));
|
||||||
|
$this->assertEquals('shaare', t($text, $nText, 1));
|
||||||
|
$this->assertEquals('shaares', t($text, $nText, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an invalid language set in the configuration in gettext mode.
|
||||||
|
*/
|
||||||
|
public function testTranslateWithInvalidConfLanguageGettext()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
$this->conf->set('translation.language', 'nope');
|
||||||
|
new Languages('fr', $this->conf);
|
||||||
|
$text = 'grumble';
|
||||||
|
$this->assertEquals($text, t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an invalid language set in the configuration in PHP mode.
|
||||||
|
*/
|
||||||
|
public function testTranslateWithInvalidConfLanguagePhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
$this->conf->set('translation.language', 'nope');
|
||||||
|
new Languages('fr', $this->conf);
|
||||||
|
$text = 'grumble';
|
||||||
|
$this->assertEquals($text, t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an invalid language set with auto language in gettext mode.
|
||||||
|
*/
|
||||||
|
public function testTranslateWithInvalidAutoLanguageGettext()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
new Languages('nope', $this->conf);
|
||||||
|
$text = 'grumble';
|
||||||
|
$this->assertEquals($text, t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an invalid language set with auto language in PHP mode.
|
||||||
|
*/
|
||||||
|
public function testTranslateWithInvalidAutoLanguagePhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
new Languages('nope', $this->conf);
|
||||||
|
$text = 'grumble';
|
||||||
|
$this->assertEquals($text, t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an extension language file in gettext mode
|
||||||
|
*/
|
||||||
|
public function testTranslationExtensionGettext()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
$this->conf->set('translation.extensions.test', 'tests/utils/languages/');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$txt = 'car'; // ignore me poedit
|
||||||
|
$this->assertEquals('car', t($txt, $txt, 1, 'test'));
|
||||||
|
$this->assertEquals('Search', t('Search', 'Search', 1, 'test'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an extension language file in PHP mode
|
||||||
|
*/
|
||||||
|
public function testTranslationExtensionPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
$this->conf->set('translation.extensions.test', 'tests/utils/languages/');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$txt = 'car'; // ignore me poedit
|
||||||
|
$this->assertEquals('car', t($txt, $txt, 1, 'test'));
|
||||||
|
$this->assertEquals('Search', t('Search', 'Search', 1, 'test'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
*/
|
*/
|
||||||
class LinkFilterTest extends PHPUnit_Framework_TestCase
|
class LinkFilterTest extends PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var string Test datastore path.
|
||||||
|
*/
|
||||||
|
protected static $testDatastore = 'sandbox/datastore.php';
|
||||||
/**
|
/**
|
||||||
* @var LinkFilter instance.
|
* @var LinkFilter instance.
|
||||||
*/
|
*/
|
||||||
|
@ -17,13 +21,20 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
||||||
*/
|
*/
|
||||||
protected static $refDB;
|
protected static $refDB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LinkDB instance
|
||||||
|
*/
|
||||||
|
protected static $linkDB;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instanciate linkFilter with ReferenceLinkDB data.
|
* Instanciate linkFilter with ReferenceLinkDB data.
|
||||||
*/
|
*/
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass()
|
||||||
{
|
{
|
||||||
self::$refDB = new ReferenceLinkDB();
|
self::$refDB = new ReferenceLinkDB();
|
||||||
self::$linkFilter = new LinkFilter(self::$refDB->getLinks());
|
self::$refDB->write(self::$testDatastore);
|
||||||
|
self::$linkDB = new LinkDB(self::$testDatastore, true, false);
|
||||||
|
self::$linkFilter = new LinkFilter(self::$linkDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -28,28 +28,14 @@ public function testHtmlExtractNonExistentTitle()
|
||||||
$this->assertFalse(html_extract_title($html));
|
$this->assertFalse(html_extract_title($html));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test get_charset() with all priorities.
|
|
||||||
*/
|
|
||||||
public function testGetCharset()
|
|
||||||
{
|
|
||||||
$headers = array('Content-Type' => 'text/html; charset=Headers');
|
|
||||||
$html = '<html><meta>stuff</meta><meta charset="Html"/></html>';
|
|
||||||
$default = 'default';
|
|
||||||
$this->assertEquals('headers', get_charset($headers, $html, $default));
|
|
||||||
$this->assertEquals('html', get_charset(array(), $html, $default));
|
|
||||||
$this->assertEquals($default, get_charset(array(), '', $default));
|
|
||||||
$this->assertEquals('utf-8', get_charset(array(), ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test headers_extract_charset() when the charset is found.
|
* Test headers_extract_charset() when the charset is found.
|
||||||
*/
|
*/
|
||||||
public function testHeadersExtractExistentCharset()
|
public function testHeadersExtractExistentCharset()
|
||||||
{
|
{
|
||||||
$charset = 'x-MacCroatian';
|
$charset = 'x-MacCroatian';
|
||||||
$headers = array('Content-Type' => 'text/html; charset='. $charset);
|
$headers = 'text/html; charset='. $charset;
|
||||||
$this->assertEquals(strtolower($charset), headers_extract_charset($headers));
|
$this->assertEquals(strtolower($charset), header_extract_charset($headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,11 +43,11 @@ public function testHeadersExtractExistentCharset()
|
||||||
*/
|
*/
|
||||||
public function testHeadersExtractNonExistentCharset()
|
public function testHeadersExtractNonExistentCharset()
|
||||||
{
|
{
|
||||||
$headers = array();
|
$headers = '';
|
||||||
$this->assertFalse(headers_extract_charset($headers));
|
$this->assertFalse(header_extract_charset($headers));
|
||||||
|
|
||||||
$headers = array('Content-Type' => 'text/html');
|
$headers = 'text/html';
|
||||||
$this->assertFalse(headers_extract_charset($headers));
|
$this->assertFalse(header_extract_charset($headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,6 +71,131 @@ public function testHtmlExtractNonExistentCharset()
|
||||||
$this->assertFalse(html_extract_charset($html));
|
$this->assertFalse(html_extract_charset($html));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the download callback with valid value
|
||||||
|
*/
|
||||||
|
public function testCurlDownloadCallbackOk()
|
||||||
|
{
|
||||||
|
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok');
|
||||||
|
$data = [
|
||||||
|
'HTTP/1.1 200 OK',
|
||||||
|
'Server: GitHub.com',
|
||||||
|
'Date: Sat, 28 Oct 2017 12:01:33 GMT',
|
||||||
|
'Content-Type: text/html; charset=utf-8',
|
||||||
|
'Status: 200 OK',
|
||||||
|
'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea',
|
||||||
|
'<title>ignored</title>',
|
||||||
|
];
|
||||||
|
foreach ($data as $key => $line) {
|
||||||
|
$ignore = null;
|
||||||
|
$expected = $key !== 'end' ? strlen($line) : false;
|
||||||
|
$this->assertEquals($expected, $callback($ignore, $line));
|
||||||
|
if ($expected === false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertEquals('utf-8', $charset);
|
||||||
|
$this->assertEquals('Refactoring · GitHub', $title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the download callback with valid values and no charset
|
||||||
|
*/
|
||||||
|
public function testCurlDownloadCallbackOkNoCharset()
|
||||||
|
{
|
||||||
|
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset');
|
||||||
|
$data = [
|
||||||
|
'HTTP/1.1 200 OK',
|
||||||
|
'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea',
|
||||||
|
'<title>ignored</title>',
|
||||||
|
];
|
||||||
|
foreach ($data as $key => $line) {
|
||||||
|
$ignore = null;
|
||||||
|
$this->assertEquals(strlen($line), $callback($ignore, $line));
|
||||||
|
}
|
||||||
|
$this->assertEmpty($charset);
|
||||||
|
$this->assertEquals('Refactoring · GitHub', $title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the download callback with valid values and no charset
|
||||||
|
*/
|
||||||
|
public function testCurlDownloadCallbackOkHtmlCharset()
|
||||||
|
{
|
||||||
|
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset');
|
||||||
|
$data = [
|
||||||
|
'HTTP/1.1 200 OK',
|
||||||
|
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />',
|
||||||
|
'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea',
|
||||||
|
'<title>ignored</title>',
|
||||||
|
];
|
||||||
|
foreach ($data as $key => $line) {
|
||||||
|
$ignore = null;
|
||||||
|
$expected = $key !== 'end' ? strlen($line) : false;
|
||||||
|
$this->assertEquals($expected, $callback($ignore, $line));
|
||||||
|
if ($expected === false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertEquals('utf-8', $charset);
|
||||||
|
$this->assertEquals('Refactoring · GitHub', $title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the download callback with valid values and no title
|
||||||
|
*/
|
||||||
|
public function testCurlDownloadCallbackOkNoTitle()
|
||||||
|
{
|
||||||
|
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok');
|
||||||
|
$data = [
|
||||||
|
'HTTP/1.1 200 OK',
|
||||||
|
'end' => 'th=device-width">Refactoring · GitHub<link rel="search" type="application/opensea',
|
||||||
|
'ignored',
|
||||||
|
];
|
||||||
|
foreach ($data as $key => $line) {
|
||||||
|
$ignore = null;
|
||||||
|
$this->assertEquals(strlen($line), $callback($ignore, $line));
|
||||||
|
}
|
||||||
|
$this->assertEquals('utf-8', $charset);
|
||||||
|
$this->assertEmpty($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the download callback with an invalid content type.
|
||||||
|
*/
|
||||||
|
public function testCurlDownloadCallbackInvalidContentType()
|
||||||
|
{
|
||||||
|
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ct_ko');
|
||||||
|
$ignore = null;
|
||||||
|
$this->assertFalse($callback($ignore, ''));
|
||||||
|
$this->assertEmpty($charset);
|
||||||
|
$this->assertEmpty($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the download callback with an invalid response code.
|
||||||
|
*/
|
||||||
|
public function testCurlDownloadCallbackInvalidResponseCode()
|
||||||
|
{
|
||||||
|
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rc_ko');
|
||||||
|
$ignore = null;
|
||||||
|
$this->assertFalse($callback($ignore, ''));
|
||||||
|
$this->assertEmpty($charset);
|
||||||
|
$this->assertEmpty($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the download callback with an invalid content type and response code.
|
||||||
|
*/
|
||||||
|
public function testCurlDownloadCallbackInvalidContentTypeAndResponseCode()
|
||||||
|
{
|
||||||
|
$callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rs_ct_ko');
|
||||||
|
$ignore = null;
|
||||||
|
$this->assertFalse($callback($ignore, ''));
|
||||||
|
$this->assertEmpty($charset);
|
||||||
|
$this->assertEmpty($title);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test count_private.
|
* Test count_private.
|
||||||
*/
|
*/
|
||||||
|
@ -130,6 +241,21 @@ public function testText2clickableWithRedirector()
|
||||||
$this->assertEquals($expectedText, $processedText);
|
$this->assertEquals($expectedText, $processedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test text2clickable a redirector set and without URL encode.
|
||||||
|
*/
|
||||||
|
public function testText2clickableWithRedirectorDontEncode()
|
||||||
|
{
|
||||||
|
$text = 'stuff http://hello.there/?is=someone&or=something#here otherstuff';
|
||||||
|
$redirector = 'http://redirector.to';
|
||||||
|
$expectedText = 'stuff <a href="'.
|
||||||
|
$redirector .
|
||||||
|
'http://hello.there/?is=someone&or=something#here' .
|
||||||
|
'">http://hello.there/?is=someone&or=something#here</a> otherstuff';
|
||||||
|
$processedText = text2clickable($text, $redirector, false);
|
||||||
|
$this->assertEquals($expectedText, $processedText);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test testSpace2nbsp.
|
* Test testSpace2nbsp.
|
||||||
*/
|
*/
|
||||||
|
@ -192,3 +318,96 @@ private function getHashtagLink($hashtag, $index = '')
|
||||||
return str_replace('$1', $hashtag, $hashtagLink);
|
return str_replace('$1', $hashtag, $hashtagLink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// old style mock: PHPUnit doesn't allow function mock
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns code 200 or html content type.
|
||||||
|
*
|
||||||
|
* @param resource $ch cURL resource
|
||||||
|
* @param int $type cURL info type
|
||||||
|
*
|
||||||
|
* @return int|string 200 or 'text/html'
|
||||||
|
*/
|
||||||
|
function ut_curl_getinfo_ok($ch, $type)
|
||||||
|
{
|
||||||
|
switch ($type) {
|
||||||
|
case CURLINFO_RESPONSE_CODE:
|
||||||
|
return 200;
|
||||||
|
case CURLINFO_CONTENT_TYPE:
|
||||||
|
return 'text/html; charset=utf-8';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns code 200 or html content type without charset.
|
||||||
|
*
|
||||||
|
* @param resource $ch cURL resource
|
||||||
|
* @param int $type cURL info type
|
||||||
|
*
|
||||||
|
* @return int|string 200 or 'text/html'
|
||||||
|
*/
|
||||||
|
function ut_curl_getinfo_no_charset($ch, $type)
|
||||||
|
{
|
||||||
|
switch ($type) {
|
||||||
|
case CURLINFO_RESPONSE_CODE:
|
||||||
|
return 200;
|
||||||
|
case CURLINFO_CONTENT_TYPE:
|
||||||
|
return 'text/html';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid response code.
|
||||||
|
*
|
||||||
|
* @param resource $ch cURL resource
|
||||||
|
* @param int $type cURL info type
|
||||||
|
*
|
||||||
|
* @return int|string 404 or 'text/html'
|
||||||
|
*/
|
||||||
|
function ut_curl_getinfo_rc_ko($ch, $type)
|
||||||
|
{
|
||||||
|
switch ($type) {
|
||||||
|
case CURLINFO_RESPONSE_CODE:
|
||||||
|
return 404;
|
||||||
|
case CURLINFO_CONTENT_TYPE:
|
||||||
|
return 'text/html; charset=utf-8';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid content type.
|
||||||
|
*
|
||||||
|
* @param resource $ch cURL resource
|
||||||
|
* @param int $type cURL info type
|
||||||
|
*
|
||||||
|
* @return int|string 200 or 'text/plain'
|
||||||
|
*/
|
||||||
|
function ut_curl_getinfo_ct_ko($ch, $type)
|
||||||
|
{
|
||||||
|
switch ($type) {
|
||||||
|
case CURLINFO_RESPONSE_CODE:
|
||||||
|
return 200;
|
||||||
|
case CURLINFO_CONTENT_TYPE:
|
||||||
|
return 'text/plain';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid response code and content type.
|
||||||
|
*
|
||||||
|
* @param resource $ch cURL resource
|
||||||
|
* @param int $type cURL info type
|
||||||
|
*
|
||||||
|
* @return int|string 404 or 'text/plain'
|
||||||
|
*/
|
||||||
|
function ut_curl_getinfo_rs_ct_ko($ch, $type)
|
||||||
|
{
|
||||||
|
switch ($type) {
|
||||||
|
case CURLINFO_RESPONSE_CODE:
|
||||||
|
return 404;
|
||||||
|
case CURLINFO_CONTENT_TYPE:
|
||||||
|
return 'text/plain';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,8 +132,8 @@ public function testImportNoDoctype()
|
||||||
public function testImportInternetExplorerEncoding()
|
public function testImportInternetExplorerEncoding()
|
||||||
{
|
{
|
||||||
$files = file2array('internet_explorer_encoding.htm');
|
$files = file2array('internet_explorer_encoding.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File internet_explorer_encoding.htm (356 bytes) was successfully processed:'
|
'File internet_explorer_encoding.htm (356 bytes) was successfully processed in %d seconds:'
|
||||||
.' 1 links imported, 0 links overwritten, 0 links skipped.',
|
.' 1 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -161,8 +161,8 @@ public function testImportInternetExplorerEncoding()
|
||||||
public function testImportNested()
|
public function testImportNested()
|
||||||
{
|
{
|
||||||
$files = file2array('netscape_nested.htm');
|
$files = file2array('netscape_nested.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_nested.htm (1337 bytes) was successfully processed:'
|
'File netscape_nested.htm (1337 bytes) was successfully processed in %d seconds:'
|
||||||
.' 8 links imported, 0 links overwritten, 0 links skipped.',
|
.' 8 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -283,8 +283,8 @@ public function testImportNested()
|
||||||
public function testImportDefaultPrivacyNoPost()
|
public function testImportDefaultPrivacyNoPost()
|
||||||
{
|
{
|
||||||
$files = file2array('netscape_basic.htm');
|
$files = file2array('netscape_basic.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -328,8 +328,8 @@ public function testImportKeepPrivacy()
|
||||||
{
|
{
|
||||||
$post = array('privacy' => 'default');
|
$post = array('privacy' => 'default');
|
||||||
$files = file2array('netscape_basic.htm');
|
$files = file2array('netscape_basic.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -372,8 +372,8 @@ public function testImportAsPublic()
|
||||||
{
|
{
|
||||||
$post = array('privacy' => 'public');
|
$post = array('privacy' => 'public');
|
||||||
$files = file2array('netscape_basic.htm');
|
$files = file2array('netscape_basic.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -396,8 +396,8 @@ public function testImportAsPrivate()
|
||||||
{
|
{
|
||||||
$post = array('privacy' => 'private');
|
$post = array('privacy' => 'private');
|
||||||
$files = file2array('netscape_basic.htm');
|
$files = file2array('netscape_basic.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -422,8 +422,8 @@ public function testOverwriteAsPublic()
|
||||||
|
|
||||||
// import links as private
|
// import links as private
|
||||||
$post = array('privacy' => 'private');
|
$post = array('privacy' => 'private');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -442,8 +442,8 @@ public function testOverwriteAsPublic()
|
||||||
'privacy' => 'public',
|
'privacy' => 'public',
|
||||||
'overwrite' => 'true'
|
'overwrite' => 'true'
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 2 links overwritten, 0 links skipped.',
|
.' 2 links imported, 2 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -468,8 +468,8 @@ public function testOverwriteAsPrivate()
|
||||||
|
|
||||||
// import links as public
|
// import links as public
|
||||||
$post = array('privacy' => 'public');
|
$post = array('privacy' => 'public');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -489,8 +489,8 @@ public function testOverwriteAsPrivate()
|
||||||
'privacy' => 'private',
|
'privacy' => 'private',
|
||||||
'overwrite' => 'true'
|
'overwrite' => 'true'
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 2 links overwritten, 0 links skipped.',
|
.' 2 links imported, 2 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -513,8 +513,8 @@ public function testSkipOverwrite()
|
||||||
{
|
{
|
||||||
$post = array('privacy' => 'public');
|
$post = array('privacy' => 'public');
|
||||||
$files = file2array('netscape_basic.htm');
|
$files = file2array('netscape_basic.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -523,8 +523,8 @@ public function testSkipOverwrite()
|
||||||
|
|
||||||
// re-import as private, DO NOT enable overwriting
|
// re-import as private, DO NOT enable overwriting
|
||||||
$post = array('privacy' => 'private');
|
$post = array('privacy' => 'private');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 0 links imported, 0 links overwritten, 2 links skipped.',
|
.' 0 links imported, 0 links overwritten, 2 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -542,8 +542,8 @@ public function testSetDefaultTags()
|
||||||
'default_tags' => 'tag1,tag2 tag3'
|
'default_tags' => 'tag1,tag2 tag3'
|
||||||
);
|
);
|
||||||
$files = file2array('netscape_basic.htm');
|
$files = file2array('netscape_basic.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -569,8 +569,8 @@ public function testSanitizeDefaultTags()
|
||||||
'default_tags' => 'tag1&,tag2 "tag3"'
|
'default_tags' => 'tag1&,tag2 "tag3"'
|
||||||
);
|
);
|
||||||
$files = file2array('netscape_basic.htm');
|
$files = file2array('netscape_basic.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File netscape_basic.htm (482 bytes) was successfully processed:'
|
'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
|
||||||
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
.' 2 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -594,8 +594,8 @@ public function testSanitizeDefaultTags()
|
||||||
public function testImportSameDate()
|
public function testImportSameDate()
|
||||||
{
|
{
|
||||||
$files = file2array('same_date.htm');
|
$files = file2array('same_date.htm');
|
||||||
$this->assertEquals(
|
$this->assertStringMatchesFormat(
|
||||||
'File same_date.htm (453 bytes) was successfully processed:'
|
'File same_date.htm (453 bytes) was successfully processed in %d seconds:'
|
||||||
.' 3 links imported, 0 links overwritten, 0 links skipped.',
|
.' 3 links imported, 0 links overwritten, 0 links skipped.',
|
||||||
NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history)
|
NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history)
|
||||||
);
|
);
|
||||||
|
@ -622,24 +622,19 @@ public function testImportCreateUpdateHistory()
|
||||||
'overwrite' => 'true',
|
'overwrite' => 'true',
|
||||||
];
|
];
|
||||||
$files = file2array('netscape_basic.htm');
|
$files = file2array('netscape_basic.htm');
|
||||||
$nbLinks = 2;
|
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
|
||||||
$history = $this->history->getHistory();
|
$history = $this->history->getHistory();
|
||||||
$this->assertEquals($nbLinks, count($history));
|
$this->assertEquals(1, count($history));
|
||||||
foreach ($history as $value) {
|
$this->assertEquals(History::IMPORT, $history[0]['event']);
|
||||||
$this->assertEquals(History::CREATED, $value['event']);
|
$this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']);
|
||||||
$this->assertTrue(new DateTime('-5 seconds') < $value['datetime']);
|
|
||||||
$this->assertTrue(is_int($value['id']));
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-import as private, enable overwriting
|
// re-import as private, enable overwriting
|
||||||
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
|
NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
|
||||||
$history = $this->history->getHistory();
|
$history = $this->history->getHistory();
|
||||||
$this->assertEquals($nbLinks * 2, count($history));
|
$this->assertEquals(2, count($history));
|
||||||
for ($i = 0 ; $i < $nbLinks ; $i++) {
|
$this->assertEquals(History::IMPORT, $history[0]['event']);
|
||||||
$this->assertEquals(History::UPDATED, $history[$i]['event']);
|
$this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']);
|
||||||
$this->assertTrue(new DateTime('-5 seconds') < $history[$i]['datetime']);
|
$this->assertEquals(History::IMPORT, $history[1]['event']);
|
||||||
$this->assertTrue(is_int($history[$i]['id']));
|
$this->assertTrue(new DateTime('-5 seconds') < $history[1]['datetime']);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
149
tests/SessionManagerTest.php
Normal file
149
tests/SessionManagerTest.php
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
<?php
|
||||||
|
require_once 'tests/utils/FakeConfigManager.php';
|
||||||
|
|
||||||
|
// Initialize reference data _before_ PHPUnit starts a session
|
||||||
|
require_once 'tests/utils/ReferenceSessionIdHashes.php';
|
||||||
|
ReferenceSessionIdHashes::genAllHashes();
|
||||||
|
|
||||||
|
use \Shaarli\SessionManager;
|
||||||
|
use \PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test coverage for SessionManager
|
||||||
|
*/
|
||||||
|
class SessionManagerTest extends TestCase
|
||||||
|
{
|
||||||
|
// Session ID hashes
|
||||||
|
protected static $sidHashes = null;
|
||||||
|
|
||||||
|
// Fake ConfigManager
|
||||||
|
protected static $conf = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign reference data
|
||||||
|
*/
|
||||||
|
public static function setUpBeforeClass()
|
||||||
|
{
|
||||||
|
self::$sidHashes = ReferenceSessionIdHashes::getHashes();
|
||||||
|
self::$conf = new FakeConfigManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a session token
|
||||||
|
*/
|
||||||
|
public function testGenerateToken()
|
||||||
|
{
|
||||||
|
$session = [];
|
||||||
|
$sessionManager = new SessionManager($session, self::$conf);
|
||||||
|
|
||||||
|
$token = $sessionManager->generateToken();
|
||||||
|
|
||||||
|
$this->assertEquals(1, $session['tokens'][$token]);
|
||||||
|
$this->assertEquals(40, strlen($token));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a session token
|
||||||
|
*/
|
||||||
|
public function testCheckToken()
|
||||||
|
{
|
||||||
|
$token = '4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b';
|
||||||
|
$session = [
|
||||||
|
'tokens' => [
|
||||||
|
$token => 1,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$sessionManager = new SessionManager($session, self::$conf);
|
||||||
|
|
||||||
|
// check and destroy the token
|
||||||
|
$this->assertTrue($sessionManager->checkToken($token));
|
||||||
|
$this->assertFalse(isset($session['tokens'][$token]));
|
||||||
|
|
||||||
|
// ensure the token has been destroyed
|
||||||
|
$this->assertFalse($sessionManager->checkToken($token));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate and check a session token
|
||||||
|
*/
|
||||||
|
public function testGenerateAndCheckToken()
|
||||||
|
{
|
||||||
|
$session = [];
|
||||||
|
$sessionManager = new SessionManager($session, self::$conf);
|
||||||
|
|
||||||
|
$token = $sessionManager->generateToken();
|
||||||
|
|
||||||
|
// ensure a token has been generated
|
||||||
|
$this->assertEquals(1, $session['tokens'][$token]);
|
||||||
|
$this->assertEquals(40, strlen($token));
|
||||||
|
|
||||||
|
// check and destroy the token
|
||||||
|
$this->assertTrue($sessionManager->checkToken($token));
|
||||||
|
$this->assertFalse(isset($session['tokens'][$token]));
|
||||||
|
|
||||||
|
// ensure the token has been destroyed
|
||||||
|
$this->assertFalse($sessionManager->checkToken($token));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check an invalid session token
|
||||||
|
*/
|
||||||
|
public function testCheckInvalidToken()
|
||||||
|
{
|
||||||
|
$session = [];
|
||||||
|
$sessionManager = new SessionManager($session, self::$conf);
|
||||||
|
|
||||||
|
$this->assertFalse($sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test SessionManager::checkId with a valid ID - TEST ALL THE HASHES!
|
||||||
|
*
|
||||||
|
* This tests extensively covers all hash algorithms / bit representations
|
||||||
|
*/
|
||||||
|
public function testIsAnyHashSessionIdValid()
|
||||||
|
{
|
||||||
|
foreach (self::$sidHashes as $algo => $bpcs) {
|
||||||
|
foreach ($bpcs as $bpc => $hash) {
|
||||||
|
$this->assertTrue(SessionManager::checkId($hash));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test checkId with a valid ID - SHA-1 hashes
|
||||||
|
*/
|
||||||
|
public function testIsSha1SessionIdValid()
|
||||||
|
{
|
||||||
|
$this->assertTrue(SessionManager::checkId(sha1('shaarli')));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test checkId with a valid ID - SHA-256 hashes
|
||||||
|
*/
|
||||||
|
public function testIsSha256SessionIdValid()
|
||||||
|
{
|
||||||
|
$this->assertTrue(SessionManager::checkId(hash('sha256', 'shaarli')));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test checkId with a valid ID - SHA-512 hashes
|
||||||
|
*/
|
||||||
|
public function testIsSha512SessionIdValid()
|
||||||
|
{
|
||||||
|
$this->assertTrue(SessionManager::checkId(hash('sha512', 'shaarli')));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test checkId with invalid IDs.
|
||||||
|
*/
|
||||||
|
public function testIsSessionIdInvalid()
|
||||||
|
{
|
||||||
|
$this->assertFalse(SessionManager::checkId(''));
|
||||||
|
$this->assertFalse(SessionManager::checkId([]));
|
||||||
|
$this->assertFalse(
|
||||||
|
SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,6 @@
|
||||||
|
|
||||||
require_once 'application/Utils.php';
|
require_once 'application/Utils.php';
|
||||||
require_once 'application/Languages.php';
|
require_once 'application/Languages.php';
|
||||||
require_once 'tests/utils/ReferenceSessionIdHashes.php';
|
|
||||||
|
|
||||||
// Initialize reference data before PHPUnit starts a session
|
|
||||||
ReferenceSessionIdHashes::genAllHashes();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,9 +12,6 @@
|
||||||
*/
|
*/
|
||||||
class UtilsTest extends PHPUnit_Framework_TestCase
|
class UtilsTest extends PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
// Session ID hashes
|
|
||||||
protected static $sidHashes = null;
|
|
||||||
|
|
||||||
// Log file
|
// Log file
|
||||||
protected static $testLogFile = 'tests.log';
|
protected static $testLogFile = 'tests.log';
|
||||||
|
|
||||||
|
@ -30,13 +23,11 @@ class UtilsTest extends PHPUnit_Framework_TestCase
|
||||||
*/
|
*/
|
||||||
protected static $defaultTimeZone;
|
protected static $defaultTimeZone;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign reference data
|
* Assign reference data
|
||||||
*/
|
*/
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass()
|
||||||
{
|
{
|
||||||
self::$sidHashes = ReferenceSessionIdHashes::getHashes();
|
|
||||||
self::$defaultTimeZone = date_default_timezone_get();
|
self::$defaultTimeZone = date_default_timezone_get();
|
||||||
// Timezone without DST for test consistency
|
// Timezone without DST for test consistency
|
||||||
date_default_timezone_set('Africa/Nairobi');
|
date_default_timezone_set('Africa/Nairobi');
|
||||||
|
@ -221,55 +212,6 @@ public function testGenerateLocationOut() {
|
||||||
$this->assertEquals('?', generateLocation($ref, 'localhost'));
|
$this->assertEquals('?', generateLocation($ref, 'localhost'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test is_session_id_valid with a valid ID - TEST ALL THE HASHES!
|
|
||||||
*
|
|
||||||
* This tests extensively covers all hash algorithms / bit representations
|
|
||||||
*/
|
|
||||||
public function testIsAnyHashSessionIdValid()
|
|
||||||
{
|
|
||||||
foreach (self::$sidHashes as $algo => $bpcs) {
|
|
||||||
foreach ($bpcs as $bpc => $hash) {
|
|
||||||
$this->assertTrue(is_session_id_valid($hash));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test is_session_id_valid with a valid ID - SHA-1 hashes
|
|
||||||
*/
|
|
||||||
public function testIsSha1SessionIdValid()
|
|
||||||
{
|
|
||||||
$this->assertTrue(is_session_id_valid(sha1('shaarli')));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test is_session_id_valid with a valid ID - SHA-256 hashes
|
|
||||||
*/
|
|
||||||
public function testIsSha256SessionIdValid()
|
|
||||||
{
|
|
||||||
$this->assertTrue(is_session_id_valid(hash('sha256', 'shaarli')));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test is_session_id_valid with a valid ID - SHA-512 hashes
|
|
||||||
*/
|
|
||||||
public function testIsSha512SessionIdValid()
|
|
||||||
{
|
|
||||||
$this->assertTrue(is_session_id_valid(hash('sha512', 'shaarli')));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test is_session_id_valid with invalid IDs.
|
|
||||||
*/
|
|
||||||
public function testIsSessionIdInvalid()
|
|
||||||
{
|
|
||||||
$this->assertFalse(is_session_id_valid(''));
|
|
||||||
$this->assertFalse(is_session_id_valid(array()));
|
|
||||||
$this->assertFalse(
|
|
||||||
is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test generateSecretApi.
|
* Test generateSecretApi.
|
||||||
|
@ -384,18 +326,18 @@ public function testReturnBytes()
|
||||||
*/
|
*/
|
||||||
public function testHumanBytes()
|
public function testHumanBytes()
|
||||||
{
|
{
|
||||||
$this->assertEquals('2kiB', human_bytes(2 * 1024));
|
$this->assertEquals('2'. t('kiB'), human_bytes(2 * 1024));
|
||||||
$this->assertEquals('2kiB', human_bytes(strval(2 * 1024)));
|
$this->assertEquals('2'. t('kiB'), human_bytes(strval(2 * 1024)));
|
||||||
$this->assertEquals('2MiB', human_bytes(2 * (pow(1024, 2))));
|
$this->assertEquals('2'. t('MiB'), human_bytes(2 * (pow(1024, 2))));
|
||||||
$this->assertEquals('2MiB', human_bytes(strval(2 * (pow(1024, 2)))));
|
$this->assertEquals('2'. t('MiB'), human_bytes(strval(2 * (pow(1024, 2)))));
|
||||||
$this->assertEquals('2GiB', human_bytes(2 * (pow(1024, 3))));
|
$this->assertEquals('2'. t('GiB'), human_bytes(2 * (pow(1024, 3))));
|
||||||
$this->assertEquals('2GiB', human_bytes(strval(2 * (pow(1024, 3)))));
|
$this->assertEquals('2'. t('GiB'), human_bytes(strval(2 * (pow(1024, 3)))));
|
||||||
$this->assertEquals('374B', human_bytes(374));
|
$this->assertEquals('374'. t('B'), human_bytes(374));
|
||||||
$this->assertEquals('374B', human_bytes('374'));
|
$this->assertEquals('374'. t('B'), human_bytes('374'));
|
||||||
$this->assertEquals('232kiB', human_bytes(237481));
|
$this->assertEquals('232'. t('kiB'), human_bytes(237481));
|
||||||
$this->assertEquals('Unlimited', human_bytes('0'));
|
$this->assertEquals(t('Unlimited'), human_bytes('0'));
|
||||||
$this->assertEquals('Unlimited', human_bytes(0));
|
$this->assertEquals(t('Unlimited'), human_bytes(0));
|
||||||
$this->assertEquals('Setting not set', human_bytes(''));
|
$this->assertEquals(t('Setting not set'), human_bytes(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -403,9 +345,9 @@ public function testHumanBytes()
|
||||||
*/
|
*/
|
||||||
public function testGetMaxUploadSize()
|
public function testGetMaxUploadSize()
|
||||||
{
|
{
|
||||||
$this->assertEquals('1MiB', get_max_upload_size(2097152, '1024k'));
|
$this->assertEquals('1'. t('MiB'), get_max_upload_size(2097152, '1024k'));
|
||||||
$this->assertEquals('1MiB', get_max_upload_size('1m', '2m'));
|
$this->assertEquals('1'. t('MiB'), get_max_upload_size('1m', '2m'));
|
||||||
$this->assertEquals('100B', get_max_upload_size(100, 100));
|
$this->assertEquals('100'. t('B'), get_max_upload_size(100, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
6
tests/bootstrap.php
Normal file
6
tests/bootstrap.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
|
$conf = new \Shaarli\Config\ConfigManager('tests/utils/config/configJson');
|
||||||
|
new \Shaarli\Languages('en', $conf);
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
if (! empty('UT_LOCALE')) {
|
require_once 'tests/bootstrap.php';
|
||||||
|
|
||||||
|
if (! empty(getenv('UT_LOCALE'))) {
|
||||||
setlocale(LC_ALL, getenv('UT_LOCALE'));
|
setlocale(LC_ALL, getenv('UT_LOCALE'));
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once 'vendor/autoload.php';
|
|
||||||
|
|
||||||
|
|
175
tests/languages/fr/LanguagesFrTest.php
Normal file
175
tests/languages/fr/LanguagesFrTest.php
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Shaarli;
|
||||||
|
|
||||||
|
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LanguagesFrTest
|
||||||
|
*
|
||||||
|
* Test the translation system in PHP and gettext mode with French language.
|
||||||
|
*
|
||||||
|
* @package Shaarli
|
||||||
|
*/
|
||||||
|
class LanguagesFrTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string Config file path (without extension).
|
||||||
|
*/
|
||||||
|
protected static $configFile = 'tests/utils/config/configJson';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ConfigManager
|
||||||
|
*/
|
||||||
|
protected $conf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init: force French
|
||||||
|
*/
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->conf = new ConfigManager(self::$configFile);
|
||||||
|
$this->conf->set('translation.language', 'fr');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the locale since gettext seems to mess with it, making it too long
|
||||||
|
*/
|
||||||
|
public static function tearDownAfterClass()
|
||||||
|
{
|
||||||
|
if (! empty(getenv('UT_LOCALE'))) {
|
||||||
|
setlocale(LC_ALL, getenv('UT_LOCALE'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with a simple non identified value.
|
||||||
|
*/
|
||||||
|
public function testTranslateSingleNotIDGettext()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'abcdé 564 fgK';
|
||||||
|
$this->assertEquals($text, t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with a simple identified value in gettext mode.
|
||||||
|
*/
|
||||||
|
public function testTranslateSingleIDGettext()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'permalink';
|
||||||
|
$this->assertEquals('permalien', t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with a non identified plural form in gettext mode.
|
||||||
|
*/
|
||||||
|
public function testTranslatePluralNotIDGettext()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'sandwich';
|
||||||
|
$nText = 'sandwiches';
|
||||||
|
// Not ID, so English fallback, and in english, plural 0
|
||||||
|
$this->assertEquals('sandwiches', t($text, $nText, 0));
|
||||||
|
$this->assertEquals('sandwich', t($text, $nText, 1));
|
||||||
|
$this->assertEquals('sandwiches', t($text, $nText, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an identified plural form in gettext mode.
|
||||||
|
*/
|
||||||
|
public function testTranslatePluralIDGettext()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'shaare';
|
||||||
|
$nText = 'shaares';
|
||||||
|
$this->assertEquals('shaare', t($text, $nText, 0));
|
||||||
|
$this->assertEquals('shaare', t($text, $nText, 1));
|
||||||
|
$this->assertEquals('shaares', t($text, $nText, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with a simple non identified value.
|
||||||
|
*/
|
||||||
|
public function testTranslateSingleNotIDPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'abcdé 564 fgK';
|
||||||
|
$this->assertEquals($text, t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with a simple identified value in PHP mode.
|
||||||
|
*/
|
||||||
|
public function testTranslateSingleIDPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'permalink';
|
||||||
|
$this->assertEquals('permalien', t($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with a non identified plural form in PHP mode.
|
||||||
|
*/
|
||||||
|
public function testTranslatePluralNotIDPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'sandwich';
|
||||||
|
$nText = 'sandwiches';
|
||||||
|
// Not ID, so English fallback, and in english, plural 0
|
||||||
|
$this->assertEquals('sandwiches', t($text, $nText, 0));
|
||||||
|
$this->assertEquals('sandwich', t($text, $nText, 1));
|
||||||
|
$this->assertEquals('sandwiches', t($text, $nText, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an identified plural form in PHP mode.
|
||||||
|
*/
|
||||||
|
public function testTranslatePluralIDPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$text = 'shaare';
|
||||||
|
$nText = 'shaares';
|
||||||
|
// In english, zero is followed by plural form
|
||||||
|
$this->assertEquals('shaare', t($text, $nText, 0));
|
||||||
|
$this->assertEquals('shaare', t($text, $nText, 1));
|
||||||
|
$this->assertEquals('shaares', t($text, $nText, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an extension language file in gettext mode
|
||||||
|
*/
|
||||||
|
public function testTranslationExtensionGettext()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'gettext');
|
||||||
|
$this->conf->set('translation.extensions.test', 'tests/utils/languages/');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$txt = 'car'; // ignore me poedit
|
||||||
|
$this->assertEquals('voiture', t($txt, $txt, 1, 'test'));
|
||||||
|
$this->assertEquals('Fouille', t('Search', 'Search', 1, 'test'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test t() with an extension language file in PHP mode
|
||||||
|
*/
|
||||||
|
public function testTranslationExtensionPhp()
|
||||||
|
{
|
||||||
|
$this->conf->set('translation.mode', 'php');
|
||||||
|
$this->conf->set('translation.extensions.test', 'tests/utils/languages/');
|
||||||
|
new Languages('en', $this->conf);
|
||||||
|
$txt = 'car'; // ignore me poedit
|
||||||
|
$this->assertEquals('voiture', t($txt, $txt, 1, 'test'));
|
||||||
|
$this->assertEquals('Fouille', t('Search', 'Search', 1, 'test'));
|
||||||
|
}
|
||||||
|
}
|
12
tests/utils/FakeConfigManager.php
Normal file
12
tests/utils/FakeConfigManager.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fake ConfigManager
|
||||||
|
*/
|
||||||
|
class FakeConfigManager
|
||||||
|
{
|
||||||
|
public static function get($key)
|
||||||
|
{
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
}
|
|
@ -141,12 +141,34 @@ protected function addLink($id, $title, $url, $description, $private, $date, $ta
|
||||||
*/
|
*/
|
||||||
public function write($filename)
|
public function write($filename)
|
||||||
{
|
{
|
||||||
|
$this->reorder();
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
$filename,
|
$filename,
|
||||||
'<?php /* '.base64_encode(gzdeflate(serialize($this->_links))).' */ ?>'
|
'<?php /* '.base64_encode(gzdeflate(serialize($this->_links))).' */ ?>'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder links by creation date (newest first).
|
||||||
|
*
|
||||||
|
* Also update the urls and ids mapping arrays.
|
||||||
|
*
|
||||||
|
* @param string $order ASC|DESC
|
||||||
|
*/
|
||||||
|
public function reorder($order = 'DESC')
|
||||||
|
{
|
||||||
|
// backward compatibility: ignore reorder if the the `created` field doesn't exist
|
||||||
|
if (! isset(array_values($this->_links)[0]['created'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$order = $order === 'ASC' ? -1 : 1;
|
||||||
|
// Reorder array by dates.
|
||||||
|
usort($this->_links, function($a, $b) use ($order) {
|
||||||
|
return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of links in the reference data
|
* Returns the number of links in the reference data
|
||||||
*/
|
*/
|
||||||
|
@ -187,6 +209,7 @@ public function countUntaggedLinks()
|
||||||
|
|
||||||
public function getLinks()
|
public function getLinks()
|
||||||
{
|
{
|
||||||
|
$this->reorder();
|
||||||
return $this->_links;
|
return $this->_links;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue