diff options
118 files changed, 4266 insertions, 852 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..4a6589a2 --- /dev/null +++ b/.editorconfig | |||
@@ -0,0 +1,23 @@ | |||
1 | # EditorConfig: http://EditorConfig.org | ||
2 | |||
3 | root = true | ||
4 | |||
5 | [*] | ||
6 | charset = utf-8 | ||
7 | end_of_line = lf | ||
8 | insert_final_newline = true | ||
9 | trim_trailing_whitespace = true | ||
10 | indent_style = space | ||
11 | indent_size = 4 | ||
12 | |||
13 | [*.{htaccess,html,xml}] | ||
14 | indent_size = 2 | ||
15 | |||
16 | [*.php] | ||
17 | max_line_length = 100 | ||
18 | |||
19 | [Dockerfile] | ||
20 | max_line_length = 80 | ||
21 | |||
22 | [Makefile] | ||
23 | indent_style = tab | ||
diff --git a/.gitattributes b/.gitattributes index dd0e573c..b191e227 100644 --- a/.gitattributes +++ b/.gitattributes | |||
@@ -22,8 +22,10 @@ Dockerfile text | |||
22 | *.ttf binary | 22 | *.ttf binary |
23 | *.min.css binary | 23 | *.min.css binary |
24 | *.min.js binary | 24 | *.min.js binary |
25 | *.mo binary | ||
25 | 26 | ||
26 | # Exclude from Git archives | 27 | # Exclude from Git archives |
28 | .editorconfig export-ignore | ||
27 | .gitattributes export-ignore | 29 | .gitattributes export-ignore |
28 | .github export-ignore | 30 | .github export-ignore |
29 | .gitignore export-ignore | 31 | .gitignore export-ignore |
diff --git a/.github/mailmap b/.github/mailmap index bbdb7908..7633afcf 100644 --- a/.github/mailmap +++ b/.github/mailmap | |||
@@ -1,6 +1,8 @@ | |||
1 | ArthurHoaro <arthur@hoa.ro> | 1 | ArthurHoaro <arthur@hoa.ro> |
2 | Florian Eula <eula.florian@gmail.com> feula | 2 | Florian Eula <eula.florian@gmail.com> feula |
3 | Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com> | 3 | Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com> |
4 | Immánuel Fodor <immanuelfactor+github@gmail.com> | ||
5 | kalvn <kalvnthereal@gmail.com> <kalvn@users.noreply.github.com> | ||
4 | Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm | 6 | Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm |
5 | Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar> | 7 | Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar> |
6 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com> | 8 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com> |
@@ -18,6 +18,7 @@ vendor/ | |||
18 | # Release archives | 18 | # Release archives |
19 | *.tar.gz | 19 | *.tar.gz |
20 | *.zip | 20 | *.zip |
21 | inc/languages/*/LC_MESSAGES/shaarli.mo | ||
21 | 22 | ||
22 | # Development and test resources | 23 | # Development and test resources |
23 | coverage | 24 | coverage |
diff --git a/.travis.yml b/.travis.yml index b6b9bddf..322e4337 100644 --- a/.travis.yml +++ b/.travis.yml | |||
@@ -13,6 +13,8 @@ install: | |||
13 | - composer self-update | 13 | - composer self-update |
14 | - composer install --prefer-dist | 14 | - composer install --prefer-dist |
15 | - locale -a | 15 | - locale -a |
16 | before_script: | ||
17 | - PATH=${PATH//:\.\/node_modules\/\.bin/} | ||
16 | script: | 18 | script: |
17 | - make clean | 19 | - make clean |
18 | - make check_permissions | 20 | - make check_permissions |
@@ -1,6 +1,6 @@ | |||
1 | 542 ArthurHoaro <arthur@hoa.ro> | 1 | 577 ArthurHoaro <arthur@hoa.ro> |
2 | 255 VirtualTam <virtualtam@flibidi.net> | 2 | 283 VirtualTam <virtualtam@flibidi.net> |
3 | 148 nodiscc <nodiscc@gmail.com> | 3 | 179 nodiscc <nodiscc@gmail.com> |
4 | 56 Sébastien Sauvage <sebsauvage@sebsauvage.net> | 4 | 56 Sébastien Sauvage <sebsauvage@sebsauvage.net> |
5 | 15 Florian Eula <eula.florian@gmail.com> | 5 | 15 Florian Eula <eula.florian@gmail.com> |
6 | 13 Emilien Klein <emilien@klein.st> | 6 | 13 Emilien Klein <emilien@klein.st> |
@@ -11,8 +11,9 @@ | |||
11 | 5 Lucas Cimon <lucas.cimon@gmail.com> | 11 | 5 Lucas Cimon <lucas.cimon@gmail.com> |
12 | 4 Alexandre Alapetite <alexandre@alapetite.fr> | 12 | 4 Alexandre Alapetite <alexandre@alapetite.fr> |
13 | 4 David Sferruzza <david.sferruzza@gmail.com> | 13 | 4 David Sferruzza <david.sferruzza@gmail.com> |
14 | 4 Immánuel Fodor <immanuelfactor+github@gmail.com> | ||
15 | 4 kalvn <kalvnthereal@gmail.com> | ||
14 | 3 Teromene <teromene@teromene.fr> | 16 | 3 Teromene <teromene@teromene.fr> |
15 | 3 kalvn <kalvnthereal@gmail.com> | ||
16 | 2 Chris Kuethe <chris.kuethe@gmail.com> | 17 | 2 Chris Kuethe <chris.kuethe@gmail.com> |
17 | 2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org> | 18 | 2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org> |
18 | 2 Mathieu Chabanon <git@matchab.fr> | 19 | 2 Mathieu Chabanon <git@matchab.fr> |
@@ -27,11 +28,13 @@ | |||
27 | 1 BoboTiG <bobotig@gmail.com> | 28 | 1 BoboTiG <bobotig@gmail.com> |
28 | 1 Bronco <bronco@warriordudimanche.net> | 29 | 1 Bronco <bronco@warriordudimanche.net> |
29 | 1 D Low <daniellowtw@gmail.com> | 30 | 1 D Low <daniellowtw@gmail.com> |
31 | 1 Daniel Jakots <vigdis@chown.me> | ||
30 | 1 Dimtion <zizou.xena@gmail.com> | 32 | 1 Dimtion <zizou.xena@gmail.com> |
31 | 1 Fanch <fanch-github@qth.fr> | 33 | 1 Fanch <fanch-github@qth.fr> |
32 | 1 Felix Bartels <felix@host-consultants.de> | 34 | 1 Felix Bartels <felix@host-consultants.de> |
33 | 1 Felix Kästner <github.com-fpunktk@fpunktk.de> | 35 | 1 Felix Kästner <github.com-fpunktk@fpunktk.de> |
34 | 1 Florian Voigt <flvoigt@me.com> | 36 | 1 Florian Voigt <flvoigt@me.com> |
37 | 1 Franck Kerbiriou <FranckKe@users.noreply.github.com> | ||
35 | 1 Gary Marigliano <gmarigliano93@gmail.com> | 38 | 1 Gary Marigliano <gmarigliano93@gmail.com> |
36 | 1 Guillaume Virlet <github@virlet.org> | 39 | 1 Guillaume Virlet <github@virlet.org> |
37 | 1 Jonathan Druart <jonathan.druart@gmail.com> | 40 | 1 Jonathan Druart <jonathan.druart@gmail.com> |
@@ -41,6 +44,8 @@ | |||
41 | 1 Lionel Martin <renarddesmers@gmail.com> | 44 | 1 Lionel Martin <renarddesmers@gmail.com> |
42 | 1 Mark Gerarts <mark.gerarts@gmail.com> | 45 | 1 Mark Gerarts <mark.gerarts@gmail.com> |
43 | 1 Marsup <marsup@gmail.com> | 46 | 1 Marsup <marsup@gmail.com> |
47 | 1 Neros <contact@neros.fr> | ||
44 | 1 Sbgodin <Sbgodin@users.noreply.github.com> | 48 | 1 Sbgodin <Sbgodin@users.noreply.github.com> |
45 | 1 TsT <tst2005@gmail.com> | 49 | 1 TsT <tst2005@gmail.com> |
46 | 1 dimtion <zizou.xena@gmail.com> | 50 | 1 dimtion <zizou.xena@gmail.com> |
51 | 1 durcheinandr <jochen@durcheinandr.de> | ||
diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a7b120c..29e1fd6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -4,12 +4,42 @@ All notable changes to this project will be documented in this file. | |||
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) |
5 | and this project adheres to [Semantic Versioning](http://semver.org/). | 5 | and this project adheres to [Semantic Versioning](http://semver.org/). |
6 | 6 | ||
7 | ## [v0.9.3](https://github.com/shaarli/Shaarli/releases/tag/v0.9.3) - 2018-01-04 | 7 | ## [v0.10.0](https://github.com/shaarli/Shaarli/releases/tag/v0.10.0) - UNPUBLISHED |
8 | |||
9 | ## [v0.9.4](https://github.com/shaarli/Shaarli/releases/tag/v0.9.4) - 2018-01-30 | ||
10 | ### Added | ||
11 | - Enable translations: Shaarli is now also available in French. Other language translations are welcome! | ||
12 | - Add EditorConfig configuration | ||
13 | - Add favicons for mobile devices | ||
14 | - Add Alpine Linux arm32v7 Dockerfiles (master, latest) | ||
15 | |||
16 | ### Changed | ||
17 | - Do not write bookmark edition history during file imports (performance) | ||
18 | - Migrate Docker images (master, latest) to Alpine Linux | ||
19 | - Improve unitary tests and code coverage | ||
20 | - Improve thumbnail display | ||
21 | - Improve theme ergonomics | ||
22 | - Improve messages if there is no plugin or parameter available in the admin page | ||
23 | - Increase buffer size for cURL download | ||
24 | - Force HTTPS if the original port is 443 behind a reverse proxy (workaround) | ||
25 | - Improve page title retrieval performances | ||
26 | |||
27 | ### Removed | ||
28 | - Remove redirector setting from Configure page | ||
29 | |||
30 | ### Fixed | ||
31 | - Fix broken links in the documentation | ||
32 | - Enable access to `data/user.css` (Apache 2.2 & 2.4) | ||
33 | - Don't URL encode description links if parameter `redirector.encode_url` is set to false | ||
34 | - Fix an issue preventing the Save button to appear for plugin parameters | ||
8 | 35 | ||
36 | |||
37 | ## [v0.9.3](https://github.com/shaarli/Shaarli/releases/tag/v0.9.3) - 2018-01-04 | ||
9 | **XSS vulnerability fixed. Please update.** | 38 | **XSS vulnerability fixed. Please update.** |
10 | 39 | ||
11 | ### Security | 40 | ## Security |
12 | - Fix an XSS (cross-site-scripting) vulnerability in `index.php` | 41 | - Fix an XSS (cross-site-scripting) vulnerability in `index.php` - |
42 | [CVE-2018-5249](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-5249) | ||
13 | 43 | ||
14 | 44 | ||
15 | ## [v0.9.2](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) - 2017-10-07 | 45 | ## [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/). | |||
48 | 78 | ||
49 | ### Security | 79 | ### Security |
50 | 80 | ||
51 | - Vulnerability introduced in v0.9.1 fixed. | 81 | - 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)). |
82 | |||
52 | 83 | ||
53 | ## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23 | 84 | ## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23 |
54 | 85 | ||
@@ -123,7 +154,7 @@ Theming: | |||
123 | - Introduce a new theme | 154 | - Introduce a new theme |
124 | - Allow selecting themes/templates from the configuration page | 155 | - Allow selecting themes/templates from the configuration page |
125 | - New/Edit link form can be submitted using CTRL+Enter in the textarea | 156 | - New/Edit link form can be submitted using CTRL+Enter in the textarea |
126 | - Shaarli version is displayed in the footer when logged in | 157 | - Shaarli version is displayed in the footer when logged in |
127 | - Add plugin placeholders to Atom/RSS feed templates | 158 | - Add plugin placeholders to Atom/RSS feed templates |
128 | - Add OpenSearch to feed templates | 159 | - Add OpenSearch to feed templates |
129 | - Add `campaign_` to the URL cleanup pattern list | 160 | - Add `campaign_` to the URL cleanup pattern list |
@@ -153,7 +184,7 @@ Theming: | |||
153 | - Improved date time display depending on the locale | 184 | - Improved date time display depending on the locale |
154 | - Partial namespace support for Shaarli classes | 185 | - Partial namespace support for Shaarli classes |
155 | - Shaarli version is now only present in `shaarli_version.php` | 186 | - Shaarli version is now only present in `shaarli_version.php` |
156 | - Human readable maximum file size upload | 187 | - Human readable maximum file size upload |
157 | 188 | ||
158 | 189 | ||
159 | ### Removed | 190 | ### Removed |
@@ -195,6 +226,13 @@ Theming: | |||
195 | 226 | ||
196 | - Editing a link created before the new ID system would change its permalink. | 227 | - Editing a link created before the new ID system would change its permalink. |
197 | 228 | ||
229 | ## [v0.8.5](https://github.com/shaarli/Shaarli/releases/tag/v0.8.5) - 2018-01-04 | ||
230 | **XSS vulnerability fixed. Please update.** | ||
231 | |||
232 | ## Security | ||
233 | - Fix an XSS (cross-site-scripting) vulnerability in `index.php` - | ||
234 | [CVE-2018-5249](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-5249) | ||
235 | |||
198 | ## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04 | 236 | ## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04 |
199 | ### Security | 237 | ### Security |
200 | - Markdown plugin: escape HTML entities by default | 238 | - Markdown plugin: escape HTML entities by default |
@@ -210,7 +248,7 @@ Theming: | |||
210 | 248 | ||
211 | ## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - 2016-12-12 | 249 | ## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - 2016-12-12 |
212 | 250 | ||
213 | > Note: this version will create an automatic backup of your database if anything goes wrong. | 251 | > Note: this version will create an automatic backup of your database if anything goes wrong. |
214 | 252 | ||
215 | ### Added | 253 | ### Added |
216 | - Add CHANGELOG.md to track the whole project's history | 254 | - Add CHANGELOG.md to track the whole project's history |
@@ -227,7 +265,7 @@ Theming: | |||
227 | - Link ID complete refactoring: | 265 | - Link ID complete refactoring: |
228 | - Links now have a numeric ID instead of dates | 266 | - Links now have a numeric ID instead of dates |
229 | - Short URLs are now created once and can't change over time (previous URL are kept) | 267 | - Short URLs are now created once and can't change over time (previous URL are kept) |
230 | - Templates: | 268 | - Templates: |
231 | - Changed placeholder behaviour for: `buttons_toolbar`, `fields_toolbar` and `action_plugin` | 269 | - Changed placeholder behaviour for: `buttons_toolbar`, `fields_toolbar` and `action_plugin` |
232 | - Cleanup `{loop}` declarations in templates | 270 | - Cleanup `{loop}` declarations in templates |
233 | - Tools: hide Firefox Social button when not in HTTPS | 271 | - Tools: hide Firefox Social button when not in HTTPS |
@@ -245,7 +283,7 @@ Theming: | |||
245 | - Plugins: | 283 | - Plugins: |
246 | - Tools: only display parameter description when it exists | 284 | - Tools: only display parameter description when it exists |
247 | - archive.org: do not propose archival of private notes | 285 | - archive.org: do not propose archival of private notes |
248 | - Markdown: | 286 | - Markdown: |
249 | - render links properly in code blocks | 287 | - render links properly in code blocks |
250 | - bug regarding the `nomarkdown` tag | 288 | - bug regarding the `nomarkdown` tag |
251 | - W3C compliance | 289 | - W3C compliance |
@@ -384,7 +422,7 @@ Please use our release archives, or follow the | |||
384 | ### Fixed | 422 | ### Fixed |
385 | - Fix a bug where renaming a tag was causing a 404 | 423 | - Fix a bug where renaming a tag was causing a 404 |
386 | - Fix a bug allowing to search blank terms | 424 | - Fix a bug allowing to search blank terms |
387 | - Fix a bug preventing to remove a tag with special chars when searching | 425 | - Fix a bug preventing to remove a tag with special chars when searching |
388 | 426 | ||
389 | 427 | ||
390 | ## [v0.6.2](https://github.com/shaarli/Shaarli/releases/tag/v0.6.2) - 2015-12-23 | 428 | ## [v0.6.2](https://github.com/shaarli/Shaarli/releases/tag/v0.6.2) - 2015-12-23 |
@@ -690,7 +728,7 @@ Initial release on GitHub. | |||
690 | - When you click the key to see only private links, it turns yellow | 728 | - When you click the key to see only private links, it turns yellow |
691 | 729 | ||
692 | ### Changed | 730 | ### Changed |
693 | - The "Daily" page now automatically skips empty days. | 731 | - The "Daily" page now automatically skips empty days. |
694 | 732 | ||
695 | ### Fixed | 733 | ### Fixed |
696 | - Corrected the tag encoding (there was a bug when selecting a second tag which contains accented characters) | 734 | - Corrected the tag encoding (there was a bug when selecting a second tag which contains accented characters) |
@@ -988,7 +1026,7 @@ Initial release on GitHub. | |||
988 | - Nicer timezone selection patch by killruana | 1026 | - Nicer timezone selection patch by killruana |
989 | 1027 | ||
990 | ### Fixed | 1028 | ### Fixed |
991 | - New lines now appear correctly in the RSS feed descriptions. | 1029 | - New lines now appear correctly in the RSS feed descriptions. |
992 | 1030 | ||
993 | 1031 | ||
994 | ## [v0.0.17beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) | 1032 | ## [v0.0.17beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) |
@@ -1042,7 +1080,7 @@ Initial release on GitHub. | |||
1042 | ## [v0.0.14beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) | 1080 | ## [v0.0.14beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) |
1043 | ### Added | 1081 | ### Added |
1044 | - You no longer need to disable `magic_quotes` on your host. | 1082 | - You no longer need to disable `magic_quotes` on your host. |
1045 | Shaarli will cope with this option beeing activated. | 1083 | Shaarli will cope with this option beeing activated. |
1046 | 1084 | ||
1047 | 1085 | ||
1048 | ## [v0.0.13beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) | 1086 | ## [v0.0.13beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) |
@@ -1,17 +1,6 @@ | |||
1 | # The personal, minimalist, super-fast, database free, bookmarking service. | 1 | # The personal, minimalist, super-fast, database free, bookmarking service. |
2 | # Makefile for PHP code analysis & testing, documentation and release generation | 2 | # Makefile for PHP code analysis & testing, documentation and release generation |
3 | 3 | ||
4 | # Prerequisites: | ||
5 | # - install Composer, either: | ||
6 | # - from your distro's package manager; | ||
7 | # - from the official website (https://getcomposer.org/download/); | ||
8 | # - install/update test dependencies: | ||
9 | # $ composer install # 1st setup | ||
10 | # $ composer update | ||
11 | # - install Xdebug for PHPUnit code coverage reports: | ||
12 | # - see http://xdebug.org/docs/install | ||
13 | # - enable in php.ini | ||
14 | |||
15 | BIN = vendor/bin | 4 | BIN = vendor/bin |
16 | PHP_SOURCE = index.php application tests plugins | 5 | PHP_SOURCE = index.php application tests plugins |
17 | PHP_COMMA_SOURCE = index.php,application,tests,plugins | 6 | PHP_COMMA_SOURCE = index.php,application,tests,plugins |
@@ -115,7 +104,7 @@ check_permissions: | |||
115 | @echo "----------------------" | 104 | @echo "----------------------" |
116 | @echo "Check file permissions" | 105 | @echo "Check file permissions" |
117 | @echo "----------------------" | 106 | @echo "----------------------" |
118 | @for file in `git ls-files`; do \ | 107 | @for file in `git ls-files | grep -v docker`; do \ |
119 | if [ -x $$file ]; then \ | 108 | if [ -x $$file ]; then \ |
120 | errors=true; \ | 109 | errors=true; \ |
121 | echo "$${file} is executable"; \ | 110 | echo "$${file} is executable"; \ |
@@ -130,12 +119,12 @@ check_permissions: | |||
130 | # See phpunit.xml for configuration | 119 | # See phpunit.xml for configuration |
131 | # https://phpunit.de/manual/current/en/appendixes.configuration.html | 120 | # https://phpunit.de/manual/current/en/appendixes.configuration.html |
132 | ## | 121 | ## |
133 | test: | 122 | test: translate |
134 | @echo "-------" | 123 | @echo "-------" |
135 | @echo "PHPUNIT" | 124 | @echo "PHPUNIT" |
136 | @echo "-------" | 125 | @echo "-------" |
137 | @mkdir -p sandbox coverage | 126 | @mkdir -p sandbox coverage |
138 | @$(BIN)/phpunit --coverage-php coverage/main.cov --testsuite unit-tests | 127 | @$(BIN)/phpunit --coverage-php coverage/main.cov --bootstrap tests/bootstrap.php --testsuite unit-tests |
139 | 128 | ||
140 | locale_test_%: | 129 | locale_test_%: |
141 | @UT_LOCALE=$*.utf8 \ | 130 | @UT_LOCALE=$*.utf8 \ |
@@ -168,15 +157,15 @@ composer_dependencies: clean | |||
168 | composer install --no-dev --prefer-dist | 157 | composer install --no-dev --prefer-dist |
169 | find vendor/ -name ".git" -type d -exec rm -rf {} + | 158 | find vendor/ -name ".git" -type d -exec rm -rf {} + |
170 | 159 | ||
171 | ### generate a release tarball and include 3rd-party dependencies | 160 | ### generate a release tarball and include 3rd-party dependencies and translations |
172 | release_tar: composer_dependencies htmldoc | 161 | release_tar: composer_dependencies htmldoc translate |
173 | git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD | 162 | git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD |
174 | tar rvf $(ARCHIVE_VERSION).tar --transform "s|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/ | 163 | tar rvf $(ARCHIVE_VERSION).tar --transform "s|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/ |
175 | tar rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/ | 164 | tar rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/ |
176 | gzip $(ARCHIVE_VERSION).tar | 165 | gzip $(ARCHIVE_VERSION).tar |
177 | 166 | ||
178 | ### generate a release zip and include 3rd-party dependencies | 167 | ### generate a release zip and include 3rd-party dependencies and translations |
179 | release_zip: composer_dependencies htmldoc | 168 | release_zip: composer_dependencies htmldoc translate |
180 | git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD | 169 | git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD |
181 | mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor} | 170 | mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor} |
182 | rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/ | 171 | rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/ |
@@ -213,3 +202,8 @@ htmldoc: | |||
213 | mkdocs build' | 202 | mkdocs build' |
214 | find doc/html/ -type f -exec chmod a-x '{}' \; | 203 | find doc/html/ -type f -exec chmod a-x '{}' \; |
215 | rm -r venv | 204 | rm -r venv |
205 | |||
206 | |||
207 | ### Generate Shaarli's translation compiled file (.mo) | ||
208 | translate: | ||
209 | @find inc/languages/ -name shaarli.po -execdir msgfmt shaarli.po -o shaarli.mo \; \ No newline at end of file | ||
@@ -6,10 +6,10 @@ _Do you want to share the links you discover?_ | |||
6 | _Shaarli is a minimalist link sharing service that you can install on your own server._ | 6 | _Shaarli is a minimalist link sharing service that you can install on your own server._ |
7 | _It is designed to be personal (single-user), fast and handy._ | 7 | _It is designed to be personal (single-user), fast and handy._ |
8 | 8 | ||
9 | [![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) | 9 | [![](https://img.shields.io/badge/stable-v0.8.5-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.5) |
10 | [![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli) | 10 | [![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli) |
11 | • | 11 | • |
12 | [![](https://img.shields.io/badge/latest-v0.9.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) | 12 | [![](https://img.shields.io/badge/latest-v0.9.3-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.3) |
13 | [![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) | 13 | [![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) |
14 | • | 14 | • |
15 | [![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli) | 15 | [![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli) |
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php index 5643f4a0..911873a0 100644 --- a/application/ApplicationUtils.php +++ b/application/ApplicationUtils.php | |||
@@ -149,12 +149,13 @@ class ApplicationUtils | |||
149 | public static function checkPHPVersion($minVersion, $curVersion) | 149 | public static function checkPHPVersion($minVersion, $curVersion) |
150 | { | 150 | { |
151 | if (version_compare($curVersion, $minVersion) < 0) { | 151 | if (version_compare($curVersion, $minVersion) < 0) { |
152 | throw new Exception( | 152 | $msg = t( |
153 | 'Your PHP version is obsolete!' | 153 | 'Your PHP version is obsolete!' |
154 | .' Shaarli requires at least PHP '.$minVersion.', and thus cannot run.' | 154 | . ' Shaarli requires at least PHP %s, and thus cannot run.' |
155 | .' Your PHP version has known security vulnerabilities and should be' | 155 | . ' Your PHP version has known security vulnerabilities and should be' |
156 | .' updated as soon as possible.' | 156 | . ' updated as soon as possible.' |
157 | ); | 157 | ); |
158 | throw new Exception(sprintf($msg, $minVersion)); | ||
158 | } | 159 | } |
159 | } | 160 | } |
160 | 161 | ||
@@ -179,7 +180,7 @@ class ApplicationUtils | |||
179 | $rainTplDir.'/'.$conf->get('resource.theme'), | 180 | $rainTplDir.'/'.$conf->get('resource.theme'), |
180 | ) as $path) { | 181 | ) as $path) { |
181 | if (! is_readable(realpath($path))) { | 182 | if (! is_readable(realpath($path))) { |
182 | $errors[] = '"'.$path.'" directory is not readable'; | 183 | $errors[] = '"'.$path.'" '. t('directory is not readable'); |
183 | } | 184 | } |
184 | } | 185 | } |
185 | 186 | ||
@@ -191,10 +192,10 @@ class ApplicationUtils | |||
191 | $conf->get('resource.raintpl_tmp'), | 192 | $conf->get('resource.raintpl_tmp'), |
192 | ) as $path) { | 193 | ) as $path) { |
193 | if (! is_readable(realpath($path))) { | 194 | if (! is_readable(realpath($path))) { |
194 | $errors[] = '"'.$path.'" directory is not readable'; | 195 | $errors[] = '"'.$path.'" '. t('directory is not readable'); |
195 | } | 196 | } |
196 | if (! is_writable(realpath($path))) { | 197 | if (! is_writable(realpath($path))) { |
197 | $errors[] = '"'.$path.'" directory is not writable'; | 198 | $errors[] = '"'.$path.'" '. t('directory is not writable'); |
198 | } | 199 | } |
199 | } | 200 | } |
200 | 201 | ||
@@ -212,10 +213,10 @@ class ApplicationUtils | |||
212 | } | 213 | } |
213 | 214 | ||
214 | if (! is_readable(realpath($path))) { | 215 | if (! is_readable(realpath($path))) { |
215 | $errors[] = '"'.$path.'" file is not readable'; | 216 | $errors[] = '"'.$path.'" '. t('file is not readable'); |
216 | } | 217 | } |
217 | if (! is_writable(realpath($path))) { | 218 | if (! is_writable(realpath($path))) { |
218 | $errors[] = '"'.$path.'" file is not writable'; | 219 | $errors[] = '"'.$path.'" '. t('file is not writable'); |
219 | } | 220 | } |
220 | } | 221 | } |
221 | 222 | ||
diff --git a/application/Cache.php b/application/Cache.php index 5d050165..e5d43e61 100644 --- a/application/Cache.php +++ b/application/Cache.php | |||
@@ -13,7 +13,7 @@ | |||
13 | function purgeCachedPages($pageCacheDir) | 13 | function purgeCachedPages($pageCacheDir) |
14 | { | 14 | { |
15 | if (! is_dir($pageCacheDir)) { | 15 | if (! is_dir($pageCacheDir)) { |
16 | $error = 'Cannot purge '.$pageCacheDir.': no directory'; | 16 | $error = sprintf(t('Cannot purge %s: no directory'), $pageCacheDir); |
17 | error_log($error); | 17 | error_log($error); |
18 | return $error; | 18 | return $error; |
19 | } | 19 | } |
diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php index 7377bcec..ebae18b4 100644 --- a/application/FeedBuilder.php +++ b/application/FeedBuilder.php | |||
@@ -148,11 +148,11 @@ class FeedBuilder | |||
148 | $link['url'] = $pageaddr . $link['url']; | 148 | $link['url'] = $pageaddr . $link['url']; |
149 | } | 149 | } |
150 | if ($this->usePermalinks === true) { | 150 | if ($this->usePermalinks === true) { |
151 | $permalink = '<a href="'. $link['url'] .'" title="Direct link">Direct link</a>'; | 151 | $permalink = '<a href="'. $link['url'] .'" title="'. t('Direct link') .'">'. t('Direct link') .'</a>'; |
152 | } else { | 152 | } else { |
153 | $permalink = '<a href="'. $link['guid'] .'" title="Permalink">Permalink</a>'; | 153 | $permalink = '<a href="'. $link['guid'] .'" title="'. t('Permalink') .'">'. t('Permalink') .'</a>'; |
154 | } | 154 | } |
155 | $link['description'] = format_description($link['description'], '', $pageaddr); | 155 | $link['description'] = format_description($link['description'], '', false, $pageaddr); |
156 | $link['description'] .= PHP_EOL .'<br>— '. $permalink; | 156 | $link['description'] .= PHP_EOL .'<br>— '. $permalink; |
157 | 157 | ||
158 | $pubDate = $link['created']; | 158 | $pubDate = $link['created']; |
diff --git a/application/History.php b/application/History.php index 116b9264..35ec016a 100644 --- a/application/History.php +++ b/application/History.php | |||
@@ -16,6 +16,7 @@ | |||
16 | * - UPDATED: link updated | 16 | * - UPDATED: link updated |
17 | * - DELETED: link deleted | 17 | * - DELETED: link deleted |
18 | * - SETTINGS: the settings have been updated through the UI. | 18 | * - SETTINGS: the settings have been updated through the UI. |
19 | * - IMPORT: bulk links import | ||
19 | * | 20 | * |
20 | * Note: new events are put at the beginning of the file and history array. | 21 | * Note: new events are put at the beginning of the file and history array. |
21 | */ | 22 | */ |
@@ -42,6 +43,11 @@ class History | |||
42 | const SETTINGS = 'SETTINGS'; | 43 | const SETTINGS = 'SETTINGS'; |
43 | 44 | ||
44 | /** | 45 | /** |
46 | * @var string Action key: a bulk import has been processed. | ||
47 | */ | ||
48 | const IMPORT = 'IMPORT'; | ||
49 | |||
50 | /** | ||
45 | * @var string History file path. | 51 | * @var string History file path. |
46 | */ | 52 | */ |
47 | protected $historyFilePath; | 53 | protected $historyFilePath; |
@@ -122,6 +128,16 @@ class History | |||
122 | } | 128 | } |
123 | 129 | ||
124 | /** | 130 | /** |
131 | * Add Event: bulk import. | ||
132 | * | ||
133 | * Note: we don't store links add/update one by one since it can have a huge impact on performances. | ||
134 | */ | ||
135 | public function importLinks() | ||
136 | { | ||
137 | $this->addEvent(self::IMPORT); | ||
138 | } | ||
139 | |||
140 | /** | ||
125 | * Save a new event and write it in the history file. | 141 | * Save a new event and write it in the history file. |
126 | * | 142 | * |
127 | * @param string $status Event key, should be defined as constant. | 143 | * @param string $status Event key, should be defined as constant. |
@@ -155,7 +171,7 @@ class History | |||
155 | } | 171 | } |
156 | 172 | ||
157 | if (! is_writable($this->historyFilePath)) { | 173 | if (! is_writable($this->historyFilePath)) { |
158 | throw new Exception('History file isn\'t readable or writable'); | 174 | throw new Exception(t('History file isn\'t readable or writable')); |
159 | } | 175 | } |
160 | } | 176 | } |
161 | 177 | ||
@@ -166,7 +182,7 @@ class History | |||
166 | { | 182 | { |
167 | $this->history = FileUtils::readFlatDB($this->historyFilePath, []); | 183 | $this->history = FileUtils::readFlatDB($this->historyFilePath, []); |
168 | if ($this->history === false) { | 184 | if ($this->history === false) { |
169 | throw new Exception('Could not parse history file'); | 185 | throw new Exception(t('Could not parse history file')); |
170 | } | 186 | } |
171 | } | 187 | } |
172 | 188 | ||
diff --git a/application/HttpUtils.php b/application/HttpUtils.php index 00835966..83a4c5e2 100644 --- a/application/HttpUtils.php +++ b/application/HttpUtils.php | |||
@@ -3,9 +3,11 @@ | |||
3 | * GET an HTTP URL to retrieve its content | 3 | * GET an HTTP URL to retrieve its content |
4 | * Uses the cURL library or a fallback method | 4 | * Uses the cURL library or a fallback method |
5 | * | 5 | * |
6 | * @param string $url URL to get (http://...) | 6 | * @param string $url URL to get (http://...) |
7 | * @param int $timeout network timeout (in seconds) | 7 | * @param int $timeout network timeout (in seconds) |
8 | * @param int $maxBytes maximum downloaded bytes (default: 4 MiB) | 8 | * @param int $maxBytes maximum downloaded bytes (default: 4 MiB) |
9 | * @param callable|string $curlWriteFunction Optional callback called during the download (cURL CURLOPT_WRITEFUNCTION). | ||
10 | * Can be used to add download conditions on the headers (response code, content type, etc.). | ||
9 | * | 11 | * |
10 | * @return array HTTP response headers, downloaded content | 12 | * @return array HTTP response headers, downloaded content |
11 | * | 13 | * |
@@ -29,7 +31,7 @@ | |||
29 | * @see http://stackoverflow.com/q/9183178 | 31 | * @see http://stackoverflow.com/q/9183178 |
30 | * @see http://stackoverflow.com/q/1462720 | 32 | * @see http://stackoverflow.com/q/1462720 |
31 | */ | 33 | */ |
32 | function get_http_response($url, $timeout = 30, $maxBytes = 4194304) | 34 | function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteFunction = null) |
33 | { | 35 | { |
34 | $urlObj = new Url($url); | 36 | $urlObj = new Url($url); |
35 | $cleanUrl = $urlObj->idnToAscii(); | 37 | $cleanUrl = $urlObj->idnToAscii(); |
@@ -75,8 +77,12 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304) | |||
75 | curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); | 77 | curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); |
76 | curl_setopt($ch, CURLOPT_USERAGENT, $userAgent); | 78 | curl_setopt($ch, CURLOPT_USERAGENT, $userAgent); |
77 | 79 | ||
80 | if (is_callable($curlWriteFunction)) { | ||
81 | curl_setopt($ch, CURLOPT_WRITEFUNCTION, $curlWriteFunction); | ||
82 | } | ||
83 | |||
78 | // Max download size management | 84 | // Max download size management |
79 | curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024); | 85 | curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16); |
80 | curl_setopt($ch, CURLOPT_NOPROGRESS, false); | 86 | curl_setopt($ch, CURLOPT_NOPROGRESS, false); |
81 | curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, | 87 | curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, |
82 | function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes) | 88 | function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes) |
@@ -302,6 +308,13 @@ function server_url($server) | |||
302 | $port = $server['HTTP_X_FORWARDED_PORT']; | 308 | $port = $server['HTTP_X_FORWARDED_PORT']; |
303 | } | 309 | } |
304 | 310 | ||
311 | // This is a workaround for proxies that don't forward the scheme properly. | ||
312 | // Connecting over port 443 has to be in HTTPS. | ||
313 | // See https://github.com/shaarli/Shaarli/issues/1022 | ||
314 | if ($port == '443') { | ||
315 | $scheme = 'https'; | ||
316 | } | ||
317 | |||
305 | if (($scheme == 'http' && $port != '80') | 318 | if (($scheme == 'http' && $port != '80') |
306 | || ($scheme == 'https' && $port != '443') | 319 | || ($scheme == 'https' && $port != '443') |
307 | ) { | 320 | ) { |
diff --git a/application/Languages.php b/application/Languages.php index c8b0a25a..357c7524 100644 --- a/application/Languages.php +++ b/application/Languages.php | |||
@@ -1,21 +1,164 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | namespace Shaarli; | ||
4 | |||
5 | use Gettext\GettextTranslator; | ||
6 | use Gettext\Merge; | ||
7 | use Gettext\Translations; | ||
8 | use Gettext\Translator; | ||
9 | use Gettext\TranslatorInterface; | ||
10 | use Shaarli\Config\ConfigManager; | ||
11 | |||
3 | /** | 12 | /** |
4 | * Wrapper function for translation which match the API | 13 | * Class Languages |
5 | * of gettext()/_() and ngettext(). | 14 | * |
15 | * Load Shaarli translations using 'gettext/gettext'. | ||
16 | * This class allows to either use PHP gettext extension, or a PHP implementation of gettext, | ||
17 | * with a fixed language, or dynamically using autoLocale(). | ||
6 | * | 18 | * |
7 | * Not doing translation for now. | 19 | * Translation files PO/MO files follow gettext standard and must be placed under: |
20 | * <translation path>/<language>/LC_MESSAGES/<domain>.[po|mo] | ||
8 | * | 21 | * |
9 | * @param string $text Text to translate. | 22 | * Pros/cons: |
10 | * @param string $nText The plural message ID. | 23 | * - gettext extension is faster |
11 | * @param int $nb The number of items for plural forms. | 24 | * - gettext is very system dependent (PHP extension, the locale must be installed, and web server reloaded) |
12 | * | 25 | * |
13 | * @return String Text translated. | 26 | * Settings: |
27 | * - translation.mode: | ||
28 | * - auto: use default setting (PHP implementation) | ||
29 | * - php: use PHP implementation | ||
30 | * - gettext: use gettext wrapper | ||
31 | * - translation.language: | ||
32 | * - auto: use autoLocale() and the language change according to user HTTP headers | ||
33 | * - fixed language: e.g. 'fr' | ||
34 | * - translation.extensions: | ||
35 | * - domain => translation_path: allow plugins and themes to extend the defaut extension | ||
36 | * The domain must be unique, and translation path must be relative, and contains the tree mentioned above. | ||
37 | * | ||
38 | * @package Shaarli | ||
14 | */ | 39 | */ |
15 | function t($text, $nText = '', $nb = 0) { | 40 | class Languages |
16 | if (empty($nText)) { | 41 | { |
17 | return $text; | 42 | /** |
43 | * Core translations domain | ||
44 | */ | ||
45 | const DEFAULT_DOMAIN = 'shaarli'; | ||
46 | |||
47 | /** | ||
48 | * @var TranslatorInterface | ||
49 | */ | ||
50 | protected $translator; | ||
51 | |||
52 | /** | ||
53 | * @var string | ||
54 | */ | ||
55 | protected $language; | ||
56 | |||
57 | /** | ||
58 | * @var ConfigManager | ||
59 | */ | ||
60 | protected $conf; | ||
61 | |||
62 | /** | ||
63 | * Languages constructor. | ||
64 | * | ||
65 | * @param string $language lang determined by autoLocale(), can be overridden. | ||
66 | * @param ConfigManager $conf instance. | ||
67 | */ | ||
68 | public function __construct($language, $conf) | ||
69 | { | ||
70 | $this->conf = $conf; | ||
71 | $confLanguage = $this->conf->get('translation.language', 'auto'); | ||
72 | if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) { | ||
73 | $this->language = substr($language, 0, 5); | ||
74 | } else { | ||
75 | $this->language = $confLanguage; | ||
76 | } | ||
77 | |||
78 | if (! extension_loaded('gettext') | ||
79 | || in_array($this->conf->get('translation.mode', 'auto'), ['auto', 'php']) | ||
80 | ) { | ||
81 | $this->initPhpTranslator(); | ||
82 | } else { | ||
83 | $this->initGettextTranslator(); | ||
84 | } | ||
85 | |||
86 | // Register default functions (e.g. '__()') to use our Translator | ||
87 | $this->translator->register(); | ||
88 | } | ||
89 | |||
90 | /** | ||
91 | * Initialize the translator using php gettext extension (gettext dependency act as a wrapper). | ||
92 | */ | ||
93 | protected function initGettextTranslator () | ||
94 | { | ||
95 | $this->translator = new GettextTranslator(); | ||
96 | $this->translator->setLanguage($this->language); | ||
97 | $this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages'); | ||
98 | |||
99 | foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) { | ||
100 | if ($domain !== self::DEFAULT_DOMAIN) { | ||
101 | $this->translator->loadDomain($domain, $translationPath, false); | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Initialize the translator using a PHP implementation of gettext. | ||
108 | * | ||
109 | * Note that if language po file doesn't exist, errors are ignored (e.g. not installed language). | ||
110 | */ | ||
111 | protected function initPhpTranslator() | ||
112 | { | ||
113 | $this->translator = new Translator(); | ||
114 | $translations = new Translations(); | ||
115 | // Core translations | ||
116 | try { | ||
117 | /** @var Translations $translations */ | ||
118 | $translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po'); | ||
119 | $translations->setDomain('shaarli'); | ||
120 | $this->translator->loadTranslations($translations); | ||
121 | } catch (\InvalidArgumentException $e) {} | ||
122 | |||
123 | |||
124 | // Extension translations (plugins, themes, etc.). | ||
125 | foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) { | ||
126 | if ($domain === self::DEFAULT_DOMAIN) { | ||
127 | continue; | ||
128 | } | ||
129 | |||
130 | try { | ||
131 | /** @var Translations $extension */ | ||
132 | $extension = Translations::fromPoFile($translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po'); | ||
133 | $extension->setDomain($domain); | ||
134 | $this->translator->loadTranslations($extension); | ||
135 | } catch (\InvalidArgumentException $e) {} | ||
136 | } | ||
137 | } | ||
138 | |||
139 | /** | ||
140 | * Checks if a language string is valid. | ||
141 | * | ||
142 | * @param string $language e.g. 'fr' or 'en_US' | ||
143 | * | ||
144 | * @return bool true if valid, false otherwise | ||
145 | */ | ||
146 | protected function isValidLanguage($language) | ||
147 | { | ||
148 | return preg_match('/^[a-z]{2}(_[A-Z]{2})?/', $language) === 1; | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * Get the list of available languages for Shaarli. | ||
153 | * | ||
154 | * @return array List of available languages, with their label. | ||
155 | */ | ||
156 | public static function getAvailableLanguages() | ||
157 | { | ||
158 | return [ | ||
159 | 'auto' => t('Automatic'), | ||
160 | 'en' => t('English'), | ||
161 | 'fr' => t('French'), | ||
162 | ]; | ||
18 | } | 163 | } |
19 | $actualForm = $nb > 1 ? $nText : $text; | ||
20 | return sprintf($actualForm, $nb); | ||
21 | } | 164 | } |
diff --git a/application/LinkDB.php b/application/LinkDB.php index 22c1f0ab..c1661d52 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php | |||
@@ -133,16 +133,16 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
133 | { | 133 | { |
134 | // TODO: use exceptions instead of "die" | 134 | // TODO: use exceptions instead of "die" |
135 | if (!$this->loggedIn) { | 135 | if (!$this->loggedIn) { |
136 | die('You are not authorized to add a link.'); | 136 | die(t('You are not authorized to add a link.')); |
137 | } | 137 | } |
138 | if (!isset($value['id']) || empty($value['url'])) { | 138 | if (!isset($value['id']) || empty($value['url'])) { |
139 | die('Internal Error: A link should always have an id and URL.'); | 139 | die(t('Internal Error: A link should always have an id and URL.')); |
140 | } | 140 | } |
141 | if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) { | 141 | if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) { |
142 | die('You must specify an integer as a key.'); | 142 | die(t('You must specify an integer as a key.')); |
143 | } | 143 | } |
144 | if ($offset !== null && $offset !== $value['id']) { | 144 | if ($offset !== null && $offset !== $value['id']) { |
145 | die('Array offset and link ID must be equal.'); | 145 | die(t('Array offset and link ID must be equal.')); |
146 | } | 146 | } |
147 | 147 | ||
148 | // If the link exists, we reuse the real offset, otherwise new entry | 148 | // If the link exists, we reuse the real offset, otherwise new entry |
@@ -248,13 +248,13 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
248 | $this->links = array(); | 248 | $this->links = array(); |
249 | $link = array( | 249 | $link = array( |
250 | 'id' => 1, | 250 | 'id' => 1, |
251 | 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', | 251 | 'title'=> t('The personal, minimalist, super-fast, database free, bookmarking service'), |
252 | 'url'=>'https://shaarli.readthedocs.io', | 252 | 'url'=>'https://shaarli.readthedocs.io', |
253 | 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. | 253 | 'description'=>t('Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. |
254 | 254 | ||
255 | To learn how to use Shaarli, consult the link "Help/documentation" at the bottom of this page. | 255 | To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page. |
256 | 256 | ||
257 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', | 257 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'), |
258 | 'private'=>0, | 258 | 'private'=>0, |
259 | 'created'=> new DateTime(), | 259 | 'created'=> new DateTime(), |
260 | 'tags'=>'opensource software' | 260 | 'tags'=>'opensource software' |
@@ -264,9 +264,9 @@ You use the community supported version of the original Shaarli project, by Seba | |||
264 | 264 | ||
265 | $link = array( | 265 | $link = array( |
266 | 'id' => 0, | 266 | 'id' => 0, |
267 | 'title'=>'My secret stuff... - Pastebin.com', | 267 | 'title'=> t('My secret stuff... - Pastebin.com'), |
268 | 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', | 268 | 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', |
269 | 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.', | 269 | 'description'=> t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'), |
270 | 'private'=>1, | 270 | 'private'=>1, |
271 | 'created'=> new DateTime('1 minute ago'), | 271 | 'created'=> new DateTime('1 minute ago'), |
272 | 'tags'=>'secretstuff', | 272 | 'tags'=>'secretstuff', |
@@ -289,13 +289,15 @@ You use the community supported version of the original Shaarli project, by Seba | |||
289 | return; | 289 | return; |
290 | } | 290 | } |
291 | 291 | ||
292 | $this->urls = []; | ||
293 | $this->ids = []; | ||
292 | $this->links = FileUtils::readFlatDB($this->datastore, []); | 294 | $this->links = FileUtils::readFlatDB($this->datastore, []); |
293 | 295 | ||
294 | $toremove = array(); | 296 | $toremove = array(); |
295 | foreach ($this->links as $key => &$link) { | 297 | foreach ($this->links as $key => &$link) { |
296 | if (! $this->loggedIn && $link['private'] != 0) { | 298 | if (! $this->loggedIn && $link['private'] != 0) { |
297 | // Transition for not upgraded databases. | 299 | // Transition for not upgraded databases. |
298 | $toremove[] = $key; | 300 | unset($this->links[$key]); |
299 | continue; | 301 | continue; |
300 | } | 302 | } |
301 | 303 | ||
@@ -329,14 +331,10 @@ You use the community supported version of the original Shaarli project, by Seba | |||
329 | } | 331 | } |
330 | $link['shorturl'] = smallHash($link['linkdate']); | 332 | $link['shorturl'] = smallHash($link['linkdate']); |
331 | } | 333 | } |
332 | } | ||
333 | 334 | ||
334 | // If user is not logged in, filter private links. | 335 | $this->urls[$link['url']] = $key; |
335 | foreach ($toremove as $offset) { | 336 | $this->ids[$link['id']] = $key; |
336 | unset($this->links[$offset]); | ||
337 | } | 337 | } |
338 | |||
339 | $this->reorder(); | ||
340 | } | 338 | } |
341 | 339 | ||
342 | /** | 340 | /** |
@@ -346,6 +344,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
346 | */ | 344 | */ |
347 | private function write() | 345 | private function write() |
348 | { | 346 | { |
347 | $this->reorder(); | ||
349 | FileUtils::writeFlatDB($this->datastore, $this->links); | 348 | FileUtils::writeFlatDB($this->datastore, $this->links); |
350 | } | 349 | } |
351 | 350 | ||
@@ -528,8 +527,8 @@ You use the community supported version of the original Shaarli project, by Seba | |||
528 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; | 527 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; |
529 | }); | 528 | }); |
530 | 529 | ||
531 | $this->urls = array(); | 530 | $this->urls = []; |
532 | $this->ids = array(); | 531 | $this->ids = []; |
533 | foreach ($this->links as $key => $link) { | 532 | foreach ($this->links as $key => $link) { |
534 | $this->urls[$link['url']] = $key; | 533 | $this->urls[$link['url']] = $key; |
535 | $this->ids[$link['id']] = $key; | 534 | $this->ids[$link['id']] = $key; |
diff --git a/application/LinkFilter.php b/application/LinkFilter.php index 99ecd1e2..12376e27 100644 --- a/application/LinkFilter.php +++ b/application/LinkFilter.php | |||
@@ -444,5 +444,11 @@ class LinkFilter | |||
444 | 444 | ||
445 | class LinkNotFoundException extends Exception | 445 | class LinkNotFoundException extends Exception |
446 | { | 446 | { |
447 | protected $message = 'The link you are trying to reach does not exist or has been deleted.'; | 447 | /** |
448 | * LinkNotFoundException constructor. | ||
449 | */ | ||
450 | public function __construct() | ||
451 | { | ||
452 | $this->message = t('The link you are trying to reach does not exist or has been deleted.'); | ||
453 | } | ||
448 | } | 454 | } |
diff --git a/application/LinkUtils.php b/application/LinkUtils.php index 267e62cd..3705f7e9 100644 --- a/application/LinkUtils.php +++ b/application/LinkUtils.php | |||
@@ -1,60 +1,81 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | /** | 3 | /** |
4 | * Extract title from an HTML document. | 4 | * Get cURL callback function for CURLOPT_WRITEFUNCTION |
5 | * | 5 | * |
6 | * @param string $html HTML content where to look for a title. | 6 | * @param string $charset to extract from the downloaded page (reference) |
7 | * @param string $title to extract from the downloaded page (reference) | ||
8 | * @param string $curlGetInfo Optionnaly overrides curl_getinfo function | ||
7 | * | 9 | * |
8 | * @return bool|string Extracted title if found, false otherwise. | 10 | * @return Closure |
9 | */ | 11 | */ |
10 | function html_extract_title($html) | 12 | function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_getinfo') |
11 | { | 13 | { |
12 | if (preg_match('!<title.*?>(.*?)</title>!is', $html, $matches)) { | 14 | /** |
13 | return trim(str_replace("\n", '', $matches[1])); | 15 | * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download). |
14 | } | 16 | * |
15 | return false; | 17 | * While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text' |
18 | * Then we extract the title and the charset and stop the download when it's done. | ||
19 | * | ||
20 | * @param resource $ch cURL resource | ||
21 | * @param string $data chunk of data being downloaded | ||
22 | * | ||
23 | * @return int|bool length of $data or false if we need to stop the download | ||
24 | */ | ||
25 | return function(&$ch, $data) use ($curlGetInfo, &$charset, &$title) { | ||
26 | $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE); | ||
27 | if (!empty($responseCode) && $responseCode != 200) { | ||
28 | return false; | ||
29 | } | ||
30 | $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE); | ||
31 | if (!empty($contentType) && strpos($contentType, 'text/html') === false) { | ||
32 | return false; | ||
33 | } | ||
34 | if (empty($charset)) { | ||
35 | $charset = header_extract_charset($contentType); | ||
36 | } | ||
37 | if (empty($charset)) { | ||
38 | $charset = html_extract_charset($data); | ||
39 | } | ||
40 | if (empty($title)) { | ||
41 | $title = html_extract_title($data); | ||
42 | } | ||
43 | // We got everything we want, stop the download. | ||
44 | if (!empty($responseCode) && !empty($contentType) && !empty($charset) && !empty($title)) { | ||
45 | return false; | ||
46 | } | ||
47 | |||
48 | return strlen($data); | ||
49 | }; | ||
16 | } | 50 | } |
17 | 51 | ||
18 | /** | 52 | /** |
19 | * Determine charset from downloaded page. | 53 | * Extract title from an HTML document. |
20 | * Priority: | ||
21 | * 1. HTTP headers (Content type). | ||
22 | * 2. HTML content page (tag <meta charset>). | ||
23 | * 3. Use a default charset (default: UTF-8). | ||
24 | * | 54 | * |
25 | * @param array $headers HTTP headers array. | 55 | * @param string $html HTML content where to look for a title. |
26 | * @param string $htmlContent HTML content where to look for charset. | ||
27 | * @param string $defaultCharset Default charset to apply if other methods failed. | ||
28 | * | 56 | * |
29 | * @return string Determined charset. | 57 | * @return bool|string Extracted title if found, false otherwise. |
30 | */ | 58 | */ |
31 | function get_charset($headers, $htmlContent, $defaultCharset = 'utf-8') | 59 | function html_extract_title($html) |
32 | { | 60 | { |
33 | if ($charset = headers_extract_charset($headers)) { | 61 | if (preg_match('!<title.*?>(.*?)</title>!is', $html, $matches)) { |
34 | return $charset; | 62 | return trim(str_replace("\n", '', $matches[1])); |
35 | } | ||
36 | |||
37 | if ($charset = html_extract_charset($htmlContent)) { | ||
38 | return $charset; | ||
39 | } | 63 | } |
40 | 64 | return false; | |
41 | return $defaultCharset; | ||
42 | } | 65 | } |
43 | 66 | ||
44 | /** | 67 | /** |
45 | * Extract charset from HTTP headers if it's defined. | 68 | * Extract charset from HTTP header if it's defined. |
46 | * | 69 | * |
47 | * @param array $headers HTTP headers array. | 70 | * @param string $header HTTP header Content-Type line. |
48 | * | 71 | * |
49 | * @return bool|string Charset string if found (lowercase), false otherwise. | 72 | * @return bool|string Charset string if found (lowercase), false otherwise. |
50 | */ | 73 | */ |
51 | function headers_extract_charset($headers) | 74 | function header_extract_charset($header) |
52 | { | 75 | { |
53 | if (! empty($headers['Content-Type']) && strpos($headers['Content-Type'], 'charset=') !== false) { | 76 | preg_match('/charset="?([^; ]+)/i', $header, $match); |
54 | preg_match('/charset="?([^; ]+)/i', $headers['Content-Type'], $match); | 77 | if (! empty($match[1])) { |
55 | if (! empty($match[1])) { | 78 | return strtolower(trim($match[1])); |
56 | return strtolower(trim($match[1])); | ||
57 | } | ||
58 | } | 79 | } |
59 | 80 | ||
60 | return false; | 81 | return false; |
@@ -102,12 +123,13 @@ function count_private($links) | |||
102 | * | 123 | * |
103 | * @param string $text input string. | 124 | * @param string $text input string. |
104 | * @param string $redirector if a redirector is set, use it to gerenate links. | 125 | * @param string $redirector if a redirector is set, use it to gerenate links. |
126 | * @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not. | ||
105 | * | 127 | * |
106 | * @return string returns $text with all links converted to HTML links. | 128 | * @return string returns $text with all links converted to HTML links. |
107 | * | 129 | * |
108 | * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 | 130 | * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 |
109 | */ | 131 | */ |
110 | function text2clickable($text, $redirector = '') | 132 | function text2clickable($text, $redirector = '', $urlEncode = true) |
111 | { | 133 | { |
112 | $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si'; | 134 | $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si'; |
113 | 135 | ||
@@ -117,8 +139,9 @@ function text2clickable($text, $redirector = '') | |||
117 | // Redirector is set, urlencode the final URL. | 139 | // Redirector is set, urlencode the final URL. |
118 | return preg_replace_callback( | 140 | return preg_replace_callback( |
119 | $regex, | 141 | $regex, |
120 | function ($matches) use ($redirector) { | 142 | function ($matches) use ($redirector, $urlEncode) { |
121 | return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>'; | 143 | $url = $urlEncode ? urlencode($matches[1]) : $matches[1]; |
144 | return '<a href="' . $redirector . $url .'">'. $matches[1] .'</a>'; | ||
122 | }, | 145 | }, |
123 | $text | 146 | $text |
124 | ); | 147 | ); |
@@ -164,12 +187,13 @@ function space2nbsp($text) | |||
164 | * | 187 | * |
165 | * @param string $description shaare's description. | 188 | * @param string $description shaare's description. |
166 | * @param string $redirector if a redirector is set, use it to gerenate links. | 189 | * @param string $redirector if a redirector is set, use it to gerenate links. |
190 | * @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not. | ||
167 | * @param string $indexUrl URL to Shaarli's index. | 191 | * @param string $indexUrl URL to Shaarli's index. |
168 | * | 192 | |
169 | * @return string formatted description. | 193 | * @return string formatted description. |
170 | */ | 194 | */ |
171 | function format_description($description, $redirector = '', $indexUrl = '') { | 195 | function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '') { |
172 | return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl))); | 196 | return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector, $urlEncode), $indexUrl))); |
173 | } | 197 | } |
174 | 198 | ||
175 | /** | 199 | /** |
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php index 2a10ff22..dd7057f8 100644 --- a/application/NetscapeBookmarkUtils.php +++ b/application/NetscapeBookmarkUtils.php | |||
@@ -32,11 +32,10 @@ class NetscapeBookmarkUtils | |||
32 | { | 32 | { |
33 | // see tpl/export.html for possible values | 33 | // see tpl/export.html for possible values |
34 | if (! in_array($selection, array('all', 'public', 'private'))) { | 34 | if (! in_array($selection, array('all', 'public', 'private'))) { |
35 | throw new Exception('Invalid export selection: "'.$selection.'"'); | 35 | throw new Exception(t('Invalid export selection:') .' "'.$selection.'"'); |
36 | } | 36 | } |
37 | 37 | ||
38 | $bookmarkLinks = array(); | 38 | $bookmarkLinks = array(); |
39 | |||
40 | foreach ($linkDb as $link) { | 39 | foreach ($linkDb as $link) { |
41 | if ($link['private'] != 0 && $selection == 'public') { | 40 | if ($link['private'] != 0 && $selection == 'public') { |
42 | continue; | 41 | continue; |
@@ -66,6 +65,7 @@ class NetscapeBookmarkUtils | |||
66 | * @param int $importCount how many links were imported | 65 | * @param int $importCount how many links were imported |
67 | * @param int $overwriteCount how many links were overwritten | 66 | * @param int $overwriteCount how many links were overwritten |
68 | * @param int $skipCount how many links were skipped | 67 | * @param int $skipCount how many links were skipped |
68 | * @param int $duration how many seconds did the import take | ||
69 | * | 69 | * |
70 | * @return string Summary of the bookmark import status | 70 | * @return string Summary of the bookmark import status |
71 | */ | 71 | */ |
@@ -74,16 +74,18 @@ class NetscapeBookmarkUtils | |||
74 | $filesize, | 74 | $filesize, |
75 | $importCount=0, | 75 | $importCount=0, |
76 | $overwriteCount=0, | 76 | $overwriteCount=0, |
77 | $skipCount=0 | 77 | $skipCount=0, |
78 | $duration=0 | ||
78 | ) | 79 | ) |
79 | { | 80 | { |
80 | $status = 'File '.$filename.' ('.$filesize.' bytes) '; | 81 | $status = sprintf(t('File %s (%d bytes) '), $filename, $filesize); |
81 | if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { | 82 | if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { |
82 | $status .= 'has an unknown file format. Nothing was imported.'; | 83 | $status .= t('has an unknown file format. Nothing was imported.'); |
83 | } else { | 84 | } else { |
84 | $status .= 'was successfully processed: '.$importCount.' links imported, '; | 85 | $status .= vsprintf( |
85 | $status .= $overwriteCount.' links overwritten, '; | 86 | t('was successfully processed in %d seconds: %d links imported, %d links overwritten, %d links skipped.'), |
86 | $status .= $skipCount.' links skipped.'; | 87 | [$duration, $importCount, $overwriteCount, $skipCount] |
88 | ); | ||
87 | } | 89 | } |
88 | return $status; | 90 | return $status; |
89 | } | 91 | } |
@@ -101,6 +103,7 @@ class NetscapeBookmarkUtils | |||
101 | */ | 103 | */ |
102 | public static function import($post, $files, $linkDb, $conf, $history) | 104 | public static function import($post, $files, $linkDb, $conf, $history) |
103 | { | 105 | { |
106 | $start = time(); | ||
104 | $filename = $files['filetoupload']['name']; | 107 | $filename = $files['filetoupload']['name']; |
105 | $filesize = $files['filetoupload']['size']; | 108 | $filesize = $files['filetoupload']['size']; |
106 | $data = file_get_contents($files['filetoupload']['tmp_name']); | 109 | $data = file_get_contents($files['filetoupload']['tmp_name']); |
@@ -184,7 +187,6 @@ class NetscapeBookmarkUtils | |||
184 | $linkDb[$existingLink['id']] = $newLink; | 187 | $linkDb[$existingLink['id']] = $newLink; |
185 | $importCount++; | 188 | $importCount++; |
186 | $overwriteCount++; | 189 | $overwriteCount++; |
187 | $history->updateLink($newLink); | ||
188 | continue; | 190 | continue; |
189 | } | 191 | } |
190 | 192 | ||
@@ -196,16 +198,19 @@ class NetscapeBookmarkUtils | |||
196 | $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); | 198 | $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); |
197 | $linkDb[$newLink['id']] = $newLink; | 199 | $linkDb[$newLink['id']] = $newLink; |
198 | $importCount++; | 200 | $importCount++; |
199 | $history->addLink($newLink); | ||
200 | } | 201 | } |
201 | 202 | ||
202 | $linkDb->save($conf->get('resource.page_cache')); | 203 | $linkDb->save($conf->get('resource.page_cache')); |
204 | $history->importLinks(); | ||
205 | |||
206 | $duration = time() - $start; | ||
203 | return self::importStatus( | 207 | return self::importStatus( |
204 | $filename, | 208 | $filename, |
205 | $filesize, | 209 | $filesize, |
206 | $importCount, | 210 | $importCount, |
207 | $overwriteCount, | 211 | $overwriteCount, |
208 | $skipCount | 212 | $skipCount, |
213 | $duration | ||
209 | ); | 214 | ); |
210 | } | 215 | } |
211 | } | 216 | } |
diff --git a/application/PageBuilder.php b/application/PageBuilder.php index 291860ad..468f144b 100644 --- a/application/PageBuilder.php +++ b/application/PageBuilder.php | |||
@@ -32,12 +32,14 @@ class PageBuilder | |||
32 | * | 32 | * |
33 | * @param ConfigManager $conf Configuration Manager instance (reference). | 33 | * @param ConfigManager $conf Configuration Manager instance (reference). |
34 | * @param LinkDB $linkDB instance. | 34 | * @param LinkDB $linkDB instance. |
35 | * @param string $token Session token | ||
35 | */ | 36 | */ |
36 | public function __construct(&$conf, $linkDB = null) | 37 | public function __construct(&$conf, $linkDB = null, $token = null) |
37 | { | 38 | { |
38 | $this->tpl = false; | 39 | $this->tpl = false; |
39 | $this->conf = $conf; | 40 | $this->conf = $conf; |
40 | $this->linkDB = $linkDB; | 41 | $this->linkDB = $linkDB; |
42 | $this->token = $token; | ||
41 | } | 43 | } |
42 | 44 | ||
43 | /** | 45 | /** |
@@ -92,7 +94,7 @@ class PageBuilder | |||
92 | $this->tpl->assign('showatom', $this->conf->get('feed.show_atom', true)); | 94 | $this->tpl->assign('showatom', $this->conf->get('feed.show_atom', true)); |
93 | $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss'); | 95 | $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss'); |
94 | $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); | 96 | $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); |
95 | $this->tpl->assign('token', getToken($this->conf)); | 97 | $this->tpl->assign('token', $this->token); |
96 | 98 | ||
97 | if ($this->linkDB !== null) { | 99 | if ($this->linkDB !== null) { |
98 | $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); | 100 | $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); |
@@ -159,9 +161,12 @@ class PageBuilder | |||
159 | * | 161 | * |
160 | * @param string $message A messate to display what is not found | 162 | * @param string $message A messate to display what is not found |
161 | */ | 163 | */ |
162 | public function render404($message = 'The page you are trying to reach does not exist or has been deleted.') | 164 | public function render404($message = '') |
163 | { | 165 | { |
164 | header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); | 166 | if (empty($message)) { |
167 | $message = t('The page you are trying to reach does not exist or has been deleted.'); | ||
168 | } | ||
169 | header($_SERVER['SERVER_PROTOCOL'] .' '. t('404 Not Found')); | ||
165 | $this->tpl->assign('error_message', $message); | 170 | $this->tpl->assign('error_message', $message); |
166 | $this->renderPage('404'); | 171 | $this->renderPage('404'); |
167 | } | 172 | } |
diff --git a/application/PluginManager.php b/application/PluginManager.php index 59ece4fa..cf603845 100644 --- a/application/PluginManager.php +++ b/application/PluginManager.php | |||
@@ -188,6 +188,9 @@ class PluginManager | |||
188 | $metaData[$plugin] = parse_ini_file($metaFile); | 188 | $metaData[$plugin] = parse_ini_file($metaFile); |
189 | $metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins); | 189 | $metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins); |
190 | 190 | ||
191 | if (isset($metaData[$plugin]['description'])) { | ||
192 | $metaData[$plugin]['description'] = t($metaData[$plugin]['description']); | ||
193 | } | ||
191 | // Read parameters and format them into an array. | 194 | // Read parameters and format them into an array. |
192 | if (isset($metaData[$plugin]['parameters'])) { | 195 | if (isset($metaData[$plugin]['parameters'])) { |
193 | $params = explode(';', $metaData[$plugin]['parameters']); | 196 | $params = explode(';', $metaData[$plugin]['parameters']); |
@@ -203,7 +206,7 @@ class PluginManager | |||
203 | $metaData[$plugin]['parameters'][$param]['value'] = ''; | 206 | $metaData[$plugin]['parameters'][$param]['value'] = ''; |
204 | // Optional parameter description in parameter.PARAM_NAME= | 207 | // Optional parameter description in parameter.PARAM_NAME= |
205 | if (isset($metaData[$plugin]['parameter.'. $param])) { | 208 | if (isset($metaData[$plugin]['parameter.'. $param])) { |
206 | $metaData[$plugin]['parameters'][$param]['desc'] = $metaData[$plugin]['parameter.'. $param]; | 209 | $metaData[$plugin]['parameters'][$param]['desc'] = t($metaData[$plugin]['parameter.'. $param]); |
207 | } | 210 | } |
208 | } | 211 | } |
209 | } | 212 | } |
@@ -237,6 +240,6 @@ class PluginFileNotFoundException extends Exception | |||
237 | */ | 240 | */ |
238 | public function __construct($pluginName) | 241 | public function __construct($pluginName) |
239 | { | 242 | { |
240 | $this->message = 'Plugin "'. $pluginName .'" files not found.'; | 243 | $this->message = sprintf(t('Plugin "%s" files not found.'), $pluginName); |
241 | } | 244 | } |
242 | } | 245 | } |
diff --git a/application/SessionManager.php b/application/SessionManager.php new file mode 100644 index 00000000..71f0b38d --- /dev/null +++ b/application/SessionManager.php | |||
@@ -0,0 +1,83 @@ | |||
1 | <?php | ||
2 | namespace Shaarli; | ||
3 | |||
4 | /** | ||
5 | * Manages the server-side session | ||
6 | */ | ||
7 | class SessionManager | ||
8 | { | ||
9 | protected $session = []; | ||
10 | |||
11 | /** | ||
12 | * Constructor | ||
13 | * | ||
14 | * @param array $session The $_SESSION array (reference) | ||
15 | * @param ConfigManager $conf ConfigManager instance | ||
16 | */ | ||
17 | public function __construct(& $session, $conf) | ||
18 | { | ||
19 | $this->session = &$session; | ||
20 | $this->conf = $conf; | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * Generates a session token | ||
25 | * | ||
26 | * @return string token | ||
27 | */ | ||
28 | public function generateToken() | ||
29 | { | ||
30 | $token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt')); | ||
31 | $this->session['tokens'][$token] = 1; | ||
32 | return $token; | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Checks the validity of a session token, and destroys it afterwards | ||
37 | * | ||
38 | * @param string $token The token to check | ||
39 | * | ||
40 | * @return bool true if the token is valid, else false | ||
41 | */ | ||
42 | public function checkToken($token) | ||
43 | { | ||
44 | if (! isset($this->session['tokens'][$token])) { | ||
45 | // the token is wrong, or has already been used | ||
46 | return false; | ||
47 | } | ||
48 | |||
49 | // destroy the token to prevent future use | ||
50 | unset($this->session['tokens'][$token]); | ||
51 | return true; | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * Validate session ID to prevent Full Path Disclosure. | ||
56 | * | ||
57 | * See #298. | ||
58 | * The session ID's format depends on the hash algorithm set in PHP settings | ||
59 | * | ||
60 | * @param string $sessionId Session ID | ||
61 | * | ||
62 | * @return true if valid, false otherwise. | ||
63 | * | ||
64 | * @see http://php.net/manual/en/function.hash-algos.php | ||
65 | * @see http://php.net/manual/en/session.configuration.php | ||
66 | */ | ||
67 | public static function checkId($sessionId) | ||
68 | { | ||
69 | if (empty($sessionId)) { | ||
70 | return false; | ||
71 | } | ||
72 | |||
73 | if (!$sessionId) { | ||
74 | return false; | ||
75 | } | ||
76 | |||
77 | if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) { | ||
78 | return false; | ||
79 | } | ||
80 | |||
81 | return true; | ||
82 | } | ||
83 | } | ||
diff --git a/application/Updater.php b/application/Updater.php index 72b2def0..8d2bd577 100644 --- a/application/Updater.php +++ b/application/Updater.php | |||
@@ -73,7 +73,7 @@ class Updater | |||
73 | } | 73 | } |
74 | 74 | ||
75 | if ($this->methods === null) { | 75 | if ($this->methods === null) { |
76 | throw new UpdaterException('Couldn\'t retrieve Updater class methods.'); | 76 | throw new UpdaterException(t('Couldn\'t retrieve Updater class methods.')); |
77 | } | 77 | } |
78 | 78 | ||
79 | foreach ($this->methods as $method) { | 79 | foreach ($this->methods as $method) { |
@@ -436,6 +436,15 @@ class Updater | |||
436 | } | 436 | } |
437 | return true; | 437 | return true; |
438 | } | 438 | } |
439 | |||
440 | /** | ||
441 | * Save the datastore -> the link order is now applied when links are saved. | ||
442 | */ | ||
443 | public function updateMethodReorderDatastore() | ||
444 | { | ||
445 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
446 | return true; | ||
447 | } | ||
439 | } | 448 | } |
440 | 449 | ||
441 | /** | 450 | /** |
@@ -482,7 +491,7 @@ class UpdaterException extends Exception | |||
482 | } | 491 | } |
483 | 492 | ||
484 | if (! empty($this->method)) { | 493 | if (! empty($this->method)) { |
485 | $out .= 'An error occurred while running the update '. $this->method . PHP_EOL; | 494 | $out .= t('An error occurred while running the update ') . $this->method . PHP_EOL; |
486 | } | 495 | } |
487 | 496 | ||
488 | if (! empty($this->previous)) { | 497 | if (! empty($this->previous)) { |
@@ -522,11 +531,11 @@ function read_updates_file($updatesFilepath) | |||
522 | function write_updates_file($updatesFilepath, $updates) | 531 | function write_updates_file($updatesFilepath, $updates) |
523 | { | 532 | { |
524 | if (empty($updatesFilepath)) { | 533 | if (empty($updatesFilepath)) { |
525 | throw new Exception('Updates file path is not set, can\'t write updates.'); | 534 | throw new Exception(t('Updates file path is not set, can\'t write updates.')); |
526 | } | 535 | } |
527 | 536 | ||
528 | $res = file_put_contents($updatesFilepath, implode(';', $updates)); | 537 | $res = file_put_contents($updatesFilepath, implode(';', $updates)); |
529 | if ($res === false) { | 538 | if ($res === false) { |
530 | throw new Exception('Unable to write updates in '. $updatesFilepath . '.'); | 539 | throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.')); |
531 | } | 540 | } |
532 | } | 541 | } |
diff --git a/application/Utils.php b/application/Utils.php index 4a2f5561..97b12fcf 100644 --- a/application/Utils.php +++ b/application/Utils.php | |||
@@ -182,36 +182,6 @@ function generateLocation($referer, $host, $loopTerms = array()) | |||
182 | } | 182 | } |
183 | 183 | ||
184 | /** | 184 | /** |
185 | * Validate session ID to prevent Full Path Disclosure. | ||
186 | * | ||
187 | * See #298. | ||
188 | * The session ID's format depends on the hash algorithm set in PHP settings | ||
189 | * | ||
190 | * @param string $sessionId Session ID | ||
191 | * | ||
192 | * @return true if valid, false otherwise. | ||
193 | * | ||
194 | * @see http://php.net/manual/en/function.hash-algos.php | ||
195 | * @see http://php.net/manual/en/session.configuration.php | ||
196 | */ | ||
197 | function is_session_id_valid($sessionId) | ||
198 | { | ||
199 | if (empty($sessionId)) { | ||
200 | return false; | ||
201 | } | ||
202 | |||
203 | if (!$sessionId) { | ||
204 | return false; | ||
205 | } | ||
206 | |||
207 | if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) { | ||
208 | return false; | ||
209 | } | ||
210 | |||
211 | return true; | ||
212 | } | ||
213 | |||
214 | /** | ||
215 | * Sniff browser language to set the locale automatically. | 185 | * Sniff browser language to set the locale automatically. |
216 | * Note that is may not work on your server if the corresponding locale is not installed. | 186 | * Note that is may not work on your server if the corresponding locale is not installed. |
217 | * | 187 | * |
@@ -452,7 +422,7 @@ function get_max_upload_size($limitPost, $limitUpload, $format = true) | |||
452 | */ | 422 | */ |
453 | function alphabetical_sort(&$data, $reverse = false, $byKeys = false) | 423 | function alphabetical_sort(&$data, $reverse = false, $byKeys = false) |
454 | { | 424 | { |
455 | $callback = function($a, $b) use ($reverse) { | 425 | $callback = function ($a, $b) use ($reverse) { |
456 | // Collator is part of PHP intl. | 426 | // Collator is part of PHP intl. |
457 | if (class_exists('Collator')) { | 427 | if (class_exists('Collator')) { |
458 | $collator = new Collator(setlocale(LC_COLLATE, 0)); | 428 | $collator = new Collator(setlocale(LC_COLLATE, 0)); |
@@ -470,3 +440,18 @@ function alphabetical_sort(&$data, $reverse = false, $byKeys = false) | |||
470 | usort($data, $callback); | 440 | usort($data, $callback); |
471 | } | 441 | } |
472 | } | 442 | } |
443 | |||
444 | /** | ||
445 | * Wrapper function for translation which match the API | ||
446 | * of gettext()/_() and ngettext(). | ||
447 | * | ||
448 | * @param string $text Text to translate. | ||
449 | * @param string $nText The plural message ID. | ||
450 | * @param int $nb The number of items for plural forms. | ||
451 | * @param string $domain The domain where the translation is stored (default: shaarli). | ||
452 | * | ||
453 | * @return string Text translated. | ||
454 | */ | ||
455 | function t($text, $nText = '', $nb = 1, $domain = 'shaarli') { | ||
456 | return dn__($domain, $text, $nText, $nb); | ||
457 | } | ||
diff --git a/application/config/ConfigJson.php b/application/config/ConfigJson.php index 9ef2ef56..8c8d5610 100644 --- a/application/config/ConfigJson.php +++ b/application/config/ConfigJson.php | |||
@@ -22,10 +22,15 @@ class ConfigJson implements ConfigIO | |||
22 | $data = json_decode($data, true); | 22 | $data = json_decode($data, true); |
23 | if ($data === null) { | 23 | if ($data === null) { |
24 | $errorCode = json_last_error(); | 24 | $errorCode = json_last_error(); |
25 | $error = 'An error occurred while parsing JSON configuration file ('. $filepath .'): error code #'; | 25 | $error = sprintf( |
26 | $error .= $errorCode. '<br>➜ <code>' . json_last_error_msg() .'</code>'; | 26 | 'An error occurred while parsing JSON configuration file (%s): error code #%d', |
27 | $filepath, | ||
28 | $errorCode | ||
29 | ); | ||
30 | $error .= '<br>➜ <code>' . json_last_error_msg() .'</code>'; | ||
27 | if ($errorCode === JSON_ERROR_SYNTAX) { | 31 | if ($errorCode === JSON_ERROR_SYNTAX) { |
28 | $error .= '<br>Please check your JSON syntax (without PHP comment tags) using a JSON lint tool such as '; | 32 | $error .= '<br>'; |
33 | $error .= 'Please check your JSON syntax (without PHP comment tags) using a JSON lint tool such as '; | ||
29 | $error .= '<a href="http://jsonlint.com/">jsonlint.com</a>.'; | 34 | $error .= '<a href="http://jsonlint.com/">jsonlint.com</a>.'; |
30 | } | 35 | } |
31 | throw new \Exception($error); | 36 | throw new \Exception($error); |
@@ -44,8 +49,8 @@ class ConfigJson implements ConfigIO | |||
44 | if (!file_put_contents($filepath, $data)) { | 49 | if (!file_put_contents($filepath, $data)) { |
45 | throw new \IOException( | 50 | throw new \IOException( |
46 | $filepath, | 51 | $filepath, |
47 | 'Shaarli could not create the config file. | 52 | t('Shaarli could not create the config file. '. |
48 | Please make sure Shaarli has the right to write in the folder is it installed in.' | 53 | 'Please make sure Shaarli has the right to write in the folder is it installed in.') |
49 | ); | 54 | ); |
50 | } | 55 | } |
51 | } | 56 | } |
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php index 7ff2fe67..9e4c9f63 100644 --- a/application/config/ConfigManager.php +++ b/application/config/ConfigManager.php | |||
@@ -132,7 +132,7 @@ class ConfigManager | |||
132 | public function set($setting, $value, $write = false, $isLoggedIn = false) | 132 | public function set($setting, $value, $write = false, $isLoggedIn = false) |
133 | { | 133 | { |
134 | if (empty($setting) || ! is_string($setting)) { | 134 | if (empty($setting) || ! is_string($setting)) { |
135 | throw new \Exception('Invalid setting key parameter. String expected, got: '. gettype($setting)); | 135 | throw new \Exception(t('Invalid setting key parameter. String expected, got: '). gettype($setting)); |
136 | } | 136 | } |
137 | 137 | ||
138 | // During the ConfigIO transition, map legacy settings to the new ones. | 138 | // During the ConfigIO transition, map legacy settings to the new ones. |
@@ -339,6 +339,10 @@ class ConfigManager | |||
339 | $this->setEmpty('redirector.url', ''); | 339 | $this->setEmpty('redirector.url', ''); |
340 | $this->setEmpty('redirector.encode_url', true); | 340 | $this->setEmpty('redirector.encode_url', true); |
341 | 341 | ||
342 | $this->setEmpty('translation.language', 'auto'); | ||
343 | $this->setEmpty('translation.mode', 'php'); | ||
344 | $this->setEmpty('translation.extensions', []); | ||
345 | |||
342 | $this->setEmpty('plugins', array()); | 346 | $this->setEmpty('plugins', array()); |
343 | } | 347 | } |
344 | 348 | ||
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php index 2633824d..2f66e8e0 100644 --- a/application/config/ConfigPhp.php +++ b/application/config/ConfigPhp.php | |||
@@ -118,8 +118,8 @@ class ConfigPhp implements ConfigIO | |||
118 | ) { | 118 | ) { |
119 | throw new \IOException( | 119 | throw new \IOException( |
120 | $filepath, | 120 | $filepath, |
121 | 'Shaarli could not create the config file. | 121 | t('Shaarli could not create the config file. '. |
122 | Please make sure Shaarli has the right to write in the folder is it installed in.' | 122 | 'Please make sure Shaarli has the right to write in the folder is it installed in.') |
123 | ); | 123 | ); |
124 | } | 124 | } |
125 | } | 125 | } |
diff --git a/application/config/exception/MissingFieldConfigException.php b/application/config/exception/MissingFieldConfigException.php index 6346c6a9..9e0a9359 100644 --- a/application/config/exception/MissingFieldConfigException.php +++ b/application/config/exception/MissingFieldConfigException.php | |||
@@ -18,6 +18,6 @@ class MissingFieldConfigException extends \Exception | |||
18 | public function __construct($field) | 18 | public function __construct($field) |
19 | { | 19 | { |
20 | $this->field = $field; | 20 | $this->field = $field; |
21 | $this->message = 'Configuration value is required for '. $this->field; | 21 | $this->message = sprintf(t('Configuration value is required for %s'), $this->field); |
22 | } | 22 | } |
23 | } | 23 | } |
diff --git a/application/config/exception/PluginConfigOrderException.php b/application/config/exception/PluginConfigOrderException.php index f9d68750..f82ec26e 100644 --- a/application/config/exception/PluginConfigOrderException.php +++ b/application/config/exception/PluginConfigOrderException.php | |||
@@ -12,6 +12,6 @@ class PluginConfigOrderException extends \Exception | |||
12 | */ | 12 | */ |
13 | public function __construct() | 13 | public function __construct() |
14 | { | 14 | { |
15 | $this->message = 'An error occurred while trying to save plugins loading order.'; | 15 | $this->message = t('An error occurred while trying to save plugins loading order.'); |
16 | } | 16 | } |
17 | } | 17 | } |
diff --git a/application/config/exception/UnauthorizedConfigException.php b/application/config/exception/UnauthorizedConfigException.php index 79672c1b..72311fae 100644 --- a/application/config/exception/UnauthorizedConfigException.php +++ b/application/config/exception/UnauthorizedConfigException.php | |||
@@ -13,6 +13,6 @@ class UnauthorizedConfigException extends \Exception | |||
13 | */ | 13 | */ |
14 | public function __construct() | 14 | public function __construct() |
15 | { | 15 | { |
16 | $this->message = 'You are not authorized to alter config.'; | 16 | $this->message = t('You are not authorized to alter config.'); |
17 | } | 17 | } |
18 | } | 18 | } |
diff --git a/application/exceptions/IOException.php b/application/exceptions/IOException.php index b563b23d..18e46b77 100644 --- a/application/exceptions/IOException.php +++ b/application/exceptions/IOException.php | |||
@@ -16,7 +16,7 @@ class IOException extends Exception | |||
16 | public function __construct($path, $message = '') | 16 | public function __construct($path, $message = '') |
17 | { | 17 | { |
18 | $this->path = $path; | 18 | $this->path = $path; |
19 | $this->message = empty($message) ? 'Error accessing' : $message; | 19 | $this->message = empty($message) ? t('Error accessing') : $message; |
20 | $this->message .= ' "' . $this->path .'"'; | 20 | $this->message .= ' "' . $this->path .'"'; |
21 | } | 21 | } |
22 | } | 22 | } |
diff --git a/composer.json b/composer.json index afb8aca4..f331d6ca 100644 --- a/composer.json +++ b/composer.json | |||
@@ -19,7 +19,8 @@ | |||
19 | "shaarli/netscape-bookmark-parser": "^2.0", | 19 | "shaarli/netscape-bookmark-parser": "^2.0", |
20 | "erusev/parsedown": "1.6", | 20 | "erusev/parsedown": "1.6", |
21 | "slim/slim": "^3.0", | 21 | "slim/slim": "^3.0", |
22 | "pubsubhubbub/publisher": "dev-master" | 22 | "pubsubhubbub/publisher": "dev-master", |
23 | "gettext/gettext": "^4.4" | ||
23 | }, | 24 | }, |
24 | "require-dev": { | 25 | "require-dev": { |
25 | "phpmd/phpmd" : "@stable", | 26 | "phpmd/phpmd" : "@stable", |
diff --git a/composer.lock b/composer.lock index 435d6a88..39909b8f 100644 --- a/composer.lock +++ b/composer.lock | |||
@@ -4,7 +4,7 @@ | |||
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", | 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", |
5 | "This file is @generated automatically" | 5 | "This file is @generated automatically" |
6 | ], | 6 | ], |
7 | "content-hash": "68beedbfa104c788029b079800cfd6e8", | 7 | "content-hash": "13b7e1e474fe9264b098ba86face0feb", |
8 | "packages": [ | 8 | "packages": [ |
9 | { | 9 | { |
10 | "name": "container-interop/container-interop", | 10 | "name": "container-interop/container-interop", |
@@ -77,6 +77,129 @@ | |||
77 | "time": "2015-10-04T16:44:32+00:00" | 77 | "time": "2015-10-04T16:44:32+00:00" |
78 | }, | 78 | }, |
79 | { | 79 | { |
80 | "name": "gettext/gettext", | ||
81 | "version": "v4.4.3", | ||
82 | "source": { | ||
83 | "type": "git", | ||
84 | "url": "https://github.com/oscarotero/Gettext.git", | ||
85 | "reference": "4f57f004635cc6311a20815ebfdc0757cb337113" | ||
86 | }, | ||
87 | "dist": { | ||
88 | "type": "zip", | ||
89 | "url": "https://api.github.com/repos/oscarotero/Gettext/zipball/4f57f004635cc6311a20815ebfdc0757cb337113", | ||
90 | "reference": "4f57f004635cc6311a20815ebfdc0757cb337113", | ||
91 | "shasum": "" | ||
92 | }, | ||
93 | "require": { | ||
94 | "gettext/languages": "^2.3", | ||
95 | "php": ">=5.4.0" | ||
96 | }, | ||
97 | "require-dev": { | ||
98 | "illuminate/view": "*", | ||
99 | "phpunit/phpunit": "^4.8|^5.7", | ||
100 | "squizlabs/php_codesniffer": "^3.0", | ||
101 | "symfony/yaml": "~2", | ||
102 | "twig/extensions": "*", | ||
103 | "twig/twig": "^1.31|^2.0" | ||
104 | }, | ||
105 | "suggest": { | ||
106 | "illuminate/view": "Is necessary if you want to use the Blade extractor", | ||
107 | "symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator", | ||
108 | "twig/extensions": "Is necessary if you want to use the Twig extractor", | ||
109 | "twig/twig": "Is necessary if you want to use the Twig extractor" | ||
110 | }, | ||
111 | "type": "library", | ||
112 | "autoload": { | ||
113 | "psr-4": { | ||
114 | "Gettext\\": "src" | ||
115 | } | ||
116 | }, | ||
117 | "notification-url": "https://packagist.org/downloads/", | ||
118 | "license": [ | ||
119 | "MIT" | ||
120 | ], | ||
121 | "authors": [ | ||
122 | { | ||
123 | "name": "Oscar Otero", | ||
124 | "email": "oom@oscarotero.com", | ||
125 | "homepage": "http://oscarotero.com", | ||
126 | "role": "Developer" | ||
127 | } | ||
128 | ], | ||
129 | "description": "PHP gettext manager", | ||
130 | "homepage": "https://github.com/oscarotero/Gettext", | ||
131 | "keywords": [ | ||
132 | "JS", | ||
133 | "gettext", | ||
134 | "i18n", | ||
135 | "mo", | ||
136 | "po", | ||
137 | "translation" | ||
138 | ], | ||
139 | "time": "2017-08-09T16:59:46+00:00" | ||
140 | }, | ||
141 | { | ||
142 | "name": "gettext/languages", | ||
143 | "version": "2.3.0", | ||
144 | "source": { | ||
145 | "type": "git", | ||
146 | "url": "https://github.com/mlocati/cldr-to-gettext-plural-rules.git", | ||
147 | "reference": "49c39e51569963cc917a924b489e7025bfb9d8c7" | ||
148 | }, | ||
149 | "dist": { | ||
150 | "type": "zip", | ||
151 | "url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/49c39e51569963cc917a924b489e7025bfb9d8c7", | ||
152 | "reference": "49c39e51569963cc917a924b489e7025bfb9d8c7", | ||
153 | "shasum": "" | ||
154 | }, | ||
155 | "require": { | ||
156 | "php": ">=5.3" | ||
157 | }, | ||
158 | "require-dev": { | ||
159 | "phpunit/phpunit": "^4" | ||
160 | }, | ||
161 | "bin": [ | ||
162 | "bin/export-plural-rules", | ||
163 | "bin/export-plural-rules.php" | ||
164 | ], | ||
165 | "type": "library", | ||
166 | "autoload": { | ||
167 | "psr-4": { | ||
168 | "Gettext\\Languages\\": "src/" | ||
169 | } | ||
170 | }, | ||
171 | "notification-url": "https://packagist.org/downloads/", | ||
172 | "license": [ | ||
173 | "MIT" | ||
174 | ], | ||
175 | "authors": [ | ||
176 | { | ||
177 | "name": "Michele Locati", | ||
178 | "email": "mlocati@gmail.com", | ||
179 | "role": "Developer" | ||
180 | } | ||
181 | ], | ||
182 | "description": "gettext languages with plural rules", | ||
183 | "homepage": "https://github.com/mlocati/cldr-to-gettext-plural-rules", | ||
184 | "keywords": [ | ||
185 | "cldr", | ||
186 | "i18n", | ||
187 | "internationalization", | ||
188 | "l10n", | ||
189 | "language", | ||
190 | "languages", | ||
191 | "localization", | ||
192 | "php", | ||
193 | "plural", | ||
194 | "plural rules", | ||
195 | "plurals", | ||
196 | "translate", | ||
197 | "translations", | ||
198 | "unicode" | ||
199 | ], | ||
200 | "time": "2017-03-23T17:02:28+00:00" | ||
201 | }, | ||
202 | { | ||
80 | "name": "katzgrau/klogger", | 203 | "name": "katzgrau/klogger", |
81 | "version": "1.2.1", | 204 | "version": "1.2.1", |
82 | "source": { | 205 | "source": { |
@@ -371,12 +494,12 @@ | |||
371 | "source": { | 494 | "source": { |
372 | "type": "git", | 495 | "type": "git", |
373 | "url": "https://github.com/pubsubhubbub/php-publisher.git", | 496 | "url": "https://github.com/pubsubhubbub/php-publisher.git", |
374 | "reference": "a5d6a0e1cc9d49101c3904480e5b06cbb8addba7" | 497 | "reference": "0d224daebd504ab61c22fee4db58f8d1fc18945f" |
375 | }, | 498 | }, |
376 | "dist": { | 499 | "dist": { |
377 | "type": "zip", | 500 | "type": "zip", |
378 | "url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/a5d6a0e1cc9d49101c3904480e5b06cbb8addba7", | 501 | "url": "https://api.github.com/repos/pubsubhubbub/php-publisher/zipball/0d224daebd504ab61c22fee4db58f8d1fc18945f", |
379 | "reference": "a5d6a0e1cc9d49101c3904480e5b06cbb8addba7", | 502 | "reference": "0d224daebd504ab61c22fee4db58f8d1fc18945f", |
380 | "shasum": "" | 503 | "shasum": "" |
381 | }, | 504 | }, |
382 | "require": { | 505 | "require": { |
@@ -406,7 +529,7 @@ | |||
406 | "publishers", | 529 | "publishers", |
407 | "pubsubhubbub" | 530 | "pubsubhubbub" |
408 | ], | 531 | ], |
409 | "time": "2016-11-15T06:24:01+00:00" | 532 | "time": "2017-10-08T10:59:41+00:00" |
410 | }, | 533 | }, |
411 | { | 534 | { |
412 | "name": "shaarli/netscape-bookmark-parser", | 535 | "name": "shaarli/netscape-bookmark-parser", |
@@ -632,16 +755,16 @@ | |||
632 | }, | 755 | }, |
633 | { | 756 | { |
634 | "name": "phpdocumentor/reflection-common", | 757 | "name": "phpdocumentor/reflection-common", |
635 | "version": "1.0", | 758 | "version": "1.0.1", |
636 | "source": { | 759 | "source": { |
637 | "type": "git", | 760 | "type": "git", |
638 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", | 761 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", |
639 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" | 762 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" |
640 | }, | 763 | }, |
641 | "dist": { | 764 | "dist": { |
642 | "type": "zip", | 765 | "type": "zip", |
643 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", | 766 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", |
644 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", | 767 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", |
645 | "shasum": "" | 768 | "shasum": "" |
646 | }, | 769 | }, |
647 | "require": { | 770 | "require": { |
@@ -682,20 +805,20 @@ | |||
682 | "reflection", | 805 | "reflection", |
683 | "static analysis" | 806 | "static analysis" |
684 | ], | 807 | ], |
685 | "time": "2015-12-27T11:43:31+00:00" | 808 | "time": "2017-09-11T18:02:19+00:00" |
686 | }, | 809 | }, |
687 | { | 810 | { |
688 | "name": "phpdocumentor/reflection-docblock", | 811 | "name": "phpdocumentor/reflection-docblock", |
689 | "version": "3.2.1", | 812 | "version": "3.2.2", |
690 | "source": { | 813 | "source": { |
691 | "type": "git", | 814 | "type": "git", |
692 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", | 815 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", |
693 | "reference": "183824db76118b9dddffc7e522b91fa175f75119" | 816 | "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157" |
694 | }, | 817 | }, |
695 | "dist": { | 818 | "dist": { |
696 | "type": "zip", | 819 | "type": "zip", |
697 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/183824db76118b9dddffc7e522b91fa175f75119", | 820 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/4aada1f93c72c35e22fb1383b47fee43b8f1d157", |
698 | "reference": "183824db76118b9dddffc7e522b91fa175f75119", | 821 | "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157", |
699 | "shasum": "" | 822 | "shasum": "" |
700 | }, | 823 | }, |
701 | "require": { | 824 | "require": { |
@@ -727,7 +850,7 @@ | |||
727 | } | 850 | } |
728 | ], | 851 | ], |
729 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", | 852 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", |
730 | "time": "2017-08-04T20:55:59+00:00" | 853 | "time": "2017-08-08T06:39:58+00:00" |
731 | }, | 854 | }, |
732 | { | 855 | { |
733 | "name": "phpdocumentor/type-resolver", | 856 | "name": "phpdocumentor/type-resolver", |
@@ -844,22 +967,22 @@ | |||
844 | }, | 967 | }, |
845 | { | 968 | { |
846 | "name": "phpspec/prophecy", | 969 | "name": "phpspec/prophecy", |
847 | "version": "v1.7.0", | 970 | "version": "v1.7.2", |
848 | "source": { | 971 | "source": { |
849 | "type": "git", | 972 | "type": "git", |
850 | "url": "https://github.com/phpspec/prophecy.git", | 973 | "url": "https://github.com/phpspec/prophecy.git", |
851 | "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" | 974 | "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" |
852 | }, | 975 | }, |
853 | "dist": { | 976 | "dist": { |
854 | "type": "zip", | 977 | "type": "zip", |
855 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", | 978 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", |
856 | "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", | 979 | "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", |
857 | "shasum": "" | 980 | "shasum": "" |
858 | }, | 981 | }, |
859 | "require": { | 982 | "require": { |
860 | "doctrine/instantiator": "^1.0.2", | 983 | "doctrine/instantiator": "^1.0.2", |
861 | "php": "^5.3|^7.0", | 984 | "php": "^5.3|^7.0", |
862 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", | 985 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", |
863 | "sebastian/comparator": "^1.1|^2.0", | 986 | "sebastian/comparator": "^1.1|^2.0", |
864 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" | 987 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" |
865 | }, | 988 | }, |
@@ -870,7 +993,7 @@ | |||
870 | "type": "library", | 993 | "type": "library", |
871 | "extra": { | 994 | "extra": { |
872 | "branch-alias": { | 995 | "branch-alias": { |
873 | "dev-master": "1.6.x-dev" | 996 | "dev-master": "1.7.x-dev" |
874 | } | 997 | } |
875 | }, | 998 | }, |
876 | "autoload": { | 999 | "autoload": { |
@@ -903,7 +1026,7 @@ | |||
903 | "spy", | 1026 | "spy", |
904 | "stub" | 1027 | "stub" |
905 | ], | 1028 | ], |
906 | "time": "2017-03-02T20:05:34+00:00" | 1029 | "time": "2017-09-04T11:05:03+00:00" |
907 | }, | 1030 | }, |
908 | { | 1031 | { |
909 | "name": "phpunit/php-code-coverage", | 1032 | "name": "phpunit/php-code-coverage", |
@@ -1875,20 +1998,20 @@ | |||
1875 | }, | 1998 | }, |
1876 | { | 1999 | { |
1877 | "name": "symfony/config", | 2000 | "name": "symfony/config", |
1878 | "version": "v3.3.6", | 2001 | "version": "v3.3.10", |
1879 | "source": { | 2002 | "source": { |
1880 | "type": "git", | 2003 | "type": "git", |
1881 | "url": "https://github.com/symfony/config.git", | 2004 | "url": "https://github.com/symfony/config.git", |
1882 | "reference": "54ee12b0dd60f294132cabae6f5da9573d2e5297" | 2005 | "reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd" |
1883 | }, | 2006 | }, |
1884 | "dist": { | 2007 | "dist": { |
1885 | "type": "zip", | 2008 | "type": "zip", |
1886 | "url": "https://api.github.com/repos/symfony/config/zipball/54ee12b0dd60f294132cabae6f5da9573d2e5297", | 2009 | "url": "https://api.github.com/repos/symfony/config/zipball/4ab62407bff9cd97c410a7feaef04c375aaa5cfd", |
1887 | "reference": "54ee12b0dd60f294132cabae6f5da9573d2e5297", | 2010 | "reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd", |
1888 | "shasum": "" | 2011 | "shasum": "" |
1889 | }, | 2012 | }, |
1890 | "require": { | 2013 | "require": { |
1891 | "php": ">=5.5.9", | 2014 | "php": "^5.5.9|>=7.0.8", |
1892 | "symfony/filesystem": "~2.8|~3.0" | 2015 | "symfony/filesystem": "~2.8|~3.0" |
1893 | }, | 2016 | }, |
1894 | "conflict": { | 2017 | "conflict": { |
@@ -1933,20 +2056,20 @@ | |||
1933 | ], | 2056 | ], |
1934 | "description": "Symfony Config Component", | 2057 | "description": "Symfony Config Component", |
1935 | "homepage": "https://symfony.com", | 2058 | "homepage": "https://symfony.com", |
1936 | "time": "2017-07-19T07:37:29+00:00" | 2059 | "time": "2017-10-04T18:56:58+00:00" |
1937 | }, | 2060 | }, |
1938 | { | 2061 | { |
1939 | "name": "symfony/console", | 2062 | "name": "symfony/console", |
1940 | "version": "v2.8.26", | 2063 | "version": "v2.8.28", |
1941 | "source": { | 2064 | "source": { |
1942 | "type": "git", | 2065 | "type": "git", |
1943 | "url": "https://github.com/symfony/console.git", | 2066 | "url": "https://github.com/symfony/console.git", |
1944 | "reference": "32a3c6b3398de5db8ed381f4ef92970c59c2fcdd" | 2067 | "reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853" |
1945 | }, | 2068 | }, |
1946 | "dist": { | 2069 | "dist": { |
1947 | "type": "zip", | 2070 | "type": "zip", |
1948 | "url": "https://api.github.com/repos/symfony/console/zipball/32a3c6b3398de5db8ed381f4ef92970c59c2fcdd", | 2071 | "url": "https://api.github.com/repos/symfony/console/zipball/f81549d2c5fdee8d711c9ab3c7e7362353ea5853", |
1949 | "reference": "32a3c6b3398de5db8ed381f4ef92970c59c2fcdd", | 2072 | "reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853", |
1950 | "shasum": "" | 2073 | "shasum": "" |
1951 | }, | 2074 | }, |
1952 | "require": { | 2075 | "require": { |
@@ -1994,7 +2117,7 @@ | |||
1994 | ], | 2117 | ], |
1995 | "description": "Symfony Console Component", | 2118 | "description": "Symfony Console Component", |
1996 | "homepage": "https://symfony.com", | 2119 | "homepage": "https://symfony.com", |
1997 | "time": "2017-07-29T21:26:04+00:00" | 2120 | "time": "2017-10-01T21:00:16+00:00" |
1998 | }, | 2121 | }, |
1999 | { | 2122 | { |
2000 | "name": "symfony/debug", | 2123 | "name": "symfony/debug", |
@@ -2055,20 +2178,20 @@ | |||
2055 | }, | 2178 | }, |
2056 | { | 2179 | { |
2057 | "name": "symfony/dependency-injection", | 2180 | "name": "symfony/dependency-injection", |
2058 | "version": "v3.3.6", | 2181 | "version": "v3.3.10", |
2059 | "source": { | 2182 | "source": { |
2060 | "type": "git", | 2183 | "type": "git", |
2061 | "url": "https://github.com/symfony/dependency-injection.git", | 2184 | "url": "https://github.com/symfony/dependency-injection.git", |
2062 | "reference": "8d70987f991481e809c63681ffe8ce3f3fde68a0" | 2185 | "reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1" |
2063 | }, | 2186 | }, |
2064 | "dist": { | 2187 | "dist": { |
2065 | "type": "zip", | 2188 | "type": "zip", |
2066 | "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8d70987f991481e809c63681ffe8ce3f3fde68a0", | 2189 | "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8ebad929aee3ca185b05f55d9cc5521670821ad1", |
2067 | "reference": "8d70987f991481e809c63681ffe8ce3f3fde68a0", | 2190 | "reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1", |
2068 | "shasum": "" | 2191 | "shasum": "" |
2069 | }, | 2192 | }, |
2070 | "require": { | 2193 | "require": { |
2071 | "php": ">=5.5.9", | 2194 | "php": "^5.5.9|>=7.0.8", |
2072 | "psr/container": "^1.0" | 2195 | "psr/container": "^1.0" |
2073 | }, | 2196 | }, |
2074 | "conflict": { | 2197 | "conflict": { |
@@ -2121,24 +2244,24 @@ | |||
2121 | ], | 2244 | ], |
2122 | "description": "Symfony DependencyInjection Component", | 2245 | "description": "Symfony DependencyInjection Component", |
2123 | "homepage": "https://symfony.com", | 2246 | "homepage": "https://symfony.com", |
2124 | "time": "2017-07-28T15:27:31+00:00" | 2247 | "time": "2017-10-04T17:15:30+00:00" |
2125 | }, | 2248 | }, |
2126 | { | 2249 | { |
2127 | "name": "symfony/filesystem", | 2250 | "name": "symfony/filesystem", |
2128 | "version": "v3.3.6", | 2251 | "version": "v3.3.10", |
2129 | "source": { | 2252 | "source": { |
2130 | "type": "git", | 2253 | "type": "git", |
2131 | "url": "https://github.com/symfony/filesystem.git", | 2254 | "url": "https://github.com/symfony/filesystem.git", |
2132 | "reference": "427987eb4eed764c3b6e38d52a0f87989e010676" | 2255 | "reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1" |
2133 | }, | 2256 | }, |
2134 | "dist": { | 2257 | "dist": { |
2135 | "type": "zip", | 2258 | "type": "zip", |
2136 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676", | 2259 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/90bc45abf02ae6b7deb43895c1052cb0038506f1", |
2137 | "reference": "427987eb4eed764c3b6e38d52a0f87989e010676", | 2260 | "reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1", |
2138 | "shasum": "" | 2261 | "shasum": "" |
2139 | }, | 2262 | }, |
2140 | "require": { | 2263 | "require": { |
2141 | "php": ">=5.5.9" | 2264 | "php": "^5.5.9|>=7.0.8" |
2142 | }, | 2265 | }, |
2143 | "type": "library", | 2266 | "type": "library", |
2144 | "extra": { | 2267 | "extra": { |
@@ -2170,24 +2293,24 @@ | |||
2170 | ], | 2293 | ], |
2171 | "description": "Symfony Filesystem Component", | 2294 | "description": "Symfony Filesystem Component", |
2172 | "homepage": "https://symfony.com", | 2295 | "homepage": "https://symfony.com", |
2173 | "time": "2017-07-11T07:17:58+00:00" | 2296 | "time": "2017-10-03T13:33:10+00:00" |
2174 | }, | 2297 | }, |
2175 | { | 2298 | { |
2176 | "name": "symfony/finder", | 2299 | "name": "symfony/finder", |
2177 | "version": "v3.3.6", | 2300 | "version": "v3.3.10", |
2178 | "source": { | 2301 | "source": { |
2179 | "type": "git", | 2302 | "type": "git", |
2180 | "url": "https://github.com/symfony/finder.git", | 2303 | "url": "https://github.com/symfony/finder.git", |
2181 | "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" | 2304 | "reference": "773e19a491d97926f236942484cb541560ce862d" |
2182 | }, | 2305 | }, |
2183 | "dist": { | 2306 | "dist": { |
2184 | "type": "zip", | 2307 | "type": "zip", |
2185 | "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", | 2308 | "url": "https://api.github.com/repos/symfony/finder/zipball/773e19a491d97926f236942484cb541560ce862d", |
2186 | "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", | 2309 | "reference": "773e19a491d97926f236942484cb541560ce862d", |
2187 | "shasum": "" | 2310 | "shasum": "" |
2188 | }, | 2311 | }, |
2189 | "require": { | 2312 | "require": { |
2190 | "php": ">=5.5.9" | 2313 | "php": "^5.5.9|>=7.0.8" |
2191 | }, | 2314 | }, |
2192 | "type": "library", | 2315 | "type": "library", |
2193 | "extra": { | 2316 | "extra": { |
@@ -2219,20 +2342,20 @@ | |||
2219 | ], | 2342 | ], |
2220 | "description": "Symfony Finder Component", | 2343 | "description": "Symfony Finder Component", |
2221 | "homepage": "https://symfony.com", | 2344 | "homepage": "https://symfony.com", |
2222 | "time": "2017-06-01T21:01:25+00:00" | 2345 | "time": "2017-10-02T06:42:24+00:00" |
2223 | }, | 2346 | }, |
2224 | { | 2347 | { |
2225 | "name": "symfony/polyfill-mbstring", | 2348 | "name": "symfony/polyfill-mbstring", |
2226 | "version": "v1.4.0", | 2349 | "version": "v1.6.0", |
2227 | "source": { | 2350 | "source": { |
2228 | "type": "git", | 2351 | "type": "git", |
2229 | "url": "https://github.com/symfony/polyfill-mbstring.git", | 2352 | "url": "https://github.com/symfony/polyfill-mbstring.git", |
2230 | "reference": "f29dca382a6485c3cbe6379f0c61230167681937" | 2353 | "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" |
2231 | }, | 2354 | }, |
2232 | "dist": { | 2355 | "dist": { |
2233 | "type": "zip", | 2356 | "type": "zip", |
2234 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", | 2357 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", |
2235 | "reference": "f29dca382a6485c3cbe6379f0c61230167681937", | 2358 | "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", |
2236 | "shasum": "" | 2359 | "shasum": "" |
2237 | }, | 2360 | }, |
2238 | "require": { | 2361 | "require": { |
@@ -2244,7 +2367,7 @@ | |||
2244 | "type": "library", | 2367 | "type": "library", |
2245 | "extra": { | 2368 | "extra": { |
2246 | "branch-alias": { | 2369 | "branch-alias": { |
2247 | "dev-master": "1.4-dev" | 2370 | "dev-master": "1.6-dev" |
2248 | } | 2371 | } |
2249 | }, | 2372 | }, |
2250 | "autoload": { | 2373 | "autoload": { |
@@ -2278,24 +2401,24 @@ | |||
2278 | "portable", | 2401 | "portable", |
2279 | "shim" | 2402 | "shim" |
2280 | ], | 2403 | ], |
2281 | "time": "2017-06-09T14:24:12+00:00" | 2404 | "time": "2017-10-11T12:05:26+00:00" |
2282 | }, | 2405 | }, |
2283 | { | 2406 | { |
2284 | "name": "symfony/yaml", | 2407 | "name": "symfony/yaml", |
2285 | "version": "v3.3.6", | 2408 | "version": "v3.3.10", |
2286 | "source": { | 2409 | "source": { |
2287 | "type": "git", | 2410 | "type": "git", |
2288 | "url": "https://github.com/symfony/yaml.git", | 2411 | "url": "https://github.com/symfony/yaml.git", |
2289 | "reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed" | 2412 | "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46" |
2290 | }, | 2413 | }, |
2291 | "dist": { | 2414 | "dist": { |
2292 | "type": "zip", | 2415 | "type": "zip", |
2293 | "url": "https://api.github.com/repos/symfony/yaml/zipball/ddc23324e6cfe066f3dd34a37ff494fa80b617ed", | 2416 | "url": "https://api.github.com/repos/symfony/yaml/zipball/8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", |
2294 | "reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed", | 2417 | "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", |
2295 | "shasum": "" | 2418 | "shasum": "" |
2296 | }, | 2419 | }, |
2297 | "require": { | 2420 | "require": { |
2298 | "php": ">=5.5.9" | 2421 | "php": "^5.5.9|>=7.0.8" |
2299 | }, | 2422 | }, |
2300 | "require-dev": { | 2423 | "require-dev": { |
2301 | "symfony/console": "~2.8|~3.0" | 2424 | "symfony/console": "~2.8|~3.0" |
@@ -2333,7 +2456,7 @@ | |||
2333 | ], | 2456 | ], |
2334 | "description": "Symfony Yaml Component", | 2457 | "description": "Symfony Yaml Component", |
2335 | "homepage": "https://symfony.com", | 2458 | "homepage": "https://symfony.com", |
2336 | "time": "2017-07-23T12:43:26+00:00" | 2459 | "time": "2017-10-05T14:43:42+00:00" |
2337 | }, | 2460 | }, |
2338 | { | 2461 | { |
2339 | "name": "theseer/fdomdocument", | 2462 | "name": "theseer/fdomdocument", |
diff --git a/data/.htaccess b/data/.htaccess index f601c1ee..1d49da37 100644 --- a/data/.htaccess +++ b/data/.htaccess | |||
@@ -1,10 +1,16 @@ | |||
1 | <IfModule version_module> | 1 | <IfModule version_module> |
2 | <IfVersion >= 2.4> | 2 | <IfVersion >= 2.4> |
3 | Require all denied | 3 | Require all denied |
4 | <Files "user.css"> | ||
5 | Require all granted | ||
6 | </Files> | ||
4 | </IfVersion> | 7 | </IfVersion> |
5 | <IfVersion < 2.4> | 8 | <IfVersion < 2.4> |
6 | Allow from none | 9 | Allow from none |
7 | Deny from all | 10 | Deny from all |
11 | <Files "user.css"> | ||
12 | Allow from all | ||
13 | </Files> | ||
8 | </IfVersion> | 14 | </IfVersion> |
9 | </IfModule> | 15 | </IfModule> |
10 | 16 | ||
diff --git a/doc/md/Backup,-restore,-import-and-export.md b/doc/md/Backup,-restore,-import-and-export.md index 89724857..bb790074 100644 --- a/doc/md/Backup,-restore,-import-and-export.md +++ b/doc/md/Backup,-restore,-import-and-export.md | |||
@@ -45,6 +45,10 @@ Shaarli cannot import data directly from [Scuttle](https://github.com/scronide/s | |||
45 | However, you can use the third-party [scuttle-to-shaarli](https://github.com/q2apro/scuttle-to-shaarli) | 45 | However, you can use the third-party [scuttle-to-shaarli](https://github.com/q2apro/scuttle-to-shaarli) |
46 | tool to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer. | 46 | tool to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer. |
47 | 47 | ||
48 | ### Refind | ||
49 | |||
50 | 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. | ||
51 | |||
48 | ## Import Shaarli links to Firefox | 52 | ## Import Shaarli links to Firefox |
49 | 53 | ||
50 | - Export your Shaarli links as described above. | 54 | - Export your Shaarli links as described above. |
diff --git a/doc/md/Bookmarklet.md b/doc/md/Bookmarklet.md index e53e3261..c899e3cf 100644 --- a/doc/md/Bookmarklet.md +++ b/doc/md/Bookmarklet.md | |||
@@ -21,7 +21,7 @@ _This bookmarklet button is compatible with Firefox, Opera, Chrome and Safari. U | |||
21 | 21 | ||
22 | Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunatly, there is nothing Shaarli can do about it. | 22 | Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunatly, there is nothing Shaarli can do about it. |
23 | 23 | ||
24 | See [#196](https://github.com/shaarli/Shaarli#196). | 24 | See [#196](https://github.com/shaarli/Shaarli/issues/196). |
25 | 25 | ||
26 | There is an open bug for both Firefox and Chromium: | 26 | There is an open bug for both Firefox and Chromium: |
27 | 27 | ||
diff --git a/doc/md/Browsing-and-searching.md b/doc/md/Browsing-and-searching.md index 35707482..16c69855 100644 --- a/doc/md/Browsing-and-searching.md +++ b/doc/md/Browsing-and-searching.md | |||
@@ -14,10 +14,24 @@ Use the `Filter by tags` field to restrict displayed links to entries tagged wit | |||
14 | 14 | ||
15 | **Hidden tags:** Tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in. | 15 | **Hidden tags:** Tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in. |
16 | 16 | ||
17 | Alternatively you can use the `Tag cloud` to discover all tags and click on any of them to display related links. | 17 | ### Tag cloud |
18 | 18 | ||
19 | To search for links that are not tagged, enter `""` in the tag search field. | 19 | The `Tag cloud` page diplays a "cloud" view of all tags in your Shaarli. |
20 | |||
21 | * The most frequently used tags are displayed with a bigger font size. | ||
22 | * When sorting by `Most used` or `Alphabetical`, tags are displayed as a _list_, along with counters and edit/delete buttons for each tag. | ||
23 | * Clicking on any tag will display a list of all Shaares matching this tag. | ||
24 | * 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. | ||
20 | 25 | ||
21 | ## Filtering RSS feeds/Picture wall | 26 | ## Filtering RSS feeds/Picture wall |
22 | 27 | ||
23 | RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS feeds). | 28 | RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS-feeds). |
29 | |||
30 | ## Filter buttons | ||
31 | |||
32 | Filter buttons can be found at the top left of the link list. They allow you to apply different filters to the list: | ||
33 | |||
34 | * **Private links:** When this toggle button is enabled, only shaares set to `private` will be shown. | ||
35 | * **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. | ||
36 | |||
37 | Filter buttons are only available when logged in. | ||
diff --git a/doc/md/Community-&-Related-software.md b/doc/md/Community-&-Related-software.md index 8edbeefa..207153b6 100644 --- a/doc/md/Community-&-Related-software.md +++ b/doc/md/Community-&-Related-software.md | |||
@@ -1,23 +1,7 @@ | |||
1 | _Unofficial but related work on Shaarli. If you maintain one of these, | 1 | _Unofficial but related work on Shaarli. If you maintain one of these, |
2 | please get in touch with us to help us find a way to adapt your work to our fork._ | 2 | please get in touch with us to help us find a way to adapt your work to our fork._ |
3 | 3 | ||
4 | ## Community | 4 | ## Related software |
5 | - [Liens en vrac de sebsauvage](http://sebsauvage.net/links/) - the original Shaarli | ||
6 | - [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html) | ||
7 | - [A list of working Shaarli aggregators](https://raw.githubusercontent.com/Oros42/find_shaarlis/master/annuaires.json) | ||
8 | - [A list of some known Shaarlis](https://github.com/Oros42/shaarlis_list) | ||
9 | - [Adieu Delicious, Diigo et StumbleUpon. Salut Shaarli ! - sebsauvage.net](http://sebsauvage.net/rhaa/index.php?2011/09/16/09/29/58-adieu-delicious-diigo-et-stumbleupon-salut-shaarli-) (fr) _16/09/2011 - the original post about Shaarli_ | ||
10 | - [Original ideas/fixme/TODO page](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:ideas) | ||
11 | - [Original discussion page](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:discussion) (fr) | ||
12 | - [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) | ||
13 | - [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni) | ||
14 | |||
15 | |||
16 | ### Articles and social media discussions | ||
17 | - 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176 | ||
18 | - 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/) | ||
19 | - 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366 | ||
20 | - 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/) | ||
21 | 5 | ||
22 | 6 | ||
23 | ### REST API clients | 7 | ### REST API clients |
@@ -29,28 +13,34 @@ See [REST API](REST-API) for a list of official and community clients. | |||
29 | - [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter. | 13 | - [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter. |
30 | - [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli. | 14 | - [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli. |
31 | - [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli. | 15 | - [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli. |
16 | - [twemoji](https://github.com/NerosTie/twemoji) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli (Twemoji version) | ||
32 | - [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support | 17 | - [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support |
33 | - [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli. | 18 | - [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli. |
19 | - [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. | ||
34 | - [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related links based on the number of identical tags. | 20 | - [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related links based on the number of identical tags. |
35 | - [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks. | 21 | - [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks. |
36 | - [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli | 22 | - [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli |
23 | - [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. | ||
24 | - [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. | ||
37 | 25 | ||
38 | 26 | ||
39 | ### Third-party themes | 27 | ### Third-party themes |
40 | See [Theming](Theming) for a list of community-contributed themes, and an installation guide. | 28 | See [Theming](Theming) for a list of community-contributed themes, and an installation guide. |
41 | 29 | ||
42 | 30 | ||
43 | ## Integration with other platforms | 31 | ### Integration with other platforms |
44 | - [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 | 32 | - [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 |
45 | - [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar | 33 | - [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar |
46 | - [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle | 34 | - [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle |
47 | 35 | ||
48 | 36 | ||
49 | ### Mobile Apps | 37 | ### Mobile Apps |
50 | - [ShaarliOS](https://github.com/mro/ShaarliOS) iOS share extension - see [#308](https://github.com/shaarli/Shaarli/issues/308#issuecomment-184592070) for some promo codes, | 38 | - [ShaarliOS](https://github.com/mro/ShaarliOS) - Apple iOS share extension. |
51 | - [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider | 39 | - [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider |
52 | - [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add links directly into your Shaarli | 40 | - [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add links directly into your Shaarli |
53 | 41 | ||
42 | ### Browser addons | ||
43 | * [Shaarli Web Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli. | ||
54 | 44 | ||
55 | ### Server apps | 45 | ### Server apps |
56 | - [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content | 46 | - [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content |
@@ -61,7 +51,22 @@ See [Theming](Theming) for a list of community-contributed themes, and an instal | |||
61 | - [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). | 51 | - [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). |
62 | - [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. | 52 | - [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. |
63 | 53 | ||
64 | |||
65 | ## Alternatives to Shaarli | 54 | ## Alternatives to Shaarli |
66 | See the [bookmarks & link sharing](https://github.com/Kickball/awesome-selfhosted/#bookmarks--link-sharing) | 55 | See [awesome-selfhosted: bookmarks & link sharing](https://github.com/Kickball/awesome-selfhosted/#bookmarks--link-sharing). |
67 | section on [awesome-selfhosted](https://github.com/Kickball/awesome-selfhosted/). | 56 | |
57 | ## Community | ||
58 | - [Liens en vrac de sebsauvage](http://sebsauvage.net/links/) - the original Shaarli | ||
59 | - [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html) | ||
60 | - [A list of working Shaarli aggregators](https://raw.githubusercontent.com/Oros42/find_shaarlis/master/annuaires.json) | ||
61 | - [A list of some known Shaarlis](https://github.com/Oros42/shaarlis_list) | ||
62 | - [Adieu Delicious, Diigo et StumbleUpon. Salut Shaarli ! - sebsauvage.net](http://sebsauvage.net/rhaa/index.php?2011/09/16/09/29/58-adieu-delicious-diigo-et-stumbleupon-salut-shaarli-) (fr) _16/09/2011 - the original post about Shaarli_ | ||
63 | - [Original ideas/fixme/TODO page](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:ideas) | ||
64 | - [Original discussion page](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:discussion) (fr) | ||
65 | - [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) | ||
66 | - [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni) | ||
67 | |||
68 | ### Articles and social media discussions | ||
69 | - 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176 | ||
70 | - 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/) | ||
71 | - 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366 | ||
72 | - 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/) | ||
diff --git a/doc/md/Download-and-Installation.md b/doc/md/Download-and-Installation.md index e5e929ef..0fdbd27d 100644 --- a/doc/md/Download-and-Installation.md +++ b/doc/md/Download-and-Installation.md | |||
@@ -4,44 +4,57 @@ Document Root (or directly at the document root). | |||
4 | Also, please make sure your server meets the [requirements](Server-requirements) | 4 | Also, please make sure your server meets the [requirements](Server-requirements) |
5 | and is properly [configured](Server-configuration). | 5 | and is properly [configured](Server-configuration). |
6 | 6 | ||
7 | Several releases are available: | 7 | Multiple releases branches are available: |
8 | |||
9 | - latest (last release) | ||
10 | - stable (previous major release) | ||
11 | - master (development) | ||
12 | |||
13 | Using one of the following methods: | ||
8 | 14 | ||
9 | - by downloading full release archives including all dependencies | 15 | - by downloading full release archives including all dependencies |
10 | - by downloading Github archives | 16 | - by downloading Github archives |
11 | - by cloning the Git repository | 17 | - by cloning the Git repository |
18 | - using Docker: [see the documentation](docker/shaarli-images.md) | ||
12 | 19 | ||
13 | --- | 20 | -------------------------------------------------------------------------------- |
14 | 21 | ||
15 | ## Latest release (recommended) | 22 | ## Latest release (recommended) |
16 | ### Download as an archive | ||
17 | Get the latest released version from the [releases](https://github.com/shaarli/Shaarli/releases) page. | ||
18 | 23 | ||
19 | **Download our *shaarli-full* archive** to include dependencies. | 24 | ### Download as an archive |
20 | 25 | ||
21 | The current latest released version is `v0.9.1` | 26 | 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. |
22 | 27 | ||
23 | Or in command lines: | 28 | The current latest released version is `v0.9.3` |
24 | 29 | ||
25 | ```bash | 30 | ```bash |
26 | $ wget https://github.com/shaarli/Shaarli/releases/download/v0.9.1/shaarli-v0.9.1-full.zip | 31 | $ wget https://github.com/shaarli/Shaarli/releases/download/v0.9.3/shaarli-v0.9.3-full.zip |
27 | $ unzip shaarli-v0.9.1-full.zip | 32 | $ unzip shaarli-v0.9.3-full.zip |
28 | $ mv Shaarli /path/to/shaarli/ | 33 | $ mv Shaarli /path/to/shaarli/ |
29 | ``` | 34 | ``` |
30 | 35 | ||
31 | 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).| | ||
32 | |||
33 | ### Using git | 36 | ### Using git |
34 | 37 | ||
38 | Cloning using `git` or downloading Github branches as zip files requires additional steps: | ||
39 | |||
40 | * Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies. | ||
41 | * Install [python3-virtualenv](https://pypi.python.org/pypi/virtualenv) to build the local HTML documentation. | ||
42 | |||
35 | ``` | 43 | ``` |
36 | $ mkdir -p /path/to/shaarli && cd /path/to/shaarli/ | 44 | $ mkdir -p /path/to/shaarli && cd /path/to/shaarli/ |
37 | $ git clone -b v0.9 https://github.com/shaarli/Shaarli.git . | 45 | $ git clone -b latest https://github.com/shaarli/Shaarli.git . |
38 | $ composer install --no-dev --prefer-dist | 46 | $ composer install --no-dev --prefer-dist |
47 | $ make translate | ||
48 | $ make htmldoc | ||
39 | ``` | 49 | ``` |
40 | 50 | ||
51 | -------------------------------------------------------------------------------- | ||
52 | |||
41 | ## Stable version | 53 | ## Stable version |
42 | 54 | ||
43 | The stable version has been experienced by Shaarli users, and will receive security updates. | 55 | The stable version has been experienced by Shaarli users, and will receive security updates. |
44 | 56 | ||
57 | |||
45 | ### Download as an archive | 58 | ### Download as an archive |
46 | 59 | ||
47 | As a .zip archive: | 60 | As a .zip archive: |
@@ -60,9 +73,9 @@ $ tar xvf stable.tar.gz | |||
60 | $ mv Shaarli-stable /path/to/shaarli/ | 73 | $ mv Shaarli-stable /path/to/shaarli/ |
61 | ``` | 74 | ``` |
62 | 75 | ||
63 | ### Clone with Git | 76 | ### Using git |
64 | 77 | ||
65 | [Composer](https://getcomposer.org/) is required to build a functional Shaarli installation when pulling from git. | 78 | Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies. |
66 | 79 | ||
67 | ```bash | 80 | ```bash |
68 | $ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/ | 81 | $ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/ |
@@ -71,25 +84,34 @@ $ cd /path/to/shaarli/ | |||
71 | $ composer install --no-dev --prefer-dist | 84 | $ composer install --no-dev --prefer-dist |
72 | ``` | 85 | ``` |
73 | 86 | ||
87 | |||
88 | -------------------------------------------------------------------------------- | ||
89 | |||
74 | ## Development version (mainline) | 90 | ## Development version (mainline) |
75 | 91 | ||
76 | _Use at your own risk!_ | 92 | _Use at your own risk!_ |
77 | 93 | ||
94 | Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies. | ||
95 | |||
78 | To get the latest changes from the `master` branch: | 96 | To get the latest changes from the `master` branch: |
79 | 97 | ||
80 | ```bash | 98 | ```bash |
81 | # clone the repository | 99 | # clone the repository |
82 | $ git clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/ | 100 | $ git clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/ |
83 | # install/update third-party dependencies | 101 | # install/update third-party dependencies |
84 | $ cd /path/to/shaarli | 102 | $ cd /path/to/shaarli |
85 | $ composer install --no-dev --prefer-dist | 103 | $ composer install --no-dev --prefer-dist |
104 | $ make translate | ||
105 | $ make htmldoc | ||
86 | ``` | 106 | ``` |
87 | 107 | ||
108 | ------------------------------------------------------------------------------- | ||
109 | |||
88 | ## Finish Installation | 110 | ## Finish Installation |
89 | 111 | ||
90 | Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser. | 112 | Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser. |
91 | 113 | ||
92 | ![install screenshot](http://i.imgur.com/wuMpDSN.png) | 114 | ![install screenshot](images/install-shaarli.png) |
93 | 115 | ||
94 | Setup your Shaarli installation, and it's ready to use! | 116 | Setup your Shaarli installation, and it's ready to use! |
95 | 117 | ||
diff --git a/doc/md/Features.md b/doc/md/Features.md deleted file mode 100644 index eef88d03..00000000 --- a/doc/md/Features.md +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | ### Main features | ||
2 | Shaarli is intended: | ||
3 | |||
4 | - to share, comment and save interesting links and news | ||
5 | - to bookmark useful/frequent personal links (as private links) and share them between computers | ||
6 | - as a minimal blog/microblog/writing platform (no character limit) | ||
7 | - as a read-it-later list (for example items tagged `readlater`) | ||
8 | - to draft and save articles/ideas | ||
9 | - to keep code snippets | ||
10 | - to keep notes and documentation | ||
11 | - as a shared clipboard between machines | ||
12 | - as a todo list | ||
13 | - to store playlists (e.g. with the `music` or `video` tags) | ||
14 | - to keep extracts/comments from webpages that may disappear | ||
15 | - to keep track of ongoing discussions (for example items tagged `discussion`) | ||
16 | - [to feed RSS aggregators](http://shaarli.chassegnouf.net/?9Efeiw) (planets) with specific tags | ||
17 | - to feed other social networks, blogs... using RSS feeds and external services (dlvr.it, ifttt.com ...) | ||
18 | |||
19 | ### Using Shaarli as a blog, notepad, pastebin... | ||
20 | |||
21 | - Go to your Shaarli setup and log in | ||
22 | - Click the `Add Link` button | ||
23 | - To share text only, do not enter any URL in the corresponding input field and click `Add Link` | ||
24 | - Pick a title and enter your article, or note, in the description field; add a few tags; optionally check `Private` then click `Save` | ||
25 | - Voilà ! Your article is now published (privately if you selected that option) and accessible using its permalink. | ||
diff --git a/doc/md/Firefox-share.md b/doc/md/Firefox-share.md index 878884a4..9a46b185 100644 --- a/doc/md/Firefox-share.md +++ b/doc/md/Firefox-share.md | |||
@@ -1,3 +1,6 @@ | |||
1 | | Note | Firefox Share is no longer available for Firefox 57 and later versions. | | ||
2 | |---------|---------| | ||
3 | |||
1 | ### Add Shaarli as a sharing service to Firefox | 4 | ### Add Shaarli as a sharing service to Firefox |
2 | 5 | ||
3 | - Open your Shaarli and `Login` | 6 | - Open your Shaarli and `Login` |
diff --git a/doc/md/Server-requirements.md b/doc/md/Server-requirements.md index 707af762..2dc442df 100644 --- a/doc/md/Server-requirements.md +++ b/doc/md/Server-requirements.md | |||
@@ -35,7 +35,8 @@ Library | Required? | Usage | |||
35 | Extension | Required? | Usage | 35 | Extension | Required? | Usage |
36 | ---|:---:|--- | 36 | ---|:---:|--- |
37 | [`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS | 37 | [`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS |
38 | [`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows | multibyte (Unicode) string support | 38 | [`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows, some hosting providers | multibyte (Unicode) string support |
39 | [`php-gd`](http://php.net/manual/en/book.image.php) | optional | thumbnail resizing | 39 | [`php-gd`](http://php.net/manual/en/book.image.php) | optional | thumbnail resizing |
40 | [`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`) | 40 | [`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`) |
41 | [`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way | 41 | [`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way |
42 | [`php-gettext`](http://php.net/manual/en/book.gettext.php) | optional | Use the translation system in gettext mode (faster) | ||
diff --git a/doc/md/Shaarli-configuration.md b/doc/md/Shaarli-configuration.md index 99b25ba7..920c7e27 100644 --- a/doc/md/Shaarli-configuration.md +++ b/doc/md/Shaarli-configuration.md | |||
@@ -81,6 +81,20 @@ _These settings should not be edited_ | |||
81 | - **page_cache**: Shaarli's internal cache directory. | 81 | - **page_cache**: Shaarli's internal cache directory. |
82 | - **ban_file**: Banned IP file path. | 82 | - **ban_file**: Banned IP file path. |
83 | 83 | ||
84 | ### Translation | ||
85 | |||
86 | - **language**: translation language (also see [Translations](Translations)) | ||
87 | - **auto** (default): The translation language is chosen from the browser locale. | ||
88 | It means that the language can be different for 2 different visitors depending on their locale. | ||
89 | - **en**: Use the English translation. | ||
90 | - **fr**: Use the French translation. | ||
91 | - **mode**: | ||
92 | - **auto** or **php** (default): Use the PHP implementation of gettext (slower) | ||
93 | - **gettext**: Use PHP builtin gettext extension | ||
94 | (faster, but requires `php-gettext` to be installed and to reload the web server on update) | ||
95 | - **extension**: Translation extensions for custom themes or plugins. | ||
96 | Must be an associative array: `translation domain => translation path`. | ||
97 | |||
84 | ### Updates | 98 | ### Updates |
85 | 99 | ||
86 | - **check_updates**: Enable or disable update check to the git repository. | 100 | - **check_updates**: Enable or disable update check to the git repository. |
@@ -211,6 +225,13 @@ _These settings should not be edited_ | |||
211 | "plugins": { | 225 | "plugins": { |
212 | "WALLABAG_URL": "http://demo.wallabag.org", | 226 | "WALLABAG_URL": "http://demo.wallabag.org", |
213 | "WALLABAG_VERSION": "1" | 227 | "WALLABAG_VERSION": "1" |
228 | }, | ||
229 | "translation": { | ||
230 | "language": "fr", | ||
231 | "mode": "php", | ||
232 | "extensions": { | ||
233 | "demo": "plugins/demo_plugin/languages/" | ||
234 | } | ||
214 | } | 235 | } |
215 | } ?> | 236 | } ?> |
216 | ``` | 237 | ``` |
diff --git a/doc/md/Translations.md b/doc/md/Translations.md new file mode 100644 index 00000000..54a36655 --- /dev/null +++ b/doc/md/Translations.md | |||
@@ -0,0 +1,152 @@ | |||
1 | ## Translations | ||
2 | |||
3 | Shaarli supports [gettext](https://www.gnu.org/software/gettext/manual/gettext.html) translations | ||
4 | since `>= v0.9.2`. | ||
5 | |||
6 | Note that only the `default` theme supports translations. | ||
7 | |||
8 | ### Contributing | ||
9 | |||
10 | We encourage the community to contribute to Shaarli's translation either by improving existing | ||
11 | translations or submitting a new language. | ||
12 | |||
13 | Contributing to the translation does not require development skill. | ||
14 | |||
15 | Please submit a pull request with the `.po` file updated/created. Note that the compiled file (`.mo`) | ||
16 | is not stored on the repository, and is generated during the release process. | ||
17 | |||
18 | ### How to | ||
19 | |||
20 | First, install [Poedit](https://poedit.net/) tool. | ||
21 | |||
22 | Poedit will extract strings to translate from the PHP source code. | ||
23 | |||
24 | **Important**: due to the usage of a template engine, it's important to generate PHP cache files to extract | ||
25 | every translatable string. | ||
26 | |||
27 | You can either use [this script](https://gist.github.com/ArthurHoaro/5d0323f758ab2401ef444a53f54e9a07) (recommended) | ||
28 | or visit every template page in your browser to generate cache files, while logged in. | ||
29 | |||
30 | Here is a list : | ||
31 | |||
32 | ``` | ||
33 | http://<replace_domain>/ | ||
34 | http://<replace_domain>/?nonope | ||
35 | http://<replace_domain>/?do=addlink | ||
36 | http://<replace_domain>/?do=changepasswd | ||
37 | http://<replace_domain>/?do=changetag | ||
38 | http://<replace_domain>/?do=configure | ||
39 | http://<replace_domain>/?do=tools | ||
40 | http://<replace_domain>/?do=daily | ||
41 | http://<replace_domain>/?post | ||
42 | http://<replace_domain>/?do=export | ||
43 | http://<replace_domain>/?do=import | ||
44 | http://<replace_domain>/?do=login | ||
45 | http://<replace_domain>/?do=picwall | ||
46 | http://<replace_domain>/?do=pluginadmin | ||
47 | http://<replace_domain>/?do=tagcloud | ||
48 | http://<replace_domain>/?do=taglist | ||
49 | ``` | ||
50 | |||
51 | #### Improve existing translation | ||
52 | |||
53 | In Poedit, click on "Edit a Translation", and from Shaarli's directory open | ||
54 | `inc/languages/<lang>/LC_MESSAGES/shaarli.po`. | ||
55 | |||
56 | The existing list of translatable strings should have been loaded, then click on the "Update" button. | ||
57 | |||
58 | You can start editing the translation. | ||
59 | |||
60 | ![poedit-screenshot](images/poedit-1.jpg) | ||
61 | |||
62 | Save when you're done, then you can submit a pull request containing the updated `shaarli.po`. | ||
63 | |||
64 | #### Add a new language | ||
65 | |||
66 | Open Poedit and select "Create New Translation", then from Shaarli's directory open | ||
67 | `inc/languages/<lang>/LC_MESSAGES/shaarli.po`. | ||
68 | |||
69 | Then select the language you want to create. | ||
70 | |||
71 | Click on `File > Save as...`, and save your file in `<shaarli directory>/inc/language/<new language>/LC_MESSAGES/shaarli.po`. | ||
72 | `<new language>` here should be the language code respecting the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-2) | ||
73 | format in lowercase (e.g. `de` for German). | ||
74 | |||
75 | Then click on the "Update" button, and you can start to translate every available string. | ||
76 | |||
77 | Save when you're done, then you can submit a pull request containing the new `shaarli.po`. | ||
78 | |||
79 | ### Extend Shaarli's translation | ||
80 | |||
81 | If you're writing a custom theme, or a non official plugin, you might want to use the translation system, | ||
82 | but you won't be able to able to override Shaarli's translation. | ||
83 | |||
84 | However, you can add your own translation domain which extends the main translation list. | ||
85 | |||
86 | > Note that you can find a live example of translation extension in the `demo_plugin`. | ||
87 | |||
88 | First, create your translation files tree directory: | ||
89 | |||
90 | ``` | ||
91 | <your_module>/languages/<ISO 3166-1 alpha-2 language code>/LC_MESSAGES/ | ||
92 | ``` | ||
93 | |||
94 | Your `.po` files must be named like your domain. E.g. if your translation domain is `my_theme`, then your file will be | ||
95 | `my_theme.po`. | ||
96 | |||
97 | Users have to register your extension in their configuration with the parameter | ||
98 | `translation.extensions.<domain>: <translation files path>`. | ||
99 | |||
100 | Example: | ||
101 | |||
102 | ```php | ||
103 | if (! $conf->exists('translation.extensions.my_theme')) { | ||
104 | $conf->set('translation.extensions.my_theme', '<your_module>/languages/'); | ||
105 | $conf->write(true); | ||
106 | } | ||
107 | ``` | ||
108 | |||
109 | > Note that the page needs to be reloaded after the registration. | ||
110 | |||
111 | It is then recommended to create a custom translation function which will call the `t()` function with your domain. | ||
112 | For example : | ||
113 | |||
114 | ```php | ||
115 | function my_theme_t($text, $nText = '', $nb = 1) | ||
116 | { | ||
117 | return t($text, $nText, $nb, 'my_theme'); // the last parameter is your translation domain. | ||
118 | } | ||
119 | ``` | ||
120 | |||
121 | All strings which can be translated should be processed through your function: | ||
122 | |||
123 | ```php | ||
124 | my_theme_t('Comment'); | ||
125 | my_theme_t('Comment', 'Comments', 2); | ||
126 | ``` | ||
127 | |||
128 | Or in templates: | ||
129 | |||
130 | ```php | ||
131 | {'Comment'|my_theme_t} | ||
132 | {function="my_theme_t('Comment', 'Comments', 2)"} | ||
133 | ``` | ||
134 | |||
135 | > Note than in template, you need to visit your page at least once to generate a cache file. | ||
136 | |||
137 | When you're done, open Poedit and load translation strings from sources: | ||
138 | |||
139 | 1. `File > New` | ||
140 | 2. Choose your language | ||
141 | 3. Save your `PO` file in `<your_module>/languages/<language code>/LC_MESSAGES/my_theme.po`. | ||
142 | 4. Go to `Catalog > Properties...` | ||
143 | 5. Fill the `Translation Properties` tab | ||
144 | 6. Add your source path in the `Sources Paths` tab | ||
145 | 7. In the `Sources Keywords` tab uncheck "Also use default keywords" and add the following lines: | ||
146 | |||
147 | ``` | ||
148 | my_theme_t | ||
149 | my_theme_t:1,2 | ||
150 | ``` | ||
151 | |||
152 | Click on the "Update" button and you're free to start your translations! | ||
diff --git a/doc/md/Unit-tests.md b/doc/md/Unit-tests.md index d200634f..f6030d5c 100644 --- a/doc/md/Unit-tests.md +++ b/doc/md/Unit-tests.md | |||
@@ -2,12 +2,12 @@ | |||
2 | 2 | ||
3 | The framework used is [PHPUnit](https://phpunit.de/); it can be installed with [Composer](https://getcomposer.org/), which is a dependency management tool. | 3 | The framework used is [PHPUnit](https://phpunit.de/); it can be installed with [Composer](https://getcomposer.org/), which is a dependency management tool. |
4 | 4 | ||
5 | Regarding Composer, you can either use: | 5 | ### Install composer |
6 | 6 | ||
7 | - a system-wide version, e.g. installed through your distro's package manager | 7 | You can either use: |
8 | - a local version, downloadable [here](https://getcomposer.org/download/) | ||
9 | 8 | ||
10 | #### Sample usage | 9 | - a system-wide version, e.g. installed through your distro's package manager |
10 | - a local version, downloadable [here](https://getcomposer.org/download/). | ||
11 | 11 | ||
12 | ```bash | 12 | ```bash |
13 | # system-wide version | 13 | # system-wide version |
@@ -29,6 +29,8 @@ $ composer update | |||
29 | 29 | ||
30 | #### Install and enable Xdebug to generate PHPUnit coverage reports | 30 | #### Install and enable Xdebug to generate PHPUnit coverage reports |
31 | 31 | ||
32 | See http://xdebug.org/docs/install | ||
33 | |||
32 | For Debian-based distros: | 34 | For Debian-based distros: |
33 | ```bash | 35 | ```bash |
34 | $ aptitude install php5-xdebug | 36 | $ aptitude install php5-xdebug |
diff --git a/doc/md/Upgrade-and-migration.md b/doc/md/Upgrade-and-migration.md index b3a08764..1dc07339 100644 --- a/doc/md/Upgrade-and-migration.md +++ b/doc/md/Upgrade-and-migration.md | |||
@@ -14,7 +14,7 @@ Shaarli stores all user data under the `data` directory: | |||
14 | - `data/ipbans.php` - banned IP addresses | 14 | - `data/ipbans.php` - banned IP addresses |
15 | - `data/updates.txt` - contains all automatic update to the configuration and datastore files already run | 15 | - `data/updates.txt` - contains all automatic update to the configuration and datastore files already run |
16 | 16 | ||
17 | See [Shaarli configuration](Shaarli configuration) for more information about Shaarli resources. | 17 | See [Shaarli configuration](Shaarli-configuration) for more information about Shaarli resources. |
18 | 18 | ||
19 | It is recommended to backup this repository _before_ starting updating/upgrading Shaarli: | 19 | It is recommended to backup this repository _before_ starting updating/upgrading Shaarli: |
20 | 20 | ||
@@ -27,7 +27,7 @@ As all user data is kept under `data`, this is the only directory you need to wo | |||
27 | 27 | ||
28 | - backup the `data` directory | 28 | - backup the `data` directory |
29 | - install or update Shaarli: | 29 | - install or update Shaarli: |
30 | - fresh installation - see [Download and installation](Download and installation) | 30 | - fresh installation - see [Download and installation](Download-and-installation) |
31 | - update - see the following sections | 31 | - update - see the following sections |
32 | - check or restore the `data` directory | 32 | - check or restore the `data` directory |
33 | 33 | ||
@@ -35,10 +35,13 @@ As all user data is kept under `data`, this is the only directory you need to wo | |||
35 | 35 | ||
36 | All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page. | 36 | All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page. |
37 | 37 | ||
38 | 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. | 38 | 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. |
39 | 39 | ||
40 | 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! | 40 | 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! |
41 | 41 | ||
42 | If you use translations in gettext mode - meaning you manually changed the default mode -, | ||
43 | reload your web server. | ||
44 | |||
42 | 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). | 45 | 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). |
43 | 46 | ||
44 | ## Upgrading with Git | 47 | ## Upgrading with Git |
@@ -72,6 +75,14 @@ Updating dependencies | |||
72 | Downloading: 100% | 75 | Downloading: 100% |
73 | ``` | 76 | ``` |
74 | 77 | ||
78 | Shaarli >= `v0.9.2` supports translations: | ||
79 | |||
80 | ```bash | ||
81 | $ make translate | ||
82 | ``` | ||
83 | |||
84 | If you use translations in gettext mode, reload your web server. | ||
85 | |||
75 | ### Migrating and upgrading from Sebsauvage's repository | 86 | ### Migrating and upgrading from Sebsauvage's repository |
76 | 87 | ||
77 | 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. | 88 | 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 | |||
151 | Downloading: 100% | 162 | Downloading: 100% |
152 | ``` | 163 | ``` |
153 | 164 | ||
165 | Shaarli >= `v0.9.2` supports translations: | ||
166 | |||
167 | ```bash | ||
168 | $ make translate | ||
169 | ``` | ||
170 | |||
171 | If you use translations in gettext mode, reload your web server. | ||
172 | |||
154 | Optionally, you can delete information related to the legacy version: | 173 | Optionally, you can delete information related to the legacy version: |
155 | 174 | ||
156 | ```bash | 175 | ```bash |
@@ -173,7 +192,7 @@ Total 3317 (delta 2050), reused 3301 (delta 2034)to | |||
173 | 192 | ||
174 | #### Step 3: configuration | 193 | #### Step 3: configuration |
175 | 194 | ||
176 | 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). | 195 | 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). |
177 | 196 | ||
178 | ## Troubleshooting | 197 | ## Troubleshooting |
179 | 198 | ||
diff --git a/doc/md/docker/reverse-proxy-configuration.md b/doc/md/docker/reverse-proxy-configuration.md index 91ffecff..6066140e 100644 --- a/doc/md/docker/reverse-proxy-configuration.md +++ b/doc/md/docker/reverse-proxy-configuration.md | |||
@@ -1,6 +1,120 @@ | |||
1 | ## Foreword | ||
2 | |||
3 | This guide assumes that: | ||
4 | |||
5 | - Shaarli runs in a Docker container | ||
6 | - The host's `10080` port is mapped to the container's `80` port | ||
7 | - Shaarli's Fully Qualified Domain Name (FQDN) is `shaarli.domain.tld` | ||
8 | - HTTP traffic is redirected to HTTPS | ||
9 | |||
10 | ## Apache | ||
11 | |||
12 | - [Apache 2.4 documentation](https://httpd.apache.org/docs/2.4/) | ||
13 | - [mod_proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html) | ||
14 | - [Reverse Proxy Request Headers](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#x-headers) | ||
15 | |||
16 | The following HTTP headers are set by using the `ProxyPass` directive: | ||
17 | |||
18 | - `X-Forwarded-For` | ||
19 | - `X-Forwarded-Host` | ||
20 | - `X-Forwarded-Server` | ||
21 | |||
22 | ```apache | ||
23 | <VirtualHost *:80> | ||
24 | ServerName shaarli.domain.tld | ||
25 | Redirect permanent / https://shaarli.domain.tld | ||
26 | </VirtualHost> | ||
27 | |||
28 | <VirtualHost *:443> | ||
29 | ServerName shaarli.domain.tld | ||
30 | |||
31 | SSLEngine on | ||
32 | SSLCertificateFile /path/to/cert | ||
33 | SSLCertificateKeyFile /path/to/certkey | ||
34 | |||
35 | LogLevel warn | ||
36 | ErrorLog /var/log/apache2/shaarli-error.log | ||
37 | CustomLog /var/log/apache2/shaarli-access.log combined | ||
38 | |||
39 | RequestHeader set X-Forwarded-Proto "https" | ||
40 | |||
41 | ProxyPass / http://127.0.0.1:10080/ | ||
42 | ProxyPassReverse / http://127.0.0.1:10080/ | ||
43 | </VirtualHost> | ||
44 | ``` | ||
1 | 45 | ||
2 | TODO, see https://github.com/shaarli/Shaarli/issues/888 | ||
3 | 46 | ||
4 | ## HAProxy | 47 | ## HAProxy |
5 | 48 | ||
49 | - [HAProxy documentation](https://cbonte.github.io/haproxy-dconv/) | ||
50 | |||
51 | ```conf | ||
52 | global | ||
53 | [...] | ||
54 | |||
55 | defaults | ||
56 | [...] | ||
57 | |||
58 | frontend http-in | ||
59 | bind :80 | ||
60 | redirect scheme https code 301 if !{ ssl_fc } | ||
61 | |||
62 | bind :443 ssl crt /path/to/cert.pem | ||
63 | |||
64 | default_backend shaarli | ||
65 | |||
66 | |||
67 | backend shaarli | ||
68 | mode http | ||
69 | option http-server-close | ||
70 | option forwardfor | ||
71 | reqadd X-Forwarded-Proto: https | ||
72 | |||
73 | server shaarli1 127.0.0.1:10080 | ||
74 | ``` | ||
75 | |||
76 | |||
6 | ## Nginx | 77 | ## Nginx |
78 | |||
79 | - [Nginx documentation](https://nginx.org/en/docs/) | ||
80 | |||
81 | ```nginx | ||
82 | http { | ||
83 | [...] | ||
84 | |||
85 | index index.html index.php; | ||
86 | |||
87 | root /home/john/web; | ||
88 | access_log /var/log/nginx/access.log; | ||
89 | error_log /var/log/nginx/error.log; | ||
90 | |||
91 | server { | ||
92 | listen 80; | ||
93 | server_name shaarli.domain.tld; | ||
94 | return 301 https://shaarli.domain.tld$request_uri; | ||
95 | } | ||
96 | |||
97 | server { | ||
98 | listen 443 ssl http2; | ||
99 | server_name shaarli.domain.tld; | ||
100 | |||
101 | ssl_certificate /path/to/cert | ||
102 | ssl_certificate_key /path/to/certkey | ||
103 | |||
104 | location / { | ||
105 | proxy_set_header X-Real-IP $remote_addr; | ||
106 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
107 | proxy_set_header X-Forwarded-Proto $scheme; | ||
108 | proxy_set_header X-Forwarded-Host $host; | ||
109 | |||
110 | proxy_pass http://localhost:10080/; | ||
111 | proxy_set_header Host $host; | ||
112 | proxy_connect_timeout 30s; | ||
113 | proxy_read_timeout 120s; | ||
114 | |||
115 | access_log /var/log/nginx/shaarli.access.log; | ||
116 | error_log /var/log/nginx/shaarli.error.log; | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | ``` | ||
diff --git a/doc/md/docker/shaarli-images.md b/doc/md/docker/shaarli-images.md index 6d108d21..12f7b5d1 100644 --- a/doc/md/docker/shaarli-images.md +++ b/doc/md/docker/shaarli-images.md | |||
@@ -1,3 +1,6 @@ | |||
1 | A brief guide on getting starting using docker is given in [Docker 101](docker-101.md). | ||
2 | To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](../Upgrade-and-migration.md). | ||
3 | |||
1 | ## Get and run a Shaarli image | 4 | ## Get and run a Shaarli image |
2 | 5 | ||
3 | ### DockerHub repository | 6 | ### DockerHub repository |
@@ -5,14 +8,24 @@ The images can be found in the [`shaarli/shaarli`](https://hub.docker.com/r/shaa | |||
5 | repository. | 8 | repository. |
6 | 9 | ||
7 | ### Available image tags | 10 | ### Available image tags |
8 | - `latest`: master branch (tarball release) | 11 | - `latest`: latest branch (tarball release) |
12 | - `master`: master branch (tarball release) | ||
9 | - `stable`: stable branch (tarball release) | 13 | - `stable`: stable branch (tarball release) |
10 | 14 | ||
11 | All images rely on: | 15 | The `latest` and `master` images rely on: |
16 | |||
17 | - [Alpine Linux](https://www.alpinelinux.org/) | ||
18 | - [PHP7-FPM](http://php-fpm.org/) | ||
19 | - [Nginx](http://nginx.org/) | ||
20 | |||
21 | The `stable` image relies on: | ||
22 | |||
12 | - [Debian 8 Jessie](https://hub.docker.com/_/debian/) | 23 | - [Debian 8 Jessie](https://hub.docker.com/_/debian/) |
13 | - [PHP5-FPM](http://php-fpm.org/) | 24 | - [PHP5-FPM](http://php-fpm.org/) |
14 | - [Nginx](http://nginx.org/) | 25 | - [Nginx](http://nginx.org/) |
15 | 26 | ||
27 | 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/). | ||
28 | |||
16 | ### Download from DockerHub | 29 | ### Download from DockerHub |
17 | ```bash | 30 | ```bash |
18 | $ docker pull shaarli/shaarli | 31 | $ docker pull shaarli/shaarli |
@@ -69,3 +82,14 @@ backstabbing_galileo | |||
69 | $ docker ps -a | 82 | $ docker ps -a |
70 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES | 83 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
71 | ``` | 84 | ``` |
85 | |||
86 | ### Automatic builds | ||
87 | |||
88 | 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): | ||
89 | ``` | ||
90 | MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P) | ||
91 | docker run -ti --rm \ | ||
92 | -p 8000:80 \ | ||
93 | -v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \ | ||
94 | shaarli/shaarli | ||
95 | ``` | ||
diff --git a/doc/md/images/install-shaarli.png b/doc/md/images/install-shaarli.png new file mode 100644 index 00000000..7ae33816 --- /dev/null +++ b/doc/md/images/install-shaarli.png | |||
Binary files differ | |||
diff --git a/doc/md/images/poedit-1.jpg b/doc/md/images/poedit-1.jpg new file mode 100644 index 00000000..673ae6d6 --- /dev/null +++ b/doc/md/images/poedit-1.jpg | |||
Binary files differ | |||
diff --git a/doc/md/index.md b/doc/md/index.md index 2b7d0f00..e77b4d3a 100644 --- a/doc/md/index.md +++ b/doc/md/index.md | |||
@@ -22,20 +22,25 @@ It runs the latest development version of Shaarli and is updated/reset daily. | |||
22 | 22 | ||
23 | Login: `demo`; Password: `demo` | 23 | Login: `demo`; Password: `demo` |
24 | 24 | ||
25 | 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): | ||
26 | ``` | ||
27 | MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P) | ||
28 | docker run -ti --rm \ | ||
29 | -p 8000:80 \ | ||
30 | -v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \ | ||
31 | shaarli/shaarli | ||
32 | ``` | ||
33 | |||
34 | A brief guide on getting starting using docker is given in [Docker 101](docker/docker-101). | ||
35 | To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](Upgrade-and-migration) documentation. | ||
36 | |||
37 | ## Features | 25 | ## Features |
38 | 26 | ||
27 | Shaarli can be used: | ||
28 | |||
29 | - to share, comment and save interesting links and news. | ||
30 | - to bookmark useful/frequent personal links (as private links) and share them between computers. | ||
31 | - as a minimal blog/microblog/writing platform (no character limit). | ||
32 | - as a read-it-later list (for example items tagged `readlater`). | ||
33 | - to draft and save articles/posts/ideas. | ||
34 | - to keep code snippets. | ||
35 | - to keep notes and documentation. | ||
36 | - as a shared clipboard/notepad/pastebin between machines. | ||
37 | - as a todo list. | ||
38 | - to store playlists (e.g. with the `music` or `video` tags). | ||
39 | - to keep extracts/comments from webpages that may disappear. | ||
40 | - to keep track of ongoing discussions (for example items tagged `discussion`). | ||
41 | - [to feed RSS aggregators](http://shaarli.chassegnouf.net/?9Efeiw) (planets) with specific tags. | ||
42 | - to feed other social networks, blogs... using RSS feeds and external services (dlvr.it, ifttt.com ...). | ||
43 | |||
39 | ### Interface | 44 | ### Interface |
40 | - minimalist design (simple is beautiful) | 45 | - minimalist design (simple is beautiful) |
41 | - FAST | 46 | - FAST |
@@ -89,14 +94,12 @@ Easily extensible by any client using the REST API exposed by Shaarli. | |||
89 | 94 | ||
90 | See the [API documentation](http://shaarli.github.io/api-documentation/). | 95 | See the [API documentation](http://shaarli.github.io/api-documentation/). |
91 | 96 | ||
92 | ### Other usages | 97 | ### Using Shaarli as a blog, notepad, pastebin... |
93 | Though Shaarli is primarily a bookmarking application, it can serve other purposes | 98 | - Go to your Shaarli setup and log in |
94 | (see [Features](Features)): | 99 | - Click the `Add Link` button |
95 | 100 | - To share text only, do not enter any URL in the corresponding input field and click `Add Link` | |
96 | - micro-blogging | 101 | - Pick a title and enter your article, or note, in the description field; add a few tags; optionally check `Private` then click `Save` |
97 | - pastebin | 102 | - Voilà ! Your article is now published (privately if you selected that option) and accessible using its permalink. |
98 | - online notepad | ||
99 | - snippet archive | ||
100 | 103 | ||
101 | ## About | 104 | ## About |
102 | ### Shaarli community fork | 105 | ### Shaarli community fork |
diff --git a/docker/alpine/Dockerfile.armhf.latest b/docker/alpine/Dockerfile.armhf.latest new file mode 100644 index 00000000..c923834a --- /dev/null +++ b/docker/alpine/Dockerfile.armhf.latest | |||
@@ -0,0 +1,47 @@ | |||
1 | FROM lsiobase/alpine.armhf:3.6 | ||
2 | MAINTAINER Shaarli Community | ||
3 | |||
4 | RUN apk --update --no-cache add \ | ||
5 | ca-certificates \ | ||
6 | curl \ | ||
7 | nginx \ | ||
8 | php7 \ | ||
9 | php7-ctype \ | ||
10 | php7-curl \ | ||
11 | php7-fpm \ | ||
12 | php7-gd \ | ||
13 | php7-iconv \ | ||
14 | php7-intl \ | ||
15 | php7-json \ | ||
16 | php7-mbstring \ | ||
17 | php7-openssl \ | ||
18 | php7-phar \ | ||
19 | php7-session \ | ||
20 | php7-xml \ | ||
21 | php7-zlib \ | ||
22 | s6 | ||
23 | |||
24 | COPY nginx.conf /etc/nginx/nginx.conf | ||
25 | COPY php-fpm.conf /etc/php7/php-fpm.conf | ||
26 | COPY services.d /etc/services.d | ||
27 | |||
28 | RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \ | ||
29 | && rm -rf /etc/php7/php-fpm.d/www.conf \ | ||
30 | && sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \ | ||
31 | && sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini | ||
32 | |||
33 | |||
34 | WORKDIR /var/www | ||
35 | RUN curl -L https://github.com/shaarli/Shaarli/archive/latest.tar.gz | tar xzf - \ | ||
36 | && mv Shaarli-latest shaarli \ | ||
37 | && cd shaarli \ | ||
38 | && composer --prefer-dist --no-dev install \ | ||
39 | && rm -rf ~/.composer \ | ||
40 | && chown -R nginx:nginx . | ||
41 | |||
42 | VOLUME /var/www/shaarli/data | ||
43 | |||
44 | EXPOSE 80 | ||
45 | |||
46 | ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"] | ||
47 | CMD [] | ||
diff --git a/docker/alpine/Dockerfile.armhf.master b/docker/alpine/Dockerfile.armhf.master new file mode 100644 index 00000000..7f1bdf85 --- /dev/null +++ b/docker/alpine/Dockerfile.armhf.master | |||
@@ -0,0 +1,47 @@ | |||
1 | FROM lsiobase/alpine.armhf:3.6 | ||
2 | MAINTAINER Shaarli Community | ||
3 | |||
4 | RUN apk --update --no-cache add \ | ||
5 | ca-certificates \ | ||
6 | curl \ | ||
7 | nginx \ | ||
8 | php7 \ | ||
9 | php7-ctype \ | ||
10 | php7-curl \ | ||
11 | php7-fpm \ | ||
12 | php7-gd \ | ||
13 | php7-iconv \ | ||
14 | php7-intl \ | ||
15 | php7-json \ | ||
16 | php7-mbstring \ | ||
17 | php7-openssl \ | ||
18 | php7-phar \ | ||
19 | php7-session \ | ||
20 | php7-xml \ | ||
21 | php7-zlib \ | ||
22 | s6 | ||
23 | |||
24 | COPY nginx.conf /etc/nginx/nginx.conf | ||
25 | COPY php-fpm.conf /etc/php7/php-fpm.conf | ||
26 | COPY services.d /etc/services.d | ||
27 | |||
28 | RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \ | ||
29 | && rm -rf /etc/php7/php-fpm.d/www.conf \ | ||
30 | && sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \ | ||
31 | && sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini | ||
32 | |||
33 | |||
34 | WORKDIR /var/www | ||
35 | RUN curl -L https://github.com/shaarli/Shaarli/archive/master.tar.gz | tar xzf - \ | ||
36 | && mv Shaarli-master shaarli \ | ||
37 | && cd shaarli \ | ||
38 | && composer --prefer-dist --no-dev install \ | ||
39 | && rm -rf ~/.composer \ | ||
40 | && chown -R nginx:nginx . | ||
41 | |||
42 | VOLUME /var/www/shaarli/data | ||
43 | |||
44 | EXPOSE 80 | ||
45 | |||
46 | ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"] | ||
47 | CMD [] | ||
diff --git a/docker/alpine/Dockerfile.latest b/docker/alpine/Dockerfile.latest new file mode 100644 index 00000000..dd4a173c --- /dev/null +++ b/docker/alpine/Dockerfile.latest | |||
@@ -0,0 +1,47 @@ | |||
1 | FROM alpine:3.6 | ||
2 | MAINTAINER Shaarli Community | ||
3 | |||
4 | RUN apk --update --no-cache add \ | ||
5 | ca-certificates \ | ||
6 | curl \ | ||
7 | nginx \ | ||
8 | php7 \ | ||
9 | php7-ctype \ | ||
10 | php7-curl \ | ||
11 | php7-fpm \ | ||
12 | php7-gd \ | ||
13 | php7-iconv \ | ||
14 | php7-intl \ | ||
15 | php7-json \ | ||
16 | php7-mbstring \ | ||
17 | php7-openssl \ | ||
18 | php7-phar \ | ||
19 | php7-session \ | ||
20 | php7-xml \ | ||
21 | php7-zlib \ | ||
22 | s6 | ||
23 | |||
24 | COPY nginx.conf /etc/nginx/nginx.conf | ||
25 | COPY php-fpm.conf /etc/php7/php-fpm.conf | ||
26 | COPY services.d /etc/services.d | ||
27 | |||
28 | RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \ | ||
29 | && rm -rf /etc/php7/php-fpm.d/www.conf \ | ||
30 | && sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \ | ||
31 | && sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini | ||
32 | |||
33 | |||
34 | WORKDIR /var/www | ||
35 | RUN curl -L https://github.com/shaarli/Shaarli/archive/latest.tar.gz | tar xzf - \ | ||
36 | && mv Shaarli-latest shaarli \ | ||
37 | && cd shaarli \ | ||
38 | && composer --prefer-dist --no-dev install \ | ||
39 | && rm -rf ~/.composer \ | ||
40 | && chown -R nginx:nginx . | ||
41 | |||
42 | VOLUME /var/www/shaarli/data | ||
43 | |||
44 | EXPOSE 80 | ||
45 | |||
46 | ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"] | ||
47 | CMD [] | ||
diff --git a/docker/alpine/Dockerfile.master b/docker/alpine/Dockerfile.master new file mode 100644 index 00000000..58f7c6e7 --- /dev/null +++ b/docker/alpine/Dockerfile.master | |||
@@ -0,0 +1,47 @@ | |||
1 | FROM alpine:3.6 | ||
2 | MAINTAINER Shaarli Community | ||
3 | |||
4 | RUN apk --update --no-cache add \ | ||
5 | ca-certificates \ | ||
6 | curl \ | ||
7 | nginx \ | ||
8 | php7 \ | ||
9 | php7-ctype \ | ||
10 | php7-curl \ | ||
11 | php7-fpm \ | ||
12 | php7-gd \ | ||
13 | php7-iconv \ | ||
14 | php7-intl \ | ||
15 | php7-json \ | ||
16 | php7-mbstring \ | ||
17 | php7-openssl \ | ||
18 | php7-phar \ | ||
19 | php7-session \ | ||
20 | php7-xml \ | ||
21 | php7-zlib \ | ||
22 | s6 | ||
23 | |||
24 | COPY nginx.conf /etc/nginx/nginx.conf | ||
25 | COPY php-fpm.conf /etc/php7/php-fpm.conf | ||
26 | COPY services.d /etc/services.d | ||
27 | |||
28 | RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \ | ||
29 | && rm -rf /etc/php7/php-fpm.d/www.conf \ | ||
30 | && sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \ | ||
31 | && sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini | ||
32 | |||
33 | |||
34 | WORKDIR /var/www | ||
35 | RUN curl -L https://github.com/shaarli/Shaarli/archive/master.tar.gz | tar xzf - \ | ||
36 | && mv Shaarli-master shaarli \ | ||
37 | && cd shaarli \ | ||
38 | && composer --prefer-dist --no-dev install \ | ||
39 | && rm -rf ~/.composer \ | ||
40 | && chown -R nginx:nginx . | ||
41 | |||
42 | VOLUME /var/www/shaarli/data | ||
43 | |||
44 | EXPOSE 80 | ||
45 | |||
46 | ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"] | ||
47 | CMD [] | ||
diff --git a/docker/alpine/IMAGE.md b/docker/alpine/IMAGE.md new file mode 100644 index 00000000..a8952257 --- /dev/null +++ b/docker/alpine/IMAGE.md | |||
@@ -0,0 +1,10 @@ | |||
1 | ## Alpine images | ||
2 | - [Alpine Linux](https://www.alpinelinux.org/) | ||
3 | - [PHP-FPM](http://php-fpm.org/) | ||
4 | - [Nginx](http://nginx.org/) | ||
5 | |||
6 | ### `shaarli/shaarli:latest` | ||
7 | - [Shaarli](https://github.com/shaarli/Shaarli), `latest` branch | ||
8 | |||
9 | ### `shaarli/shaarli:master` | ||
10 | - [Shaarli](https://github.com/shaarli/Shaarli), `master` branch | ||
diff --git a/docker/production/stable/nginx.conf b/docker/alpine/nginx.conf index e8754d9b..07fba33f 100644 --- a/docker/production/stable/nginx.conf +++ b/docker/alpine/nginx.conf | |||
@@ -1,6 +1,7 @@ | |||
1 | user www-data www-data; | 1 | user nginx nginx; |
2 | daemon off; | 2 | daemon off; |
3 | worker_processes 4; | 3 | worker_processes 4; |
4 | pid /var/run/nginx.pid; | ||
4 | 5 | ||
5 | events { | 6 | events { |
6 | worker_connections 768; | 7 | worker_connections 768; |
@@ -59,7 +60,7 @@ http { | |||
59 | fastcgi_split_path_info ^(.+\.php)(/.+)$; | 60 | fastcgi_split_path_info ^(.+\.php)(/.+)$; |
60 | 61 | ||
61 | # filter and proxy PHP requests to PHP-FPM | 62 | # filter and proxy PHP requests to PHP-FPM |
62 | fastcgi_pass unix:/var/run/php5-fpm.sock; | 63 | fastcgi_pass unix:/var/run/php-fpm.sock; |
63 | fastcgi_index index.php; | 64 | fastcgi_index index.php; |
64 | include fastcgi.conf; | 65 | include fastcgi.conf; |
65 | } | 66 | } |
diff --git a/docker/alpine/php-fpm.conf b/docker/alpine/php-fpm.conf new file mode 100644 index 00000000..0843c164 --- /dev/null +++ b/docker/alpine/php-fpm.conf | |||
@@ -0,0 +1,16 @@ | |||
1 | [global] | ||
2 | daemonize = no | ||
3 | |||
4 | [www] | ||
5 | user = nginx | ||
6 | group = nginx | ||
7 | listen.owner = nginx | ||
8 | listen.group = nginx | ||
9 | catch_workers_output = yes | ||
10 | listen = /var/run/php-fpm.sock | ||
11 | pm = dynamic | ||
12 | pm.max_children = 20 | ||
13 | pm.start_servers = 1 | ||
14 | pm.min_spare_servers = 1 | ||
15 | pm.max_spare_servers = 3 | ||
16 | pm.max_requests = 2048 | ||
diff --git a/docker/alpine/services.d/.s6-svscan/finish b/docker/alpine/services.d/.s6-svscan/finish new file mode 100755 index 00000000..1dadeeaf --- /dev/null +++ b/docker/alpine/services.d/.s6-svscan/finish | |||
@@ -0,0 +1,2 @@ | |||
1 | #!/bin/sh | ||
2 | /bin/true | ||
diff --git a/docker/alpine/services.d/nginx/run b/docker/alpine/services.d/nginx/run new file mode 100755 index 00000000..21e7b0d6 --- /dev/null +++ b/docker/alpine/services.d/nginx/run | |||
@@ -0,0 +1,2 @@ | |||
1 | #!/bin/execlineb -P | ||
2 | nginx | ||
diff --git a/docker/alpine/services.d/php-fpm/run b/docker/alpine/services.d/php-fpm/run new file mode 100755 index 00000000..21dd0107 --- /dev/null +++ b/docker/alpine/services.d/php-fpm/run | |||
@@ -0,0 +1,2 @@ | |||
1 | #!/bin/execlineb -P | ||
2 | php-fpm7 -F | ||
diff --git a/docker/production/stable/Dockerfile b/docker/debian/Dockerfile.stable index fc9588b0..fc9588b0 100644 --- a/docker/production/stable/Dockerfile +++ b/docker/debian/Dockerfile.stable | |||
diff --git a/docker/production/stable/IMAGE.md b/docker/debian/IMAGE.md index d85b1d7a..d85b1d7a 100644 --- a/docker/production/stable/IMAGE.md +++ b/docker/debian/IMAGE.md | |||
diff --git a/docker/production/nginx.conf b/docker/debian/nginx.conf index e8754d9b..e8754d9b 100644 --- a/docker/production/nginx.conf +++ b/docker/debian/nginx.conf | |||
diff --git a/docker/production/stable/supervised.conf b/docker/debian/supervised.conf index 5acd9795..5acd9795 100644 --- a/docker/production/stable/supervised.conf +++ b/docker/debian/supervised.conf | |||
diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile deleted file mode 100644 index d0509115..00000000 --- a/docker/production/Dockerfile +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | FROM debian:jessie | ||
2 | MAINTAINER Shaarli Community | ||
3 | |||
4 | ENV TERM dumb | ||
5 | RUN apt-get update \ | ||
6 | && apt-get install --no-install-recommends -y \ | ||
7 | ca-certificates \ | ||
8 | curl \ | ||
9 | nginx-light \ | ||
10 | php5-curl \ | ||
11 | php5-fpm \ | ||
12 | php5-gd \ | ||
13 | php5-intl \ | ||
14 | supervisor \ | ||
15 | && apt-get clean | ||
16 | |||
17 | RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini | ||
18 | RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini | ||
19 | COPY nginx.conf /etc/nginx/nginx.conf | ||
20 | COPY supervised.conf /etc/supervisor/conf.d/supervised.conf | ||
21 | |||
22 | ADD https://getcomposer.org/composer.phar /usr/local/bin/composer | ||
23 | RUN chmod 755 /usr/local/bin/composer | ||
24 | |||
25 | WORKDIR /var/www | ||
26 | RUN curl -L https://github.com/shaarli/Shaarli/archive/master.tar.gz | tar xzf - \ | ||
27 | && mv Shaarli-master shaarli \ | ||
28 | && cd shaarli \ | ||
29 | && composer --prefer-dist --no-dev install | ||
30 | RUN rm -rf html \ | ||
31 | && chown -R www-data:www-data . | ||
32 | |||
33 | VOLUME /var/www/shaarli/data | ||
34 | |||
35 | EXPOSE 80 | ||
36 | |||
37 | CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"] | ||
diff --git a/docker/production/IMAGE.md b/docker/production/IMAGE.md deleted file mode 100644 index 6f827b35..00000000 --- a/docker/production/IMAGE.md +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | ## shaarli:latest | ||
2 | - [Debian 8 Jessie](https://hub.docker.com/_/debian/) | ||
3 | - [PHP5-FPM](http://php-fpm.org/) | ||
4 | - [Nginx](http://nginx.org/) | ||
5 | - [Shaarli](https://github.com/shaarli/Shaarli) | ||
diff --git a/docker/production/supervised.conf b/docker/production/supervised.conf deleted file mode 100644 index 5acd9795..00000000 --- a/docker/production/supervised.conf +++ /dev/null | |||
@@ -1,13 +0,0 @@ | |||
1 | [program:php5-fpm] | ||
2 | command=/usr/sbin/php5-fpm -F | ||
3 | priority=5 | ||
4 | autostart=true | ||
5 | autorestart=true | ||
6 | |||
7 | [program:nginx] | ||
8 | command=/usr/sbin/nginx | ||
9 | priority=10 | ||
10 | autostart=true | ||
11 | autorestart=true | ||
12 | stdout_events_enabled=true | ||
13 | stderr_events_enabled=true | ||
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po new file mode 100644 index 00000000..323c6111 --- /dev/null +++ b/inc/languages/fr/LC_MESSAGES/shaarli.po | |||
@@ -0,0 +1,1367 @@ | |||
1 | msgid "" | ||
2 | msgstr "" | ||
3 | "Project-Id-Version: Shaarli\n" | ||
4 | "POT-Creation-Date: 2017-11-11 10:59+0100\n" | ||
5 | "PO-Revision-Date: 2017-11-11 11:00+0100\n" | ||
6 | "Last-Translator: \n" | ||
7 | "Language-Team: Shaarli\n" | ||
8 | "Language: fr_FR\n" | ||
9 | "MIME-Version: 1.0\n" | ||
10 | "Content-Type: text/plain; charset=UTF-8\n" | ||
11 | "Content-Transfer-Encoding: 8bit\n" | ||
12 | "X-Generator: Poedit 2.0.4\n" | ||
13 | "X-Poedit-Basepath: ../../../..\n" | ||
14 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" | ||
15 | "X-Poedit-SourceCharset: UTF-8\n" | ||
16 | "X-Poedit-KeywordsList: t:1,2;t\n" | ||
17 | "X-Poedit-SearchPath-0: .\n" | ||
18 | |||
19 | #: application/ApplicationUtils.php:153 | ||
20 | #, php-format | ||
21 | msgid "" | ||
22 | "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " | ||
23 | "cannot run. Your PHP version has known security vulnerabilities and should " | ||
24 | "be updated as soon as possible." | ||
25 | msgstr "" | ||
26 | "Votre version de PHP est obsolète ! Shaarli nécessite au moins PHP %s, et ne " | ||
27 | "peut donc pas fonctionner. Votre version de PHP a des failles de sécurités " | ||
28 | "connues et devrait être mise à jour au plus tôt." | ||
29 | |||
30 | #: application/ApplicationUtils.php:183 application/ApplicationUtils.php:195 | ||
31 | msgid "directory is not readable" | ||
32 | msgstr "le répertoire n'est pas accessible en lecture" | ||
33 | |||
34 | #: application/ApplicationUtils.php:198 | ||
35 | msgid "directory is not writable" | ||
36 | msgstr "le répertoire n'est pas accessible en écriture" | ||
37 | |||
38 | #: application/ApplicationUtils.php:216 | ||
39 | msgid "file is not readable" | ||
40 | msgstr "le fichier n'est pas accessible en lecture" | ||
41 | |||
42 | #: application/ApplicationUtils.php:219 | ||
43 | msgid "file is not writable" | ||
44 | msgstr "le fichier n'est pas accessible en écriture" | ||
45 | |||
46 | #: application/Cache.php:16 | ||
47 | #, php-format | ||
48 | msgid "Cannot purge %s: no directory" | ||
49 | msgstr "Impossible de purger %s: le répertoire n'existe pas" | ||
50 | |||
51 | #: application/FeedBuilder.php:151 | ||
52 | msgid "Direct link" | ||
53 | msgstr "Liens directs" | ||
54 | |||
55 | #: application/FeedBuilder.php:153 | ||
56 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 | ||
57 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:178 | ||
58 | msgid "Permalink" | ||
59 | msgstr "Permalien" | ||
60 | |||
61 | #: application/History.php:174 | ||
62 | msgid "History file isn't readable or writable" | ||
63 | msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture" | ||
64 | |||
65 | #: application/History.php:185 | ||
66 | msgid "Could not parse history file" | ||
67 | msgstr "Format incorrect pour le fichier d'historique" | ||
68 | |||
69 | #: application/Languages.php:159 | ||
70 | msgid "Automatic" | ||
71 | msgstr "Automatique" | ||
72 | |||
73 | #: application/Languages.php:160 | ||
74 | msgid "English" | ||
75 | msgstr "Anglais" | ||
76 | |||
77 | #: application/Languages.php:161 | ||
78 | msgid "French" | ||
79 | msgstr "Français" | ||
80 | |||
81 | #: application/LinkDB.php:136 | ||
82 | msgid "You are not authorized to add a link." | ||
83 | msgstr "Vous n'êtes pas autorisé à ajouter un lien." | ||
84 | |||
85 | #: application/LinkDB.php:139 | ||
86 | msgid "Internal Error: A link should always have an id and URL." | ||
87 | msgstr "Erreur interne : un lien devrait toujours avoir un ID et une URL." | ||
88 | |||
89 | #: application/LinkDB.php:142 | ||
90 | msgid "You must specify an integer as a key." | ||
91 | msgstr "Vous devez utiliser un entier comme clé." | ||
92 | |||
93 | #: application/LinkDB.php:145 | ||
94 | msgid "Array offset and link ID must be equal." | ||
95 | msgstr "La clé du tableau et l'ID du lien doivent être égaux." | ||
96 | |||
97 | #: application/LinkDB.php:251 | ||
98 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
99 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | ||
100 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14 | ||
101 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48 | ||
102 | msgid "" | ||
103 | "The personal, minimalist, super-fast, database free, bookmarking service" | ||
104 | msgstr "" | ||
105 | "Le gestionnaire de marque-page personnel, minimaliste, et sans base de " | ||
106 | "données" | ||
107 | |||
108 | #: application/LinkDB.php:253 | ||
109 | msgid "" | ||
110 | "Welcome to Shaarli! This is your first public bookmark. To edit or delete " | ||
111 | "me, you must first login.\n" | ||
112 | "\n" | ||
113 | "To learn how to use Shaarli, consult the link \"Documentation\" at the " | ||
114 | "bottom of this page.\n" | ||
115 | "\n" | ||
116 | "You use the community supported version of the original Shaarli project, by " | ||
117 | "Sebastien Sauvage." | ||
118 | msgstr "" | ||
119 | "Bienvenue sur Shaarli ! Ceci est votre premier marque-page public. Pour me " | ||
120 | "modifier ou me supprimer, vous devez d'abord vous connecter.\n" | ||
121 | "\n" | ||
122 | "Pour apprendre comment utiliser Shaarli, consultez le lien « Documentation » " | ||
123 | "en bas de page.\n" | ||
124 | "\n" | ||
125 | "Vous utilisez la version supportée par la communauté du projet original " | ||
126 | "Shaarli, de Sébastien Sauvage." | ||
127 | |||
128 | #: application/LinkDB.php:267 | ||
129 | msgid "My secret stuff... - Pastebin.com" | ||
130 | msgstr "Mes trucs secrets... - Pastebin.com" | ||
131 | |||
132 | #: application/LinkDB.php:269 | ||
133 | msgid "Shhhh! I'm a private link only YOU can see. You can delete me too." | ||
134 | msgstr "" | ||
135 | "Pssst ! Je suis un lien privé que VOUS êtes le seul à voir. Vous pouvez me " | ||
136 | "supprimer aussi." | ||
137 | |||
138 | #: application/LinkFilter.php:452 | ||
139 | msgid "The link you are trying to reach does not exist or has been deleted." | ||
140 | msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé." | ||
141 | |||
142 | #: application/NetscapeBookmarkUtils.php:35 | ||
143 | msgid "Invalid export selection:" | ||
144 | msgstr "Sélection d'export invalide :" | ||
145 | |||
146 | #: application/NetscapeBookmarkUtils.php:81 | ||
147 | #, php-format | ||
148 | msgid "File %s (%d bytes) " | ||
149 | msgstr "Le fichier %s (%d octets) " | ||
150 | |||
151 | #: application/NetscapeBookmarkUtils.php:83 | ||
152 | msgid "has an unknown file format. Nothing was imported." | ||
153 | msgstr "a un format inconnu. Rien n'a été importé." | ||
154 | |||
155 | #: application/NetscapeBookmarkUtils.php:86 | ||
156 | #, php-format | ||
157 | msgid "" | ||
158 | "was successfully processed in %d seconds: %d links imported, %d links " | ||
159 | "overwritten, %d links skipped." | ||
160 | msgstr "" | ||
161 | "a été importé avec succès en %d secondes : %d liens importés, %d liens " | ||
162 | "écrasés, %d liens ignorés." | ||
163 | |||
164 | #: application/PageBuilder.php:167 | ||
165 | msgid "The page you are trying to reach does not exist or has been deleted." | ||
166 | msgstr "La page que vous essayez de consulter n'existe pas ou a été supprimée." | ||
167 | |||
168 | #: application/PageBuilder.php:169 | ||
169 | msgid "404 Not Found" | ||
170 | msgstr "404 Introuvable" | ||
171 | |||
172 | #: application/PluginManager.php:243 | ||
173 | #, php-format | ||
174 | msgid "Plugin \"%s\" files not found." | ||
175 | msgstr "Les fichiers de l'extension \"%s\" sont introuvables." | ||
176 | |||
177 | #: application/Updater.php:76 | ||
178 | msgid "Couldn't retrieve Updater class methods." | ||
179 | msgstr "Impossible de récupérer les méthodes de la classe Updater." | ||
180 | |||
181 | #: application/Updater.php:493 | ||
182 | msgid "An error occurred while running the update " | ||
183 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " | ||
184 | |||
185 | #: application/Updater.php:533 | ||
186 | msgid "Updates file path is not set, can't write updates." | ||
187 | msgstr "" | ||
188 | "Le chemin vers le fichier de mise à jour n'est pas défini, impossible " | ||
189 | "d'écrire les mises à jour." | ||
190 | |||
191 | #: application/Updater.php:538 | ||
192 | msgid "Unable to write updates in " | ||
193 | msgstr "Impossible d'écrire les mises à jour dans " | ||
194 | |||
195 | #: application/Utils.php:376 tests/UtilsTest.php:340 | ||
196 | msgid "Setting not set" | ||
197 | msgstr "Paramètre non défini" | ||
198 | |||
199 | #: application/Utils.php:383 tests/UtilsTest.php:338 tests/UtilsTest.php:339 | ||
200 | msgid "Unlimited" | ||
201 | msgstr "Illimité" | ||
202 | |||
203 | #: application/Utils.php:386 tests/UtilsTest.php:335 tests/UtilsTest.php:336 | ||
204 | #: tests/UtilsTest.php:350 | ||
205 | msgid "B" | ||
206 | msgstr "o" | ||
207 | |||
208 | #: application/Utils.php:386 tests/UtilsTest.php:329 tests/UtilsTest.php:330 | ||
209 | #: tests/UtilsTest.php:337 | ||
210 | msgid "kiB" | ||
211 | msgstr "ko" | ||
212 | |||
213 | #: application/Utils.php:386 tests/UtilsTest.php:331 tests/UtilsTest.php:332 | ||
214 | #: tests/UtilsTest.php:348 tests/UtilsTest.php:349 | ||
215 | msgid "MiB" | ||
216 | msgstr "Mo" | ||
217 | |||
218 | #: application/Utils.php:386 tests/UtilsTest.php:333 tests/UtilsTest.php:334 | ||
219 | msgid "GiB" | ||
220 | msgstr "Go" | ||
221 | |||
222 | #: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:121 | ||
223 | msgid "" | ||
224 | "Shaarli could not create the config file. Please make sure Shaarli has the " | ||
225 | "right to write in the folder is it installed in." | ||
226 | msgstr "" | ||
227 | "Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que " | ||
228 | "Shaarli a les droits d'écriture dans le dossier dans lequel il est installé." | ||
229 | |||
230 | #: application/config/ConfigManager.php:135 | ||
231 | msgid "Invalid setting key parameter. String expected, got: " | ||
232 | msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : " | ||
233 | |||
234 | #: application/config/exception/MissingFieldConfigException.php:21 | ||
235 | #, php-format | ||
236 | msgid "Configuration value is required for %s" | ||
237 | msgstr "Le paramètre %s est obligatoire" | ||
238 | |||
239 | #: application/config/exception/PluginConfigOrderException.php:15 | ||
240 | msgid "An error occurred while trying to save plugins loading order." | ||
241 | msgstr "" | ||
242 | "Une erreur s'est produite lors de la sauvegarde de l'ordre des extensions." | ||
243 | |||
244 | #: application/config/exception/UnauthorizedConfigException.php:16 | ||
245 | msgid "You are not authorized to alter config." | ||
246 | msgstr "Vous n'êtes pas autorisé à modifier la configuration." | ||
247 | |||
248 | #: application/exceptions/IOException.php:19 | ||
249 | msgid "Error accessing" | ||
250 | msgstr "Une erreur s'est produite en accédant à " | ||
251 | |||
252 | #: index.php:135 | ||
253 | msgid "Shared links on " | ||
254 | msgstr "Liens partagés sur " | ||
255 | |||
256 | #: index.php:157 | ||
257 | msgid "Insufficient permissions:" | ||
258 | msgstr "Permissions insuffisantes :" | ||
259 | |||
260 | #: index.php:384 | ||
261 | msgid "I said: NO. You are banned for the moment. Go away." | ||
262 | msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard." | ||
263 | |||
264 | #: index.php:449 | ||
265 | msgid "Wrong login/password." | ||
266 | msgstr "Nom d'utilisateur ou mot de passe incorrects." | ||
267 | |||
268 | #: index.php:1092 | ||
269 | msgid "You are not supposed to change a password on an Open Shaarli." | ||
270 | msgstr "" | ||
271 | "Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert." | ||
272 | |||
273 | #: index.php:1097 index.php:1138 index.php:1214 index.php:1244 index.php:1344 | ||
274 | msgid "Wrong token." | ||
275 | msgstr "Jeton invalide." | ||
276 | |||
277 | #: index.php:1102 | ||
278 | msgid "The old password is not correct." | ||
279 | msgstr "L'ancien mot de passe est incorrect." | ||
280 | |||
281 | #: index.php:1122 | ||
282 | msgid "Your password has been changed" | ||
283 | msgstr "Votre mot de passe a été modifié" | ||
284 | |||
285 | #: index.php:1175 | ||
286 | msgid "Configuration was saved." | ||
287 | msgstr "La configuration a été sauvegardé." | ||
288 | |||
289 | #: index.php:1226 | ||
290 | #, php-format | ||
291 | msgid "The tag was removed from %d link." | ||
292 | msgid_plural "The tag was removed from %d links." | ||
293 | msgstr[0] "Le tag a été supprimé de %d lien." | ||
294 | msgstr[1] "Le tag a été supprimé de %d liens." | ||
295 | |||
296 | #: index.php:1227 | ||
297 | #, php-format | ||
298 | msgid "The tag was renamed in %d link." | ||
299 | msgid_plural "The tag was renamed in %d links." | ||
300 | msgstr[0] "Le tag a été renommé dans %d lien." | ||
301 | msgstr[1] "Le tag a été renommé dans %d liens." | ||
302 | |||
303 | #: index.php:1443 | ||
304 | msgid "Note: " | ||
305 | msgstr "Note : " | ||
306 | |||
307 | #: index.php:1552 | ||
308 | #, php-format | ||
309 | msgid "" | ||
310 | "The file you are trying to upload is probably bigger than what this " | ||
311 | "webserver can accept (%s). Please upload in smaller chunks." | ||
312 | msgstr "" | ||
313 | "Le fichier que vous essayer d'envoyer est probablement plus lourd que ce que " | ||
314 | "le serveur web peut accepter (%s). Merci de l'envoyer en parties plus " | ||
315 | "légères." | ||
316 | |||
317 | #: index.php:1972 | ||
318 | #, php-format | ||
319 | msgid "" | ||
320 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " | ||
321 | "variable \"session.save_path\" is set correctly in your PHP config, and that " | ||
322 | "you have write access to it.<br>It currently points to %s.<br>On some " | ||
323 | "browsers, accessing your server via a hostname like 'localhost' or any " | ||
324 | "custom hostname without a dot causes cookie storage to fail. We recommend " | ||
325 | "accessing your server via it's IP address or Fully Qualified Domain Name.<br>" | ||
326 | msgstr "" | ||
327 | "<pre>Les sesssions ne semble pas fonctionner sur ce serveur.<br>Assurez vous " | ||
328 | "que la variable « session.save_path » est correctement définie dans votre " | ||
329 | "fichier de configuration PHP, et que vous y avez les droits d'écriture." | ||
330 | "<br>Ce paramètre pointe actuellement sur %s.<br>Sur certains navigateurs, " | ||
331 | "accéder à votre serveur depuis un nom d'hôte comme « localhost » ou autre " | ||
332 | "nom personnalisé sans point '.' entraine l'échec de la sauvegarde des " | ||
333 | "cookies. Nous vous recommandons d'accéder à votre serveur depuis son adresse " | ||
334 | "IP ou un <em>Fully Qualified Domain Name</em>.<br>" | ||
335 | |||
336 | #: index.php:1982 | ||
337 | msgid "Click to try again." | ||
338 | msgstr "Cliquer ici pour réessayer." | ||
339 | |||
340 | #: plugins/addlink_toolbar/addlink_toolbar.php:29 | ||
341 | msgid "URI" | ||
342 | msgstr "URI" | ||
343 | |||
344 | #: plugins/addlink_toolbar/addlink_toolbar.php:33 | ||
345 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
346 | msgid "Add link" | ||
347 | msgstr "Shaare" | ||
348 | |||
349 | #: plugins/addlink_toolbar/addlink_toolbar.php:50 | ||
350 | msgid "Adds the addlink input on the linklist page." | ||
351 | msgstr "Ajout le formulaire d'ajout de liens sur la page principale." | ||
352 | |||
353 | #: plugins/archiveorg/archiveorg.php:23 | ||
354 | msgid "View on archive.org" | ||
355 | msgstr "Voir sur archive.org" | ||
356 | |||
357 | #: plugins/archiveorg/archiveorg.php:36 | ||
358 | msgid "For each link, add an Archive.org icon." | ||
359 | msgstr "Pour chaque lien, ajoute une icône pour Archive.org." | ||
360 | |||
361 | #: plugins/demo_plugin/demo_plugin.php:469 | ||
362 | msgid "" | ||
363 | "A demo plugin covering all use cases for template designers and plugin " | ||
364 | "developers." | ||
365 | msgstr "" | ||
366 | "Une extension de démonstration couvrant tous les cas d'utilisation pour les " | ||
367 | "designers et les développeurs." | ||
368 | |||
369 | #: plugins/isso/isso.php:20 | ||
370 | msgid "" | ||
371 | "Isso plugin error: Please define the \"ISSO_SERVER\" setting in the plugin " | ||
372 | "administration page." | ||
373 | msgstr "" | ||
374 | "Erreur de l'extension Isso : Merci de définir le paramètre « ISSO_SERVER » " | ||
375 | "dans la page d'administration des extensions." | ||
376 | |||
377 | #: plugins/isso/isso.php:63 | ||
378 | msgid "Let visitor comment your shaares on permalinks with Isso." | ||
379 | msgstr "" | ||
380 | "Permet aux visiteurs de commenter vos shaares sur les permaliens avec Isso." | ||
381 | |||
382 | #: plugins/isso/isso.php:64 | ||
383 | msgid "Isso server URL (without 'http://')" | ||
384 | msgstr "URL du serveur Isso (sans 'http://')" | ||
385 | |||
386 | #: plugins/markdown/markdown.php:159 | ||
387 | msgid "Description will be rendered with" | ||
388 | msgstr "La description sera générée avec" | ||
389 | |||
390 | #: plugins/markdown/markdown.php:160 | ||
391 | msgid "Markdown syntax documentation" | ||
392 | msgstr "Documentation sur la syntaxe Markdown" | ||
393 | |||
394 | #: plugins/markdown/markdown.php:161 | ||
395 | msgid "Markdown syntax" | ||
396 | msgstr "la syntaxe Markdown" | ||
397 | |||
398 | #: plugins/markdown/markdown.php:340 | ||
399 | msgid "" | ||
400 | "Render shaare description with Markdown syntax.<br><strong>Warning</" | ||
401 | "strong>:\n" | ||
402 | "If your shaared descriptions contained HTML tags before enabling the " | ||
403 | "markdown plugin,\n" | ||
404 | "enabling it might break your page.\n" | ||
405 | "See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
406 | "markdown#html-rendering\">README</a>." | ||
407 | msgstr "" | ||
408 | "Utilise la syntaxe Markdown pour la description des liens." | ||
409 | "<br><strong>Attention</strong> :\n" | ||
410 | "Si vous aviez des descriptions contenant du HTML avant d'activer cette " | ||
411 | "extension,\n" | ||
412 | "l'activer pourrait déformer vos pages.\n" | ||
413 | "Voir le <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
414 | "markdown#html-rendering\">README</a>." | ||
415 | |||
416 | #: plugins/piwik/piwik.php:21 | ||
417 | msgid "" | ||
418 | "Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin " | ||
419 | "administration page." | ||
420 | msgstr "" | ||
421 | "Erreur de l'extension Piwik : Merci de définir les paramètres PIWIK_URL et " | ||
422 | "PIWIK_SITEID dans la page d'administration des extensions." | ||
423 | |||
424 | #: plugins/piwik/piwik.php:70 | ||
425 | msgid "A plugin that adds Piwik tracking code to Shaarli pages." | ||
426 | msgstr "Ajoute le code de traçage de Piwik sur les pages de Shaarli." | ||
427 | |||
428 | #: plugins/piwik/piwik.php:71 | ||
429 | msgid "Piwik URL" | ||
430 | msgstr "URL de Piwik" | ||
431 | |||
432 | #: plugins/piwik/piwik.php:72 | ||
433 | msgid "Piwik site ID" | ||
434 | msgstr "Site ID de Piwik" | ||
435 | |||
436 | #: plugins/playvideos/playvideos.php:22 | ||
437 | msgid "Video player" | ||
438 | msgstr "Lecteur vidéo" | ||
439 | |||
440 | #: plugins/playvideos/playvideos.php:25 | ||
441 | msgid "Play Videos" | ||
442 | msgstr "Jouer les vidéos" | ||
443 | |||
444 | #: plugins/playvideos/playvideos.php:56 | ||
445 | msgid "Add a button in the toolbar allowing to watch all videos." | ||
446 | msgstr "" | ||
447 | "Ajoute un bouton dans la barre de menu pour regarder toutes les vidéos." | ||
448 | |||
449 | #: plugins/playvideos/youtube_playlist.js:214 | ||
450 | msgid "plugins/playvideos/jquery-1.11.2.min.js" | ||
451 | msgstr "" | ||
452 | |||
453 | #: plugins/pubsubhubbub/pubsubhubbub.php:69 | ||
454 | #, php-format | ||
455 | msgid "Could not publish to PubSubHubbub: %s" | ||
456 | msgstr "Impossible de publier vers PubSubHubbub : %s" | ||
457 | |||
458 | #: plugins/pubsubhubbub/pubsubhubbub.php:95 | ||
459 | #, php-format | ||
460 | msgid "Could not post to %s" | ||
461 | msgstr "Impossible de publier vers %s" | ||
462 | |||
463 | #: plugins/pubsubhubbub/pubsubhubbub.php:99 | ||
464 | #, php-format | ||
465 | msgid "Bad response from the hub %s" | ||
466 | msgstr "Mauvaise réponse du hub %s" | ||
467 | |||
468 | #: plugins/pubsubhubbub/pubsubhubbub.php:110 | ||
469 | msgid "Enable PubSubHubbub feed publishing." | ||
470 | msgstr "Active la publication de flux vers PubSubHubbub." | ||
471 | |||
472 | #: plugins/qrcode/qrcode.php:69 plugins/wallabag/wallabag.php:68 | ||
473 | msgid "For each link, add a QRCode icon." | ||
474 | msgstr "Pour chaque liens, ajouter une icône de QRCode." | ||
475 | |||
476 | #: plugins/wallabag/wallabag.php:21 | ||
477 | msgid "" | ||
478 | "Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the " | ||
479 | "plugin administration page." | ||
480 | msgstr "" | ||
481 | "Erreur de l'extension Wallabag : Merci de définir le paramètre « " | ||
482 | "WALLABAG_URL » dans la page d'administration des extensions." | ||
483 | |||
484 | #: plugins/wallabag/wallabag.php:47 | ||
485 | msgid "Save to wallabag" | ||
486 | msgstr "Sauvegarder dans Wallabag" | ||
487 | |||
488 | #: plugins/wallabag/wallabag.php:69 | ||
489 | msgid "Wallabag API URL" | ||
490 | msgstr "URL de l'API Wallabag" | ||
491 | |||
492 | #: plugins/wallabag/wallabag.php:70 | ||
493 | msgid "Wallabag API version (1 or 2)" | ||
494 | msgstr "Version de l'API Wallabag (1 ou 2)" | ||
495 | |||
496 | #: tests/LanguagesTest.php:188 tests/LanguagesTest.php:201 | ||
497 | #: tests/languages/fr/LanguagesFrTest.php:160 | ||
498 | #: tests/languages/fr/LanguagesFrTest.php:173 | ||
499 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81 | ||
500 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:81 | ||
501 | msgid "Search" | ||
502 | msgid_plural "Search" | ||
503 | msgstr[0] "Rechercher" | ||
504 | msgstr[1] "Rechercher" | ||
505 | |||
506 | #: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12 | ||
507 | msgid "Sorry, nothing to see here." | ||
508 | msgstr "Désolé, il y a rien à voir ici." | ||
509 | |||
510 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
511 | msgid "Shaare a new link" | ||
512 | msgstr "Partager un nouveau lien" | ||
513 | |||
514 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
515 | msgid "URL or leave empty to post a note" | ||
516 | msgstr "URL ou laisser vide pour créer une note" | ||
517 | |||
518 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
519 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
520 | msgid "Change password" | ||
521 | msgstr "Modification du mot de passe" | ||
522 | |||
523 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
524 | msgid "Current password" | ||
525 | msgstr "Mot de passe actuel" | ||
526 | |||
527 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
528 | msgid "New password" | ||
529 | msgstr "Nouveau mot de passe" | ||
530 | |||
531 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | ||
532 | msgid "Change" | ||
533 | msgstr "Changer" | ||
534 | |||
535 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
536 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
537 | msgid "Manage tags" | ||
538 | msgstr "Gérer les tags" | ||
539 | |||
540 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
541 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | ||
542 | msgid "Tag" | ||
543 | msgstr "Tag" | ||
544 | |||
545 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | ||
546 | msgid "New name" | ||
547 | msgstr "Nouveau nom" | ||
548 | |||
549 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | ||
550 | msgid "Case sensitive" | ||
551 | msgstr "Sensible à la casse" | ||
552 | |||
553 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | ||
554 | msgid "Rename" | ||
555 | msgstr "Renommer" | ||
556 | |||
557 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | ||
558 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79 | ||
559 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:172 | ||
560 | msgid "Delete" | ||
561 | msgstr "Supprimer" | ||
562 | |||
563 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39 | ||
564 | msgid "You can also edit tags in the" | ||
565 | msgstr "Vous pouvez aussi modifier les tags dans la" | ||
566 | |||
567 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39 | ||
568 | msgid "tag list" | ||
569 | msgstr "liste des tags" | ||
570 | |||
571 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | ||
572 | msgid "Configure" | ||
573 | msgstr "Configurer" | ||
574 | |||
575 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
576 | msgid "title" | ||
577 | msgstr "titre" | ||
578 | |||
579 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | ||
580 | msgid "Home link" | ||
581 | msgstr "Lien vers l'accueil" | ||
582 | |||
583 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | ||
584 | msgid "Default value" | ||
585 | msgstr "Valeur par défaut" | ||
586 | |||
587 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 | ||
588 | msgid "Theme" | ||
589 | msgstr "Thème" | ||
590 | |||
591 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87 | ||
592 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78 | ||
593 | msgid "Language" | ||
594 | msgstr "Langue" | ||
595 | |||
596 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116 | ||
597 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 | ||
598 | msgid "Timezone" | ||
599 | msgstr "Fuseau horaire" | ||
600 | |||
601 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | ||
602 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 | ||
603 | msgid "Continent" | ||
604 | msgstr "Continent" | ||
605 | |||
606 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | ||
607 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 | ||
608 | msgid "City" | ||
609 | msgstr "Ville" | ||
610 | |||
611 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164 | ||
612 | msgid "Disable session cookie hijacking protection" | ||
613 | msgstr "Désactiver la protection contre le détournement de cookies" | ||
614 | |||
615 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166 | ||
616 | msgid "Check this if you get disconnected or if your IP address changes often" | ||
617 | msgstr "" | ||
618 | "Cocher cette case si vous êtes souvent déconnecté ou si votre adresse IP " | ||
619 | "change souvent" | ||
620 | |||
621 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183 | ||
622 | msgid "Private links by default" | ||
623 | msgstr "Liens privés par défaut" | ||
624 | |||
625 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:184 | ||
626 | msgid "All new links are private by default" | ||
627 | msgstr "Tous les nouveaux liens sont privés par défaut" | ||
628 | |||
629 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 | ||
630 | msgid "RSS direct links" | ||
631 | msgstr "Liens directs dans le flux RSS" | ||
632 | |||
633 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:200 | ||
634 | msgid "Check this to use direct URL instead of permalink in feeds" | ||
635 | msgstr "" | ||
636 | "Cocher cette case pour utiliser des liens directs au lieu des permaliens " | ||
637 | "dans le flux RSS" | ||
638 | |||
639 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:215 | ||
640 | msgid "Hide public links" | ||
641 | msgstr "Cacher les liens publics" | ||
642 | |||
643 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:216 | ||
644 | msgid "Do not show any links if the user is not logged in" | ||
645 | msgstr "N'afficher aucun lien sans être connecté" | ||
646 | |||
647 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231 | ||
648 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150 | ||
649 | msgid "Check updates" | ||
650 | msgstr "Vérifier les mises à jour" | ||
651 | |||
652 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232 | ||
653 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152 | ||
654 | msgid "Notify me when a new release is ready" | ||
655 | msgstr "Me notifier lorsqu'une nouvelle version est disponible" | ||
656 | |||
657 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247 | ||
658 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | ||
659 | msgid "Enable REST API" | ||
660 | msgstr "Activer l'API REST" | ||
661 | |||
662 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248 | ||
663 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170 | ||
664 | msgid "Allow third party software to use Shaarli such as mobile application" | ||
665 | msgstr "" | ||
666 | "Permets aux applications tierces d'utiliser Shaarli, par exemple les " | ||
667 | "applications mobiles" | ||
668 | |||
669 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263 | ||
670 | msgid "API secret" | ||
671 | msgstr "Clé d'API secrète" | ||
672 | |||
673 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274 | ||
674 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | ||
675 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 | ||
676 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 | ||
677 | msgid "Save" | ||
678 | msgstr "Enregistrer" | ||
679 | |||
680 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
681 | msgid "The Daily Shaarli" | ||
682 | msgstr "Le Quotidien Shaarli" | ||
683 | |||
684 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17 | ||
685 | msgid "1 RSS entry per day" | ||
686 | msgstr "1 entrée RSS par jour" | ||
687 | |||
688 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37 | ||
689 | msgid "Previous day" | ||
690 | msgstr "Jour précédent" | ||
691 | |||
692 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | ||
693 | msgid "All links of one day in a single page." | ||
694 | msgstr "Tous les liens d'un jour sur une page." | ||
695 | |||
696 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51 | ||
697 | msgid "Next day" | ||
698 | msgstr "Jour suivant" | ||
699 | |||
700 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
701 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170 | ||
702 | msgid "Edit" | ||
703 | msgstr "Modifier" | ||
704 | |||
705 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
706 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | ||
707 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26 | ||
708 | msgid "Shaare" | ||
709 | msgstr "Shaare" | ||
710 | |||
711 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 | ||
712 | msgid "Created:" | ||
713 | msgstr "Création :" | ||
714 | |||
715 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
716 | msgid "URL" | ||
717 | msgstr "URL" | ||
718 | |||
719 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | ||
720 | msgid "Title" | ||
721 | msgstr "Titre" | ||
722 | |||
723 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40 | ||
724 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | ||
725 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 | ||
726 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 | ||
727 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124 | ||
728 | msgid "Description" | ||
729 | msgstr "Description" | ||
730 | |||
731 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
732 | msgid "Tags" | ||
733 | msgstr "Tags" | ||
734 | |||
735 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59 | ||
736 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
737 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168 | ||
738 | msgid "Private" | ||
739 | msgstr "Privé" | ||
740 | |||
741 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | ||
742 | msgid "Apply Changes" | ||
743 | msgstr "Appliquer les changements" | ||
744 | |||
745 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
746 | msgid "Export Database" | ||
747 | msgstr "Exporter les données" | ||
748 | |||
749 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | ||
750 | msgid "Selection" | ||
751 | msgstr "Choisir" | ||
752 | |||
753 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | ||
754 | msgid "All" | ||
755 | msgstr "Tous" | ||
756 | |||
757 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
758 | msgid "Public" | ||
759 | msgstr "Publics" | ||
760 | |||
761 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52 | ||
762 | msgid "Prepend note permalinks with this Shaarli instance's URL" | ||
763 | msgstr "Préfixer les liens de notes avec l'URL de l'instance de Shaarli" | ||
764 | |||
765 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53 | ||
766 | msgid "Useful to import bookmarks in a web browser" | ||
767 | msgstr "Utile pour importer les marques-pages dans un navigateur" | ||
768 | |||
769 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 | ||
770 | msgid "Export" | ||
771 | msgstr "Exporter" | ||
772 | |||
773 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
774 | msgid "Import Database" | ||
775 | msgstr "Importer des données" | ||
776 | |||
777 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | ||
778 | msgid "Maximum size allowed:" | ||
779 | msgstr "Taille maximum autorisée :" | ||
780 | |||
781 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
782 | msgid "Visibility" | ||
783 | msgstr "Visibilité" | ||
784 | |||
785 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
786 | msgid "Use values from the imported file, default to public" | ||
787 | msgstr "" | ||
788 | "Utiliser les valeurs présentes dans le fichier d'import, public par défaut" | ||
789 | |||
790 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
791 | msgid "Import all bookmarks as private" | ||
792 | msgstr "Importer tous les liens comme privés" | ||
793 | |||
794 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
795 | msgid "Import all bookmarks as public" | ||
796 | msgstr "Importer tous les liens comme publics" | ||
797 | |||
798 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57 | ||
799 | msgid "Overwrite existing bookmarks" | ||
800 | msgstr "Remplacer les liens existants" | ||
801 | |||
802 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 | ||
803 | msgid "Duplicates based on URL" | ||
804 | msgstr "Les doublons s'appuient sur les URL" | ||
805 | |||
806 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 | ||
807 | msgid "Add default tags" | ||
808 | msgstr "Ajouter des tags par défaut" | ||
809 | |||
810 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 | ||
811 | msgid "Import" | ||
812 | msgstr "Importer" | ||
813 | |||
814 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
815 | msgid "Install Shaarli" | ||
816 | msgstr "Installation de Shaarli" | ||
817 | |||
818 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 | ||
819 | msgid "It looks like it's the first time you run Shaarli. Please configure it." | ||
820 | msgstr "" | ||
821 | "Il semblerait que ça soit la première fois que vous lancez Shaarli. Merci de " | ||
822 | "le configurer." | ||
823 | |||
824 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33 | ||
825 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 | ||
826 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 | ||
827 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147 | ||
828 | msgid "Username" | ||
829 | msgstr "Nom d'utilisateur" | ||
830 | |||
831 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | ||
832 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | ||
833 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148 | ||
834 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:148 | ||
835 | msgid "Password" | ||
836 | msgstr "Mot de passe" | ||
837 | |||
838 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63 | ||
839 | msgid "Shaarli title" | ||
840 | msgstr "Titre du Shaarli" | ||
841 | |||
842 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69 | ||
843 | msgid "My links" | ||
844 | msgstr "Mes liens" | ||
845 | |||
846 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182 | ||
847 | msgid "Install" | ||
848 | msgstr "Installer" | ||
849 | |||
850 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
851 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80 | ||
852 | msgid "shaare" | ||
853 | msgid_plural "shaares" | ||
854 | msgstr[0] "shaare" | ||
855 | msgstr[1] "shaares" | ||
856 | |||
857 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 | ||
858 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84 | ||
859 | msgid "private link" | ||
860 | msgid_plural "private links" | ||
861 | msgstr[0] "lien privé" | ||
862 | msgstr[1] "liens privés" | ||
863 | |||
864 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | ||
865 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | ||
866 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:117 | ||
867 | msgid "Search text" | ||
868 | msgstr "Recherche texte" | ||
869 | |||
870 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38 | ||
871 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124 | ||
872 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:124 | ||
873 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33 | ||
874 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61 | ||
875 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33 | ||
876 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71 | ||
877 | msgid "Filter by tag" | ||
878 | msgstr "Filtrer par tag" | ||
879 | |||
880 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111 | ||
881 | msgid "Nothing found." | ||
882 | msgstr "Aucun résultat." | ||
883 | |||
884 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:119 | ||
885 | #, php-format | ||
886 | msgid "%s result" | ||
887 | msgid_plural "%s results" | ||
888 | msgstr[0] "%s résultat" | ||
889 | msgstr[1] "%s résultats" | ||
890 | |||
891 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123 | ||
892 | msgid "for" | ||
893 | msgstr "pour" | ||
894 | |||
895 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130 | ||
896 | msgid "tagged" | ||
897 | msgstr "taggé" | ||
898 | |||
899 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 | ||
900 | msgid "Remove tag" | ||
901 | msgstr "Retirer le tag" | ||
902 | |||
903 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143 | ||
904 | msgid "with status" | ||
905 | msgstr "avec le statut" | ||
906 | |||
907 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154 | ||
908 | msgid "without any tag" | ||
909 | msgstr "sans tag" | ||
910 | |||
911 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:174 | ||
912 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | ||
913 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42 | ||
914 | msgid "Fold" | ||
915 | msgstr "Replier" | ||
916 | |||
917 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176 | ||
918 | msgid "Edited: " | ||
919 | msgstr "Modifié : " | ||
920 | |||
921 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:180 | ||
922 | msgid "permalink" | ||
923 | msgstr "permalien" | ||
924 | |||
925 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182 | ||
926 | msgid "Add tag" | ||
927 | msgstr "Ajouter un tag" | ||
928 | |||
929 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7 | ||
930 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:7 | ||
931 | msgid "Filters" | ||
932 | msgstr "Filtres" | ||
933 | |||
934 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12 | ||
935 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:12 | ||
936 | msgid "Filter private links" | ||
937 | msgstr "Filtrer par liens privés" | ||
938 | |||
939 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 | ||
940 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:18 | ||
941 | msgid "Filter untagged links" | ||
942 | msgstr "Filtrer par liens privés" | ||
943 | |||
944 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
945 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | ||
946 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:22 | ||
947 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:74 | ||
948 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | ||
949 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43 | ||
950 | msgid "Fold all" | ||
951 | msgstr "Replier tout" | ||
952 | |||
953 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67 | ||
954 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:67 | ||
955 | msgid "Links per page" | ||
956 | msgstr "Liens par page" | ||
957 | |||
958 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
959 | msgid "" | ||
960 | "You have been banned after too many failed login attempts. Try again later." | ||
961 | msgstr "" | ||
962 | "Vous avez été banni après trop d'échec d'authentification. Merci de " | ||
963 | "réessayer plus tard." | ||
964 | |||
965 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
966 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | ||
967 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71 | ||
968 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:95 | ||
969 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:71 | ||
970 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:95 | ||
971 | msgid "Login" | ||
972 | msgstr "Connexion" | ||
973 | |||
974 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
975 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151 | ||
976 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:151 | ||
977 | msgid "Remember me" | ||
978 | msgstr "Rester connecté" | ||
979 | |||
980 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
981 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | ||
982 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14 | ||
983 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48 | ||
984 | msgid "by the Shaarli community" | ||
985 | msgstr "par la communauté Shaarli" | ||
986 | |||
987 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
988 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15 | ||
989 | msgid "Documentation" | ||
990 | msgstr "Documentation" | ||
991 | |||
992 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | ||
993 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44 | ||
994 | msgid "Expand" | ||
995 | msgstr "Déplier" | ||
996 | |||
997 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45 | ||
998 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45 | ||
999 | msgid "Expand all" | ||
1000 | msgstr "Déplier tout" | ||
1001 | |||
1002 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
1003 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46 | ||
1004 | msgid "Are you sure you want to delete this link?" | ||
1005 | msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?" | ||
1006 | |||
1007 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | ||
1008 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31 | ||
1009 | msgid "Tools" | ||
1010 | msgstr "Outils" | ||
1011 | |||
1012 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
1013 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36 | ||
1014 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1015 | msgid "Tag cloud" | ||
1016 | msgstr "Nuage de tags" | ||
1017 | |||
1018 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39 | ||
1019 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:39 | ||
1020 | msgid "Picture wall" | ||
1021 | msgstr "Mur d'images" | ||
1022 | |||
1023 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | ||
1024 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:42 | ||
1025 | msgid "Daily" | ||
1026 | msgstr "Quotidien" | ||
1027 | |||
1028 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61 | ||
1029 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 | ||
1030 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:61 | ||
1031 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:86 | ||
1032 | msgid "RSS Feed" | ||
1033 | msgstr "Flux RSS" | ||
1034 | |||
1035 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66 | ||
1036 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 | ||
1037 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:66 | ||
1038 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:102 | ||
1039 | msgid "Logout" | ||
1040 | msgstr "Déconnexion" | ||
1041 | |||
1042 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | ||
1043 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:169 | ||
1044 | msgid "is available" | ||
1045 | msgstr "est disponible" | ||
1046 | |||
1047 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176 | ||
1048 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:176 | ||
1049 | msgid "Error" | ||
1050 | msgstr "Erreur" | ||
1051 | |||
1052 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
1053 | msgid "Picture Wall" | ||
1054 | msgstr "Mur d'images" | ||
1055 | |||
1056 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
1057 | msgid "pics" | ||
1058 | msgstr "images" | ||
1059 | |||
1060 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
1061 | msgid "You need to enable Javascript to change plugin loading order." | ||
1062 | msgstr "" | ||
1063 | "Vous devez activer Javascript pour pouvoir modifier l'ordre des extensions." | ||
1064 | |||
1065 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | ||
1066 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
1067 | msgid "Plugin administration" | ||
1068 | msgstr "Administration des extensions" | ||
1069 | |||
1070 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
1071 | msgid "Enabled Plugins" | ||
1072 | msgstr "Extensions activées" | ||
1073 | |||
1074 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | ||
1075 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155 | ||
1076 | msgid "No plugin enabled." | ||
1077 | msgstr "Aucune extension activée." | ||
1078 | |||
1079 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40 | ||
1080 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:73 | ||
1081 | msgid "Disable" | ||
1082 | msgstr "Désactiver" | ||
1083 | |||
1084 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
1085 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | ||
1086 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:98 | ||
1087 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123 | ||
1088 | msgid "Name" | ||
1089 | msgstr "Nom" | ||
1090 | |||
1091 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | ||
1092 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76 | ||
1093 | msgid "Order" | ||
1094 | msgstr "Ordre" | ||
1095 | |||
1096 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 | ||
1097 | msgid "Disabled Plugins" | ||
1098 | msgstr "Extensions désactivées" | ||
1099 | |||
1100 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:91 | ||
1101 | msgid "No plugin disabled." | ||
1102 | msgstr "Aucune extension désactivée." | ||
1103 | |||
1104 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:97 | ||
1105 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122 | ||
1106 | msgid "Enable" | ||
1107 | msgstr "Activer" | ||
1108 | |||
1109 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 | ||
1110 | msgid "More plugins available" | ||
1111 | msgstr "Plus d'extensions disponibles" | ||
1112 | |||
1113 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:136 | ||
1114 | msgid "in the documentation" | ||
1115 | msgstr "dans la documentation" | ||
1116 | |||
1117 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150 | ||
1118 | msgid "Plugin configuration" | ||
1119 | msgstr "Configuration des extensions" | ||
1120 | |||
1121 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:195 | ||
1122 | msgid "No parameter available." | ||
1123 | msgstr "Aucun paramètre disponible." | ||
1124 | |||
1125 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1126 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1127 | msgid "tags" | ||
1128 | msgstr "tags" | ||
1129 | |||
1130 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | ||
1131 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | ||
1132 | msgid "List all links with those tags" | ||
1133 | msgstr "Lister tous les liens avec ces tags" | ||
1134 | |||
1135 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1136 | msgid "Tag list" | ||
1137 | msgstr "List des tags" | ||
1138 | |||
1139 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3 | ||
1140 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3 | ||
1141 | msgid "Sort by:" | ||
1142 | msgstr "Trier par :" | ||
1143 | |||
1144 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5 | ||
1145 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:5 | ||
1146 | msgid "Cloud" | ||
1147 | msgstr "Nuage" | ||
1148 | |||
1149 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:6 | ||
1150 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:6 | ||
1151 | msgid "Most used" | ||
1152 | msgstr "Plus utilisés" | ||
1153 | |||
1154 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7 | ||
1155 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:7 | ||
1156 | msgid "Alphabetical" | ||
1157 | msgstr "Alphabétique" | ||
1158 | |||
1159 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
1160 | msgid "Settings" | ||
1161 | msgstr "Paramètres" | ||
1162 | |||
1163 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
1164 | msgid "Change Shaarli settings: title, timezone, etc." | ||
1165 | msgstr "Changer les paramètres de Shaarli : titre, fuseau horaire, etc." | ||
1166 | |||
1167 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17 | ||
1168 | msgid "Configure your Shaarli" | ||
1169 | msgstr "Conguration de Shaarli" | ||
1170 | |||
1171 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21 | ||
1172 | msgid "Enable, disable and configure plugins" | ||
1173 | msgstr "Activer, désactiver et configurer les extensions" | ||
1174 | |||
1175 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
1176 | msgid "Change your password" | ||
1177 | msgstr "Modification du mot de passe" | ||
1178 | |||
1179 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | ||
1180 | msgid "Rename or delete a tag in all links" | ||
1181 | msgstr "Rename or delete a tag in all links" | ||
1182 | |||
1183 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
1184 | msgid "" | ||
1185 | "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, " | ||
1186 | "delicious...)" | ||
1187 | msgstr "" | ||
1188 | "Importer des marques pages au format Netscape HTML (comme exportés depuis " | ||
1189 | "Firefox, Chrome, Opera, delicious...)" | ||
1190 | |||
1191 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | ||
1192 | msgid "Import links" | ||
1193 | msgstr "Importer des liens" | ||
1194 | |||
1195 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 | ||
1196 | msgid "" | ||
1197 | "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, " | ||
1198 | "Opera, delicious...)" | ||
1199 | msgstr "" | ||
1200 | "Exporter les marques pages au format Netscape HTML (comme exportés depuis " | ||
1201 | "Firefox, Chrome, Opera, delicious...)" | ||
1202 | |||
1203 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | ||
1204 | msgid "Export database" | ||
1205 | msgstr "Exporter les données" | ||
1206 | |||
1207 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71 | ||
1208 | msgid "" | ||
1209 | "Drag one of these button to your bookmarks toolbar or right-click it and " | ||
1210 | "\"Bookmark This Link\"" | ||
1211 | msgstr "" | ||
1212 | "Glisser un de ces bouttons dans votre barre de favoris ou cliquer droit " | ||
1213 | "dessus et « Ajouter aux favoris »" | ||
1214 | |||
1215 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 | ||
1216 | msgid "then click on the bookmarklet in any page you want to share." | ||
1217 | msgstr "" | ||
1218 | "puis cliquer sur le marque page depuis un site que vous souhaitez partager." | ||
1219 | |||
1220 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76 | ||
1221 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:100 | ||
1222 | msgid "" | ||
1223 | "Drag this link to your bookmarks toolbar or right-click it and Bookmark This " | ||
1224 | "Link" | ||
1225 | msgstr "" | ||
1226 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " | ||
1227 | "Ajouter aux favoris »" | ||
1228 | |||
1229 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | ||
1230 | msgid "then click ✚Shaare link button in any page you want to share" | ||
1231 | msgstr "puis cliquer sur ✚Shaare depuis un site que vous souhaitez partager" | ||
1232 | |||
1233 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 | ||
1234 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108 | ||
1235 | msgid "The selected text is too long, it will be truncated." | ||
1236 | msgstr "Le texte sélectionné est trop long, il sera tronqué." | ||
1237 | |||
1238 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 | ||
1239 | msgid "Shaare link" | ||
1240 | msgstr "Shaare" | ||
1241 | |||
1242 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101 | ||
1243 | msgid "" | ||
1244 | "Then click ✚Add Note button anytime to start composing a private Note (text " | ||
1245 | "post) to your Shaarli" | ||
1246 | msgstr "" | ||
1247 | "Puis cliquer sur ✚Add Note pour commencer à rédiger une Note sur Shaarli" | ||
1248 | |||
1249 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | ||
1250 | msgid "Add Note" | ||
1251 | msgstr "Ajouter une Note" | ||
1252 | |||
1253 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:129 | ||
1254 | msgid "" | ||
1255 | "You need to browse your Shaarli over <strong>HTTPS</strong> to use this " | ||
1256 | "functionality." | ||
1257 | msgstr "" | ||
1258 | "Vous devez utiliser Shaarli en <strong>HTTPS</strong> pour utiliser cette " | ||
1259 | "fonctionalité." | ||
1260 | |||
1261 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 | ||
1262 | msgid "Add to" | ||
1263 | msgstr "Ajouter à " | ||
1264 | |||
1265 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:145 | ||
1266 | msgid "3rd party" | ||
1267 | msgstr "Applications tierces" | ||
1268 | |||
1269 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 | ||
1270 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153 | ||
1271 | msgid "Plugin" | ||
1272 | msgstr "Extension" | ||
1273 | |||
1274 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148 | ||
1275 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154 | ||
1276 | msgid "plugin" | ||
1277 | msgstr "extension" | ||
1278 | |||
1279 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175 | ||
1280 | msgid "" | ||
1281 | "Drag this link to your bookmarks toolbar, or right-click it and choose " | ||
1282 | "Bookmark This Link" | ||
1283 | msgstr "" | ||
1284 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " | ||
1285 | "Ajouter aux favoris »" | ||
1286 | |||
1287 | #~ msgid "Redirector" | ||
1288 | #~ msgstr "Redirecteur" | ||
1289 | |||
1290 | #~ msgid "e. g." | ||
1291 | #~ msgstr "ex :" | ||
1292 | |||
1293 | #~ msgid "will mask the HTTP_REFERER" | ||
1294 | #~ msgstr "masque le HTTP_REFERER" | ||
1295 | |||
1296 | #~ msgid "" | ||
1297 | #~ "An error occurred while parsing JSON configuration file (%s): error code #" | ||
1298 | #~ "%d" | ||
1299 | #~ msgstr "" | ||
1300 | #~ "Une erreur s'est produite lors de la lecture du fichier de configuration " | ||
1301 | #~ "JSON (%s) : code d'erreur #%d" | ||
1302 | |||
1303 | #~ msgid "" | ||
1304 | #~ "Please check your JSON syntax (without PHP comment tags) using a JSON " | ||
1305 | #~ "lint tool such as " | ||
1306 | #~ msgstr "" | ||
1307 | #~ "Merci de vérifier la syntaxe JSON (sans les balises de commentaires PHP) " | ||
1308 | #~ "en utilisant un validateur de JSON tel que " | ||
1309 | |||
1310 | #~ msgid "" | ||
1311 | #~ "Error: missing Composer dependencies\n" | ||
1312 | #~ "\n" | ||
1313 | #~ "If you installed Shaarli through Git or using the development branch,\n" | ||
1314 | #~ "please refer to the installation documentation to install PHP " | ||
1315 | #~ "dependencies using Composer:\n" | ||
1316 | #~ msgstr "" | ||
1317 | #~ "Erreur : les dépendances Composer sont manquantes\n" | ||
1318 | #~ "\n" | ||
1319 | #~ "Si vous avez installé Shaarli avec Git ou depuis la branche de " | ||
1320 | #~ "développement\n" | ||
1321 | #~ "merci de consulter la documentation d'installation pour installer les " | ||
1322 | #~ "dépendances Composer :\n" | ||
1323 | #~ "\n" | ||
1324 | |||
1325 | #~ msgid "Sessions do not seem to work correctly on your server." | ||
1326 | #~ msgstr "Les sessions ne semblent " | ||
1327 | |||
1328 | #~ msgid "Tag was renamed in " | ||
1329 | #~ msgstr "Le tag a été renommé dans " | ||
1330 | |||
1331 | #, fuzzy | ||
1332 | #~| msgid "My links" | ||
1333 | #~ msgid " links" | ||
1334 | #~ msgstr "Mes liens" | ||
1335 | |||
1336 | #, fuzzy | ||
1337 | #~| msgid "" | ||
1338 | #~| "Error: missing Composer configuration\n" | ||
1339 | #~| "\n" | ||
1340 | #~ msgid "Error: missing Composer configuration" | ||
1341 | #~ msgstr "" | ||
1342 | #~ "Erreur : la configuration Composer est manquante\n" | ||
1343 | #~ "\n" | ||
1344 | |||
1345 | #, fuzzy | ||
1346 | #~| msgid "" | ||
1347 | #~| "Shaarli could not create the config file. Please make sure Shaarli has " | ||
1348 | #~| "the right to write in the folder is it installed in." | ||
1349 | #~ msgid "" | ||
1350 | #~ "Shaarli could not create the config file. \n" | ||
1351 | #~ " Please make sure Shaarli has the right to write in the " | ||
1352 | #~ "folder is it installed in." | ||
1353 | #~ msgstr "" | ||
1354 | #~ "Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier " | ||
1355 | #~ "que Shaarli a les droits d'écriture dans le dossier dans lequel il est " | ||
1356 | #~ "installé." | ||
1357 | |||
1358 | #, fuzzy | ||
1359 | #~| msgid "Plugin" | ||
1360 | #~ msgid "Plugin \"" | ||
1361 | #~ msgstr "Extension" | ||
1362 | |||
1363 | #~ msgid "Your PHP version is obsolete!" | ||
1364 | #~ msgstr "Votre version de PHP est obsolète !" | ||
1365 | |||
1366 | #~ msgid " Shaarli requires at least PHP " | ||
1367 | #~ msgstr "Shaarli nécessite au moins PHP" | ||
@@ -64,7 +64,6 @@ require_once 'application/FeedBuilder.php'; | |||
64 | require_once 'application/FileUtils.php'; | 64 | require_once 'application/FileUtils.php'; |
65 | require_once 'application/History.php'; | 65 | require_once 'application/History.php'; |
66 | require_once 'application/HttpUtils.php'; | 66 | require_once 'application/HttpUtils.php'; |
67 | require_once 'application/Languages.php'; | ||
68 | require_once 'application/LinkDB.php'; | 67 | require_once 'application/LinkDB.php'; |
69 | require_once 'application/LinkFilter.php'; | 68 | require_once 'application/LinkFilter.php'; |
70 | require_once 'application/LinkUtils.php'; | 69 | require_once 'application/LinkUtils.php'; |
@@ -76,8 +75,10 @@ require_once 'application/Utils.php'; | |||
76 | require_once 'application/PluginManager.php'; | 75 | require_once 'application/PluginManager.php'; |
77 | require_once 'application/Router.php'; | 76 | require_once 'application/Router.php'; |
78 | require_once 'application/Updater.php'; | 77 | require_once 'application/Updater.php'; |
78 | use \Shaarli\Languages; | ||
79 | use \Shaarli\ThemeUtils; | 79 | use \Shaarli\ThemeUtils; |
80 | use \Shaarli\Config\ConfigManager; | 80 | use \Shaarli\Config\ConfigManager; |
81 | use \Shaarli\SessionManager; | ||
81 | 82 | ||
82 | // Ensure the PHP version is supported | 83 | // Ensure the PHP version is supported |
83 | try { | 84 | try { |
@@ -115,14 +116,23 @@ if (session_id() == '') { | |||
115 | } | 116 | } |
116 | 117 | ||
117 | // Regenerate session ID if invalid or not defined in cookie. | 118 | // Regenerate session ID if invalid or not defined in cookie. |
118 | if (isset($_COOKIE['shaarli']) && !is_session_id_valid($_COOKIE['shaarli'])) { | 119 | if (isset($_COOKIE['shaarli']) && !SessionManager::checkId($_COOKIE['shaarli'])) { |
119 | session_regenerate_id(true); | 120 | session_regenerate_id(true); |
120 | $_COOKIE['shaarli'] = session_id(); | 121 | $_COOKIE['shaarli'] = session_id(); |
121 | } | 122 | } |
122 | 123 | ||
123 | $conf = new ConfigManager(); | 124 | $conf = new ConfigManager(); |
125 | $sessionManager = new SessionManager($_SESSION, $conf); | ||
126 | |||
127 | // Sniff browser language and set date format accordingly. | ||
128 | if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { | ||
129 | autoLocale($_SERVER['HTTP_ACCEPT_LANGUAGE']); | ||
130 | } | ||
131 | |||
132 | new Languages(setlocale(LC_MESSAGES, 0), $conf); | ||
133 | |||
124 | $conf->setEmpty('general.timezone', date_default_timezone_get()); | 134 | $conf->setEmpty('general.timezone', date_default_timezone_get()); |
125 | $conf->setEmpty('general.title', 'Shared links on '. escape(index_url($_SERVER))); | 135 | $conf->setEmpty('general.title', t('Shared links on '). escape(index_url($_SERVER))); |
126 | RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory | 136 | RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory |
127 | RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory | 137 | RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory |
128 | 138 | ||
@@ -144,7 +154,7 @@ if (! is_file($conf->getConfigFileExt())) { | |||
144 | $errors = ApplicationUtils::checkResourcePermissions($conf); | 154 | $errors = ApplicationUtils::checkResourcePermissions($conf); |
145 | 155 | ||
146 | if ($errors != array()) { | 156 | if ($errors != array()) { |
147 | $message = '<p>Insufficient permissions:</p><ul>'; | 157 | $message = '<p>'. t('Insufficient permissions:') .'</p><ul>'; |
148 | 158 | ||
149 | foreach ($errors as $error) { | 159 | foreach ($errors as $error) { |
150 | $message .= '<li>'.$error.'</li>'; | 160 | $message .= '<li>'.$error.'</li>'; |
@@ -157,17 +167,12 @@ if (! is_file($conf->getConfigFileExt())) { | |||
157 | } | 167 | } |
158 | 168 | ||
159 | // Display the installation form if no existing config is found | 169 | // Display the installation form if no existing config is found |
160 | install($conf); | 170 | install($conf, $sessionManager); |
161 | } | 171 | } |
162 | 172 | ||
163 | // a token depending of deployment salt, user password, and the current ip | 173 | // a token depending of deployment salt, user password, and the current ip |
164 | define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt'))); | 174 | define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt'))); |
165 | 175 | ||
166 | // Sniff browser language and set date format accordingly. | ||
167 | if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { | ||
168 | autoLocale($_SERVER['HTTP_ACCEPT_LANGUAGE']); | ||
169 | } | ||
170 | |||
171 | /** | 176 | /** |
172 | * Checking session state (i.e. is the user still logged in) | 177 | * Checking session state (i.e. is the user still logged in) |
173 | * | 178 | * |
@@ -376,9 +381,9 @@ function ban_canLogin($conf) | |||
376 | // Process login form: Check if login/password is correct. | 381 | // Process login form: Check if login/password is correct. |
377 | if (isset($_POST['login'])) | 382 | if (isset($_POST['login'])) |
378 | { | 383 | { |
379 | if (!ban_canLogin($conf)) die('I said: NO. You are banned for the moment. Go away.'); | 384 | if (!ban_canLogin($conf)) die(t('I said: NO. You are banned for the moment. Go away.')); |
380 | if (isset($_POST['password']) | 385 | if (isset($_POST['password']) |
381 | && tokenOk($_POST['token']) | 386 | && $sessionManager->checkToken($_POST['token']) |
382 | && (check_auth($_POST['login'], $_POST['password'], $conf)) | 387 | && (check_auth($_POST['login'], $_POST['password'], $conf)) |
383 | ) { // Login/password is OK. | 388 | ) { // Login/password is OK. |
384 | ban_loginOk($conf); | 389 | ban_loginOk($conf); |
@@ -440,7 +445,8 @@ if (isset($_POST['login'])) | |||
440 | } | 445 | } |
441 | } | 446 | } |
442 | } | 447 | } |
443 | echo '<script>alert("Wrong login/password.");document.location=\'?do=login'.$redir.'\';</script>'; // Redirect to login screen. | 448 | // Redirect to login screen. |
449 | echo '<script>alert("'. t("Wrong login/password.") .'");document.location=\'?do=login'.$redir.'\';</script>'; | ||
444 | exit; | 450 | exit; |
445 | } | 451 | } |
446 | } | 452 | } |
@@ -451,32 +457,6 @@ if (isset($_POST['login'])) | |||
451 | if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. | 457 | if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. |
452 | 458 | ||
453 | /** | 459 | /** |
454 | * Returns a token. | ||
455 | * | ||
456 | * @param ConfigManager $conf Configuration Manager instance. | ||
457 | * | ||
458 | * @return string token. | ||
459 | */ | ||
460 | function getToken($conf) | ||
461 | { | ||
462 | $rnd = sha1(uniqid('', true) .'_'. mt_rand() . $conf->get('credentials.salt')); // We generate a random string. | ||
463 | $_SESSION['tokens'][$rnd]=1; // Store it on the server side. | ||
464 | return $rnd; | ||
465 | } | ||
466 | |||
467 | // Tells if a token is OK. Using this function will destroy the token. | ||
468 | // true=token is OK. | ||
469 | function tokenOk($token) | ||
470 | { | ||
471 | if (isset($_SESSION['tokens'][$token])) | ||
472 | { | ||
473 | unset($_SESSION['tokens'][$token]); // Token is used: destroy it. | ||
474 | return true; // Token is OK. | ||
475 | } | ||
476 | return false; // Wrong token, or already used. | ||
477 | } | ||
478 | |||
479 | /** | ||
480 | * Daily RSS feed: 1 RSS entry per day giving all the links on that day. | 460 | * Daily RSS feed: 1 RSS entry per day giving all the links on that day. |
481 | * Gives the last 7 days (which have links). | 461 | * Gives the last 7 days (which have links). |
482 | * This RSS feed cannot be filtered. | 462 | * This RSS feed cannot be filtered. |
@@ -546,7 +526,11 @@ function showDailyRSS($conf) { | |||
546 | 526 | ||
547 | // We pre-format some fields for proper output. | 527 | // We pre-format some fields for proper output. |
548 | foreach ($links as &$link) { | 528 | foreach ($links as &$link) { |
549 | $link['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url')); | 529 | $link['formatedDescription'] = format_description( |
530 | $link['description'], | ||
531 | $conf->get('redirector.url'), | ||
532 | $conf->get('redirector.encode_url') | ||
533 | ); | ||
550 | $link['thumbnail'] = thumbnail($conf, $link['url']); | 534 | $link['thumbnail'] = thumbnail($conf, $link['url']); |
551 | $link['timestamp'] = $link['created']->getTimestamp(); | 535 | $link['timestamp'] = $link['created']->getTimestamp(); |
552 | if (startsWith($link['url'], '?')) { | 536 | if (startsWith($link['url'], '?')) { |
@@ -618,7 +602,11 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager) | |||
618 | $taglist = explode(' ',$link['tags']); | 602 | $taglist = explode(' ',$link['tags']); |
619 | uasort($taglist, 'strcasecmp'); | 603 | uasort($taglist, 'strcasecmp'); |
620 | $linksToDisplay[$key]['taglist']=$taglist; | 604 | $linksToDisplay[$key]['taglist']=$taglist; |
621 | $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url')); | 605 | $linksToDisplay[$key]['formatedDescription'] = format_description( |
606 | $link['description'], | ||
607 | $conf->get('redirector.url'), | ||
608 | $conf->get('redirector.encode_url') | ||
609 | ); | ||
622 | $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']); | 610 | $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']); |
623 | $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp(); | 611 | $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp(); |
624 | } | 612 | } |
@@ -683,12 +671,13 @@ function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager) { | |||
683 | /** | 671 | /** |
684 | * Render HTML page (according to URL parameters and user rights) | 672 | * Render HTML page (according to URL parameters and user rights) |
685 | * | 673 | * |
686 | * @param ConfigManager $conf Configuration Manager instance. | 674 | * @param ConfigManager $conf Configuration Manager instance. |
687 | * @param PluginManager $pluginManager Plugin Manager instance, | 675 | * @param PluginManager $pluginManager Plugin Manager instance, |
688 | * @param LinkDB $LINKSDB | 676 | * @param LinkDB $LINKSDB |
689 | * @param History $history instance | 677 | * @param History $history instance |
678 | * @param SessionManager $sessionManager SessionManager instance | ||
690 | */ | 679 | */ |
691 | function renderPage($conf, $pluginManager, $LINKSDB, $history) | 680 | function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager) |
692 | { | 681 | { |
693 | $updater = new Updater( | 682 | $updater = new Updater( |
694 | read_updates_file($conf->get('resource.updates')), | 683 | read_updates_file($conf->get('resource.updates')), |
@@ -709,7 +698,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
709 | die($e->getMessage()); | 698 | die($e->getMessage()); |
710 | } | 699 | } |
711 | 700 | ||
712 | $PAGE = new PageBuilder($conf, $LINKSDB); | 701 | $PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken()); |
713 | $PAGE->assign('linkcount', count($LINKSDB)); | 702 | $PAGE->assign('linkcount', count($LINKSDB)); |
714 | $PAGE->assign('privateLinkcount', count_private($LINKSDB)); | 703 | $PAGE->assign('privateLinkcount', count_private($LINKSDB)); |
715 | $PAGE->assign('plugin_errors', $pluginManager->getErrors()); | 704 | $PAGE->assign('plugin_errors', $pluginManager->getErrors()); |
@@ -1100,16 +1089,19 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1100 | if ($targetPage == Router::$PAGE_CHANGEPASSWORD) | 1089 | if ($targetPage == Router::$PAGE_CHANGEPASSWORD) |
1101 | { | 1090 | { |
1102 | if ($conf->get('security.open_shaarli')) { | 1091 | if ($conf->get('security.open_shaarli')) { |
1103 | die('You are not supposed to change a password on an Open Shaarli.'); | 1092 | die(t('You are not supposed to change a password on an Open Shaarli.')); |
1104 | } | 1093 | } |
1105 | 1094 | ||
1106 | if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) | 1095 | if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) |
1107 | { | 1096 | { |
1108 | if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away! | 1097 | if (!$sessionManager->checkToken($_POST['token'])) die(t('Wrong token.')); // Go away! |
1109 | 1098 | ||
1110 | // Make sure old password is correct. | 1099 | // Make sure old password is correct. |
1111 | $oldhash = sha1($_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt')); | 1100 | $oldhash = sha1($_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt')); |
1112 | if ($oldhash!= $conf->get('credentials.hash')) { echo '<script>alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; } | 1101 | if ($oldhash!= $conf->get('credentials.hash')) { |
1102 | echo '<script>alert("'. t('The old password is not correct.') .'");document.location=\'?do=changepasswd\';</script>'; | ||
1103 | exit; | ||
1104 | } | ||
1113 | // Save new password | 1105 | // Save new password |
1114 | // Salt renders rainbow-tables attacks useless. | 1106 | // Salt renders rainbow-tables attacks useless. |
1115 | $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); | 1107 | $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); |
@@ -1127,7 +1119,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1127 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>'; | 1119 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>'; |
1128 | exit; | 1120 | exit; |
1129 | } | 1121 | } |
1130 | echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>'; | 1122 | echo '<script>alert("'. t('Your password has been changed') .'");document.location=\'?do=tools\';</script>'; |
1131 | exit; | 1123 | exit; |
1132 | } | 1124 | } |
1133 | else // show the change password form. | 1125 | else // show the change password form. |
@@ -1142,8 +1134,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1142 | { | 1134 | { |
1143 | if (!empty($_POST['title']) ) | 1135 | if (!empty($_POST['title']) ) |
1144 | { | 1136 | { |
1145 | if (!tokenOk($_POST['token'])) { | 1137 | if (!$sessionManager->checkToken($_POST['token'])) { |
1146 | die('Wrong token.'); // Go away! | 1138 | die(t('Wrong token.')); // Go away! |
1147 | } | 1139 | } |
1148 | $tz = 'UTC'; | 1140 | $tz = 'UTC'; |
1149 | if (!empty($_POST['continent']) && !empty($_POST['city']) | 1141 | if (!empty($_POST['continent']) && !empty($_POST['city']) |
@@ -1163,6 +1155,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1163 | $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); | 1155 | $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); |
1164 | $conf->set('api.enabled', !empty($_POST['enableApi'])); | 1156 | $conf->set('api.enabled', !empty($_POST['enableApi'])); |
1165 | $conf->set('api.secret', escape($_POST['apiSecret'])); | 1157 | $conf->set('api.secret', escape($_POST['apiSecret'])); |
1158 | $conf->set('translation.language', escape($_POST['language'])); | ||
1159 | |||
1166 | try { | 1160 | try { |
1167 | $conf->write(isLoggedIn()); | 1161 | $conf->write(isLoggedIn()); |
1168 | $history->updateSettings(); | 1162 | $history->updateSettings(); |
@@ -1178,7 +1172,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1178 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=configure\';</script>'; | 1172 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=configure\';</script>'; |
1179 | exit; | 1173 | exit; |
1180 | } | 1174 | } |
1181 | echo '<script>alert("Configuration was saved.");document.location=\'?do=configure\';</script>'; | 1175 | echo '<script>alert("'. t('Configuration was saved.') .'");document.location=\'?do=configure\';</script>'; |
1182 | exit; | 1176 | exit; |
1183 | } | 1177 | } |
1184 | else // Show the configuration form. | 1178 | else // Show the configuration form. |
@@ -1200,6 +1194,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1200 | $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); | 1194 | $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); |
1201 | $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); | 1195 | $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); |
1202 | $PAGE->assign('api_secret', $conf->get('api.secret')); | 1196 | $PAGE->assign('api_secret', $conf->get('api.secret')); |
1197 | $PAGE->assign('languages', Languages::getAvailableLanguages()); | ||
1198 | $PAGE->assign('language', $conf->get('translation.language')); | ||
1203 | $PAGE->renderPage('configure'); | 1199 | $PAGE->renderPage('configure'); |
1204 | exit; | 1200 | exit; |
1205 | } | 1201 | } |
@@ -1214,8 +1210,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1214 | exit; | 1210 | exit; |
1215 | } | 1211 | } |
1216 | 1212 | ||
1217 | if (!tokenOk($_POST['token'])) { | 1213 | if (!$sessionManager->checkToken($_POST['token'])) { |
1218 | die('Wrong token.'); | 1214 | die(t('Wrong token.')); |
1219 | } | 1215 | } |
1220 | 1216 | ||
1221 | $alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), escape($_POST['totag'])); | 1217 | $alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), escape($_POST['totag'])); |
@@ -1225,9 +1221,10 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1225 | } | 1221 | } |
1226 | $delete = empty($_POST['totag']); | 1222 | $delete = empty($_POST['totag']); |
1227 | $redirect = $delete ? 'do=changetag' : 'searchtags='. urlencode(escape($_POST['totag'])); | 1223 | $redirect = $delete ? 'do=changetag' : 'searchtags='. urlencode(escape($_POST['totag'])); |
1224 | $count = count($alteredLinks); | ||
1228 | $alert = $delete | 1225 | $alert = $delete |
1229 | ? sprintf(t('The tag was removed from %d links.'), count($alteredLinks)) | 1226 | ? sprintf(t('The tag was removed from %d link.', 'The tag was removed from %d links.', $count), $count) |
1230 | : sprintf(t('The tag was renamed in %d links.'), count($alteredLinks)); | 1227 | : sprintf(t('The tag was renamed in %d link.', 'The tag was renamed in %d links.', $count), $count); |
1231 | echo '<script>alert("'. $alert .'");document.location=\'?'. $redirect .'\';</script>'; | 1228 | echo '<script>alert("'. $alert .'");document.location=\'?'. $redirect .'\';</script>'; |
1232 | exit; | 1229 | exit; |
1233 | } | 1230 | } |
@@ -1243,8 +1240,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1243 | if (isset($_POST['save_edit'])) | 1240 | if (isset($_POST['save_edit'])) |
1244 | { | 1241 | { |
1245 | // Go away! | 1242 | // Go away! |
1246 | if (! tokenOk($_POST['token'])) { | 1243 | if (! $sessionManager->checkToken($_POST['token'])) { |
1247 | die('Wrong token.'); | 1244 | die(t('Wrong token.')); |
1248 | } | 1245 | } |
1249 | 1246 | ||
1250 | // lf_id should only be present if the link exists. | 1247 | // lf_id should only be present if the link exists. |
@@ -1343,8 +1340,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1343 | // -------- User clicked the "Delete" button when editing a link: Delete link from database. | 1340 | // -------- User clicked the "Delete" button when editing a link: Delete link from database. |
1344 | if ($targetPage == Router::$PAGE_DELETELINK) | 1341 | if ($targetPage == Router::$PAGE_DELETELINK) |
1345 | { | 1342 | { |
1346 | if (! tokenOk($_GET['token'])) { | 1343 | if (! $sessionManager->checkToken($_GET['token'])) { |
1347 | die('Wrong token.'); | 1344 | die(t('Wrong token.')); |
1348 | } | 1345 | } |
1349 | 1346 | ||
1350 | $ids = trim($_GET['lf_linkdate']); | 1347 | $ids = trim($_GET['lf_linkdate']); |
@@ -1428,22 +1425,16 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1428 | // 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.) | 1425 | // 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.) |
1429 | if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { | 1426 | if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { |
1430 | // Short timeout to keep the application responsive | 1427 | // Short timeout to keep the application responsive |
1431 | list($headers, $content) = get_http_response($url, 4); | 1428 | // The callback will fill $charset and $title with data from the downloaded page. |
1432 | if (strpos($headers[0], '200 OK') !== false) { | 1429 | get_http_response($url, 25, 4194304, get_curl_download_callback($charset, $title)); |
1433 | // Retrieve charset. | 1430 | if (! empty($title) && strtolower($charset) != 'utf-8') { |
1434 | $charset = get_charset($headers, $content); | 1431 | $title = mb_convert_encoding($title, 'utf-8', $charset); |
1435 | // Extract title. | ||
1436 | $title = html_extract_title($content); | ||
1437 | // Re-encode title in utf-8 if necessary. | ||
1438 | if (! empty($title) && strtolower($charset) != 'utf-8') { | ||
1439 | $title = mb_convert_encoding($title, 'utf-8', $charset); | ||
1440 | } | ||
1441 | } | 1432 | } |
1442 | } | 1433 | } |
1443 | 1434 | ||
1444 | if ($url == '') { | 1435 | if ($url == '') { |
1445 | $url = '?' . smallHash($linkdate . $LINKSDB->getNextId()); | 1436 | $url = '?' . smallHash($linkdate . $LINKSDB->getNextId()); |
1446 | $title = $conf->get('general.default_note_title', 'Note: '); | 1437 | $title = $conf->get('general.default_note_title', t('Note: ')); |
1447 | } | 1438 | } |
1448 | $url = escape($url); | 1439 | $url = escape($url); |
1449 | $title = escape($title); | 1440 | $title = escape($title); |
@@ -1550,14 +1541,17 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1550 | // Import bookmarks from an uploaded file | 1541 | // Import bookmarks from an uploaded file |
1551 | if (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size'] == 0) { | 1542 | if (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size'] == 0) { |
1552 | // The file is too big or some form field may be missing. | 1543 | // The file is too big or some form field may be missing. |
1553 | echo '<script>alert("The file you are trying to upload is probably' | 1544 | $msg = sprintf( |
1554 | .' bigger than what this webserver can accept (' | 1545 | t( |
1555 | .get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')).').' | 1546 | 'The file you are trying to upload is probably bigger than what this webserver can accept' |
1556 | .' Please upload in smaller chunks.");document.location=\'?do=' | 1547 | .' (%s). Please upload in smaller chunks.' |
1557 | .Router::$PAGE_IMPORT .'\';</script>'; | 1548 | ), |
1549 | get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')) | ||
1550 | ); | ||
1551 | echo '<script>alert("'. $msg .'");document.location=\'?do='.Router::$PAGE_IMPORT .'\';</script>'; | ||
1558 | exit; | 1552 | exit; |
1559 | } | 1553 | } |
1560 | if (! tokenOk($_POST['token'])) { | 1554 | if (! $sessionManager->checkToken($_POST['token'])) { |
1561 | die('Wrong token.'); | 1555 | die('Wrong token.'); |
1562 | } | 1556 | } |
1563 | $status = NetscapeBookmarkUtils::import( | 1557 | $status = NetscapeBookmarkUtils::import( |
@@ -1624,7 +1618,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
1624 | // Get a fresh token | 1618 | // Get a fresh token |
1625 | if ($targetPage == Router::$GET_TOKEN) { | 1619 | if ($targetPage == Router::$GET_TOKEN) { |
1626 | header('Content-Type:text/plain'); | 1620 | header('Content-Type:text/plain'); |
1627 | echo getToken($conf); | 1621 | echo $sessionManager->generateToken($conf); |
1628 | exit; | 1622 | exit; |
1629 | } | 1623 | } |
1630 | 1624 | ||
@@ -1696,7 +1690,11 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager) | |||
1696 | while ($i<$end && $i<count($keys)) | 1690 | while ($i<$end && $i<count($keys)) |
1697 | { | 1691 | { |
1698 | $link = $linksToDisplay[$keys[$i]]; | 1692 | $link = $linksToDisplay[$keys[$i]]; |
1699 | $link['description'] = format_description($link['description'], $conf->get('redirector.url')); | 1693 | $link['description'] = format_description( |
1694 | $link['description'], | ||
1695 | $conf->get('redirector.url'), | ||
1696 | $conf->get('redirector.encode_url') | ||
1697 | ); | ||
1700 | $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; | 1698 | $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; |
1701 | $link['class'] = $link['private'] == 0 ? $classLi : 'private'; | 1699 | $link['class'] = $link['private'] == 0 ? $classLi : 'private'; |
1702 | $link['timestamp'] = $link['created']->getTimestamp(); | 1700 | $link['timestamp'] = $link['created']->getTimestamp(); |
@@ -1950,10 +1948,10 @@ function lazyThumbnail($conf, $url,$href=false) | |||
1950 | * Installation | 1948 | * Installation |
1951 | * This function should NEVER be called if the file data/config.php exists. | 1949 | * This function should NEVER be called if the file data/config.php exists. |
1952 | * | 1950 | * |
1953 | * @param ConfigManager $conf Configuration Manager instance. | 1951 | * @param ConfigManager $conf Configuration Manager instance. |
1952 | * @param SessionManager $sessionManager SessionManager instance | ||
1954 | */ | 1953 | */ |
1955 | function install($conf) | 1954 | function install($conf, $sessionManager) { |
1956 | { | ||
1957 | // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. | 1955 | // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. |
1958 | if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705); | 1956 | if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705); |
1959 | 1957 | ||
@@ -1962,12 +1960,20 @@ function install($conf) | |||
1962 | // (Because on some hosts, session.save_path may not be set correctly, | 1960 | // (Because on some hosts, session.save_path may not be set correctly, |
1963 | // or we may not have write access to it.) | 1961 | // or we may not have write access to it.) |
1964 | if (isset($_GET['test_session']) && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working')) | 1962 | if (isset($_GET['test_session']) && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working')) |
1965 | { // Step 2: Check if data in session is correct. | 1963 | { |
1966 | echo '<pre>Sessions do not seem to work correctly on your server.<br>'; | 1964 | // Step 2: Check if data in session is correct. |
1967 | echo 'Make sure the variable session.save_path is set correctly in your php config, and that you have write access to it.<br>'; | 1965 | $msg = t( |
1968 | echo 'It currently points to '.session_save_path().'<br>'; | 1966 | '<pre>Sessions do not seem to work correctly on your server.<br>'. |
1969 | 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>'; | 1967 | 'Make sure the variable "session.save_path" is set correctly in your PHP config, '. |
1970 | echo '<br><a href="?">Click to try again.</a></pre>'; | 1968 | 'and that you have write access to it.<br>'. |
1969 | 'It currently points to %s.<br>'. | ||
1970 | 'On some browsers, accessing your server via a hostname like \'localhost\' '. | ||
1971 | 'or any custom hostname without a dot causes cookie storage to fail. '. | ||
1972 | 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>' | ||
1973 | ); | ||
1974 | $msg = sprintf($msg, session_save_path()); | ||
1975 | echo $msg; | ||
1976 | echo '<br><a href="?">'. t('Click to try again.') .'</a></pre>'; | ||
1971 | die; | 1977 | die; |
1972 | } | 1978 | } |
1973 | if (!isset($_SESSION['session_tested'])) | 1979 | if (!isset($_SESSION['session_tested'])) |
@@ -2000,6 +2006,7 @@ function install($conf) | |||
2000 | } else { | 2006 | } else { |
2001 | $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER))); | 2007 | $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER))); |
2002 | } | 2008 | } |
2009 | $conf->set('translation.language', escape($_POST['language'])); | ||
2003 | $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); | 2010 | $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); |
2004 | $conf->set('api.enabled', !empty($_POST['enableApi'])); | 2011 | $conf->set('api.enabled', !empty($_POST['enableApi'])); |
2005 | $conf->set( | 2012 | $conf->set( |
@@ -2027,10 +2034,11 @@ function install($conf) | |||
2027 | exit; | 2034 | exit; |
2028 | } | 2035 | } |
2029 | 2036 | ||
2030 | $PAGE = new PageBuilder($conf); | 2037 | $PAGE = new PageBuilder($conf, null, $sessionManager->generateToken()); |
2031 | list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); | 2038 | list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); |
2032 | $PAGE->assign('continents', $continents); | 2039 | $PAGE->assign('continents', $continents); |
2033 | $PAGE->assign('cities', $cities); | 2040 | $PAGE->assign('cities', $cities); |
2041 | $PAGE->assign('languages', Languages::getAvailableLanguages()); | ||
2034 | $PAGE->renderPage('install'); | 2042 | $PAGE->renderPage('install'); |
2035 | exit; | 2043 | exit; |
2036 | } | 2044 | } |
@@ -2303,7 +2311,7 @@ $response = $app->run(true); | |||
2303 | if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) { | 2311 | if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) { |
2304 | // We use UTF-8 for proper international characters handling. | 2312 | // We use UTF-8 for proper international characters handling. |
2305 | header('Content-Type: text/html; charset=utf-8'); | 2313 | header('Content-Type: text/html; charset=utf-8'); |
2306 | renderPage($conf, $pluginManager, $linkDb, $history); | 2314 | renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager); |
2307 | } else { | 2315 | } else { |
2308 | $app->respond($response); | 2316 | $app->respond($response); |
2309 | } | 2317 | } |
@@ -22,16 +22,15 @@ pages: | |||
22 | - Reverse proxy configuration: docker/reverse-proxy-configuration.md | 22 | - Reverse proxy configuration: docker/reverse-proxy-configuration.md |
23 | - Docker resources: docker/resources.md | 23 | - Docker resources: docker/resources.md |
24 | - Usage: | 24 | - Usage: |
25 | - Features: Features.md | ||
26 | - Bookmarklet: Bookmarklet.md | 25 | - Bookmarklet: Bookmarklet.md |
27 | - Browsing and searching: Browsing-and-searching.md | 26 | - Browsing and searching: Browsing-and-searching.md |
28 | - Firefox share: Firefox-share.md | 27 | - Firefox share: Firefox-share.md |
29 | - RSS feeds: RSS-feeds.md | 28 | - RSS feeds: RSS-feeds.md |
30 | - REST API: REST-API.md | 29 | - REST API: REST-API.md |
30 | - Community & Related software: Community-&-Related-software.md | ||
31 | - How To: | 31 | - How To: |
32 | - Backup, restore, import and export: Backup,-restore,-import-and-export.md | 32 | - Backup, restore, import and export: Backup,-restore,-import-and-export.md |
33 | - Various hacks: Various-hacks.md | 33 | - Various hacks: Various-hacks.md |
34 | - Troubleshooting: Troubleshooting.md | ||
35 | - Development: | 34 | - Development: |
36 | - Development guidelines: Development-guidelines.md | 35 | - Development guidelines: Development-guidelines.md |
37 | - Continuous integration tools: Continuous-integration-tools.md | 36 | - Continuous integration tools: Continuous-integration-tools.md |
@@ -43,9 +42,9 @@ pages: | |||
43 | - Versioning and Branches: Versioning-and-Branches.md | 42 | - Versioning and Branches: Versioning-and-Branches.md |
44 | - Security: Security.md | 43 | - Security: Security.md |
45 | - Static analysis: Static-analysis.md | 44 | - Static analysis: Static-analysis.md |
45 | - Translations: Translations.md | ||
46 | - Theming: Theming.md | 46 | - Theming: Theming.md |
47 | - Unit tests: Unit-tests.md | 47 | - Unit tests: Unit-tests.md |
48 | - Unit tests inside Docker: Unit-tests-Docker.md | 48 | - Unit tests inside Docker: Unit-tests-Docker.md |
49 | - About: | 49 | - FAQ: FAQ.md |
50 | - FAQ: FAQ.md | 50 | - Troubleshooting: Troubleshooting.md |
51 | - Community & Related software: Community-&-Related-software.md | ||
diff --git a/plugins/TODO.md b/plugins/TODO.md deleted file mode 100644 index e3313d67..00000000 --- a/plugins/TODO.md +++ /dev/null | |||
@@ -1,28 +0,0 @@ | |||
1 | https://github.com/shaarli/Shaarli/issues/181 - Add Disqus or Isso comments box on a permalink page | ||
2 | |||
3 | * http://posativ.org/isso/ | ||
4 | * install debian package https://packages.debian.org/sid/isso | ||
5 | * configure server http://posativ.org/isso/docs/configuration/server/ | ||
6 | * configure client http://posativ.org/isso/docs/configuration/client/ | ||
7 | * 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) | ||
8 | |||
9 | |||
10 | Problem: by default, Isso thread ID is guessed from the current url (only one thread per page). | ||
11 | if we want multiple threads on a single page (shaarli linklist), we must use : the `data-isso-id` client config, | ||
12 | with data-isso-id being the permalink of an item. | ||
13 | |||
14 | `<section data-isso-id="aH7klxW" id="isso-thread"></section>` | ||
15 | `data-isso-id: Set a custom thread id, defaults to current URI.` | ||
16 | |||
17 | Problem: feature is currently broken https://github.com/posativ/isso/issues/27 | ||
18 | |||
19 | Another option, only display isso threads when current URL is a permalink (`\?(A-Z|a-z|0-9|-){7}`) (only show thread | ||
20 | when displaying only this link), and just display a "comments" button on each linklist item. Optionally show the comment | ||
21 | count on each item using the API (http://posativ.org/isso/docs/extras/api/#get-comment-count). API requests can be done | ||
22 | by raintpl `{function` or client-side with js. The former should be faster if isso and shaarli are on ther same server. | ||
23 | |||
24 | Showing all full isso threads in the linklist would destroy layout | ||
25 | |||
26 | ----------------------------------------------------------- | ||
27 | |||
28 | http://www.git-attitude.fr/2014/11/04/git-rerere/ for the merge | ||
diff --git a/plugins/addlink_toolbar/addlink_toolbar.php b/plugins/addlink_toolbar/addlink_toolbar.php index ddf50aaf..8c05a231 100644 --- a/plugins/addlink_toolbar/addlink_toolbar.php +++ b/plugins/addlink_toolbar/addlink_toolbar.php | |||
@@ -26,11 +26,11 @@ function hook_addlink_toolbar_render_header($data) | |||
26 | array( | 26 | array( |
27 | 'type' => 'text', | 27 | 'type' => 'text', |
28 | 'name' => 'post', | 28 | 'name' => 'post', |
29 | 'placeholder' => 'URI', | 29 | 'placeholder' => t('URI'), |
30 | ), | 30 | ), |
31 | array( | 31 | array( |
32 | 'type' => 'submit', | 32 | 'type' => 'submit', |
33 | 'value' => 'Add link', | 33 | 'value' => t('Add link'), |
34 | 'class' => 'bigbutton', | 34 | 'class' => 'bigbutton', |
35 | ), | 35 | ), |
36 | ), | 36 | ), |
@@ -40,3 +40,12 @@ function hook_addlink_toolbar_render_header($data) | |||
40 | 40 | ||
41 | return $data; | 41 | return $data; |
42 | } | 42 | } |
43 | |||
44 | /** | ||
45 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
46 | */ | ||
47 | function addlink_toolbar_dummy_translation() | ||
48 | { | ||
49 | // meta | ||
50 | t('Adds the addlink input on the linklist page.'); | ||
51 | } | ||
diff --git a/plugins/archiveorg/archiveorg.html b/plugins/archiveorg/archiveorg.html index 0781fe35..ad501f47 100644 --- a/plugins/archiveorg/archiveorg.html +++ b/plugins/archiveorg/archiveorg.html | |||
@@ -1 +1,5 @@ | |||
1 | <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> | 1 | <span> |
2 | <a href="https://web.archive.org/web/%s"> | ||
3 | <img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="%s" alt="archive.org" /> | ||
4 | </a> | ||
5 | </span> | ||
diff --git a/plugins/archiveorg/archiveorg.php b/plugins/archiveorg/archiveorg.php index 03d13d0e..cda35751 100644 --- a/plugins/archiveorg/archiveorg.php +++ b/plugins/archiveorg/archiveorg.php | |||
@@ -20,9 +20,18 @@ function hook_archiveorg_render_linklist($data) | |||
20 | if($value['private'] && preg_match('/^\?[a-zA-Z0-9-_@]{6}($|&|#)/', $value['real_url'])) { | 20 | if($value['private'] && preg_match('/^\?[a-zA-Z0-9-_@]{6}($|&|#)/', $value['real_url'])) { |
21 | continue; | 21 | continue; |
22 | } | 22 | } |
23 | $archive = sprintf($archive_html, $value['url']); | 23 | $archive = sprintf($archive_html, $value['url'], t('View on archive.org')); |
24 | $value['link_plugin'][] = $archive; | 24 | $value['link_plugin'][] = $archive; |
25 | } | 25 | } |
26 | 26 | ||
27 | return $data; | 27 | return $data; |
28 | } | 28 | } |
29 | |||
30 | /** | ||
31 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
32 | */ | ||
33 | function archiveorg_dummy_translation() | ||
34 | { | ||
35 | // meta | ||
36 | t('For each link, add an Archive.org icon.'); | ||
37 | } | ||
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php index 8fdbf663..b80a2b6d 100644 --- a/plugins/demo_plugin/demo_plugin.php +++ b/plugins/demo_plugin/demo_plugin.php | |||
@@ -14,6 +14,26 @@ | |||
14 | * and check user status with _LOGGEDIN_. | 14 | * and check user status with _LOGGEDIN_. |
15 | */ | 15 | */ |
16 | 16 | ||
17 | use Shaarli\Config\ConfigManager; | ||
18 | |||
19 | /** | ||
20 | * In the footer hook, there is a working example of a translation extension for Shaarli. | ||
21 | * | ||
22 | * The extension must be attached to a new translation domain (i.e. NOT 'shaarli'). | ||
23 | * Use case: any custom theme or non official plugin can use the translation system. | ||
24 | * | ||
25 | * See the documentation for more information. | ||
26 | */ | ||
27 | const EXT_TRANSLATION_DOMAIN = 'demo'; | ||
28 | |||
29 | /* | ||
30 | * This is not necessary, but it's easier if you don't want Poedit to mix up your translations. | ||
31 | */ | ||
32 | function demo_plugin_t($text, $nText = '', $nb = 1) | ||
33 | { | ||
34 | return t($text, $nText, $nb, EXT_TRANSLATION_DOMAIN); | ||
35 | } | ||
36 | |||
17 | /** | 37 | /** |
18 | * Initialization function. | 38 | * Initialization function. |
19 | * It will be called when the plugin is loaded. | 39 | * It will be called when the plugin is loaded. |
@@ -27,6 +47,12 @@ function demo_plugin_init($conf) | |||
27 | { | 47 | { |
28 | $conf->get('toto', 'nope'); | 48 | $conf->get('toto', 'nope'); |
29 | 49 | ||
50 | if (! $conf->exists('translation.extensions.demo')) { | ||
51 | // Custom translation with the domain 'demo' | ||
52 | $conf->set('translation.extensions.demo', 'plugins/demo_plugin/languages/'); | ||
53 | $conf->write(true); | ||
54 | } | ||
55 | |||
30 | $errors[] = 'This a demo init error.'; | 56 | $errors[] = 'This a demo init error.'; |
31 | return $errors; | 57 | return $errors; |
32 | } | 58 | } |
@@ -160,7 +186,7 @@ function hook_demo_plugin_render_includes($data) | |||
160 | function hook_demo_plugin_render_footer($data) | 186 | function hook_demo_plugin_render_footer($data) |
161 | { | 187 | { |
162 | // footer text | 188 | // footer text |
163 | $data['text'][] = 'Shaarli is now enhanced by the awesome demo_plugin.'; | 189 | $data['text'][] = '<br>'. demo_plugin_t('Shaarli is now enhanced by the awesome demo_plugin.'); |
164 | 190 | ||
165 | // Free elements at the end of the page. | 191 | // Free elements at the end of the page. |
166 | $data['endofpage'][] = '<marquee id="demo_marquee">' . | 192 | $data['endofpage'][] = '<marquee id="demo_marquee">' . |
@@ -433,3 +459,12 @@ function hook_demo_plugin_render_feed($data) | |||
433 | } | 459 | } |
434 | return $data; | 460 | return $data; |
435 | } | 461 | } |
462 | |||
463 | /** | ||
464 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
465 | */ | ||
466 | function demo_dummy_translation() | ||
467 | { | ||
468 | // meta | ||
469 | t('A demo plugin covering all use cases for template designers and plugin developers.'); | ||
470 | } | ||
diff --git a/plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.mo b/plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.mo new file mode 100644 index 00000000..0f80f6ed --- /dev/null +++ b/plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.mo | |||
Binary files differ | |||
diff --git a/plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.po b/plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.po new file mode 100644 index 00000000..921379c0 --- /dev/null +++ b/plugins/demo_plugin/languages/fr/LC_MESSAGES/demo.po | |||
@@ -0,0 +1,21 @@ | |||
1 | msgid "" | ||
2 | msgstr "" | ||
3 | "Project-Id-Version: Demo plugin\n" | ||
4 | "POT-Creation-Date: 2017-08-19 10:45+0200\n" | ||
5 | "PO-Revision-Date: 2017-08-19 11:28+0200\n" | ||
6 | "Last-Translator: \n" | ||
7 | "Language-Team: demo\n" | ||
8 | "Language: fr\n" | ||
9 | "MIME-Version: 1.0\n" | ||
10 | "Content-Type: text/plain; charset=UTF-8\n" | ||
11 | "Content-Transfer-Encoding: 8bit\n" | ||
12 | "X-Generator: Poedit 2.0.2\n" | ||
13 | "X-Poedit-Basepath: ../../..\n" | ||
14 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" | ||
15 | "X-Poedit-KeywordsList: ;demo_plugin_t:1,2;demo_plugin_t\n" | ||
16 | "X-Poedit-SourceCharset: UTF-8\n" | ||
17 | "X-Poedit-SearchPath-0: .\n" | ||
18 | |||
19 | #: demo_plugin.php:173 | ||
20 | msgid "Shaarli is now enhanced by the awesome demo_plugin." | ||
21 | msgstr "Shaarli est maintenant amélioré avec le fantastique demo_plugin." | ||
diff --git a/plugins/isso/isso.php b/plugins/isso/isso.php index ce16645f..5bc1cce2 100644 --- a/plugins/isso/isso.php +++ b/plugins/isso/isso.php | |||
@@ -4,10 +4,11 @@ | |||
4 | * Plugin Isso. | 4 | * Plugin Isso. |
5 | */ | 5 | */ |
6 | 6 | ||
7 | use Shaarli\Config\ConfigManager; | ||
8 | |||
7 | /** | 9 | /** |
8 | * Display an error everywhere if the plugin is enabled without configuration. | 10 | * Display an error everywhere if the plugin is enabled without configuration. |
9 | * | 11 | * |
10 | * @param $data array List of links | ||
11 | * @param $conf ConfigManager instance | 12 | * @param $conf ConfigManager instance |
12 | * | 13 | * |
13 | * @return mixed - linklist data with Isso plugin. | 14 | * @return mixed - linklist data with Isso plugin. |
@@ -16,8 +17,8 @@ function isso_init($conf) | |||
16 | { | 17 | { |
17 | $issoUrl = $conf->get('plugins.ISSO_SERVER'); | 18 | $issoUrl = $conf->get('plugins.ISSO_SERVER'); |
18 | if (empty($issoUrl)) { | 19 | if (empty($issoUrl)) { |
19 | $error = 'Isso plugin error: '. | 20 | $error = t('Isso plugin error: '. |
20 | 'Please define the "ISSO_SERVER" setting in the plugin administration page.'; | 21 | 'Please define the "ISSO_SERVER" setting in the plugin administration page.'); |
21 | return array($error); | 22 | return array($error); |
22 | } | 23 | } |
23 | } | 24 | } |
@@ -52,3 +53,13 @@ function hook_isso_render_linklist($data, $conf) | |||
52 | 53 | ||
53 | return $data; | 54 | return $data; |
54 | } | 55 | } |
56 | |||
57 | /** | ||
58 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
59 | */ | ||
60 | function isso_dummy_translation() | ||
61 | { | ||
62 | // meta | ||
63 | t('Let visitor comment your shaares on permalinks with Isso.'); | ||
64 | t('Isso server URL (without \'http://\')'); | ||
65 | } | ||
diff --git a/plugins/markdown/help.html b/plugins/markdown/help.html index 9c4e5ae0..ded3d347 100644 --- a/plugins/markdown/help.html +++ b/plugins/markdown/help.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <div class="md_help"> | 1 | <div class="md_help"> |
2 | Description will be rendered with | 2 | %s |
3 | <a href="http://daringfireball.net/projects/markdown/syntax" title="Markdown syntax documentation"> | 3 | <a href="http://daringfireball.net/projects/markdown/syntax" title="%s"> |
4 | Markdown syntax</a>. | 4 | %s</a>. |
5 | </div> | 5 | </div> |
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php index 772c56e8..1531549d 100644 --- a/plugins/markdown/markdown.php +++ b/plugins/markdown/markdown.php | |||
@@ -154,8 +154,13 @@ function hook_markdown_render_includes($data) | |||
154 | function hook_markdown_render_editlink($data) | 154 | function hook_markdown_render_editlink($data) |
155 | { | 155 | { |
156 | // Load help HTML into a string | 156 | // Load help HTML into a string |
157 | $data['edit_link_plugin'][] = file_get_contents(PluginManager::$PLUGINS_PATH .'/markdown/help.html'); | 157 | $txt = file_get_contents(PluginManager::$PLUGINS_PATH .'/markdown/help.html'); |
158 | 158 | $translations = [ | |
159 | t('Description will be rendered with'), | ||
160 | t('Markdown syntax documentation'), | ||
161 | t('Markdown syntax'), | ||
162 | ]; | ||
163 | $data['edit_link_plugin'][] = vsprintf($txt, $translations); | ||
159 | // Add no markdown 'meta-tag' in tag list if it was never used, for autocompletion. | 164 | // Add no markdown 'meta-tag' in tag list if it was never used, for autocompletion. |
160 | if (! in_array(NO_MD_TAG, $data['tags'])) { | 165 | if (! in_array(NO_MD_TAG, $data['tags'])) { |
161 | $data['tags'][NO_MD_TAG] = 0; | 166 | $data['tags'][NO_MD_TAG] = 0; |
@@ -325,3 +330,15 @@ function process_markdown($description, $escape = true, $allowedProtocols = []) | |||
325 | 330 | ||
326 | return $processedDescription; | 331 | return $processedDescription; |
327 | } | 332 | } |
333 | |||
334 | /** | ||
335 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
336 | */ | ||
337 | function markdown_dummy_translation() | ||
338 | { | ||
339 | // meta | ||
340 | t('Render shaare description with Markdown syntax.<br><strong>Warning</strong>: | ||
341 | If your shaared descriptions contained HTML tags before enabling the markdown plugin, | ||
342 | enabling it might break your page. | ||
343 | See the <a href="https://github.com/shaarli/Shaarli/tree/master/plugins/markdown#html-rendering">README</a>.'); | ||
344 | } | ||
diff --git a/plugins/piwik/piwik.php b/plugins/piwik/piwik.php index 4a2b48a1..ca00c2be 100644 --- a/plugins/piwik/piwik.php +++ b/plugins/piwik/piwik.php | |||
@@ -18,8 +18,8 @@ function piwik_init($conf) | |||
18 | $piwikUrl = $conf->get('plugins.PIWIK_URL'); | 18 | $piwikUrl = $conf->get('plugins.PIWIK_URL'); |
19 | $piwikSiteid = $conf->get('plugins.PIWIK_SITEID'); | 19 | $piwikSiteid = $conf->get('plugins.PIWIK_SITEID'); |
20 | if (empty($piwikUrl) || empty($piwikSiteid)) { | 20 | if (empty($piwikUrl) || empty($piwikSiteid)) { |
21 | $error = 'Piwik plugin error: ' . | 21 | $error = t('Piwik plugin error: ' . |
22 | 'Please define PIWIK_URL and PIWIK_SITEID in the plugin administration page.'; | 22 | 'Please define PIWIK_URL and PIWIK_SITEID in the plugin administration page.'); |
23 | return array($error); | 23 | return array($error); |
24 | } | 24 | } |
25 | } | 25 | } |
@@ -60,3 +60,14 @@ function hook_piwik_render_footer($data, $conf) | |||
60 | 60 | ||
61 | return $data; | 61 | return $data; |
62 | } | 62 | } |
63 | |||
64 | /** | ||
65 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
66 | */ | ||
67 | function piwik_dummy_translation() | ||
68 | { | ||
69 | // meta | ||
70 | t('A plugin that adds Piwik tracking code to Shaarli pages.'); | ||
71 | t('Piwik URL'); | ||
72 | t('Piwik site ID'); | ||
73 | } | ||
diff --git a/plugins/playvideos/playvideos.php b/plugins/playvideos/playvideos.php index 64484504..c6d6b0cc 100644 --- a/plugins/playvideos/playvideos.php +++ b/plugins/playvideos/playvideos.php | |||
@@ -19,10 +19,10 @@ function hook_playvideos_render_header($data) | |||
19 | $playvideo = array( | 19 | $playvideo = array( |
20 | 'attr' => array( | 20 | 'attr' => array( |
21 | 'href' => '#', | 21 | 'href' => '#', |
22 | 'title' => 'Video player', | 22 | 'title' => t('Video player'), |
23 | 'id' => 'playvideos', | 23 | 'id' => 'playvideos', |
24 | ), | 24 | ), |
25 | 'html' => 'â–º Play Videos' | 25 | 'html' => 'â–º '. t('Play Videos') |
26 | ); | 26 | ); |
27 | $data['buttons_toolbar'][] = $playvideo; | 27 | $data['buttons_toolbar'][] = $playvideo; |
28 | } | 28 | } |
@@ -46,3 +46,12 @@ function hook_playvideos_render_footer($data) | |||
46 | 46 | ||
47 | return $data; | 47 | return $data; |
48 | } | 48 | } |
49 | |||
50 | /** | ||
51 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
52 | */ | ||
53 | function playvideos_dummy_translation() | ||
54 | { | ||
55 | // meta | ||
56 | t('Add a button in the toolbar allowing to watch all videos.'); | ||
57 | } | ||
diff --git a/plugins/pubsubhubbub/pubsubhubbub.php b/plugins/pubsubhubbub/pubsubhubbub.php index 03b6757b..184b588b 100644 --- a/plugins/pubsubhubbub/pubsubhubbub.php +++ b/plugins/pubsubhubbub/pubsubhubbub.php | |||
@@ -10,6 +10,7 @@ | |||
10 | */ | 10 | */ |
11 | 11 | ||
12 | use pubsubhubbub\publisher\Publisher; | 12 | use pubsubhubbub\publisher\Publisher; |
13 | use Shaarli\Config\ConfigManager; | ||
13 | 14 | ||
14 | /** | 15 | /** |
15 | * Plugin init function - set the hub to the default appspot one. | 16 | * Plugin init function - set the hub to the default appspot one. |
@@ -65,7 +66,7 @@ function hook_pubsubhubbub_save_link($data, $conf) | |||
65 | $p = new Publisher($conf->get('plugins.PUBSUBHUB_URL')); | 66 | $p = new Publisher($conf->get('plugins.PUBSUBHUB_URL')); |
66 | $p->publish_update($feeds, $httpPost); | 67 | $p->publish_update($feeds, $httpPost); |
67 | } catch (Exception $e) { | 68 | } catch (Exception $e) { |
68 | error_log('Could not publish to PubSubHubbub: ' . $e->getMessage()); | 69 | error_log(sprintf(t('Could not publish to PubSubHubbub: %s'), $e->getMessage())); |
69 | } | 70 | } |
70 | 71 | ||
71 | return $data; | 72 | return $data; |
@@ -91,11 +92,20 @@ function nocurl_http_post($url, $postString) { | |||
91 | $context = stream_context_create($params); | 92 | $context = stream_context_create($params); |
92 | $fp = @fopen($url, 'rb', false, $context); | 93 | $fp = @fopen($url, 'rb', false, $context); |
93 | if (!$fp) { | 94 | if (!$fp) { |
94 | throw new Exception('Could not post to '. $url); | 95 | throw new Exception(sprintf(t('Could not post to %s'), $url)); |
95 | } | 96 | } |
96 | $response = @stream_get_contents($fp); | 97 | $response = @stream_get_contents($fp); |
97 | if ($response === false) { | 98 | if ($response === false) { |
98 | throw new Exception('Bad response from the hub '. $url); | 99 | throw new Exception(sprintf(t('Bad response from the hub %s'), $url)); |
99 | } | 100 | } |
100 | return $response; | 101 | return $response; |
101 | } | 102 | } |
103 | |||
104 | /** | ||
105 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
106 | */ | ||
107 | function pubsubhubbub_dummy_translation() | ||
108 | { | ||
109 | // meta | ||
110 | t('Enable PubSubHubbub feed publishing.'); | ||
111 | } | ||
diff --git a/plugins/qrcode/qrcode.meta b/plugins/qrcode/qrcode.meta index cbf371ea..1812cd21 100644 --- a/plugins/qrcode/qrcode.meta +++ b/plugins/qrcode/qrcode.meta | |||
@@ -1 +1 @@ | |||
description="For each link, add a QRCode icon ." | description="For each link, add a QRCode icon." | ||
diff --git a/plugins/qrcode/qrcode.php b/plugins/qrcode/qrcode.php index 8bc610d1..0f96a106 100644 --- a/plugins/qrcode/qrcode.php +++ b/plugins/qrcode/qrcode.php | |||
@@ -59,3 +59,12 @@ function hook_qrcode_render_includes($data) | |||
59 | 59 | ||
60 | return $data; | 60 | return $data; |
61 | } | 61 | } |
62 | |||
63 | /** | ||
64 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
65 | */ | ||
66 | function qrcode_dummy_translation() | ||
67 | { | ||
68 | // meta | ||
69 | t('For each link, add a QRCode icon.'); | ||
70 | } | ||
diff --git a/plugins/wallabag/wallabag.html b/plugins/wallabag/wallabag.html index e861536d..4c57691d 100644 --- a/plugins/wallabag/wallabag.html +++ b/plugins/wallabag/wallabag.html | |||
@@ -1 +1,5 @@ | |||
1 | <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> | 1 | <span> |
2 | <a href="%s%s" target="_blank"> | ||
3 | <img class="linklist-plugin-icon" src="%s/wallabag/wallabag.png" title="%s" alt="wallabag" /> | ||
4 | </a> | ||
5 | </span> | ||
diff --git a/plugins/wallabag/wallabag.php b/plugins/wallabag/wallabag.php index 641e4cc2..9dfd079e 100644 --- a/plugins/wallabag/wallabag.php +++ b/plugins/wallabag/wallabag.php | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | 6 | ||
7 | require_once 'WallabagInstance.php'; | 7 | require_once 'WallabagInstance.php'; |
8 | use Shaarli\Config\ConfigManager; | ||
8 | 9 | ||
9 | /** | 10 | /** |
10 | * Init function, return an error if the server is not set. | 11 | * Init function, return an error if the server is not set. |
@@ -17,8 +18,8 @@ function wallabag_init($conf) | |||
17 | { | 18 | { |
18 | $wallabagUrl = $conf->get('plugins.WALLABAG_URL'); | 19 | $wallabagUrl = $conf->get('plugins.WALLABAG_URL'); |
19 | if (empty($wallabagUrl)) { | 20 | if (empty($wallabagUrl)) { |
20 | $error = 'Wallabag plugin error: '. | 21 | $error = t('Wallabag plugin error: '. |
21 | 'Please define the "WALLABAG_URL" setting in the plugin administration page.'; | 22 | 'Please define the "WALLABAG_URL" setting in the plugin administration page.'); |
22 | return array($error); | 23 | return array($error); |
23 | } | 24 | } |
24 | } | 25 | } |
@@ -43,12 +44,14 @@ function hook_wallabag_render_linklist($data, $conf) | |||
43 | 44 | ||
44 | $wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html'); | 45 | $wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html'); |
45 | 46 | ||
47 | $linkTitle = t('Save to wallabag'); | ||
46 | foreach ($data['links'] as &$value) { | 48 | foreach ($data['links'] as &$value) { |
47 | $wallabag = sprintf( | 49 | $wallabag = sprintf( |
48 | $wallabagHtml, | 50 | $wallabagHtml, |
49 | $wallabagInstance->getWallabagUrl(), | 51 | $wallabagInstance->getWallabagUrl(), |
50 | urlencode($value['url']), | 52 | urlencode($value['url']), |
51 | PluginManager::$PLUGINS_PATH | 53 | PluginManager::$PLUGINS_PATH, |
54 | $linkTitle | ||
52 | ); | 55 | ); |
53 | $value['link_plugin'][] = $wallabag; | 56 | $value['link_plugin'][] = $wallabag; |
54 | } | 57 | } |
@@ -56,3 +59,14 @@ function hook_wallabag_render_linklist($data, $conf) | |||
56 | return $data; | 59 | return $data; |
57 | } | 60 | } |
58 | 61 | ||
62 | /** | ||
63 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
64 | */ | ||
65 | function wallabag_dummy_translation() | ||
66 | { | ||
67 | // meta | ||
68 | t('For each link, add a QRCode icon.'); | ||
69 | t('Wallabag API URL'); | ||
70 | t('Wallabag API version (1 or 2)'); | ||
71 | } | ||
72 | |||
diff --git a/shaarli_version.php b/shaarli_version.php index a92b5619..8cd38931 100644 --- a/shaarli_version.php +++ b/shaarli_version.php | |||
@@ -1 +1 @@ | |||
<?php /* 0.9.3 */ ?> | <?php /* 0.9.4 */ ?> | ||
diff --git a/tests/HttpUtils/ServerUrlTest.php b/tests/HttpUtils/ServerUrlTest.php index dac02b3e..324b827a 100644 --- a/tests/HttpUtils/ServerUrlTest.php +++ b/tests/HttpUtils/ServerUrlTest.php | |||
@@ -186,4 +186,36 @@ class ServerUrlTest extends PHPUnit_Framework_TestCase | |||
186 | ) | 186 | ) |
187 | ); | 187 | ); |
188 | } | 188 | } |
189 | |||
190 | /** | ||
191 | * Misconfigured server (see #1022): Proxy HTTP but 443 | ||
192 | */ | ||
193 | public function testHttpWithPort433() | ||
194 | { | ||
195 | $this->assertEquals( | ||
196 | 'https://host.tld', | ||
197 | server_url( | ||
198 | array( | ||
199 | 'HTTPS' => 'Off', | ||
200 | 'SERVER_NAME' => 'host.tld', | ||
201 | 'SERVER_PORT' => '80', | ||
202 | 'HTTP_X_FORWARDED_PROTO' => 'http', | ||
203 | 'HTTP_X_FORWARDED_PORT' => '443' | ||
204 | ) | ||
205 | ) | ||
206 | ); | ||
207 | |||
208 | $this->assertEquals( | ||
209 | 'https://host.tld', | ||
210 | server_url( | ||
211 | array( | ||
212 | 'HTTPS' => 'Off', | ||
213 | 'SERVER_NAME' => 'host.tld', | ||
214 | 'SERVER_PORT' => '80', | ||
215 | 'HTTP_X_FORWARDED_PROTO' => 'https, http', | ||
216 | 'HTTP_X_FORWARDED_PORT' => '443, 80' | ||
217 | ) | ||
218 | ) | ||
219 | ); | ||
220 | } | ||
189 | } | 221 | } |
diff --git a/tests/LanguagesTest.php b/tests/LanguagesTest.php index 79c136c8..864ce630 100644 --- a/tests/LanguagesTest.php +++ b/tests/LanguagesTest.php | |||
@@ -1,41 +1,203 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | require_once 'application/Languages.php'; | 3 | namespace Shaarli; |
4 | |||
5 | use Shaarli\Config\ConfigManager; | ||
4 | 6 | ||
5 | /** | 7 | /** |
6 | * Class LanguagesTest. | 8 | * Class LanguagesTest. |
7 | */ | 9 | */ |
8 | class LanguagesTest extends PHPUnit_Framework_TestCase | 10 | class LanguagesTest extends \PHPUnit_Framework_TestCase |
9 | { | 11 | { |
10 | /** | 12 | /** |
13 | * @var string Config file path (without extension). | ||
14 | */ | ||
15 | protected static $configFile = 'tests/utils/config/configJson'; | ||
16 | |||
17 | /** | ||
18 | * @var ConfigManager | ||
19 | */ | ||
20 | protected $conf; | ||
21 | |||
22 | /** | ||
23 | * | ||
24 | */ | ||
25 | public function setUp() | ||
26 | { | ||
27 | $this->conf = new ConfigManager(self::$configFile); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Test t() with a simple non identified value. | ||
32 | */ | ||
33 | public function testTranslateSingleNotIDGettext() | ||
34 | { | ||
35 | $this->conf->set('translation.mode', 'gettext'); | ||
36 | new Languages('en', $this->conf); | ||
37 | $text = 'abcdé 564 fgK'; | ||
38 | $this->assertEquals($text, t($text)); | ||
39 | } | ||
40 | |||
41 | /** | ||
42 | * Test t() with a simple identified value in gettext mode. | ||
43 | */ | ||
44 | public function testTranslateSingleIDGettext() | ||
45 | { | ||
46 | $this->conf->set('translation.mode', 'gettext'); | ||
47 | new Languages('en', $this->conf); | ||
48 | $text = 'permalink'; | ||
49 | $this->assertEquals($text, t($text)); | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Test t() with a non identified plural form in gettext mode. | ||
54 | */ | ||
55 | public function testTranslatePluralNotIDGettext() | ||
56 | { | ||
57 | $this->conf->set('translation.mode', 'gettext'); | ||
58 | new Languages('en', $this->conf); | ||
59 | $text = 'sandwich'; | ||
60 | $nText = 'sandwiches'; | ||
61 | $this->assertEquals('sandwiches', t($text, $nText, 0)); | ||
62 | $this->assertEquals('sandwich', t($text, $nText, 1)); | ||
63 | $this->assertEquals('sandwiches', t($text, $nText, 2)); | ||
64 | } | ||
65 | |||
66 | /** | ||
67 | * Test t() with an identified plural form in gettext mode. | ||
68 | */ | ||
69 | public function testTranslatePluralIDGettext() | ||
70 | { | ||
71 | $this->conf->set('translation.mode', 'gettext'); | ||
72 | new Languages('en', $this->conf); | ||
73 | $text = 'shaare'; | ||
74 | $nText = 'shaares'; | ||
75 | // In english, zero is followed by plural form | ||
76 | $this->assertEquals('shaares', t($text, $nText, 0)); | ||
77 | $this->assertEquals('shaare', t($text, $nText, 1)); | ||
78 | $this->assertEquals('shaares', t($text, $nText, 2)); | ||
79 | } | ||
80 | |||
81 | /** | ||
11 | * Test t() with a simple non identified value. | 82 | * Test t() with a simple non identified value. |
12 | */ | 83 | */ |
13 | public function testTranslateSingleNotID() | 84 | public function testTranslateSingleNotIDPhp() |
14 | { | 85 | { |
86 | $this->conf->set('translation.mode', 'php'); | ||
87 | new Languages('en', $this->conf); | ||
15 | $text = 'abcdé 564 fgK'; | 88 | $text = 'abcdé 564 fgK'; |
16 | $this->assertEquals($text, t($text)); | 89 | $this->assertEquals($text, t($text)); |
17 | } | 90 | } |
18 | 91 | ||
19 | /** | 92 | /** |
20 | * Test t() with a non identified plural form. | 93 | * Test t() with a simple identified value in PHP mode. |
21 | */ | 94 | */ |
22 | public function testTranslatePluralNotID() | 95 | public function testTranslateSingleIDPhp() |
23 | { | 96 | { |
24 | $text = '%s sandwich'; | 97 | $this->conf->set('translation.mode', 'php'); |
25 | $nText = '%s sandwiches'; | 98 | new Languages('en', $this->conf); |
26 | $this->assertEquals('0 sandwich', t($text, $nText)); | 99 | $text = 'permalink'; |
27 | $this->assertEquals('1 sandwich', t($text, $nText, 1)); | 100 | $this->assertEquals($text, t($text)); |
28 | $this->assertEquals('2 sandwiches', t($text, $nText, 2)); | ||
29 | } | 101 | } |
30 | 102 | ||
31 | /** | 103 | /** |
32 | * Test t() with a non identified invalid plural form. | 104 | * Test t() with a non identified plural form in PHP mode. |
33 | */ | 105 | */ |
34 | public function testTranslatePluralNotIDInvalid() | 106 | public function testTranslatePluralNotIDPhp() |
35 | { | 107 | { |
108 | $this->conf->set('translation.mode', 'php'); | ||
109 | new Languages('en', $this->conf); | ||
36 | $text = 'sandwich'; | 110 | $text = 'sandwich'; |
37 | $nText = 'sandwiches'; | 111 | $nText = 'sandwiches'; |
112 | $this->assertEquals('sandwiches', t($text, $nText, 0)); | ||
38 | $this->assertEquals('sandwich', t($text, $nText, 1)); | 113 | $this->assertEquals('sandwich', t($text, $nText, 1)); |
39 | $this->assertEquals('sandwiches', t($text, $nText, 2)); | 114 | $this->assertEquals('sandwiches', t($text, $nText, 2)); |
40 | } | 115 | } |
116 | |||
117 | /** | ||
118 | * Test t() with an identified plural form in PHP mode. | ||
119 | */ | ||
120 | public function testTranslatePluralIDPhp() | ||
121 | { | ||
122 | $this->conf->set('translation.mode', 'php'); | ||
123 | new Languages('en', $this->conf); | ||
124 | $text = 'shaare'; | ||
125 | $nText = 'shaares'; | ||
126 | // In english, zero is followed by plural form | ||
127 | $this->assertEquals('shaares', t($text, $nText, 0)); | ||
128 | $this->assertEquals('shaare', t($text, $nText, 1)); | ||
129 | $this->assertEquals('shaares', t($text, $nText, 2)); | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * Test t() with an invalid language set in the configuration in gettext mode. | ||
134 | */ | ||
135 | public function testTranslateWithInvalidConfLanguageGettext() | ||
136 | { | ||
137 | $this->conf->set('translation.mode', 'gettext'); | ||
138 | $this->conf->set('translation.language', 'nope'); | ||
139 | new Languages('fr', $this->conf); | ||
140 | $text = 'grumble'; | ||
141 | $this->assertEquals($text, t($text)); | ||
142 | } | ||
143 | |||
144 | /** | ||
145 | * Test t() with an invalid language set in the configuration in PHP mode. | ||
146 | */ | ||
147 | public function testTranslateWithInvalidConfLanguagePhp() | ||
148 | { | ||
149 | $this->conf->set('translation.mode', 'php'); | ||
150 | $this->conf->set('translation.language', 'nope'); | ||
151 | new Languages('fr', $this->conf); | ||
152 | $text = 'grumble'; | ||
153 | $this->assertEquals($text, t($text)); | ||
154 | } | ||
155 | |||
156 | /** | ||
157 | * Test t() with an invalid language set with auto language in gettext mode. | ||
158 | */ | ||
159 | public function testTranslateWithInvalidAutoLanguageGettext() | ||
160 | { | ||
161 | $this->conf->set('translation.mode', 'gettext'); | ||
162 | new Languages('nope', $this->conf); | ||
163 | $text = 'grumble'; | ||
164 | $this->assertEquals($text, t($text)); | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * Test t() with an invalid language set with auto language in PHP mode. | ||
169 | */ | ||
170 | public function testTranslateWithInvalidAutoLanguagePhp() | ||
171 | { | ||
172 | $this->conf->set('translation.mode', 'php'); | ||
173 | new Languages('nope', $this->conf); | ||
174 | $text = 'grumble'; | ||
175 | $this->assertEquals($text, t($text)); | ||
176 | } | ||
177 | |||
178 | /** | ||
179 | * Test t() with an extension language file in gettext mode | ||
180 | */ | ||
181 | public function testTranslationExtensionGettext() | ||
182 | { | ||
183 | $this->conf->set('translation.mode', 'gettext'); | ||
184 | $this->conf->set('translation.extensions.test', 'tests/utils/languages/'); | ||
185 | new Languages('en', $this->conf); | ||
186 | $txt = 'car'; // ignore me poedit | ||
187 | $this->assertEquals('car', t($txt, $txt, 1, 'test')); | ||
188 | $this->assertEquals('Search', t('Search', 'Search', 1, 'test')); | ||
189 | } | ||
190 | |||
191 | /** | ||
192 | * Test t() with an extension language file in PHP mode | ||
193 | */ | ||
194 | public function testTranslationExtensionPhp() | ||
195 | { | ||
196 | $this->conf->set('translation.mode', 'php'); | ||
197 | $this->conf->set('translation.extensions.test', 'tests/utils/languages/'); | ||
198 | new Languages('en', $this->conf); | ||
199 | $txt = 'car'; // ignore me poedit | ||
200 | $this->assertEquals('car', t($txt, $txt, 1, 'test')); | ||
201 | $this->assertEquals('Search', t('Search', 'Search', 1, 'test')); | ||
202 | } | ||
41 | } | 203 | } |
diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php index d796d3a3..9cd6dbd4 100644 --- a/tests/LinkFilterTest.php +++ b/tests/LinkFilterTest.php | |||
@@ -8,6 +8,10 @@ require_once 'application/LinkFilter.php'; | |||
8 | class LinkFilterTest extends PHPUnit_Framework_TestCase | 8 | class LinkFilterTest extends PHPUnit_Framework_TestCase |
9 | { | 9 | { |
10 | /** | 10 | /** |
11 | * @var string Test datastore path. | ||
12 | */ | ||
13 | protected static $testDatastore = 'sandbox/datastore.php'; | ||
14 | /** | ||
11 | * @var LinkFilter instance. | 15 | * @var LinkFilter instance. |
12 | */ | 16 | */ |
13 | protected static $linkFilter; | 17 | protected static $linkFilter; |
@@ -18,12 +22,19 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase | |||
18 | protected static $refDB; | 22 | protected static $refDB; |
19 | 23 | ||
20 | /** | 24 | /** |
25 | * @var LinkDB instance | ||
26 | */ | ||
27 | protected static $linkDB; | ||
28 | |||
29 | /** | ||
21 | * Instanciate linkFilter with ReferenceLinkDB data. | 30 | * Instanciate linkFilter with ReferenceLinkDB data. |
22 | */ | 31 | */ |
23 | public static function setUpBeforeClass() | 32 | public static function setUpBeforeClass() |
24 | { | 33 | { |
25 | self::$refDB = new ReferenceLinkDB(); | 34 | self::$refDB = new ReferenceLinkDB(); |
26 | self::$linkFilter = new LinkFilter(self::$refDB->getLinks()); | 35 | self::$refDB->write(self::$testDatastore); |
36 | self::$linkDB = new LinkDB(self::$testDatastore, true, false); | ||
37 | self::$linkFilter = new LinkFilter(self::$linkDB); | ||
27 | } | 38 | } |
28 | 39 | ||
29 | /** | 40 | /** |
diff --git a/tests/LinkUtilsTest.php b/tests/LinkUtilsTest.php index c77922ec..7fbd59b0 100644 --- a/tests/LinkUtilsTest.php +++ b/tests/LinkUtilsTest.php | |||
@@ -29,27 +29,13 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase | |||
29 | } | 29 | } |
30 | 30 | ||
31 | /** | 31 | /** |
32 | * Test get_charset() with all priorities. | ||
33 | */ | ||
34 | public function testGetCharset() | ||
35 | { | ||
36 | $headers = array('Content-Type' => 'text/html; charset=Headers'); | ||
37 | $html = '<html><meta>stuff</meta><meta charset="Html"/></html>'; | ||
38 | $default = 'default'; | ||
39 | $this->assertEquals('headers', get_charset($headers, $html, $default)); | ||
40 | $this->assertEquals('html', get_charset(array(), $html, $default)); | ||
41 | $this->assertEquals($default, get_charset(array(), '', $default)); | ||
42 | $this->assertEquals('utf-8', get_charset(array(), '')); | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Test headers_extract_charset() when the charset is found. | 32 | * Test headers_extract_charset() when the charset is found. |
47 | */ | 33 | */ |
48 | public function testHeadersExtractExistentCharset() | 34 | public function testHeadersExtractExistentCharset() |
49 | { | 35 | { |
50 | $charset = 'x-MacCroatian'; | 36 | $charset = 'x-MacCroatian'; |
51 | $headers = array('Content-Type' => 'text/html; charset='. $charset); | 37 | $headers = 'text/html; charset='. $charset; |
52 | $this->assertEquals(strtolower($charset), headers_extract_charset($headers)); | 38 | $this->assertEquals(strtolower($charset), header_extract_charset($headers)); |
53 | } | 39 | } |
54 | 40 | ||
55 | /** | 41 | /** |
@@ -57,11 +43,11 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase | |||
57 | */ | 43 | */ |
58 | public function testHeadersExtractNonExistentCharset() | 44 | public function testHeadersExtractNonExistentCharset() |
59 | { | 45 | { |
60 | $headers = array(); | 46 | $headers = ''; |
61 | $this->assertFalse(headers_extract_charset($headers)); | 47 | $this->assertFalse(header_extract_charset($headers)); |
62 | 48 | ||
63 | $headers = array('Content-Type' => 'text/html'); | 49 | $headers = 'text/html'; |
64 | $this->assertFalse(headers_extract_charset($headers)); | 50 | $this->assertFalse(header_extract_charset($headers)); |
65 | } | 51 | } |
66 | 52 | ||
67 | /** | 53 | /** |
@@ -86,6 +72,131 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase | |||
86 | } | 72 | } |
87 | 73 | ||
88 | /** | 74 | /** |
75 | * Test the download callback with valid value | ||
76 | */ | ||
77 | public function testCurlDownloadCallbackOk() | ||
78 | { | ||
79 | $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok'); | ||
80 | $data = [ | ||
81 | 'HTTP/1.1 200 OK', | ||
82 | 'Server: GitHub.com', | ||
83 | 'Date: Sat, 28 Oct 2017 12:01:33 GMT', | ||
84 | 'Content-Type: text/html; charset=utf-8', | ||
85 | 'Status: 200 OK', | ||
86 | 'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea', | ||
87 | '<title>ignored</title>', | ||
88 | ]; | ||
89 | foreach ($data as $key => $line) { | ||
90 | $ignore = null; | ||
91 | $expected = $key !== 'end' ? strlen($line) : false; | ||
92 | $this->assertEquals($expected, $callback($ignore, $line)); | ||
93 | if ($expected === false) { | ||
94 | break; | ||
95 | } | ||
96 | } | ||
97 | $this->assertEquals('utf-8', $charset); | ||
98 | $this->assertEquals('Refactoring · GitHub', $title); | ||
99 | } | ||
100 | |||
101 | /** | ||
102 | * Test the download callback with valid values and no charset | ||
103 | */ | ||
104 | public function testCurlDownloadCallbackOkNoCharset() | ||
105 | { | ||
106 | $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset'); | ||
107 | $data = [ | ||
108 | 'HTTP/1.1 200 OK', | ||
109 | 'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea', | ||
110 | '<title>ignored</title>', | ||
111 | ]; | ||
112 | foreach ($data as $key => $line) { | ||
113 | $ignore = null; | ||
114 | $this->assertEquals(strlen($line), $callback($ignore, $line)); | ||
115 | } | ||
116 | $this->assertEmpty($charset); | ||
117 | $this->assertEquals('Refactoring · GitHub', $title); | ||
118 | } | ||
119 | |||
120 | /** | ||
121 | * Test the download callback with valid values and no charset | ||
122 | */ | ||
123 | public function testCurlDownloadCallbackOkHtmlCharset() | ||
124 | { | ||
125 | $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset'); | ||
126 | $data = [ | ||
127 | 'HTTP/1.1 200 OK', | ||
128 | '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />', | ||
129 | 'end' => 'th=device-width"><title>Refactoring · GitHub</title><link rel="search" type="application/opensea', | ||
130 | '<title>ignored</title>', | ||
131 | ]; | ||
132 | foreach ($data as $key => $line) { | ||
133 | $ignore = null; | ||
134 | $expected = $key !== 'end' ? strlen($line) : false; | ||
135 | $this->assertEquals($expected, $callback($ignore, $line)); | ||
136 | if ($expected === false) { | ||
137 | break; | ||
138 | } | ||
139 | } | ||
140 | $this->assertEquals('utf-8', $charset); | ||
141 | $this->assertEquals('Refactoring · GitHub', $title); | ||
142 | } | ||
143 | |||
144 | /** | ||
145 | * Test the download callback with valid values and no title | ||
146 | */ | ||
147 | public function testCurlDownloadCallbackOkNoTitle() | ||
148 | { | ||
149 | $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok'); | ||
150 | $data = [ | ||
151 | 'HTTP/1.1 200 OK', | ||
152 | 'end' => 'th=device-width">Refactoring · GitHub<link rel="search" type="application/opensea', | ||
153 | 'ignored', | ||
154 | ]; | ||
155 | foreach ($data as $key => $line) { | ||
156 | $ignore = null; | ||
157 | $this->assertEquals(strlen($line), $callback($ignore, $line)); | ||
158 | } | ||
159 | $this->assertEquals('utf-8', $charset); | ||
160 | $this->assertEmpty($title); | ||
161 | } | ||
162 | |||
163 | /** | ||
164 | * Test the download callback with an invalid content type. | ||
165 | */ | ||
166 | public function testCurlDownloadCallbackInvalidContentType() | ||
167 | { | ||
168 | $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ct_ko'); | ||
169 | $ignore = null; | ||
170 | $this->assertFalse($callback($ignore, '')); | ||
171 | $this->assertEmpty($charset); | ||
172 | $this->assertEmpty($title); | ||
173 | } | ||
174 | |||
175 | /** | ||
176 | * Test the download callback with an invalid response code. | ||
177 | */ | ||
178 | public function testCurlDownloadCallbackInvalidResponseCode() | ||
179 | { | ||
180 | $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rc_ko'); | ||
181 | $ignore = null; | ||
182 | $this->assertFalse($callback($ignore, '')); | ||
183 | $this->assertEmpty($charset); | ||
184 | $this->assertEmpty($title); | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * Test the download callback with an invalid content type and response code. | ||
189 | */ | ||
190 | public function testCurlDownloadCallbackInvalidContentTypeAndResponseCode() | ||
191 | { | ||
192 | $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rs_ct_ko'); | ||
193 | $ignore = null; | ||
194 | $this->assertFalse($callback($ignore, '')); | ||
195 | $this->assertEmpty($charset); | ||
196 | $this->assertEmpty($title); | ||
197 | } | ||
198 | |||
199 | /** | ||
89 | * Test count_private. | 200 | * Test count_private. |
90 | */ | 201 | */ |
91 | public function testCountPrivateLinks() | 202 | public function testCountPrivateLinks() |
@@ -131,6 +242,21 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase | |||
131 | } | 242 | } |
132 | 243 | ||
133 | /** | 244 | /** |
245 | * Test text2clickable a redirector set and without URL encode. | ||
246 | */ | ||
247 | public function testText2clickableWithRedirectorDontEncode() | ||
248 | { | ||
249 | $text = 'stuff http://hello.there/?is=someone&or=something#here otherstuff'; | ||
250 | $redirector = 'http://redirector.to'; | ||
251 | $expectedText = 'stuff <a href="'. | ||
252 | $redirector . | ||
253 | 'http://hello.there/?is=someone&or=something#here' . | ||
254 | '">http://hello.there/?is=someone&or=something#here</a> otherstuff'; | ||
255 | $processedText = text2clickable($text, $redirector, false); | ||
256 | $this->assertEquals($expectedText, $processedText); | ||
257 | } | ||
258 | |||
259 | /** | ||
134 | * Test testSpace2nbsp. | 260 | * Test testSpace2nbsp. |
135 | */ | 261 | */ |
136 | public function testSpace2nbsp() | 262 | public function testSpace2nbsp() |
@@ -192,3 +318,96 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase | |||
192 | return str_replace('$1', $hashtag, $hashtagLink); | 318 | return str_replace('$1', $hashtag, $hashtagLink); |
193 | } | 319 | } |
194 | } | 320 | } |
321 | |||
322 | // old style mock: PHPUnit doesn't allow function mock | ||
323 | |||
324 | /** | ||
325 | * Returns code 200 or html content type. | ||
326 | * | ||
327 | * @param resource $ch cURL resource | ||
328 | * @param int $type cURL info type | ||
329 | * | ||
330 | * @return int|string 200 or 'text/html' | ||
331 | */ | ||
332 | function ut_curl_getinfo_ok($ch, $type) | ||
333 | { | ||
334 | switch ($type) { | ||
335 | case CURLINFO_RESPONSE_CODE: | ||
336 | return 200; | ||
337 | case CURLINFO_CONTENT_TYPE: | ||
338 | return 'text/html; charset=utf-8'; | ||
339 | } | ||
340 | } | ||
341 | |||
342 | /** | ||
343 | * Returns code 200 or html content type without charset. | ||
344 | * | ||
345 | * @param resource $ch cURL resource | ||
346 | * @param int $type cURL info type | ||
347 | * | ||
348 | * @return int|string 200 or 'text/html' | ||
349 | */ | ||
350 | function ut_curl_getinfo_no_charset($ch, $type) | ||
351 | { | ||
352 | switch ($type) { | ||
353 | case CURLINFO_RESPONSE_CODE: | ||
354 | return 200; | ||
355 | case CURLINFO_CONTENT_TYPE: | ||
356 | return 'text/html'; | ||
357 | } | ||
358 | } | ||
359 | |||
360 | /** | ||
361 | * Invalid response code. | ||
362 | * | ||
363 | * @param resource $ch cURL resource | ||
364 | * @param int $type cURL info type | ||
365 | * | ||
366 | * @return int|string 404 or 'text/html' | ||
367 | */ | ||
368 | function ut_curl_getinfo_rc_ko($ch, $type) | ||
369 | { | ||
370 | switch ($type) { | ||
371 | case CURLINFO_RESPONSE_CODE: | ||
372 | return 404; | ||
373 | case CURLINFO_CONTENT_TYPE: | ||
374 | return 'text/html; charset=utf-8'; | ||
375 | } | ||
376 | } | ||
377 | |||
378 | /** | ||
379 | * Invalid content type. | ||
380 | * | ||
381 | * @param resource $ch cURL resource | ||
382 | * @param int $type cURL info type | ||
383 | * | ||
384 | * @return int|string 200 or 'text/plain' | ||
385 | */ | ||
386 | function ut_curl_getinfo_ct_ko($ch, $type) | ||
387 | { | ||
388 | switch ($type) { | ||
389 | case CURLINFO_RESPONSE_CODE: | ||
390 | return 200; | ||
391 | case CURLINFO_CONTENT_TYPE: | ||
392 | return 'text/plain'; | ||
393 | } | ||
394 | } | ||
395 | |||
396 | /** | ||
397 | * Invalid response code and content type. | ||
398 | * | ||
399 | * @param resource $ch cURL resource | ||
400 | * @param int $type cURL info type | ||
401 | * | ||
402 | * @return int|string 404 or 'text/plain' | ||
403 | */ | ||
404 | function ut_curl_getinfo_rs_ct_ko($ch, $type) | ||
405 | { | ||
406 | switch ($type) { | ||
407 | case CURLINFO_RESPONSE_CODE: | ||
408 | return 404; | ||
409 | case CURLINFO_CONTENT_TYPE: | ||
410 | return 'text/plain'; | ||
411 | } | ||
412 | } | ||
413 | |||
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php index 5fc1d1e8..4961aa2c 100644 --- a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php +++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php | |||
@@ -132,8 +132,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
132 | public function testImportInternetExplorerEncoding() | 132 | public function testImportInternetExplorerEncoding() |
133 | { | 133 | { |
134 | $files = file2array('internet_explorer_encoding.htm'); | 134 | $files = file2array('internet_explorer_encoding.htm'); |
135 | $this->assertEquals( | 135 | $this->assertStringMatchesFormat( |
136 | 'File internet_explorer_encoding.htm (356 bytes) was successfully processed:' | 136 | 'File internet_explorer_encoding.htm (356 bytes) was successfully processed in %d seconds:' |
137 | .' 1 links imported, 0 links overwritten, 0 links skipped.', | 137 | .' 1 links imported, 0 links overwritten, 0 links skipped.', |
138 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) | 138 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) |
139 | ); | 139 | ); |
@@ -161,8 +161,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
161 | public function testImportNested() | 161 | public function testImportNested() |
162 | { | 162 | { |
163 | $files = file2array('netscape_nested.htm'); | 163 | $files = file2array('netscape_nested.htm'); |
164 | $this->assertEquals( | 164 | $this->assertStringMatchesFormat( |
165 | 'File netscape_nested.htm (1337 bytes) was successfully processed:' | 165 | 'File netscape_nested.htm (1337 bytes) was successfully processed in %d seconds:' |
166 | .' 8 links imported, 0 links overwritten, 0 links skipped.', | 166 | .' 8 links imported, 0 links overwritten, 0 links skipped.', |
167 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) | 167 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) |
168 | ); | 168 | ); |
@@ -283,8 +283,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
283 | public function testImportDefaultPrivacyNoPost() | 283 | public function testImportDefaultPrivacyNoPost() |
284 | { | 284 | { |
285 | $files = file2array('netscape_basic.htm'); | 285 | $files = file2array('netscape_basic.htm'); |
286 | $this->assertEquals( | 286 | $this->assertStringMatchesFormat( |
287 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 287 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
288 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 288 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
289 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) | 289 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) |
290 | ); | 290 | ); |
@@ -328,8 +328,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
328 | { | 328 | { |
329 | $post = array('privacy' => 'default'); | 329 | $post = array('privacy' => 'default'); |
330 | $files = file2array('netscape_basic.htm'); | 330 | $files = file2array('netscape_basic.htm'); |
331 | $this->assertEquals( | 331 | $this->assertStringMatchesFormat( |
332 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 332 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
333 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 333 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
334 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 334 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
335 | ); | 335 | ); |
@@ -372,8 +372,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
372 | { | 372 | { |
373 | $post = array('privacy' => 'public'); | 373 | $post = array('privacy' => 'public'); |
374 | $files = file2array('netscape_basic.htm'); | 374 | $files = file2array('netscape_basic.htm'); |
375 | $this->assertEquals( | 375 | $this->assertStringMatchesFormat( |
376 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 376 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
377 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 377 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
378 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 378 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
379 | ); | 379 | ); |
@@ -396,8 +396,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
396 | { | 396 | { |
397 | $post = array('privacy' => 'private'); | 397 | $post = array('privacy' => 'private'); |
398 | $files = file2array('netscape_basic.htm'); | 398 | $files = file2array('netscape_basic.htm'); |
399 | $this->assertEquals( | 399 | $this->assertStringMatchesFormat( |
400 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 400 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
401 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 401 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
402 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 402 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
403 | ); | 403 | ); |
@@ -422,8 +422,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
422 | 422 | ||
423 | // import links as private | 423 | // import links as private |
424 | $post = array('privacy' => 'private'); | 424 | $post = array('privacy' => 'private'); |
425 | $this->assertEquals( | 425 | $this->assertStringMatchesFormat( |
426 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 426 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
427 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 427 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
428 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 428 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
429 | ); | 429 | ); |
@@ -442,8 +442,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
442 | 'privacy' => 'public', | 442 | 'privacy' => 'public', |
443 | 'overwrite' => 'true' | 443 | 'overwrite' => 'true' |
444 | ); | 444 | ); |
445 | $this->assertEquals( | 445 | $this->assertStringMatchesFormat( |
446 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 446 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
447 | .' 2 links imported, 2 links overwritten, 0 links skipped.', | 447 | .' 2 links imported, 2 links overwritten, 0 links skipped.', |
448 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 448 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
449 | ); | 449 | ); |
@@ -468,8 +468,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
468 | 468 | ||
469 | // import links as public | 469 | // import links as public |
470 | $post = array('privacy' => 'public'); | 470 | $post = array('privacy' => 'public'); |
471 | $this->assertEquals( | 471 | $this->assertStringMatchesFormat( |
472 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 472 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
473 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 473 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
474 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 474 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
475 | ); | 475 | ); |
@@ -489,8 +489,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
489 | 'privacy' => 'private', | 489 | 'privacy' => 'private', |
490 | 'overwrite' => 'true' | 490 | 'overwrite' => 'true' |
491 | ); | 491 | ); |
492 | $this->assertEquals( | 492 | $this->assertStringMatchesFormat( |
493 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 493 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
494 | .' 2 links imported, 2 links overwritten, 0 links skipped.', | 494 | .' 2 links imported, 2 links overwritten, 0 links skipped.', |
495 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 495 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
496 | ); | 496 | ); |
@@ -513,8 +513,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
513 | { | 513 | { |
514 | $post = array('privacy' => 'public'); | 514 | $post = array('privacy' => 'public'); |
515 | $files = file2array('netscape_basic.htm'); | 515 | $files = file2array('netscape_basic.htm'); |
516 | $this->assertEquals( | 516 | $this->assertStringMatchesFormat( |
517 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 517 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
518 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 518 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
519 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 519 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
520 | ); | 520 | ); |
@@ -523,8 +523,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
523 | 523 | ||
524 | // re-import as private, DO NOT enable overwriting | 524 | // re-import as private, DO NOT enable overwriting |
525 | $post = array('privacy' => 'private'); | 525 | $post = array('privacy' => 'private'); |
526 | $this->assertEquals( | 526 | $this->assertStringMatchesFormat( |
527 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 527 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
528 | .' 0 links imported, 0 links overwritten, 2 links skipped.', | 528 | .' 0 links imported, 0 links overwritten, 2 links skipped.', |
529 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 529 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
530 | ); | 530 | ); |
@@ -542,8 +542,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
542 | 'default_tags' => 'tag1,tag2 tag3' | 542 | 'default_tags' => 'tag1,tag2 tag3' |
543 | ); | 543 | ); |
544 | $files = file2array('netscape_basic.htm'); | 544 | $files = file2array('netscape_basic.htm'); |
545 | $this->assertEquals( | 545 | $this->assertStringMatchesFormat( |
546 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 546 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
547 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 547 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
548 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 548 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
549 | ); | 549 | ); |
@@ -569,8 +569,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
569 | 'default_tags' => 'tag1&,tag2 "tag3"' | 569 | 'default_tags' => 'tag1&,tag2 "tag3"' |
570 | ); | 570 | ); |
571 | $files = file2array('netscape_basic.htm'); | 571 | $files = file2array('netscape_basic.htm'); |
572 | $this->assertEquals( | 572 | $this->assertStringMatchesFormat( |
573 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 573 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
574 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 574 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
575 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 575 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
576 | ); | 576 | ); |
@@ -594,8 +594,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
594 | public function testImportSameDate() | 594 | public function testImportSameDate() |
595 | { | 595 | { |
596 | $files = file2array('same_date.htm'); | 596 | $files = file2array('same_date.htm'); |
597 | $this->assertEquals( | 597 | $this->assertStringMatchesFormat( |
598 | 'File same_date.htm (453 bytes) was successfully processed:' | 598 | 'File same_date.htm (453 bytes) was successfully processed in %d seconds:' |
599 | .' 3 links imported, 0 links overwritten, 0 links skipped.', | 599 | .' 3 links imported, 0 links overwritten, 0 links skipped.', |
600 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history) | 600 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history) |
601 | ); | 601 | ); |
@@ -622,24 +622,19 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
622 | 'overwrite' => 'true', | 622 | 'overwrite' => 'true', |
623 | ]; | 623 | ]; |
624 | $files = file2array('netscape_basic.htm'); | 624 | $files = file2array('netscape_basic.htm'); |
625 | $nbLinks = 2; | ||
626 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); | 625 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); |
627 | $history = $this->history->getHistory(); | 626 | $history = $this->history->getHistory(); |
628 | $this->assertEquals($nbLinks, count($history)); | 627 | $this->assertEquals(1, count($history)); |
629 | foreach ($history as $value) { | 628 | $this->assertEquals(History::IMPORT, $history[0]['event']); |
630 | $this->assertEquals(History::CREATED, $value['event']); | 629 | $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']); |
631 | $this->assertTrue(new DateTime('-5 seconds') < $value['datetime']); | ||
632 | $this->assertTrue(is_int($value['id'])); | ||
633 | } | ||
634 | 630 | ||
635 | // re-import as private, enable overwriting | 631 | // re-import as private, enable overwriting |
636 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); | 632 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); |
637 | $history = $this->history->getHistory(); | 633 | $history = $this->history->getHistory(); |
638 | $this->assertEquals($nbLinks * 2, count($history)); | 634 | $this->assertEquals(2, count($history)); |
639 | for ($i = 0 ; $i < $nbLinks ; $i++) { | 635 | $this->assertEquals(History::IMPORT, $history[0]['event']); |
640 | $this->assertEquals(History::UPDATED, $history[$i]['event']); | 636 | $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']); |
641 | $this->assertTrue(new DateTime('-5 seconds') < $history[$i]['datetime']); | 637 | $this->assertEquals(History::IMPORT, $history[1]['event']); |
642 | $this->assertTrue(is_int($history[$i]['id'])); | 638 | $this->assertTrue(new DateTime('-5 seconds') < $history[1]['datetime']); |
643 | } | ||
644 | } | 639 | } |
645 | } | 640 | } |
diff --git a/tests/SessionManagerTest.php b/tests/SessionManagerTest.php new file mode 100644 index 00000000..aa75962a --- /dev/null +++ b/tests/SessionManagerTest.php | |||
@@ -0,0 +1,149 @@ | |||
1 | <?php | ||
2 | require_once 'tests/utils/FakeConfigManager.php'; | ||
3 | |||
4 | // Initialize reference data _before_ PHPUnit starts a session | ||
5 | require_once 'tests/utils/ReferenceSessionIdHashes.php'; | ||
6 | ReferenceSessionIdHashes::genAllHashes(); | ||
7 | |||
8 | use \Shaarli\SessionManager; | ||
9 | use \PHPUnit\Framework\TestCase; | ||
10 | |||
11 | |||
12 | /** | ||
13 | * Test coverage for SessionManager | ||
14 | */ | ||
15 | class SessionManagerTest extends TestCase | ||
16 | { | ||
17 | // Session ID hashes | ||
18 | protected static $sidHashes = null; | ||
19 | |||
20 | // Fake ConfigManager | ||
21 | protected static $conf = null; | ||
22 | |||
23 | /** | ||
24 | * Assign reference data | ||
25 | */ | ||
26 | public static function setUpBeforeClass() | ||
27 | { | ||
28 | self::$sidHashes = ReferenceSessionIdHashes::getHashes(); | ||
29 | self::$conf = new FakeConfigManager(); | ||
30 | } | ||
31 | |||
32 | /** | ||
33 | * Generate a session token | ||
34 | */ | ||
35 | public function testGenerateToken() | ||
36 | { | ||
37 | $session = []; | ||
38 | $sessionManager = new SessionManager($session, self::$conf); | ||
39 | |||
40 | $token = $sessionManager->generateToken(); | ||
41 | |||
42 | $this->assertEquals(1, $session['tokens'][$token]); | ||
43 | $this->assertEquals(40, strlen($token)); | ||
44 | } | ||
45 | |||
46 | /** | ||
47 | * Check a session token | ||
48 | */ | ||
49 | public function testCheckToken() | ||
50 | { | ||
51 | $token = '4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b'; | ||
52 | $session = [ | ||
53 | 'tokens' => [ | ||
54 | $token => 1, | ||
55 | ], | ||
56 | ]; | ||
57 | $sessionManager = new SessionManager($session, self::$conf); | ||
58 | |||
59 | // check and destroy the token | ||
60 | $this->assertTrue($sessionManager->checkToken($token)); | ||
61 | $this->assertFalse(isset($session['tokens'][$token])); | ||
62 | |||
63 | // ensure the token has been destroyed | ||
64 | $this->assertFalse($sessionManager->checkToken($token)); | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * Generate and check a session token | ||
69 | */ | ||
70 | public function testGenerateAndCheckToken() | ||
71 | { | ||
72 | $session = []; | ||
73 | $sessionManager = new SessionManager($session, self::$conf); | ||
74 | |||
75 | $token = $sessionManager->generateToken(); | ||
76 | |||
77 | // ensure a token has been generated | ||
78 | $this->assertEquals(1, $session['tokens'][$token]); | ||
79 | $this->assertEquals(40, strlen($token)); | ||
80 | |||
81 | // check and destroy the token | ||
82 | $this->assertTrue($sessionManager->checkToken($token)); | ||
83 | $this->assertFalse(isset($session['tokens'][$token])); | ||
84 | |||
85 | // ensure the token has been destroyed | ||
86 | $this->assertFalse($sessionManager->checkToken($token)); | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Check an invalid session token | ||
91 | */ | ||
92 | public function testCheckInvalidToken() | ||
93 | { | ||
94 | $session = []; | ||
95 | $sessionManager = new SessionManager($session, self::$conf); | ||
96 | |||
97 | $this->assertFalse($sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b')); | ||
98 | } | ||
99 | |||
100 | /** | ||
101 | * Test SessionManager::checkId with a valid ID - TEST ALL THE HASHES! | ||
102 | * | ||
103 | * This tests extensively covers all hash algorithms / bit representations | ||
104 | */ | ||
105 | public function testIsAnyHashSessionIdValid() | ||
106 | { | ||
107 | foreach (self::$sidHashes as $algo => $bpcs) { | ||
108 | foreach ($bpcs as $bpc => $hash) { | ||
109 | $this->assertTrue(SessionManager::checkId($hash)); | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | |||
114 | /** | ||
115 | * Test checkId with a valid ID - SHA-1 hashes | ||
116 | */ | ||
117 | public function testIsSha1SessionIdValid() | ||
118 | { | ||
119 | $this->assertTrue(SessionManager::checkId(sha1('shaarli'))); | ||
120 | } | ||
121 | |||
122 | /** | ||
123 | * Test checkId with a valid ID - SHA-256 hashes | ||
124 | */ | ||
125 | public function testIsSha256SessionIdValid() | ||
126 | { | ||
127 | $this->assertTrue(SessionManager::checkId(hash('sha256', 'shaarli'))); | ||
128 | } | ||
129 | |||
130 | /** | ||
131 | * Test checkId with a valid ID - SHA-512 hashes | ||
132 | */ | ||
133 | public function testIsSha512SessionIdValid() | ||
134 | { | ||
135 | $this->assertTrue(SessionManager::checkId(hash('sha512', 'shaarli'))); | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Test checkId with invalid IDs. | ||
140 | */ | ||
141 | public function testIsSessionIdInvalid() | ||
142 | { | ||
143 | $this->assertFalse(SessionManager::checkId('')); | ||
144 | $this->assertFalse(SessionManager::checkId([])); | ||
145 | $this->assertFalse( | ||
146 | SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=') | ||
147 | ); | ||
148 | } | ||
149 | } | ||
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index 3d1aa653..6cd37a7a 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php | |||
@@ -5,10 +5,6 @@ | |||
5 | 5 | ||
6 | require_once 'application/Utils.php'; | 6 | require_once 'application/Utils.php'; |
7 | require_once 'application/Languages.php'; | 7 | require_once 'application/Languages.php'; |
8 | require_once 'tests/utils/ReferenceSessionIdHashes.php'; | ||
9 | |||
10 | // Initialize reference data before PHPUnit starts a session | ||
11 | ReferenceSessionIdHashes::genAllHashes(); | ||
12 | 8 | ||
13 | 9 | ||
14 | /** | 10 | /** |
@@ -16,9 +12,6 @@ ReferenceSessionIdHashes::genAllHashes(); | |||
16 | */ | 12 | */ |
17 | class UtilsTest extends PHPUnit_Framework_TestCase | 13 | class UtilsTest extends PHPUnit_Framework_TestCase |
18 | { | 14 | { |
19 | // Session ID hashes | ||
20 | protected static $sidHashes = null; | ||
21 | |||
22 | // Log file | 15 | // Log file |
23 | protected static $testLogFile = 'tests.log'; | 16 | protected static $testLogFile = 'tests.log'; |
24 | 17 | ||
@@ -30,13 +23,11 @@ class UtilsTest extends PHPUnit_Framework_TestCase | |||
30 | */ | 23 | */ |
31 | protected static $defaultTimeZone; | 24 | protected static $defaultTimeZone; |
32 | 25 | ||
33 | |||
34 | /** | 26 | /** |
35 | * Assign reference data | 27 | * Assign reference data |
36 | */ | 28 | */ |
37 | public static function setUpBeforeClass() | 29 | public static function setUpBeforeClass() |
38 | { | 30 | { |
39 | self::$sidHashes = ReferenceSessionIdHashes::getHashes(); | ||
40 | self::$defaultTimeZone = date_default_timezone_get(); | 31 | self::$defaultTimeZone = date_default_timezone_get(); |
41 | // Timezone without DST for test consistency | 32 | // Timezone without DST for test consistency |
42 | date_default_timezone_set('Africa/Nairobi'); | 33 | date_default_timezone_set('Africa/Nairobi'); |
@@ -221,57 +212,8 @@ class UtilsTest extends PHPUnit_Framework_TestCase | |||
221 | $this->assertEquals('?', generateLocation($ref, 'localhost')); | 212 | $this->assertEquals('?', generateLocation($ref, 'localhost')); |
222 | } | 213 | } |
223 | 214 | ||
224 | /** | ||
225 | * Test is_session_id_valid with a valid ID - TEST ALL THE HASHES! | ||
226 | * | ||
227 | * This tests extensively covers all hash algorithms / bit representations | ||
228 | */ | ||
229 | public function testIsAnyHashSessionIdValid() | ||
230 | { | ||
231 | foreach (self::$sidHashes as $algo => $bpcs) { | ||
232 | foreach ($bpcs as $bpc => $hash) { | ||
233 | $this->assertTrue(is_session_id_valid($hash)); | ||
234 | } | ||
235 | } | ||
236 | } | ||
237 | 215 | ||
238 | /** | 216 | /** |
239 | * Test is_session_id_valid with a valid ID - SHA-1 hashes | ||
240 | */ | ||
241 | public function testIsSha1SessionIdValid() | ||
242 | { | ||
243 | $this->assertTrue(is_session_id_valid(sha1('shaarli'))); | ||
244 | } | ||
245 | |||
246 | /** | ||
247 | * Test is_session_id_valid with a valid ID - SHA-256 hashes | ||
248 | */ | ||
249 | public function testIsSha256SessionIdValid() | ||
250 | { | ||
251 | $this->assertTrue(is_session_id_valid(hash('sha256', 'shaarli'))); | ||
252 | } | ||
253 | |||
254 | /** | ||
255 | * Test is_session_id_valid with a valid ID - SHA-512 hashes | ||
256 | */ | ||
257 | public function testIsSha512SessionIdValid() | ||
258 | { | ||
259 | $this->assertTrue(is_session_id_valid(hash('sha512', 'shaarli'))); | ||
260 | } | ||
261 | |||
262 | /** | ||
263 | * Test is_session_id_valid with invalid IDs. | ||
264 | */ | ||
265 | public function testIsSessionIdInvalid() | ||
266 | { | ||
267 | $this->assertFalse(is_session_id_valid('')); | ||
268 | $this->assertFalse(is_session_id_valid(array())); | ||
269 | $this->assertFalse( | ||
270 | is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=') | ||
271 | ); | ||
272 | } | ||
273 | |||
274 | /** | ||
275 | * Test generateSecretApi. | 217 | * Test generateSecretApi. |
276 | */ | 218 | */ |
277 | public function testGenerateSecretApi() | 219 | public function testGenerateSecretApi() |
@@ -384,18 +326,18 @@ class UtilsTest extends PHPUnit_Framework_TestCase | |||
384 | */ | 326 | */ |
385 | public function testHumanBytes() | 327 | public function testHumanBytes() |
386 | { | 328 | { |
387 | $this->assertEquals('2kiB', human_bytes(2 * 1024)); | 329 | $this->assertEquals('2'. t('kiB'), human_bytes(2 * 1024)); |
388 | $this->assertEquals('2kiB', human_bytes(strval(2 * 1024))); | 330 | $this->assertEquals('2'. t('kiB'), human_bytes(strval(2 * 1024))); |
389 | $this->assertEquals('2MiB', human_bytes(2 * (pow(1024, 2)))); | 331 | $this->assertEquals('2'. t('MiB'), human_bytes(2 * (pow(1024, 2)))); |
390 | $this->assertEquals('2MiB', human_bytes(strval(2 * (pow(1024, 2))))); | 332 | $this->assertEquals('2'. t('MiB'), human_bytes(strval(2 * (pow(1024, 2))))); |
391 | $this->assertEquals('2GiB', human_bytes(2 * (pow(1024, 3)))); | 333 | $this->assertEquals('2'. t('GiB'), human_bytes(2 * (pow(1024, 3)))); |
392 | $this->assertEquals('2GiB', human_bytes(strval(2 * (pow(1024, 3))))); | 334 | $this->assertEquals('2'. t('GiB'), human_bytes(strval(2 * (pow(1024, 3))))); |
393 | $this->assertEquals('374B', human_bytes(374)); | 335 | $this->assertEquals('374'. t('B'), human_bytes(374)); |
394 | $this->assertEquals('374B', human_bytes('374')); | 336 | $this->assertEquals('374'. t('B'), human_bytes('374')); |
395 | $this->assertEquals('232kiB', human_bytes(237481)); | 337 | $this->assertEquals('232'. t('kiB'), human_bytes(237481)); |
396 | $this->assertEquals('Unlimited', human_bytes('0')); | 338 | $this->assertEquals(t('Unlimited'), human_bytes('0')); |
397 | $this->assertEquals('Unlimited', human_bytes(0)); | 339 | $this->assertEquals(t('Unlimited'), human_bytes(0)); |
398 | $this->assertEquals('Setting not set', human_bytes('')); | 340 | $this->assertEquals(t('Setting not set'), human_bytes('')); |
399 | } | 341 | } |
400 | 342 | ||
401 | /** | 343 | /** |
@@ -403,9 +345,9 @@ class UtilsTest extends PHPUnit_Framework_TestCase | |||
403 | */ | 345 | */ |
404 | public function testGetMaxUploadSize() | 346 | public function testGetMaxUploadSize() |
405 | { | 347 | { |
406 | $this->assertEquals('1MiB', get_max_upload_size(2097152, '1024k')); | 348 | $this->assertEquals('1'. t('MiB'), get_max_upload_size(2097152, '1024k')); |
407 | $this->assertEquals('1MiB', get_max_upload_size('1m', '2m')); | 349 | $this->assertEquals('1'. t('MiB'), get_max_upload_size('1m', '2m')); |
408 | $this->assertEquals('100B', get_max_upload_size(100, 100)); | 350 | $this->assertEquals('100'. t('B'), get_max_upload_size(100, 100)); |
409 | } | 351 | } |
410 | 352 | ||
411 | /** | 353 | /** |
diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000..d36d73cd --- /dev/null +++ b/tests/bootstrap.php | |||
@@ -0,0 +1,6 @@ | |||
1 | <?php | ||
2 | |||
3 | require_once 'vendor/autoload.php'; | ||
4 | |||
5 | $conf = new \Shaarli\Config\ConfigManager('tests/utils/config/configJson'); | ||
6 | new \Shaarli\Languages('en', $conf); | ||
diff --git a/tests/languages/bootstrap.php b/tests/languages/bootstrap.php index 95609210..da6ac2e4 100644 --- a/tests/languages/bootstrap.php +++ b/tests/languages/bootstrap.php | |||
@@ -1,7 +1,6 @@ | |||
1 | <?php | 1 | <?php |
2 | if (! empty('UT_LOCALE')) { | 2 | require_once 'tests/bootstrap.php'; |
3 | |||
4 | if (! empty(getenv('UT_LOCALE'))) { | ||
3 | setlocale(LC_ALL, getenv('UT_LOCALE')); | 5 | setlocale(LC_ALL, getenv('UT_LOCALE')); |
4 | } | 6 | } |
5 | |||
6 | require_once 'vendor/autoload.php'; | ||
7 | |||
diff --git a/tests/languages/fr/LanguagesFrTest.php b/tests/languages/fr/LanguagesFrTest.php new file mode 100644 index 00000000..79d05172 --- /dev/null +++ b/tests/languages/fr/LanguagesFrTest.php | |||
@@ -0,0 +1,175 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | namespace Shaarli; | ||
5 | |||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | |||
9 | /** | ||
10 | * Class LanguagesFrTest | ||
11 | * | ||
12 | * Test the translation system in PHP and gettext mode with French language. | ||
13 | * | ||
14 | * @package Shaarli | ||
15 | */ | ||
16 | class LanguagesFrTest extends \PHPUnit_Framework_TestCase | ||
17 | { | ||
18 | /** | ||
19 | * @var string Config file path (without extension). | ||
20 | */ | ||
21 | protected static $configFile = 'tests/utils/config/configJson'; | ||
22 | |||
23 | /** | ||
24 | * @var ConfigManager | ||
25 | */ | ||
26 | protected $conf; | ||
27 | |||
28 | /** | ||
29 | * Init: force French | ||
30 | */ | ||
31 | public function setUp() | ||
32 | { | ||
33 | $this->conf = new ConfigManager(self::$configFile); | ||
34 | $this->conf->set('translation.language', 'fr'); | ||
35 | } | ||
36 | |||
37 | /** | ||
38 | * Reset the locale since gettext seems to mess with it, making it too long | ||
39 | */ | ||
40 | public static function tearDownAfterClass() | ||
41 | { | ||
42 | if (! empty(getenv('UT_LOCALE'))) { | ||
43 | setlocale(LC_ALL, getenv('UT_LOCALE')); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | /** | ||
48 | * Test t() with a simple non identified value. | ||
49 | */ | ||
50 | public function testTranslateSingleNotIDGettext() | ||
51 | { | ||
52 | $this->conf->set('translation.mode', 'gettext'); | ||
53 | new Languages('en', $this->conf); | ||
54 | $text = 'abcdé 564 fgK'; | ||
55 | $this->assertEquals($text, t($text)); | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * Test t() with a simple identified value in gettext mode. | ||
60 | */ | ||
61 | public function testTranslateSingleIDGettext() | ||
62 | { | ||
63 | $this->conf->set('translation.mode', 'gettext'); | ||
64 | new Languages('en', $this->conf); | ||
65 | $text = 'permalink'; | ||
66 | $this->assertEquals('permalien', t($text)); | ||
67 | } | ||
68 | |||
69 | /** | ||
70 | * Test t() with a non identified plural form in gettext mode. | ||
71 | */ | ||
72 | public function testTranslatePluralNotIDGettext() | ||
73 | { | ||
74 | $this->conf->set('translation.mode', 'gettext'); | ||
75 | new Languages('en', $this->conf); | ||
76 | $text = 'sandwich'; | ||
77 | $nText = 'sandwiches'; | ||
78 | // Not ID, so English fallback, and in english, plural 0 | ||
79 | $this->assertEquals('sandwiches', t($text, $nText, 0)); | ||
80 | $this->assertEquals('sandwich', t($text, $nText, 1)); | ||
81 | $this->assertEquals('sandwiches', t($text, $nText, 2)); | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Test t() with an identified plural form in gettext mode. | ||
86 | */ | ||
87 | public function testTranslatePluralIDGettext() | ||
88 | { | ||
89 | $this->conf->set('translation.mode', 'gettext'); | ||
90 | new Languages('en', $this->conf); | ||
91 | $text = 'shaare'; | ||
92 | $nText = 'shaares'; | ||
93 | $this->assertEquals('shaare', t($text, $nText, 0)); | ||
94 | $this->assertEquals('shaare', t($text, $nText, 1)); | ||
95 | $this->assertEquals('shaares', t($text, $nText, 2)); | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * Test t() with a simple non identified value. | ||
100 | */ | ||
101 | public function testTranslateSingleNotIDPhp() | ||
102 | { | ||
103 | $this->conf->set('translation.mode', 'php'); | ||
104 | new Languages('en', $this->conf); | ||
105 | $text = 'abcdé 564 fgK'; | ||
106 | $this->assertEquals($text, t($text)); | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * Test t() with a simple identified value in PHP mode. | ||
111 | */ | ||
112 | public function testTranslateSingleIDPhp() | ||
113 | { | ||
114 | $this->conf->set('translation.mode', 'php'); | ||
115 | new Languages('en', $this->conf); | ||
116 | $text = 'permalink'; | ||
117 | $this->assertEquals('permalien', t($text)); | ||
118 | } | ||
119 | |||
120 | /** | ||
121 | * Test t() with a non identified plural form in PHP mode. | ||
122 | */ | ||
123 | public function testTranslatePluralNotIDPhp() | ||
124 | { | ||
125 | $this->conf->set('translation.mode', 'php'); | ||
126 | new Languages('en', $this->conf); | ||
127 | $text = 'sandwich'; | ||
128 | $nText = 'sandwiches'; | ||
129 | // Not ID, so English fallback, and in english, plural 0 | ||
130 | $this->assertEquals('sandwiches', t($text, $nText, 0)); | ||
131 | $this->assertEquals('sandwich', t($text, $nText, 1)); | ||
132 | $this->assertEquals('sandwiches', t($text, $nText, 2)); | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * Test t() with an identified plural form in PHP mode. | ||
137 | */ | ||
138 | public function testTranslatePluralIDPhp() | ||
139 | { | ||
140 | $this->conf->set('translation.mode', 'php'); | ||
141 | new Languages('en', $this->conf); | ||
142 | $text = 'shaare'; | ||
143 | $nText = 'shaares'; | ||
144 | // In english, zero is followed by plural form | ||
145 | $this->assertEquals('shaare', t($text, $nText, 0)); | ||
146 | $this->assertEquals('shaare', t($text, $nText, 1)); | ||
147 | $this->assertEquals('shaares', t($text, $nText, 2)); | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Test t() with an extension language file in gettext mode | ||
152 | */ | ||
153 | public function testTranslationExtensionGettext() | ||
154 | { | ||
155 | $this->conf->set('translation.mode', 'gettext'); | ||
156 | $this->conf->set('translation.extensions.test', 'tests/utils/languages/'); | ||
157 | new Languages('en', $this->conf); | ||
158 | $txt = 'car'; // ignore me poedit | ||
159 | $this->assertEquals('voiture', t($txt, $txt, 1, 'test')); | ||
160 | $this->assertEquals('Fouille', t('Search', 'Search', 1, 'test')); | ||
161 | } | ||
162 | |||
163 | /** | ||
164 | * Test t() with an extension language file in PHP mode | ||
165 | */ | ||
166 | public function testTranslationExtensionPhp() | ||
167 | { | ||
168 | $this->conf->set('translation.mode', 'php'); | ||
169 | $this->conf->set('translation.extensions.test', 'tests/utils/languages/'); | ||
170 | new Languages('en', $this->conf); | ||
171 | $txt = 'car'; // ignore me poedit | ||
172 | $this->assertEquals('voiture', t($txt, $txt, 1, 'test')); | ||
173 | $this->assertEquals('Fouille', t('Search', 'Search', 1, 'test')); | ||
174 | } | ||
175 | } | ||
diff --git a/tests/utils/FakeConfigManager.php b/tests/utils/FakeConfigManager.php new file mode 100644 index 00000000..f29760cb --- /dev/null +++ b/tests/utils/FakeConfigManager.php | |||
@@ -0,0 +1,12 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Fake ConfigManager | ||
5 | */ | ||
6 | class FakeConfigManager | ||
7 | { | ||
8 | public static function get($key) | ||
9 | { | ||
10 | return $key; | ||
11 | } | ||
12 | } | ||
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php index f09eebc1..e887aa78 100644 --- a/tests/utils/ReferenceLinkDB.php +++ b/tests/utils/ReferenceLinkDB.php | |||
@@ -141,6 +141,7 @@ class ReferenceLinkDB | |||
141 | */ | 141 | */ |
142 | public function write($filename) | 142 | public function write($filename) |
143 | { | 143 | { |
144 | $this->reorder(); | ||
144 | file_put_contents( | 145 | file_put_contents( |
145 | $filename, | 146 | $filename, |
146 | '<?php /* '.base64_encode(gzdeflate(serialize($this->_links))).' */ ?>' | 147 | '<?php /* '.base64_encode(gzdeflate(serialize($this->_links))).' */ ?>' |
@@ -148,6 +149,27 @@ class ReferenceLinkDB | |||
148 | } | 149 | } |
149 | 150 | ||
150 | /** | 151 | /** |
152 | * Reorder links by creation date (newest first). | ||
153 | * | ||
154 | * Also update the urls and ids mapping arrays. | ||
155 | * | ||
156 | * @param string $order ASC|DESC | ||
157 | */ | ||
158 | public function reorder($order = 'DESC') | ||
159 | { | ||
160 | // backward compatibility: ignore reorder if the the `created` field doesn't exist | ||
161 | if (! isset(array_values($this->_links)[0]['created'])) { | ||
162 | return; | ||
163 | } | ||
164 | |||
165 | $order = $order === 'ASC' ? -1 : 1; | ||
166 | // Reorder array by dates. | ||
167 | usort($this->_links, function($a, $b) use ($order) { | ||
168 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; | ||
169 | }); | ||
170 | } | ||
171 | |||
172 | /** | ||
151 | * Returns the number of links in the reference data | 173 | * Returns the number of links in the reference data |
152 | */ | 174 | */ |
153 | public function countLinks() | 175 | public function countLinks() |
@@ -187,6 +209,7 @@ class ReferenceLinkDB | |||
187 | 209 | ||
188 | public function getLinks() | 210 | public function getLinks() |
189 | { | 211 | { |
212 | $this->reorder(); | ||
190 | return $this->_links; | 213 | return $this->_links; |
191 | } | 214 | } |
192 | 215 | ||
diff --git a/tests/utils/languages/fr/LC_MESSAGES/test.mo b/tests/utils/languages/fr/LC_MESSAGES/test.mo new file mode 100644 index 00000000..416c7831 --- /dev/null +++ b/tests/utils/languages/fr/LC_MESSAGES/test.mo | |||
Binary files differ | |||
diff --git a/tests/utils/languages/fr/LC_MESSAGES/test.po b/tests/utils/languages/fr/LC_MESSAGES/test.po new file mode 100644 index 00000000..89a4fd9b --- /dev/null +++ b/tests/utils/languages/fr/LC_MESSAGES/test.po | |||
@@ -0,0 +1,19 @@ | |||
1 | msgid "" | ||
2 | msgstr "" | ||
3 | "Project-Id-Version: Extension test\n" | ||
4 | "POT-Creation-Date: 2017-05-20 13:54+0200\n" | ||
5 | "PO-Revision-Date: 2017-05-20 14:16+0200\n" | ||
6 | "Last-Translator: \n" | ||
7 | "Language-Team: Shaarli\n" | ||
8 | "Language: fr_FR\n" | ||
9 | "MIME-Version: 1.0\n" | ||
10 | "Content-Type: text/plain; charset=UTF-8\n" | ||
11 | "Content-Transfer-Encoding: 8bit\n" | ||
12 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" | ||
13 | "X-Generator: Poedit 2.0.1\n" | ||
14 | |||
15 | msgid "car" | ||
16 | msgstr "voiture" | ||
17 | |||
18 | msgid "Search" | ||
19 | msgstr "Fouille" | ||
diff --git a/tpl/default/changetag.html b/tpl/default/changetag.html index 49dd20d9..6606c4fa 100644 --- a/tpl/default/changetag.html +++ b/tpl/default/changetag.html | |||
@@ -32,7 +32,7 @@ | |||
32 | </div> | 32 | </div> |
33 | </form> | 33 | </form> |
34 | 34 | ||
35 | <p>You can also edit tags in the <a href="?do=taglist&sort=usage">tag list</a>.</p> | 35 | <p>{'You can also edit tags in the'|t} <a href="?do=taglist&sort=usage">{'tag list'|t}</a>.</p> |
36 | </div> | 36 | </div> |
37 | </div> | 37 | </div> |
38 | {include="page.footer"} | 38 | {include="page.footer"} |
diff --git a/tpl/default/configure.html b/tpl/default/configure.html index 76a1b9fd..a63c7ad3 100644 --- a/tpl/default/configure.html +++ b/tpl/default/configure.html | |||
@@ -70,6 +70,30 @@ | |||
70 | </div> | 70 | </div> |
71 | </div> | 71 | </div> |
72 | <div class="pure-g"> | 72 | <div class="pure-g"> |
73 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> | ||
74 | <div class="form-label"> | ||
75 | <label for="language"> | ||
76 | <span class="label-name">{'Language'|t}</span> | ||
77 | </label> | ||
78 | </div> | ||
79 | </div> | ||
80 | <div class="pure-u-lg-{$ratioInput} pure-u-1"> | ||
81 | <div class="form-input"> | ||
82 | <select name="language" id="language" class="align"> | ||
83 | {loop="$languages"} | ||
84 | <option value="{$key}" | ||
85 | {if="$key===$language"} | ||
86 | selected="selected" | ||
87 | {/if} | ||
88 | > | ||
89 | {$value} | ||
90 | </option> | ||
91 | {/loop} | ||
92 | </select> | ||
93 | </div> | ||
94 | </div> | ||
95 | </div> | ||
96 | <div class="pure-g"> | ||
73 | <div class="pure-u-lg-{$ratioLabel} pure-u-1 "> | 97 | <div class="pure-u-lg-{$ratioLabel} pure-u-1 "> |
74 | <div class="form-label"> | 98 | <div class="form-label"> |
75 | <label> | 99 | <label> |
@@ -105,21 +129,6 @@ | |||
105 | </div> | 129 | </div> |
106 | </div> | 130 | </div> |
107 | </div> | 131 | </div> |
108 | <div class="pure-g"> | ||
109 | <div class="pure-u-lg-{$ratioLabel} pure-u-1 "> | ||
110 | <div class="form-label"> | ||
111 | <label for="redirector"> | ||
112 | <span class="label-name">{'Redirector'|t}</span><br> | ||
113 | <span class="label-desc">{'e. g.'|t} <i>http://anonym.to/?</i> {'will mask the HTTP_REFERER'|t}</span> | ||
114 | </label> | ||
115 | </div> | ||
116 | </div> | ||
117 | <div class="pure-u-lg-{$ratioInput} pure-u-1 "> | ||
118 | <div class="form-input"> | ||
119 | <input type="text" name="redirector" id="redirector" size="50" value="{$redirector}"> | ||
120 | </div> | ||
121 | </div> | ||
122 | </div> | ||
123 | <div class="clear"></div> | 132 | <div class="clear"></div> |
124 | <div class="pure-g"> | 133 | <div class="pure-g"> |
125 | <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile} "> | 134 | <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile} "> |
diff --git a/tpl/default/css/shaarli.css b/tpl/default/css/shaarli.css index ba589723..14439402 100644 --- a/tpl/default/css/shaarli.css +++ b/tpl/default/css/shaarli.css | |||
@@ -433,7 +433,7 @@ body, .pure-g [class*="pure-u"] { | |||
433 | * 64em -> lg | 433 | * 64em -> lg |
434 | */ | 434 | */ |
435 | .linklist-filters { | 435 | .linklist-filters { |
436 | margin: 10px 0; | 436 | margin: 5px 0; |
437 | color: #252525; | 437 | color: #252525; |
438 | font-size: 0.9em; | 438 | font-size: 0.9em; |
439 | } | 439 | } |
@@ -454,7 +454,7 @@ body, .pure-g [class*="pure-u"] { | |||
454 | } | 454 | } |
455 | 455 | ||
456 | .linklist-pages { | 456 | .linklist-pages { |
457 | margin: 10px 0; | 457 | margin: 5px 0; |
458 | color: #252525; | 458 | color: #252525; |
459 | text-align: center; | 459 | text-align: center; |
460 | } | 460 | } |
@@ -469,7 +469,7 @@ body, .pure-g [class*="pure-u"] { | |||
469 | } | 469 | } |
470 | 470 | ||
471 | .linksperpage { | 471 | .linksperpage { |
472 | margin: 10px 0; | 472 | margin: 5px 0; |
473 | text-align: right; | 473 | text-align: right; |
474 | color: #252525; | 474 | color: #252525; |
475 | font-size: 0.9em; | 475 | font-size: 0.9em; |
@@ -506,9 +506,29 @@ body, .pure-g [class*="pure-u"] { | |||
506 | * CONTENT - LINKLIST ITEMS | 506 | * CONTENT - LINKLIST ITEMS |
507 | */ | 507 | */ |
508 | .linklist-item { | 508 | .linklist-item { |
509 | margin: 0 0 15px 0; | 509 | margin: 0 0 10px 0; |
510 | background: #f5f5f5; | 510 | background: #f5f5f5; |
511 | box-shadow: 2px 2px 0.5em #797979; | 511 | box-shadow: 1px 1px 3px #797979; |
512 | } | ||
513 | |||
514 | .linklist-item-buttons { | ||
515 | background: transparent; | ||
516 | position: relative; | ||
517 | width: 23px; | ||
518 | z-index: 99; | ||
519 | } | ||
520 | |||
521 | .linklist-item-buttons-right { | ||
522 | float: right; | ||
523 | margin-right: -25px; | ||
524 | } | ||
525 | |||
526 | .linklist-item-buttons * { | ||
527 | display: block; | ||
528 | float: left; | ||
529 | width:100%; | ||
530 | margin: auto; | ||
531 | text-align: center; | ||
512 | } | 532 | } |
513 | 533 | ||
514 | .linklist-item-title, .linklist-item-title h2 { | 534 | .linklist-item-title, .linklist-item-title h2 { |
@@ -526,7 +546,7 @@ body, .pure-g [class*="pure-u"] { | |||
526 | line-height: 30px; | 546 | line-height: 30px; |
527 | } | 547 | } |
528 | 548 | ||
529 | .linklist-item-title a { | 549 | .linklist-item-title h2 a { |
530 | font-size: 0.7em; | 550 | font-size: 0.7em; |
531 | color: #252525; | 551 | color: #252525; |
532 | text-decoration: none; | 552 | text-decoration: none; |
@@ -538,11 +558,11 @@ body, .pure-g [class*="pure-u"] { | |||
538 | color: #1b926c; | 558 | color: #1b926c; |
539 | } | 559 | } |
540 | 560 | ||
541 | .linklist-item-title a:visited .linklist-link { | 561 | .linklist-item-title h2 a:visited .linklist-link { |
542 | color: #2a4c41; | 562 | color: #2a4c41; |
543 | } | 563 | } |
544 | 564 | ||
545 | .linklist-item-title a:hover, .linklist-item-title .linklist-link:hover{ | 565 | .linklist-item-title h2 a:hover, .linklist-item-title .linklist-link:hover{ |
546 | color: #252525; | 566 | color: #252525; |
547 | } | 567 | } |
548 | 568 | ||
@@ -554,8 +574,9 @@ body, .pure-g [class*="pure-u"] { | |||
554 | color: #F89406; | 574 | color: #F89406; |
555 | } | 575 | } |
556 | 576 | ||
557 | .linklist-item-title .fold-button { | 577 | .fold-button { |
558 | display: none; | 578 | display: none; |
579 | color: #252525; | ||
559 | } | 580 | } |
560 | 581 | ||
561 | .linklist-item-editbuttons { | 582 | .linklist-item-editbuttons { |
@@ -585,24 +606,12 @@ body, .pure-g [class*="pure-u"] { | |||
585 | 606 | ||
586 | .linklist-item-description { | 607 | .linklist-item-description { |
587 | position: relative; | 608 | position: relative; |
588 | padding: 10px; | 609 | padding: 0 10px; |
589 | word-wrap: break-word; | 610 | word-wrap: break-word; |
590 | color: #252525; | 611 | color: #252525; |
591 | line-height: 1.3em; | 612 | line-height: 1.3em; |
592 | } | 613 | } |
593 | 614 | ||
594 | { | ||
595 | position: absolute; | ||
596 | left: 3px; | ||
597 | top: 0; | ||
598 | display: block; | ||
599 | content:""; | ||
600 | background: #F89406; | ||
601 | height: 95%; | ||
602 | width: 2px; | ||
603 | z-index: 1; | ||
604 | } | ||
605 | |||
606 | .linklist-item-description a { | 615 | .linklist-item-description a { |
607 | text-decoration: none; | 616 | text-decoration: none; |
608 | color: #1b926c; | 617 | color: #1b926c; |
@@ -618,32 +627,36 @@ body, .pure-g [class*="pure-u"] { | |||
618 | 627 | ||
619 | .linklist-item-thumbnail { | 628 | .linklist-item-thumbnail { |
620 | position: relative; | 629 | position: relative; |
621 | margin-top: 10px; | 630 | padding: 0 0 0 5px; |
622 | padding: 10px; | 631 | margin: 0; |
623 | float: left; | 632 | float: right; |
624 | z-index: 50; | 633 | z-index: 50; |
634 | height: 90px; | ||
625 | } | 635 | } |
626 | 636 | ||
627 | .linklist-item.private .linklist-item-title::before, | 637 | .linklist-item.private .linklist-item-title::before, |
628 | .linklist-item.private .linklist-item-description::before, | 638 | .linklist-item.private .linklist-item-description::before { |
629 | .linklist-item.private .linklist-item-thumbnail::before { | ||
630 | position: absolute; | 639 | position: absolute; |
631 | left: 3px; | 640 | left: 3px; |
632 | top: 0; | 641 | top: 0; |
633 | display: block; | 642 | display: block; |
634 | content:""; | 643 | content:""; |
635 | background: #F89406; | 644 | background: #F89406; |
636 | height: 95%; | 645 | height: 96%; |
637 | width: 2px; | 646 | width: 2px; |
638 | z-index: 1; | 647 | z-index: 1; |
639 | } | 648 | } |
640 | 649 | ||
650 | .linklist-item.private .linklist-item-description::before { | ||
651 | height: 100%; | ||
652 | } | ||
653 | |||
641 | .linklist-item.private .linklist-item-title::before { | 654 | .linklist-item.private .linklist-item-title::before { |
642 | margin-top: 3px; | 655 | margin-top: 3px; |
643 | } | 656 | } |
644 | 657 | ||
645 | .linklist-item-infos { | 658 | .linklist-item-infos { |
646 | padding: 8px 8px 5px 8px; | 659 | padding: 4px 8px 4px 8px; |
647 | background: #ddd; | 660 | background: #ddd; |
648 | color: #252525; | 661 | color: #252525; |
649 | } | 662 | } |
@@ -680,6 +693,8 @@ body, .pure-g [class*="pure-u"] { | |||
680 | overflow: hidden; | 693 | overflow: hidden; |
681 | text-overflow: ellipsis; | 694 | text-overflow: ellipsis; |
682 | font-size: 0.8em; | 695 | font-size: 0.8em; |
696 | height:23px; | ||
697 | line-height:23px; | ||
683 | } | 698 | } |
684 | 699 | ||
685 | .linklist-item-infos .mobile-buttons { | 700 | .linklist-item-infos .mobile-buttons { |
@@ -693,6 +708,16 @@ body, .pure-g [class*="pure-u"] { | |||
693 | height: 16px; | 708 | height: 16px; |
694 | } | 709 | } |
695 | 710 | ||
711 | .linklist-item-infos-controls-group { | ||
712 | display: inline-block; | ||
713 | border-right: 1px solid #5d5d5d; | ||
714 | padding-right: 6px; | ||
715 | } | ||
716 | |||
717 | .ctrl-edit { | ||
718 | margin: 0 7px; | ||
719 | } | ||
720 | |||
696 | /** 64em -> lg **/ | 721 | /** 64em -> lg **/ |
697 | @media screen and (max-width: 64em) { | 722 | @media screen and (max-width: 64em) { |
698 | .linklist-item-infos-url { | 723 | .linklist-item-infos-url { |
@@ -1284,3 +1309,40 @@ form[name="linkform"].page-form { | |||
1284 | text-decoration: none; | 1309 | text-decoration: none; |
1285 | font-weight: bold; | 1310 | font-weight: bold; |
1286 | } | 1311 | } |
1312 | |||
1313 | /** | ||
1314 | * Markdown | ||
1315 | */ | ||
1316 | .markdown p { | ||
1317 | margin: 0 !important; | ||
1318 | } | ||
1319 | |||
1320 | .markdown p + p { | ||
1321 | margin: 0.5em 0 0 0 !important; | ||
1322 | } | ||
1323 | |||
1324 | .markdown *:first-child { | ||
1325 | margin-top: 0 !important; | ||
1326 | } | ||
1327 | |||
1328 | .markdown *:last-child { | ||
1329 | margin-bottom: 5px !important; | ||
1330 | } | ||
1331 | |||
1332 | /** | ||
1333 | * Pure Button | ||
1334 | */ | ||
1335 | .pure-button-success, | ||
1336 | .pure-button-error, | ||
1337 | .pure-button-warning, | ||
1338 | .pure-button-primary, | ||
1339 | .pure-button-shaarli, | ||
1340 | .pure-button-secondary { | ||
1341 | color: white !important; | ||
1342 | border-radius: 4px; | ||
1343 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); | ||
1344 | } | ||
1345 | |||
1346 | .pure-button-shaarli { | ||
1347 | background-color: #1B926C; | ||
1348 | } | ||
diff --git a/tpl/default/img/apple-touch-icon.png b/tpl/default/img/apple-touch-icon.png new file mode 100644 index 00000000..f29210ce --- /dev/null +++ b/tpl/default/img/apple-touch-icon.png | |||
Binary files differ | |||
diff --git a/tpl/default/import.html b/tpl/default/import.html index 1f040685..000a50ac 100644 --- a/tpl/default/import.html +++ b/tpl/default/import.html | |||
@@ -18,7 +18,7 @@ | |||
18 | <div class="center" id="import-field"> | 18 | <div class="center" id="import-field"> |
19 | <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}"> | 19 | <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}"> |
20 | <input type="file" name="filetoupload"> | 20 | <input type="file" name="filetoupload"> |
21 | <p><br>Maximum size allowed: <strong>{$maxfilesizeHuman}</strong></p> | 21 | <p><br>{'Maximum size allowed:'|t} <strong>{$maxfilesizeHuman}</strong></p> |
22 | </div> | 22 | </div> |
23 | 23 | ||
24 | <div class="pure-g"> | 24 | <div class="pure-g"> |
@@ -31,15 +31,15 @@ | |||
31 | <div class="radio-buttons"> | 31 | <div class="radio-buttons"> |
32 | <div> | 32 | <div> |
33 | <input type="radio" name="privacy" value="default" checked="checked"> | 33 | <input type="radio" name="privacy" value="default" checked="checked"> |
34 | Use values from the imported file, default to public | 34 | {'Use values from the imported file, default to public'|t} |
35 | </div> | 35 | </div> |
36 | <div> | 36 | <div> |
37 | <input type="radio" name="privacy" value="private"> | 37 | <input type="radio" name="privacy" value="private"> |
38 | Import all bookmarks as private | 38 | {'Import all bookmarks as private'|t} |
39 | </div> | 39 | </div> |
40 | <div> | 40 | <div> |
41 | <input type="radio" name="privacy" value="public"> | 41 | <input type="radio" name="privacy" value="public"> |
42 | Import all bookmarks as public | 42 | {'Import all bookmarks as public'|t} |
43 | </div> | 43 | </div> |
44 | </div> | 44 | </div> |
45 | </div> | 45 | </div> |
diff --git a/tpl/default/includes.html b/tpl/default/includes.html index 80c08333..b2bfec30 100644 --- a/tpl/default/includes.html +++ b/tpl/default/includes.html | |||
@@ -5,6 +5,7 @@ | |||
5 | <link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> | 5 | <link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> |
6 | <link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" /> | 6 | <link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" /> |
7 | <link href="img/favicon.png" rel="shortcut icon" type="image/png" /> | 7 | <link href="img/favicon.png" rel="shortcut icon" type="image/png" /> |
8 | <link href="img/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180" /> | ||
8 | <link type="text/css" rel="stylesheet" href="css/pure.min.css?v={$version_hash}" /> | 9 | <link type="text/css" rel="stylesheet" href="css/pure.min.css?v={$version_hash}" /> |
9 | <link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css?v={$version_hash}"> | 10 | <link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css?v={$version_hash}"> |
10 | <link type="text/css" rel="stylesheet" href="css/pure-extras.css?v={$version_hash}"> | 11 | <link type="text/css" rel="stylesheet" href="css/pure-extras.css?v={$version_hash}"> |
@@ -17,4 +18,4 @@ | |||
17 | {loop="$plugins_includes.css_files"} | 18 | {loop="$plugins_includes.css_files"} |
18 | <link type="text/css" rel="stylesheet" href="{$value}?v={$version_hash}#"/> | 19 | <link type="text/css" rel="stylesheet" href="{$value}?v={$version_hash}#"/> |
19 | {/loop} | 20 | {/loop} |
20 | <link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/> \ No newline at end of file | 21 | <link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/> |
diff --git a/tpl/default/install.html b/tpl/default/install.html index 164d453b..6199b33d 100644 --- a/tpl/default/install.html +++ b/tpl/default/install.html | |||
@@ -68,6 +68,27 @@ | |||
68 | <div class="pure-g"> | 68 | <div class="pure-g"> |
69 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> | 69 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> |
70 | <div class="form-label"> | 70 | <div class="form-label"> |
71 | <label for="language"> | ||
72 | <span class="label-name">{'Language'|t}</span> | ||
73 | </label> | ||
74 | </div> | ||
75 | </div> | ||
76 | <div class="pure-u-lg-{$ratioInput} pure-u-1"> | ||
77 | <div class="form-input"> | ||
78 | <select name="language" id="language" class="align"> | ||
79 | {loop="$languages"} | ||
80 | <option value="{$key}"> | ||
81 | {$value} | ||
82 | </option> | ||
83 | {/loop} | ||
84 | </select> | ||
85 | </div> | ||
86 | </div> | ||
87 | </div> | ||
88 | |||
89 | <div class="pure-g"> | ||
90 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> | ||
91 | <div class="form-label"> | ||
71 | <label> | 92 | <label> |
72 | <span class="label-name">{'Timezone'|t}</span><br> | 93 | <span class="label-name">{'Timezone'|t}</span><br> |
73 | <span class="label-desc">{'Continent'|t} · {'City'|t}</span> | 94 | <span class="label-desc">{'Continent'|t} · {'City'|t}</span> |
diff --git a/tpl/default/js/shaarli.js b/tpl/default/js/shaarli.js index 55656f80..cf628e87 100644 --- a/tpl/default/js/shaarli.js +++ b/tpl/default/js/shaarli.js | |||
@@ -138,6 +138,9 @@ window.onload = function () { | |||
138 | }); | 138 | }); |
139 | foldAllButton.firstElementChild.classList.toggle('fa-chevron-down'); | 139 | foldAllButton.firstElementChild.classList.toggle('fa-chevron-down'); |
140 | foldAllButton.firstElementChild.classList.toggle('fa-chevron-up'); | 140 | foldAllButton.firstElementChild.classList.toggle('fa-chevron-up'); |
141 | foldAllButton.title = state === 'down' | ||
142 | ? document.getElementById('translation-fold-all').innerHTML | ||
143 | : document.getElementById('translation-expand-all').innerHTML | ||
141 | }); | 144 | }); |
142 | }); | 145 | }); |
143 | } | 146 | } |
@@ -146,7 +149,7 @@ window.onload = function () { | |||
146 | { | 149 | { |
147 | // Switch fold/expand - up = fold | 150 | // Switch fold/expand - up = fold |
148 | if (button.classList.contains('fa-chevron-up')) { | 151 | if (button.classList.contains('fa-chevron-up')) { |
149 | button.title = 'Expand'; | 152 | button.title = document.getElementById('translation-expand').innerHTML; |
150 | if (description != null) { | 153 | if (description != null) { |
151 | description.style.display = 'none'; | 154 | description.style.display = 'none'; |
152 | } | 155 | } |
@@ -155,7 +158,7 @@ window.onload = function () { | |||
155 | } | 158 | } |
156 | } | 159 | } |
157 | else { | 160 | else { |
158 | button.title = 'Fold'; | 161 | button.title = document.getElementById('translation-fold').innerHTML; |
159 | if (description != null) { | 162 | if (description != null) { |
160 | description.style.display = 'block'; | 163 | description.style.display = 'block'; |
161 | } | 164 | } |
@@ -173,7 +176,7 @@ window.onload = function () { | |||
173 | var deleteLinks = document.querySelectorAll('.confirm-delete'); | 176 | var deleteLinks = document.querySelectorAll('.confirm-delete'); |
174 | [].forEach.call(deleteLinks, function(deleteLink) { | 177 | [].forEach.call(deleteLinks, function(deleteLink) { |
175 | deleteLink.addEventListener('click', function(event) { | 178 | deleteLink.addEventListener('click', function(event) { |
176 | if(! confirm('Are you sure you want to delete this link ?')) { | 179 | if(! confirm(document.getElementById('translation-delete-link').innerHTML)) { |
177 | event.preventDefault(); | 180 | event.preventDefault(); |
178 | } | 181 | } |
179 | }); | 182 | }); |
@@ -375,7 +378,7 @@ window.onload = function () { | |||
375 | var linkCheckboxes = document.querySelectorAll('.delete-checkbox'); | 378 | var linkCheckboxes = document.querySelectorAll('.delete-checkbox'); |
376 | var bar = document.getElementById('actions'); | 379 | var bar = document.getElementById('actions'); |
377 | [].forEach.call(linkCheckboxes, function(checkbox) { | 380 | [].forEach.call(linkCheckboxes, function(checkbox) { |
378 | checkbox.style.display = 'block'; | 381 | checkbox.style.display = 'inline-block'; |
379 | checkbox.addEventListener('click', function(event) { | 382 | checkbox.addEventListener('click', function(event) { |
380 | var count = 0; | 383 | var count = 0; |
381 | var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); | 384 | var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); |
@@ -618,7 +621,7 @@ function activateFirefoxSocial(node) { | |||
618 | // Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable. | 621 | // Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable. |
619 | var data = { | 622 | var data = { |
620 | name: title, | 623 | name: title, |
621 | description: "The personal, minimalist, super-fast, database free, bookmarking service by the Shaarli community.", | 624 | description: document.getElementById('translation-delete-link').innerHTML, |
622 | author: "Shaarli", | 625 | author: "Shaarli", |
623 | version: "1.0.0", | 626 | version: "1.0.0", |
624 | 627 | ||
diff --git a/tpl/default/linklist.html b/tpl/default/linklist.html index 685821e3..c666e30a 100644 --- a/tpl/default/linklist.html +++ b/tpl/default/linklist.html | |||
@@ -53,9 +53,9 @@ | |||
53 | {/loop} | 53 | {/loop} |
54 | 54 | ||
55 | <div id="linklist"> | 55 | <div id="linklist"> |
56 | <div class="pure-g"> | 56 | <div id="link-count-block" class="pure-g"> |
57 | <div class="pure-u-lg-2-24 pure-u-1-24"></div> | 57 | <div class="pure-u-lg-2-24 pure-u-1-24"></div> |
58 | <div class="pure-u-lg-20-24 pure-u-22-24"> | 58 | <div id="link-count-content" class="pure-u-lg-20-24 pure-u-22-24"> |
59 | <div class="linkcount pure-u-lg-0 center"> | 59 | <div class="linkcount pure-u-lg-0 center"> |
60 | {if="!empty($linkcount)"} | 60 | {if="!empty($linkcount)"} |
61 | <span class="strong">{$linkcount}</span> {function="t('shaare', 'shaares', $linkcount)"} | 61 | <span class="strong">{$linkcount}</span> {function="t('shaare', 'shaares', $linkcount)"} |
@@ -76,17 +76,17 @@ | |||
76 | </div> | 76 | </div> |
77 | 77 | ||
78 | {if="count($links)==0"} | 78 | {if="count($links)==0"} |
79 | <div class="pure-g pure-alert pure-alert-error search-result"> | 79 | <div id="search-result-block" class="pure-g pure-alert pure-alert-error search-result"> |
80 | <div class="pure-u-2-24"></div> | 80 | <div class="pure-u-2-24"></div> |
81 | <div class="pure-u-20-24"> | 81 | <div id="search-result-content" class="pure-u-20-24"> |
82 | <div id="searchcriteria">{'Nothing found.'|t}</div> | 82 | <div id="searchcriteria">{'Nothing found.'|t}</div> |
83 | </div> | 83 | </div> |
84 | </div> | 84 | </div> |
85 | {elseif="!empty($search_term) or $search_tags !== '' or !empty($visibility) or $untaggedonly"} | 85 | {elseif="!empty($search_term) or $search_tags !== '' or !empty($visibility) or $untaggedonly"} |
86 | <div class="pure-g pure-alert pure-alert-success search-result"> | 86 | <div id="search-result-block" class="pure-g pure-alert pure-alert-success search-result"> |
87 | <div class="pure-u-2-24"></div> | 87 | <div class="pure-u-2-24"></div> |
88 | <div class="pure-u-20-24"> | 88 | <div id="search-result-content" class="pure-u-20-24 search-result-main"> |
89 | {function="t('%s result', '%s results', $result_count)"} | 89 | {function="sprintf(t('%s result', '%s results', $result_count), $result_count)"} |
90 | {if="!empty($search_term)"} | 90 | {if="!empty($search_term)"} |
91 | {'for'|t} <em><strong>{$search_term}</strong></em> | 91 | {'for'|t} <em><strong>{$search_term}</strong></em> |
92 | {/if} | 92 | {/if} |
@@ -114,23 +114,34 @@ | |||
114 | </div> | 114 | </div> |
115 | {/if} | 115 | {/if} |
116 | 116 | ||
117 | <div class="pure-g"> | 117 | <div id="linklist-loop-block" class="pure-g"> |
118 | <div class="pure-u-lg-2-24 pure-u-1-24"></div> | 118 | <div class="pure-u-lg-2-24 pure-u-1-24"></div> |
119 | <div class="pure-u-lg-20-24 pure-u-22-24"> | 119 | <div id="linklist-loop-content" class="pure-u-lg-20-24 pure-u-22-24"> |
120 | {ignore}Set translation here, for performances{/ignore} | ||
121 | {$strPrivate=t('Private')} | ||
122 | {$strEdit=t('Edit')} | ||
123 | {$strDelete=t('Delete')} | ||
124 | {$strFold=t('Fold')} | ||
125 | {$strEdited=t('Edited: ')} | ||
126 | {$strPermalink=t('Permalink')} | ||
127 | {$strPermalinkLc=t('permalink')} | ||
128 | {$strAddTag=t('Add tag')} | ||
129 | {ignore}End of translations{/ignore} | ||
120 | {loop="links"} | 130 | {loop="links"} |
121 | <div class="anchor" id="{$value.shorturl}"></div> | 131 | <div class="anchor" id="{$value.shorturl}"></div> |
122 | <div class="linklist-item linklist-item{if="$value.class"} {$value.class}{/if}" data-id="{$value.id}"> | ||
123 | 132 | ||
133 | <div class="linklist-item linklist-item{if="$value.class"} {$value.class}{/if}" data-id="{$value.id}"> | ||
124 | <div class="linklist-item-title"> | 134 | <div class="linklist-item-title"> |
135 | {$thumb=thumbnail($value.url)} | ||
136 | {if="$thumb!=false"} | ||
137 | <div class="linklist-item-thumbnail">{$thumb}</div> | ||
138 | {/if} | ||
139 | |||
125 | {if="isLoggedIn()"} | 140 | {if="isLoggedIn()"} |
126 | <div class="linklist-item-editbuttons"> | 141 | <div class="linklist-item-editbuttons"> |
127 | {if="$value.private"} | 142 | {if="$value.private"} |
128 | <span class="label label-private">{'Private'|t}</span> | 143 | <span class="label label-private">{$strPrivate}</span> |
129 | {/if} | 144 | {/if} |
130 | <input type="checkbox" class="delete-checkbox" value="{$value.id}"> | ||
131 | <!-- FIXME! JS translation --> | ||
132 | <a href="?edit_link={$value.id}" title="{'Edit'|t}"><i class="fa fa-pencil-square-o edit-link"></i></a> | ||
133 | <a href="#" title="{'Fold'|t}" class="fold-button"><i class="fa fa-chevron-up"></i></a> | ||
134 | </div> | 145 | </div> |
135 | {/if} | 146 | {/if} |
136 | 147 | ||
@@ -147,11 +158,6 @@ | |||
147 | </h2> | 158 | </h2> |
148 | </div> | 159 | </div> |
149 | 160 | ||
150 | {$thumb=thumbnail($value.url)} | ||
151 | {if="$thumb!=false"} | ||
152 | <div class="linklist-item-thumbnail">{$thumb}</div> | ||
153 | {/if} | ||
154 | |||
155 | {if="$value.description"} | 161 | {if="$value.description"} |
156 | <div class="linklist-item-description"> | 162 | <div class="linklist-item-description"> |
157 | {$value.description} | 163 | {$value.description} |
@@ -164,7 +170,7 @@ | |||
164 | <i class="fa fa-tags"></i> | 170 | <i class="fa fa-tags"></i> |
165 | {$tag_counter=count($value.taglist)} | 171 | {$tag_counter=count($value.taglist)} |
166 | {loop="value.taglist"} | 172 | {loop="value.taglist"} |
167 | <span class="label label-tag" title="Add tag"> | 173 | <span class="label label-tag" title="{$strAddTag}"> |
168 | <a href="?addtag={$value|urlencode}">{$value}</a> | 174 | <a href="?addtag={$value|urlencode}">{$value}</a> |
169 | </span> | 175 | </span> |
170 | {if="$tag_counter - 1 != $counter"}·{/if} | 176 | {if="$tag_counter - 1 != $counter"}·{/if} |
@@ -172,11 +178,27 @@ | |||
172 | </div> | 178 | </div> |
173 | {/if} | 179 | {/if} |
174 | 180 | ||
175 | <div class="pure-g"> | 181 | <div class="linklist-item-infos-date-url-block pure-g"> |
176 | <div class="linklist-item-infos-dateblock pure-u-lg-3-8 pure-u-1"> | 182 | <div class="linklist-item-infos-dateblock pure-u-lg-7-12 pure-u-1"> |
177 | <a href="?{$value.shorturl}" title="{'Permalink'|t}"> | 183 | {if="isLoggedIn()"} |
184 | <div class="linklist-item-infos-controls-group pure-u-0 pure-u-lg-visible"> | ||
185 | <span class="linklist-item-infos-controls-item ctrl-checkbox"> | ||
186 | <input type="checkbox" class="delete-checkbox" value="{$value.id}"> | ||
187 | </span> | ||
188 | <span class="linklist-item-infos-controls-item ctrl-edit"> | ||
189 | <a href="?edit_link={$value.id}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link"></i></a> | ||
190 | </span> | ||
191 | <span class="linklist-item-infos-controls-item ctrl-delete"> | ||
192 | <a href="?delete_link&lf_linkdate={$value.id}&token={$token}" | ||
193 | title="{$strDelete}" class="delete-link pure-u-0 pure-u-lg-visible confirm-delete"> | ||
194 | <i class="fa fa-trash"></i> | ||
195 | </a> | ||
196 | </span> | ||
197 | </div> | ||
198 | {/if} | ||
199 | <a href="?{$value.shorturl}" title="{$strPermalink}"> | ||
178 | {if="!$hide_timestamps || isLoggedIn()"} | 200 | {if="!$hide_timestamps || isLoggedIn()"} |
179 | {$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'} | 201 | {$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink} |
180 | <span class="linkdate" title="{$updated}"> | 202 | <span class="linkdate" title="{$updated}"> |
181 | <i class="fa fa-clock-o"></i> | 203 | <i class="fa fa-clock-o"></i> |
182 | {$value.created|format_date} | 204 | {$value.created|format_date} |
@@ -184,7 +206,7 @@ | |||
184 | · | 206 | · |
185 | </span> | 207 | </span> |
186 | {/if} | 208 | {/if} |
187 | {'permalink'|t} | 209 | {$strPermalinkLc} |
188 | </a> | 210 | </a> |
189 | 211 | ||
190 | <div class="pure-u-0 pure-u-lg-visible"> | 212 | <div class="pure-u-0 pure-u-lg-visible"> |
@@ -199,16 +221,13 @@ | |||
199 | </div> | 221 | </div> |
200 | </div><div | 222 | </div><div |
201 | {ignore}do not add space or line break between these div - Firefox issue{/ignore} | 223 | {ignore}do not add space or line break between these div - Firefox issue{/ignore} |
202 | class="linklist-item-infos-url pure-u-lg-5-8 pure-u-1"> | 224 | class="linklist-item-infos-url pure-u-lg-5-12 pure-u-1"> |
203 | <a href="{$value.real_url}" title="{$value.title}"> | 225 | <a href="{$value.real_url}" title="{$value.title}"> |
204 | <i class="fa fa-link"></i> {$value.url} | 226 | <i class="fa fa-link"></i> {$value.url} |
205 | </a> | 227 | </a> |
206 | {if="isLoggedIn()"} | 228 | <div class="linklist-item-buttons pure-u-0 pure-u-lg-visible"> |
207 | <a href="?delete_link&lf_linkdate={$value.id}&token={$token}" | 229 | <a href="#" title="{$strFold}" class="fold-button"><i class="fa fa-chevron-up"></i></a> |
208 | title="{'Delete'|t}" class="delete-link pure-u-0 pure-u-lg-visible confirm-delete"> | 230 | </div> |
209 | <i class="fa fa-trash"></i> | ||
210 | </a> | ||
211 | {/if} | ||
212 | </div> | 231 | </div> |
213 | <div class="mobile-buttons pure-u-1 pure-u-lg-0"> | 232 | <div class="mobile-buttons pure-u-1 pure-u-lg-0"> |
214 | {if="isset($value.link_plugin)"} | 233 | {if="isset($value.link_plugin)"} |
@@ -221,9 +240,11 @@ | |||
221 | {if="isLoggedIn()"} | 240 | {if="isLoggedIn()"} |
222 | · | 241 | · |
223 | <a href="?delete_link&lf_linkdate={$value.id}&token={$token}" | 242 | <a href="?delete_link&lf_linkdate={$value.id}&token={$token}" |
224 | title="{'Delete'|t}" class="delete-link confirm-delete"> | 243 | title="{$strDelete}" class="delete-link confirm-delete"> |
225 | <i class="fa fa-trash"></i> | 244 | <i class="fa fa-trash"></i> |
226 | </a> | 245 | </a> |
246 | · | ||
247 | <a href="?edit_link={$value.id}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link"></i></a> | ||
227 | {/if} | 248 | {/if} |
228 | </div> | 249 | </div> |
229 | </div> | 250 | </div> |
@@ -240,9 +261,9 @@ | |||
240 | {/loop} | 261 | {/loop} |
241 | </div> | 262 | </div> |
242 | 263 | ||
243 | <div class="pure-g"> | 264 | <div id="linklist-paging-bottom-block" class="pure-g"> |
244 | <div class="pure-u-lg-2-24 pure-u-1-24"></div> | 265 | <div class="pure-u-lg-2-24 pure-u-1-24"></div> |
245 | <div class="pure-u-lg-20-24 pure-u-22-24"> | 266 | <div id="linklist-paging-bottom-content" class="pure-u-lg-20-24 pure-u-22-24"> |
246 | {include="linklist.paging"} | 267 | {include="linklist.paging"} |
247 | </div> | 268 | </div> |
248 | </div> | 269 | </div> |
diff --git a/tpl/default/linklist.paging.html b/tpl/default/linklist.paging.html index 41e9fa34..347b3d13 100644 --- a/tpl/default/linklist.paging.html +++ b/tpl/default/linklist.paging.html | |||
@@ -13,7 +13,7 @@ | |||
13 | <a href="?untaggedonly" title="{'Filter untagged links'|t}" | 13 | <a href="?untaggedonly" title="{'Filter untagged links'|t}" |
14 | class={if="$untaggedonly"}"filter-on"{else}"filter-off"{/if} | 14 | class={if="$untaggedonly"}"filter-on"{else}"filter-off"{/if} |
15 | ><i class="fa fa-tag"></i></a> | 15 | ><i class="fa fa-tag"></i></a> |
16 | <a href="#" class="filter-off fold-all pure-u-lg-0" title="Fold all"> | 16 | <a href="#" class="filter-off fold-all pure-u-lg-0" title="{'Fold all'|t}"> |
17 | <i class="fa fa-chevron-up"></i> | 17 | <i class="fa fa-chevron-up"></i> |
18 | </a> | 18 | </a> |
19 | {loop="$action_plugin"} | 19 | {loop="$action_plugin"} |
@@ -53,7 +53,7 @@ | |||
53 | <form method="GET" class="pure-u-0 pure-u-lg-visible"> | 53 | <form method="GET" class="pure-u-0 pure-u-lg-visible"> |
54 | <input type="text" name="linksperpage" placeholder="133"> | 54 | <input type="text" name="linksperpage" placeholder="133"> |
55 | </form> | 55 | </form> |
56 | <a href="#" class="filter-off fold-all pure-u-0 pure-u-lg-visible" title="Fold all"> | 56 | <a href="#" class="filter-off fold-all pure-u-0 pure-u-lg-visible" title="{'Fold all'|t}"> |
57 | <i class="fa fa-chevron-up"></i> | 57 | <i class="fa fa-chevron-up"></i> |
58 | </a> | 58 | </a> |
59 | </div> | 59 | </div> |
diff --git a/tpl/default/page.footer.html b/tpl/default/page.footer.html index 54b16e8a..659e8c7f 100644 --- a/tpl/default/page.footer.html +++ b/tpl/default/page.footer.html | |||
@@ -8,8 +8,8 @@ | |||
8 | {$version} | 8 | {$version} |
9 | {/if} | 9 | {/if} |
10 | · | 10 | · |
11 | The personal, minimalist, super-fast, database free, bookmarking service by the Shaarli community · | 11 | {'The personal, minimalist, super-fast, database free, bookmarking service'|t} {'by the Shaarli community'|t} · |
12 | <a href="doc/html/index.html" rel="nofollow">Documentation</a> | 12 | <a href="doc/html/index.html" rel="nofollow">{'Documentation'|t}</a> |
13 | {loop="$plugins_footer.text"} | 13 | {loop="$plugins_footer.text"} |
14 | {$value} | 14 | {$value} |
15 | {/loop} | 15 | {/loop} |
@@ -27,6 +27,17 @@ | |||
27 | <script src="{$value}#"></script> | 27 | <script src="{$value}#"></script> |
28 | {/loop} | 28 | {/loop} |
29 | 29 | ||
30 | <div id="js-translations" class="hidden"> | ||
31 | <span id="translation-fold">{'Fold'|t}</span> | ||
32 | <span id="translation-fold-all">{'Fold all'|t}</span> | ||
33 | <span id="translation-expand">{'Expand'|t}</span> | ||
34 | <span id="translation-expand-all">{'Expand all'|t}</span> | ||
35 | <span id="translation-delete-link">{'Are you sure you want to delete this link?'|t}</span> | ||
36 | <span id="translation-shaarli-desc"> | ||
37 | {'The personal, minimalist, super-fast, database free, bookmarking service'|t} {'by the Shaarli community'|t} | ||
38 | </span> | ||
39 | </div> | ||
40 | |||
30 | <script src="js/shaarli.js?v={$version_hash}"></script> | 41 | <script src="js/shaarli.js?v={$version_hash}"></script> |
31 | <script src="inc/awesomplete.js?v={$version_hash}#"></script> | 42 | <script src="inc/awesomplete.js?v={$version_hash}#"></script> |
32 | <script src="inc/awesomplete-multiple-tags.js?v={$version_hash}#"></script> | 43 | <script src="inc/awesomplete-multiple-tags.js?v={$version_hash}#"></script> |
diff --git a/tpl/default/page.header.html b/tpl/default/page.header.html index 2411703c..6f15c1c5 100644 --- a/tpl/default/page.header.html +++ b/tpl/default/page.header.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <div class="shaarli-menu pure-g" id="shaarli-menu"> | 1 | <div class="shaarli-menu pure-g" id="shaarli-menu"> |
2 | <div class="pure-u-lg-0 pure-u-1"> | 2 | <div class="pure-u-lg-0 pure-u-1"> |
3 | <div class="pure-menu"> | 3 | <div class="pure-menu"> |
4 | <a href="{$titleLink}" class="pure-menu-link"> | 4 | <a href="{$titleLink}" class="pure-menu-link shaarli-title" id="shaarli-title-mobile"> |
5 | <img src="img/icon.png" width="16" height="16" class="head-logo" alt="logo" /> | 5 | <img src="img/icon.png" width="16" height="16" class="head-logo" alt="logo" /> |
6 | {$shaarlititle} | 6 | {$shaarlititle} |
7 | </a> | 7 | </a> |
@@ -12,32 +12,32 @@ | |||
12 | <div class="pure-menu menu-transform pure-menu-horizontal pure-g"> | 12 | <div class="pure-menu menu-transform pure-menu-horizontal pure-g"> |
13 | <ul class="pure-menu-list pure-u-lg-5-6 pure-u-1"> | 13 | <ul class="pure-menu-list pure-u-lg-5-6 pure-u-1"> |
14 | <li class="pure-menu-item pure-u-0 pure-u-lg-visible"> | 14 | <li class="pure-menu-item pure-u-0 pure-u-lg-visible"> |
15 | <a href="{$titleLink}" class="pure-menu-link"> | 15 | <a href="{$titleLink}" class="pure-menu-link shaarli-title" id="shaarli-title-desktop"> |
16 | <img src="img/icon.png" width="16" height="16" class="head-logo" alt="logo" /> | 16 | <img src="img/icon.png" width="16" height="16" class="head-logo" alt="logo" /> |
17 | {$shaarlititle} | 17 | {$shaarlititle} |
18 | </a> | 18 | </a> |
19 | </li> | 19 | </li> |
20 | {if="isLoggedIn() || $openshaarli"} | 20 | {if="isLoggedIn() || $openshaarli"} |
21 | <li class="pure-menu-item"> | 21 | <li class="pure-menu-item"> |
22 | <a href="?do=addlink" class="pure-menu-link"> | 22 | <a href="?do=addlink" class="pure-menu-link" id="shaarli-menu-shaare"> |
23 | <i class="fa fa-plus" ></i> {'Shaare'|t} | 23 | <i class="fa fa-plus" ></i> {'Shaare'|t} |
24 | </a> | 24 | </a> |
25 | </li> | 25 | </li> |
26 | <li class="pure-menu-item"> | 26 | <li class="pure-menu-item" id="shaarli-menu-tools"> |
27 | <a href="?do=tools" class="pure-menu-link">{'Tools'|t}</a> | 27 | <a href="?do=tools" class="pure-menu-link">{'Tools'|t}</a> |
28 | </li> | 28 | </li> |
29 | {/if} | 29 | {/if} |
30 | <li class="pure-menu-item"> | 30 | <li class="pure-menu-item" id="shaarli-menu-tags"> |
31 | <a href="?do=tagcloud" class="pure-menu-link">{'Tag cloud'|t}</a> | 31 | <a href="?do=tagcloud" class="pure-menu-link">{'Tag cloud'|t}</a> |
32 | </li> | 32 | </li> |
33 | <li class="pure-menu-item"> | 33 | <li class="pure-menu-item" id="shaarli-menu-picwall"> |
34 | <a href="?do=picwall{$searchcrits}" class="pure-menu-link">{'Picture wall'|t}</a> | 34 | <a href="?do=picwall{$searchcrits}" class="pure-menu-link">{'Picture wall'|t}</a> |
35 | </li> | 35 | </li> |
36 | <li class="pure-menu-item"> | 36 | <li class="pure-menu-item" id="shaarli-menu-daily"> |
37 | <a href="?do=daily" class="pure-menu-link">{'Daily'|t}</a> | 37 | <a href="?do=daily" class="pure-menu-link">{'Daily'|t}</a> |
38 | </li> | 38 | </li> |
39 | {loop="$plugins_header.buttons_toolbar"} | 39 | {loop="$plugins_header.buttons_toolbar"} |
40 | <li class="pure-menu-item"> | 40 | <li class="pure-menu-item shaarli-menu-plugin"> |
41 | <a | 41 | <a |
42 | {$value.attr.class=isset($value.class) ? $value.attr.class . ' pure-menu-link' : 'pure-menu-link'} | 42 | {$value.attr.class=isset($value.class) ? $value.attr.class . ' pure-menu-link' : 'pure-menu-link'} |
43 | {loop="$value.attr"} | 43 | {loop="$value.attr"} |
@@ -47,47 +47,47 @@ | |||
47 | </a> | 47 | </a> |
48 | </li> | 48 | </li> |
49 | {/loop} | 49 | {/loop} |
50 | <li class="pure-menu-item pure-u-lg-0"> | 50 | <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss"> |
51 | <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a> | 51 | <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a> |
52 | </li> | 52 | </li> |
53 | {if="isLoggedIn()"} | 53 | {if="isLoggedIn()"} |
54 | <li class="pure-menu-item pure-u-lg-0"> | 54 | <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout"> |
55 | <a href="?do=logout" class="pure-menu-link">{'Logout'|t}</a> | 55 | <a href="?do=logout" class="pure-menu-link">{'Logout'|t}</a> |
56 | </li> | 56 | </li> |
57 | {else} | 57 | {else} |
58 | <li class="pure-menu-item pure-u-lg-0"> | 58 | <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-login"> |
59 | <a href="?do=login" class="pure-menu-link">{'Login'|t}</a> | 59 | <a href="?do=login" class="pure-menu-link">{'Login'|t}</a> |
60 | </li> | 60 | </li> |
61 | {/if} | 61 | {/if} |
62 | </ul> | 62 | </ul> |
63 | <div class="header-buttons pure-u-lg-1-6 pure-u-0 pure-u-lg-visible"> | 63 | <div class="header-buttons pure-u-lg-1-6 pure-u-0 pure-u-lg-visible"> |
64 | <ul class="pure-menu-list"> | 64 | <ul class="pure-menu-list"> |
65 | <li class="pure-menu-item"> | 65 | <li class="pure-menu-item" id="shaarli-menu-desktop-search"> |
66 | <a href="#" class="pure-menu-link subheader-opener" | 66 | <a href="#" class="pure-menu-link subheader-opener" |
67 | data-open-id="search" | 67 | data-open-id="search" |
68 | id="search-button" title="{'Search'|t}"> | 68 | id="search-button" title="{'Search'|t}"> |
69 | <i class="fa fa-search"></i> | 69 | <i class="fa fa-search"></i> |
70 | </a> | 70 | </a> |
71 | </li> | 71 | </li> |
72 | <li class="pure-menu-item"> | 72 | <li class="pure-menu-item" id="shaarli-menu-desktop-rss"> |
73 | <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}"> | 73 | <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}"> |
74 | <i class="fa fa-rss"></i> | 74 | <i class="fa fa-rss"></i> |
75 | </a> | 75 | </a> |
76 | </li> | 76 | </li> |
77 | {if="!isLoggedIn()"} | 77 | {if="!isLoggedIn()"} |
78 | <li class="pure-menu-item"> | 78 | <li class="pure-menu-item" id="shaarli-menu-desktop-login"> |
79 | <a href="?do=login" class="pure-menu-link" | 79 | <a href="?do=login" class="pure-menu-link" |
80 | data-open-id="header-login-form" | 80 | data-open-id="header-login-form" |
81 | id="login-button" title="{'Login'|t}"> | 81 | id="login-button" title="{'Login'|t}"> |
82 | <i class="fa fa-user"></i> | 82 | <i class="fa fa-user"></i> |
83 | </a> | 83 | </a> |
84 | </li> | 84 | </li> |
85 | {else} | 85 | {else} |
86 | <li class="pure-menu-item"> | 86 | <li class="pure-menu-item" id="shaarli-menu-desktop-logout"> |
87 | <a href="?do=logout" class="pure-menu-link" title="{'Logout'|t}"> | 87 | <a href="?do=logout" class="pure-menu-link" title="{'Logout'|t}"> |
88 | <i class="fa fa-sign-out"></i> | 88 | <i class="fa fa-sign-out"></i> |
89 | </a> | 89 | </a> |
90 | </li> | 90 | </li> |
91 | {/if} | 91 | {/if} |
92 | </ul> | 92 | </ul> |
93 | </div> | 93 | </div> |
@@ -156,7 +156,7 @@ | |||
156 | {/if} | 156 | {/if} |
157 | 157 | ||
158 | {if="!empty($plugin_errors) && isLoggedIn()"} | 158 | {if="!empty($plugin_errors) && isLoggedIn()"} |
159 | <div class="pure-g new-version-message pure-alert pure-alert-error pure-alert-closable"> | 159 | <div class="pure-g new-version-message pure-alert pure-alert-error pure-alert-closable" id="shaarli-errors-alert"> |
160 | <div class="pure-u-2-24"></div> | 160 | <div class="pure-u-2-24"></div> |
161 | <div class="pure-u-20-24"> | 161 | <div class="pure-u-20-24"> |
162 | {loop="plugin_errors"} | 162 | {loop="plugin_errors"} |
diff --git a/tpl/default/pluginsadmin.html b/tpl/default/pluginsadmin.html index 5cc1802f..b2d7cdc5 100644 --- a/tpl/default/pluginsadmin.html +++ b/tpl/default/pluginsadmin.html | |||
@@ -27,7 +27,7 @@ | |||
27 | 27 | ||
28 | <div> | 28 | <div> |
29 | {if="count($enabledPlugins)==0"} | 29 | {if="count($enabledPlugins)==0"} |
30 | <p>{'No plugin enabled.'|t}</p> | 30 | <p class="center">{'No plugin enabled.'|t}</p> |
31 | {else} | 31 | {else} |
32 | <table id="plugin_table"> | 32 | <table id="plugin_table"> |
33 | <thead> | 33 | <thead> |
@@ -77,7 +77,7 @@ | |||
77 | 77 | ||
78 | <div> | 78 | <div> |
79 | {if="count($disabledPlugins)==0"} | 79 | {if="count($disabledPlugins)==0"} |
80 | <p>{'No plugin disabled.'|t}</p> | 80 | <p class="center">{'No plugin disabled.'|t}</p> |
81 | {else} | 81 | {else} |
82 | <table> | 82 | <table> |
83 | <thead> | 83 | <thead> |
@@ -116,8 +116,8 @@ | |||
116 | </section> | 116 | </section> |
117 | 117 | ||
118 | <div class="center more"> | 118 | <div class="center more"> |
119 | More plugins available | 119 | {"More plugins available"|t} |
120 | <a href="doc/Community-&-Related-software.html#third-party-plugins">in the documentation</a>. | 120 | <a href="doc/Community-&-Related-software.html#third-party-plugins">{"in the documentation"|t}</a>. |
121 | </div> | 121 | </div> |
122 | <div class="center"> | 122 | <div class="center"> |
123 | <input type="submit" value="{'Save'|t}" name="save"> | 123 | <input type="submit" value="{'Save'|t}" name="save"> |
@@ -135,9 +135,11 @@ | |||
135 | <section id="plugin_parameters"> | 135 | <section id="plugin_parameters"> |
136 | <div> | 136 | <div> |
137 | {if="count($enabledPlugins)==0"} | 137 | {if="count($enabledPlugins)==0"} |
138 | <p>{'No plugin enabled.'|t}</p> | 138 | <p class="center">{'No plugin enabled.'|t}</p> |
139 | {else} | 139 | {else} |
140 | {$nbParameters=0} | ||
140 | {loop="$enabledPlugins"} | 141 | {loop="$enabledPlugins"} |
142 | {$nbParameters=$nbParameters+count($value.parameters)} | ||
141 | {if="count($value.parameters) > 0"} | 143 | {if="count($value.parameters) > 0"} |
142 | <div class="plugin_parameters"> | 144 | <div class="plugin_parameters"> |
143 | <h3 class="window-subtitle">{function="str_replace('_', ' ', $key)"}</h3> | 145 | <h3 class="window-subtitle">{function="str_replace('_', ' ', $key)"}</h3> |
@@ -159,10 +161,14 @@ | |||
159 | </div> | 161 | </div> |
160 | {/if} | 162 | {/if} |
161 | {/loop} | 163 | {/loop} |
164 | {if="$nbParameters===0"} | ||
165 | <p class="center">{'No parameter available.'|t}</p> | ||
166 | {else} | ||
167 | <div class="center"> | ||
168 | <input type="submit" name="parameters_form" value="{'Save'|t}"/> | ||
169 | </div> | ||
170 | {/if} | ||
162 | {/if} | 171 | {/if} |
163 | <div class="center"> | ||
164 | <input type="submit" name="parameters_form" value="{'Save'|t}"/> | ||
165 | </div> | ||
166 | </div> | 172 | </div> |
167 | </section> | 173 | </section> |
168 | </div> | 174 | </div> |
diff --git a/tpl/default/tag.cloud.html b/tpl/default/tag.cloud.html index 68335c70..12701465 100644 --- a/tpl/default/tag.cloud.html +++ b/tpl/default/tag.cloud.html | |||
@@ -14,8 +14,10 @@ | |||
14 | {$countTags=count($tags)} | 14 | {$countTags=count($tags)} |
15 | <h2 class="window-title">{'Tag cloud'|t} - {$countTags} {'tags'|t}</h2> | 15 | <h2 class="window-title">{'Tag cloud'|t} - {$countTags} {'tags'|t}</h2> |
16 | {if="!empty($search_tags)"} | 16 | {if="!empty($search_tags)"} |
17 | <p class="enter"> | 17 | <p class="center"> |
18 | <a href="?searchtags={$search_tags|urlencode}">{'List all links with those tags'|t}</a> | 18 | <a href="?searchtags={$search_tags|urlencode}" class="pure-button pure-button-shaarli"> |
19 | {'List all links with those tags'|t} | ||
20 | </a> | ||
19 | </p> | 21 | </p> |
20 | {/if} | 22 | {/if} |
21 | 23 | ||
diff --git a/tpl/default/tag.list.html b/tpl/default/tag.list.html index a3e741d3..7140c67a 100644 --- a/tpl/default/tag.list.html +++ b/tpl/default/tag.list.html | |||
@@ -15,7 +15,9 @@ | |||
15 | <h2 class="window-title">{'Tag list'|t} - {$countTags} {'tags'|t}</h2> | 15 | <h2 class="window-title">{'Tag list'|t} - {$countTags} {'tags'|t}</h2> |
16 | {if="!empty($search_tags)"} | 16 | {if="!empty($search_tags)"} |
17 | <p class="center"> | 17 | <p class="center"> |
18 | <a href="?searchtags={$search_tags|urlencode}">{'List all links with those tags'|t}</a> | 18 | <a href="?searchtags={$search_tags|urlencode}" class="pure-button pure-button-shaarli"> |
19 | {'List all links with those tags'|t} | ||
20 | </a> | ||
19 | </p> | 21 | </p> |
20 | {/if} | 22 | {/if} |
21 | 23 | ||