diff options
375 files changed, 30892 insertions, 14736 deletions
diff --git a/.dev/.sasslintrc b/.dev/.sasslintrc deleted file mode 100644 index 47c3145d..00000000 --- a/.dev/.sasslintrc +++ /dev/null | |||
@@ -1,17 +0,0 @@ | |||
1 | options: | ||
2 | max-warnings: 0 | ||
3 | rules: | ||
4 | property-sort-order: | ||
5 | - 0 | ||
6 | # Sort order rule does not work with CSS variables: https://github.com/sasstools/sass-lint/issues/1161 | ||
7 | # - 1 | ||
8 | # - | ||
9 | # order: 'concentric' | ||
10 | no-important: | ||
11 | - 0 | ||
12 | no-vendor-prefixes: | ||
13 | - 0 # this will be fixed with v2: see https://github.com/sasstools/sass-lint/pull/1137 | ||
14 | nesting-depth: | ||
15 | - 1 | ||
16 | - | ||
17 | max-depth: 4 | ||
diff --git a/.dev/.stylelintrc.js b/.dev/.stylelintrc.js new file mode 100644 index 00000000..a754e33b --- /dev/null +++ b/.dev/.stylelintrc.js | |||
@@ -0,0 +1,15 @@ | |||
1 | module.exports = { | ||
2 | extends: 'stylelint-config-standard', | ||
3 | plugins: [ | ||
4 | "stylelint-scss" | ||
5 | ], | ||
6 | rules: { | ||
7 | "indentation": [2], | ||
8 | "number-leading-zero": null, | ||
9 | // Replace CSS @ with SASS ones | ||
10 | "at-rule-no-unknown": null, | ||
11 | "scss/at-rule-no-unknown": true, | ||
12 | // not compatible with SASS apparently | ||
13 | "no-descending-specificity": null | ||
14 | }, | ||
15 | } | ||
diff --git a/.editorconfig b/.editorconfig index 34bd7994..c2ab80eb 100644 --- a/.editorconfig +++ b/.editorconfig | |||
@@ -14,7 +14,7 @@ indent_size = 4 | |||
14 | indent_size = 2 | 14 | indent_size = 2 |
15 | 15 | ||
16 | [*.php] | 16 | [*.php] |
17 | max_line_length = 100 | 17 | max_line_length = 120 |
18 | 18 | ||
19 | [Dockerfile] | 19 | [Dockerfile] |
20 | max_line_length = 80 | 20 | max_line_length = 80 |
diff --git a/.github/mailmap b/.github/mailmap index 7633afcf..15a25e43 100644 --- a/.github/mailmap +++ b/.github/mailmap | |||
@@ -1,13 +1,18 @@ | |||
1 | ArthurHoaro <arthur@hoa.ro> | 1 | ArthurHoaro <arthur@hoa.ro> <arthur.hoareau@wizacha.com> |
2 | ArthurHoaro <arthur@hoa.ro> Arthur | ||
2 | Florian Eula <eula.florian@gmail.com> feula | 3 | Florian Eula <eula.florian@gmail.com> feula |
3 | Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com> | 4 | Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com> |
4 | Immánuel Fodor <immanuelfactor+github@gmail.com> | 5 | Immánuel Fodor <immanuelfactor+github@gmail.com> |
6 | Immánuel Fodor <immanuelfactor+github@gmail.com> Immánuel! <21174107+immanuelfodor@users.noreply.github.com> | ||
5 | kalvn <kalvnthereal@gmail.com> <kalvn@users.noreply.github.com> | 7 | kalvn <kalvnthereal@gmail.com> <kalvn@users.noreply.github.com> |
8 | kalvn <kalvnthereal@gmail.com> <kalvn@pm.me> | ||
9 | Neros <contact@neros.fr> <NerosTie@users.noreply.github.com> | ||
6 | Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm | 10 | Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm |
7 | Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar> | 11 | Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar> |
8 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com> | 12 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com> |
9 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@users.noreply.github.com> | 13 | Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@users.noreply.github.com> |
10 | Sébastien Sauvage <sebsauvage@sebsauvage.net> | 14 | Sébastien Sauvage <sebsauvage@sebsauvage.net> |
15 | Sébastien NOBILI <code@pipoprods.org> <s-code-github@pipoprods.org> | ||
11 | Timo Van Neerden <fire@lehollandaisvolant.net> | 16 | Timo Van Neerden <fire@lehollandaisvolant.net> |
12 | Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurhollandais@gmail.com> | 17 | Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurhollandais@gmail.com> |
13 | VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com> | 18 | VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com> |
@@ -7,31 +7,20 @@ RewriteEngine On | |||
7 | RewriteRule ^(.git|doxygen|vendor) - [F] | 7 | RewriteRule ^(.git|doxygen|vendor) - [F] |
8 | 8 | ||
9 | # Forward the "Authorization" HTTP header | 9 | # Forward the "Authorization" HTTP header |
10 | # fixes JWT token not correctly forwarded on some Apache/FastCGI setups | ||
10 | RewriteCond %{HTTP:Authorization} ^(.*) | 11 | RewriteCond %{HTTP:Authorization} ^(.*) |
11 | RewriteRule .* - [e=HTTP_AUTHORIZATION:%1] | 12 | RewriteRule .* - [e=HTTP_AUTHORIZATION:%1] |
13 | # Alternative (if the 2 lines above don't work) | ||
14 | # SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0 | ||
12 | 15 | ||
13 | # REST API | 16 | # REST API |
17 | # Ionos Hosting needs RewriteBase / | ||
18 | # RewriteBase / | ||
14 | RewriteCond %{REQUEST_FILENAME} !-f | 19 | RewriteCond %{REQUEST_FILENAME} !-f |
15 | RewriteCond %{REQUEST_FILENAME} !-d | 20 | RewriteCond %{REQUEST_FILENAME} !-d |
16 | RewriteRule ^ index.php [QSA,L] | 21 | RewriteRule ^ index.php [QSA,L] |
17 | 22 | ||
18 | <Limit GET POST PUT DELETE OPTIONS> | 23 | <LimitExcept GET POST PUT DELETE PATCH OPTIONS> |
19 | <IfModule version_module> | ||
20 | <IfVersion >= 2.4> | ||
21 | Require all granted | ||
22 | </IfVersion> | ||
23 | <IfVersion < 2.4> | ||
24 | Allow from all | ||
25 | Deny from none | ||
26 | </IfVersion> | ||
27 | </IfModule> | ||
28 | |||
29 | <IfModule !version_module> | ||
30 | Require all granted | ||
31 | </IfModule> | ||
32 | </Limit> | ||
33 | |||
34 | <LimitExcept GET POST PUT DELETE OPTIONS> | ||
35 | <IfModule version_module> | 24 | <IfModule version_module> |
36 | <IfVersion >= 2.4> | 25 | <IfVersion >= 2.4> |
37 | Require all denied | 26 | Require all denied |
diff --git a/.travis.yml b/.travis.yml index d1415423..d7460947 100644 --- a/.travis.yml +++ b/.travis.yml | |||
@@ -1,31 +1,40 @@ | |||
1 | sudo: false | 1 | dist: bionic |
2 | dist: trusty | ||
3 | 2 | ||
4 | matrix: | 3 | matrix: |
5 | include: | 4 | include: |
5 | # jobs for each supported php version | ||
6 | - language: php | ||
7 | php: nightly # PHP 8.0 | ||
8 | install: | ||
9 | - composer self-update --2 | ||
10 | - composer update --ignore-platform-req=php | ||
11 | - composer remove --dev --ignore-platform-req=php phpunit/phpunit | ||
12 | - composer require --dev --ignore-platform-req=php phpunit/php-text-template ^2.0 | ||
13 | - composer require --dev --ignore-platform-req=php phpunit/phpunit ^9.0 | ||
14 | - language: php | ||
15 | php: 7.4 | ||
6 | - language: php | 16 | - language: php |
7 | php: 7.3 | 17 | php: 7.3 |
8 | - language: php | 18 | - language: php |
9 | php: 7.2 | 19 | php: 7.2 |
10 | - language: php | 20 | - language: php |
11 | php: 7.1 | 21 | php: 7.1 |
22 | # jobs for frontend builds | ||
12 | - language: node_js | 23 | - language: node_js |
13 | node_js: 8 | 24 | node_js: 10 |
14 | cache: | 25 | cache: |
15 | yarn: true | 26 | yarn: true |
16 | directories: | 27 | directories: |
17 | - $HOME/.cache/yarn | 28 | - $HOME/.cache/yarn |
18 | |||
19 | install: | 29 | install: |
20 | - yarn install | 30 | - yarn install |
21 | |||
22 | before_script: | 31 | before_script: |
23 | - PATH=${PATH//:\.\/node_modules\/\.bin/} | 32 | - PATH=${PATH//:\.\/node_modules\/\.bin/} |
24 | |||
25 | script: | 33 | script: |
26 | - yarn run build # Just to be sure that the build isn't broken | 34 | - yarn run build # verify successful frontend builds |
27 | - make eslint | 35 | - make eslint # javascript static analysis |
28 | - make sasslint | 36 | - make sasslint # linter for SASS syntax |
37 | # jobs for documentation builds | ||
29 | - language: python | 38 | - language: python |
30 | python: 3.6 | 39 | python: 3.6 |
31 | cache: | 40 | cache: |
@@ -41,7 +50,9 @@ cache: | |||
41 | - $HOME/.composer/cache | 50 | - $HOME/.composer/cache |
42 | 51 | ||
43 | install: | 52 | install: |
44 | - composer install --prefer-dist | 53 | # install/update composer and php dependencies |
54 | - composer config --unset platform && composer config platform.php $TRAVIS_PHP_VERSION | ||
55 | - composer update | ||
45 | 56 | ||
46 | before_script: | 57 | before_script: |
47 | - PATH=${PATH//:\.\/node_modules\/\.bin/} | 58 | - PATH=${PATH//:\.\/node_modules\/\.bin/} |
@@ -1,46 +1,56 @@ | |||
1 | 782 ArthurHoaro <arthur@hoa.ro> | 1 | 991 ArthurHoaro <arthur@hoa.ro> |
2 | 401 VirtualTam <virtualtam@flibidi.net> | 2 | 402 VirtualTam <virtualtam@flibidi.net> |
3 | 218 nodiscc <nodiscc@gmail.com> | 3 | 294 nodiscc <nodiscc@gmail.com> |
4 | 56 Sébastien Sauvage <sebsauvage@sebsauvage.net> | 4 | 56 Sébastien Sauvage <sebsauvage@sebsauvage.net> |
5 | 16 Luce Carević <lcarevic@access42.net> | 5 | 16 Luce Carević <lcarevic@access42.net> |
6 | 15 Florian Eula <eula.florian@gmail.com> | 6 | 15 Florian Eula <eula.florian@gmail.com> |
7 | 13 Emilien Klein <emilien@klein.st> | 7 | 13 Emilien Klein <emilien@klein.st> |
8 | 12 Nicolas Danelon <hi@nicolasmd.com.ar> | 8 | 12 Nicolas Danelon <hi@nicolasmd.com.ar> |
9 | 9 Lucas Cimon <lucas.cimon@gmail.com> | ||
9 | 9 Willi Eggeling <thewilli@gmail.com> | 10 | 9 Willi Eggeling <thewilli@gmail.com> |
10 | 8 Christophe HENRY <christophe.henry@sbgodin.fr> | 11 | 8 Christophe HENRY <christophe.henry@sbgodin.fr> |
11 | 6 B. van Berkum <dev@dotmpe.com> | 12 | 6 B. van Berkum <dev@dotmpe.com> |
13 | 6 Immánuel Fodor <immanuelfactor+github@gmail.com> | ||
14 | 6 Keith Carangelo <mail@kcaran.com> | ||
15 | 6 kalvn <kalvnthereal@gmail.com> | ||
12 | 6 llune <llune@users.noreply.github.com> | 16 | 6 llune <llune@users.noreply.github.com> |
13 | 5 Lucas Cimon <lucas.cimon@gmail.com> | ||
14 | 5 Mark Schmitz <kramred@gmail.com> | 17 | 5 Mark Schmitz <kramred@gmail.com> |
15 | 5 kalvn <kalvnthereal@gmail.com> | 18 | 5 Sébastien NOBILI <code@pipoprods.org> |
19 | 5 dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> | ||
16 | 4 Alexandre Alapetite <alexandre@alapetite.fr> | 20 | 4 Alexandre Alapetite <alexandre@alapetite.fr> |
17 | 4 David Sferruzza <david.sferruzza@gmail.com> | 21 | 4 David Sferruzza <david.sferruzza@gmail.com> |
18 | 4 Immánuel Fodor <immanuelfactor+github@gmail.com> | ||
19 | 3 Agurato <mail.vmonot@gmail.com> | 22 | 3 Agurato <mail.vmonot@gmail.com> |
23 | 3 Christoph Stoettner <christoph.stoettner@stoeps.de> | ||
20 | 3 Teromene <teromene@teromene.fr> | 24 | 3 Teromene <teromene@teromene.fr> |
21 | 2 Alexandre G.-Raymond <alex@ndre.gr> | 25 | 2 Alexandre G.-Raymond <alex@ndre.gr> |
22 | 2 Chris Kuethe <chris.kuethe@gmail.com> | 26 | 2 Chris Kuethe <chris.kuethe@gmail.com> |
23 | 2 Felix Bartels <felix@host-consultants.de> | 27 | 2 Felix Bartels <felix@host-consultants.de> |
28 | 2 Guillaume Virlet <github@virlet.org> | ||
24 | 2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org> | 29 | 2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org> |
25 | 2 Mathieu Chabanon <git@matchab.fr> | 30 | 2 Mathieu Chabanon <git@matchab.fr> |
26 | 2 Miloš Jovanović <mjovanovic@gmail.com> | 31 | 2 Miloš Jovanović <mjovanovic@gmail.com> |
32 | 2 Neros <contact@neros.fr> | ||
27 | 2 Qwerty <champlywood@free.fr> | 33 | 2 Qwerty <champlywood@free.fr> |
28 | 2 Stephen Muth <smuth4@gmail.com> | 34 | 2 Stephen Muth <smuth4@gmail.com> |
29 | 2 Timo Van Neerden <fire@lehollandaisvolant.net> | 35 | 2 Timo Van Neerden <fire@lehollandaisvolant.net> |
36 | 2 flow.gunso <flow.gunso@gmail.com> | ||
30 | 2 julienCXX <software@chmodplusx.eu> | 37 | 2 julienCXX <software@chmodplusx.eu> |
31 | 2 philipp-r <philipp-r@users.noreply.github.com> | 38 | 2 philipp-r <philipp-r@users.noreply.github.com> |
32 | 2 pips <pips@e5150.fr> | 39 | 2 pips <pips@e5150.fr> |
33 | 2 trailjeep <trailjeep@gmail.com> | 40 | 2 trailjeep <trailjeep@gmail.com> |
41 | 2 yude <yudesleepy@gmail.com> | ||
34 | 1 Adrien Oliva <adrien.oliva@yapbreak.fr> | 42 | 1 Adrien Oliva <adrien.oliva@yapbreak.fr> |
35 | 1 Adrien le Maire <adrien@alemaire.be> | 43 | 1 Adrien le Maire <adrien@alemaire.be> |
36 | 1 Alexis J <alexis@effingo.be> | 44 | 1 Alexis J <alexis@effingo.be> |
37 | 1 Angristan <angristan@users.noreply.github.com> | 45 | 1 Angristan <angristan@users.noreply.github.com> |
38 | 1 Bish Erbas <42714627+bisherbas@users.noreply.github.com> | 46 | 1 Bish Erbas <42714627+bisherbas@users.noreply.github.com> |
39 | 1 BoboTiG <bobotig@gmail.com> | 47 | 1 BoboTiG <bobotig@gmail.com> |
48 | 1 Brendan M. Sleight <bms.git@barwap.com> | ||
40 | 1 Bronco <bronco@warriordudimanche.net> | 49 | 1 Bronco <bronco@warriordudimanche.net> |
41 | 1 Buster One <37770318+buster-one@users.noreply.github.com> | 50 | 1 Buster One <37770318+buster-one@users.noreply.github.com> |
42 | 1 D Low <daniellowtw@gmail.com> | 51 | 1 D Low <daniellowtw@gmail.com> |
43 | 1 Daniel Jakots <vigdis@chown.me> | 52 | 1 Daniel Jakots <vigdis@chown.me> |
53 | 1 David Foucher <dev@tyjak.net> | ||
44 | 1 Dennis Verspuij <dennisverspuij@users.noreply.github.com> | 54 | 1 Dennis Verspuij <dennisverspuij@users.noreply.github.com> |
45 | 1 Dimtion <zizou.xena@gmail.com> | 55 | 1 Dimtion <zizou.xena@gmail.com> |
46 | 1 Fanch <fanch-github@qth.fr> | 56 | 1 Fanch <fanch-github@qth.fr> |
@@ -48,20 +58,25 @@ | |||
48 | 1 Florian Voigt <flvoigt@me.com> | 58 | 1 Florian Voigt <flvoigt@me.com> |
49 | 1 Franck Kerbiriou <FranckKe@users.noreply.github.com> | 59 | 1 Franck Kerbiriou <FranckKe@users.noreply.github.com> |
50 | 1 Gary Marigliano <gmarigliano93@gmail.com> | 60 | 1 Gary Marigliano <gmarigliano93@gmail.com> |
51 | 1 Guillaume Virlet <github@virlet.org> | ||
52 | 1 Jonathan Amiez <jonathan.amiez@gmail.com> | 61 | 1 Jonathan Amiez <jonathan.amiez@gmail.com> |
53 | 1 Jonathan Druart <jonathan.druart@gmail.com> | 62 | 1 Jonathan Druart <jonathan.druart@gmail.com> |
54 | 1 Julien Pivotto <roidelapluie@inuits.eu> | 63 | 1 Julien Pivotto <roidelapluie@inuits.eu> |
55 | 1 Kevin Canévet <kevin@streamroot.io> | 64 | 1 Kevin Canévet <kevin@streamroot.io> |
65 | 1 Kevin Masson <kevin.masson@methodinthemadness.eu> | ||
56 | 1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org> | 66 | 1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org> |
57 | 1 Lionel Martin <renarddesmers@gmail.com> | 67 | 1 Lionel Martin <renarddesmers@gmail.com> |
58 | 1 Mark Gerarts <mark.gerarts@gmail.com> | 68 | 1 Mark Gerarts <mark.gerarts@gmail.com> |
59 | 1 Marsup <marsup@gmail.com> | 69 | 1 Marsup <marsup@gmail.com> |
60 | 1 Neros <contact@neros.fr> | 70 | 1 Paul van den Burg <github@paulvandenburg.nl> |
61 | 1 Rajat Hans <rajathans9@gmail.com> | 71 | 1 Rajat Hans <rajathans9@gmail.com> |
62 | 1 Sbgodin <Sbgodin@users.noreply.github.com> | 72 | 1 Sbgodin <Sbgodin@users.noreply.github.com> |
73 | 1 Sebastien Wains <sebw@users.noreply.github.com> | ||
63 | 1 TsT <tst2005@gmail.com> | 74 | 1 TsT <tst2005@gmail.com> |
64 | 1 agentcobra <agentcobra@free.fr> | 75 | 1 agentcobra <agentcobra@free.fr> |
76 | 1 aguy <aguytech@users.noreply.github.com> | ||
65 | 1 dimtion <zizou.xena@gmail.com> | 77 | 1 dimtion <zizou.xena@gmail.com> |
66 | 1 durcheinandr <jochen@durcheinandr.de> | 78 | 1 durcheinandr <jochen@durcheinandr.de> |
67 | 1 lapineige <lapineige@users.noreply.github.com> | 79 | 1 lapineige <lapineige@users.noreply.github.com> |
80 | 1 owen bell <66233223+xfnw@users.noreply.github.com> | ||
81 | 1 rfolo9li <50079896+rfolo9li@users.noreply.github.com> | ||
82 | 1 sprak3000 <sprak3000+github@gmail.com> | ||
diff --git a/CHANGELOG.md b/CHANGELOG.md index abf802ea..f1686d67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -4,6 +4,78 @@ 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.12.1]() - UNRELEASED | ||
8 | |||
9 | ## [v0.12.0](https://github.com/shaarli/Shaarli/releases/tag/v0.12.0) - 2020-10-13 | ||
10 | |||
11 | **Save you `data/` folder before updating!** | ||
12 | |||
13 | ### Added | ||
14 | - Thumbnailer: add soundcloud.com to list of common media domains | ||
15 | - Markdown rendering is now integrated into Shaarli core | ||
16 | - Add autofocus on tag cloud filter input | ||
17 | - Japanese translations | ||
18 | - Japanese translation: add language to admin configuration page | ||
19 | - Support for PHP 8.0 | ||
20 | - Support for local anchor URL (starting with `#`) | ||
21 | - LDAP authentication | ||
22 | - Encapsulated PageCacheManager | ||
23 | - Docs: | ||
24 | - add screenshots of all pages | ||
25 | - section about mkdocs | ||
26 | - Ulauncher extension | ||
27 | - CI: run against PHP 7.4 | ||
28 | - Added $links_per_page variable to template and display on default | ||
29 | - Inject BookmarkServiceInterface in plugins data | ||
30 | - Add manual configuration for root URL | ||
31 | - Added PATCH to the allowed Apache request methods. | ||
32 | - REST API: compatibility with ionos Apache's headers | ||
33 | |||
34 | ### Changed | ||
35 | - Introduce Bookmark object and Service layer | ||
36 | - Save bookmark as objects in the datastore | ||
37 | - Handle bookmark as objects across the whole codebase (except templates and plugins) | ||
38 | - Process all Shaarli page through Slim controller, with proper URL rewriting (see #1516) | ||
39 | - Docs: the entire documentation has been reviewed, updated and improved, thanks to @nodiscc! | ||
40 | - ATOM feed: use instance name as author name instead of URL | ||
41 | - Updated French translation | ||
42 | - Default colors plugin: generate CSS file during initialization | ||
43 | - Improve default bookmarks after install | ||
44 | - Upgrade all front end dependencies and webpack build | ||
45 | - Default theme: Make tag cloud/list views buttons more obvious | ||
46 | |||
47 | ### Fixed | ||
48 | - Undefined index: thumbnail in daily page | ||
49 | - Undefined index: thumbnail on OpenGraph headers | ||
50 | - Undefined index: updated on linklist | ||
51 | - Make sure that bookmark sort is consistent, even with equal timestamps | ||
52 | - Code PHP version check as requirement bumped to PHP 7.1 | ||
53 | - Thumbnail images lazy loading | ||
54 | - Markdown plugin: fix RSS feed direct link reverse | ||
55 | - Fix RSS permalink included in Markdown bloc | ||
56 | - Demo plugin: multiple typos | ||
57 | - Makefile target for releases | ||
58 | - Makefile target for html documentation | ||
59 | - Session cookie setting being set while session is active | ||
60 | - Deprecated use of implode | ||
61 | - Division by zero in tag cloud | ||
62 | - CI: deprecated linux distribution and sudo directive | ||
63 | - Docker build: gcc is no longer included in python alpine image | ||
64 | - Default template: display pin button in mobile view | ||
65 | - Pinned bookmarks are not longer displayed first in ATOM/RSS feeds | ||
66 | - Docs: | ||
67 | - Outdated Docker documentation for stable branch | ||
68 | - Outdated links | ||
69 | - Plugin description in meta files | ||
70 | - docker-compose.yml: pin traefik image to 1.7-alpine | ||
71 | |||
72 | ### Removed | ||
73 | - Markdown plugin | ||
74 | - Docs: | ||
75 | - emojione & twemoji removed | ||
76 | - Makefile: remove static_analysis_summary from all: target | ||
77 | - doc/Makefile: remove references to composer update | ||
78 | |||
7 | ## [v0.11.1](https://github.com/shaarli/Shaarli/releases/tag/v0.11.1) - 2019-08-03 | 79 | ## [v0.11.1](https://github.com/shaarli/Shaarli/releases/tag/v0.11.1) - 2019-08-03 |
8 | 80 | ||
9 | Release to fix broken Docker build on the latest version. | 81 | Release to fix broken Docker build on the latest version. |
@@ -4,6 +4,7 @@ | |||
4 | FROM python:3-alpine as docs | 4 | FROM python:3-alpine as docs |
5 | ADD . /usr/src/app/shaarli | 5 | ADD . /usr/src/app/shaarli |
6 | RUN cd /usr/src/app/shaarli \ | 6 | RUN cd /usr/src/app/shaarli \ |
7 | && apk add --no-cache gcc musl-dev \ | ||
7 | && pip install --no-cache-dir mkdocs \ | 8 | && pip install --no-cache-dir mkdocs \ |
8 | && mkdocs build --clean | 9 | && mkdocs build --clean |
9 | 10 | ||
diff --git a/Dockerfile.armhf b/Dockerfile.armhf index b75663bb..5bbf6680 100644 --- a/Dockerfile.armhf +++ b/Dockerfile.armhf | |||
@@ -12,7 +12,7 @@ RUN apk --update --no-cache add py2-pip \ | |||
12 | # - Resolve PHP dependencies with Composer | 12 | # - Resolve PHP dependencies with Composer |
13 | FROM arm32v6/alpine:3.8 as composer | 13 | FROM arm32v6/alpine:3.8 as composer |
14 | COPY --from=docs /usr/src/app/shaarli /app/shaarli | 14 | COPY --from=docs /usr/src/app/shaarli /app/shaarli |
15 | RUN apk --update --no-cache add php7-curl php7-mbstring composer \ | 15 | RUN apk --update --no-cache add php7-curl php7-mbstring php7-simplexml composer \ |
16 | && cd /app/shaarli \ | 16 | && cd /app/shaarli \ |
17 | && composer --prefer-dist --no-dev install | 17 | && composer --prefer-dist --no-dev install |
18 | 18 | ||
@@ -3,7 +3,7 @@ | |||
3 | 3 | ||
4 | BIN = vendor/bin | 4 | BIN = vendor/bin |
5 | 5 | ||
6 | all: static_analysis_summary check_permissions test | 6 | all: check_permissions test |
7 | 7 | ||
8 | ## | 8 | ## |
9 | # Docker test adapter | 9 | # Docker test adapter |
@@ -80,10 +80,15 @@ locale_test_%: | |||
80 | --testsuite language-$(firstword $(subst _, ,$*)) | 80 | --testsuite language-$(firstword $(subst _, ,$*)) |
81 | 81 | ||
82 | all_tests: test locale_test_de_DE locale_test_en_US locale_test_fr_FR | 82 | all_tests: test locale_test_de_DE locale_test_en_US locale_test_fr_FR |
83 | @$(BIN)/phpcov merge --html coverage coverage | 83 | @# --The current version is not compatible with PHP 7.2 |
84 | @#$(BIN)/phpcov merge --html coverage coverage | ||
84 | @# --text doesn't work with phpunit 4.* (v5 requires PHP 5.6) | 85 | @# --text doesn't work with phpunit 4.* (v5 requires PHP 5.6) |
85 | @#$(BIN)/phpcov merge --text coverage/txt coverage | 86 | @#$(BIN)/phpcov merge --text coverage/txt coverage |
86 | 87 | ||
88 | ### download 3rd-party PHP libraries, including dev dependencies | ||
89 | composer_dependencies_dev: clean | ||
90 | composer install --prefer-dist | ||
91 | |||
87 | ## | 92 | ## |
88 | # Custom release archive generation | 93 | # Custom release archive generation |
89 | # | 94 | # |
@@ -122,7 +127,8 @@ release_tar: composer_dependencies htmldoc translate build_frontend | |||
122 | ### generate a release zip and include 3rd-party dependencies and translations | 127 | ### generate a release zip and include 3rd-party dependencies and translations |
123 | release_zip: composer_dependencies htmldoc translate build_frontend | 128 | release_zip: composer_dependencies htmldoc translate build_frontend |
124 | git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD | 129 | git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD |
125 | mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor} | 130 | mkdir -p $(ARCHIVE_PREFIX)/doc |
131 | mkdir -p $(ARCHIVE_PREFIX)/vendor | ||
126 | rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/ | 132 | rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/ |
127 | zip -r $(ARCHIVE_VERSION).zip $(ARCHIVE_PREFIX)doc/ | 133 | zip -r $(ARCHIVE_VERSION).zip $(ARCHIVE_PREFIX)doc/ |
128 | rsync -a vendor/ $(ARCHIVE_PREFIX)vendor/ | 134 | rsync -a vendor/ $(ARCHIVE_PREFIX)vendor/ |
@@ -154,6 +160,7 @@ phpdoc: clean | |||
154 | htmldoc: | 160 | htmldoc: |
155 | python3 -m venv venv/ | 161 | python3 -m venv venv/ |
156 | bash -c 'source venv/bin/activate; \ | 162 | bash -c 'source venv/bin/activate; \ |
163 | pip install wheel; \ | ||
157 | pip install mkdocs; \ | 164 | pip install mkdocs; \ |
158 | mkdocs build --clean' | 165 | mkdocs build --clean' |
159 | find doc/html/ -type f -exec chmod a-x '{}' \; | 166 | find doc/html/ -type f -exec chmod a-x '{}' \; |
@@ -171,4 +178,4 @@ eslint: | |||
171 | 178 | ||
172 | ### Run CSSLint check against Shaarli's SCSS files | 179 | ### Run CSSLint check against Shaarli's SCSS files |
173 | sasslint: | 180 | sasslint: |
174 | @yarn run sass-lint -c .dev/.sasslintrc 'assets/default/scss/*.scss' -v -q | 181 | @yarn run stylelint --config .dev/.stylelintrc.js 'assets/default/scss/*.scss' |
@@ -9,7 +9,7 @@ _It is designed to be personal (single-user), fast and handy._ | |||
9 | [](https://github.com/shaarli/Shaarli/releases/tag/v0.10.4) | 9 | [](https://github.com/shaarli/Shaarli/releases/tag/v0.10.4) |
10 | [](https://travis-ci.org/shaarli/Shaarli) | 10 | [](https://travis-ci.org/shaarli/Shaarli) |
11 | • | 11 | • |
12 | [](https://github.com/shaarli/Shaarli/releases/tag/v0.11.0) | 12 | [](https://github.com/shaarli/Shaarli/releases/tag/v0.11.1) |
13 | [](https://travis-ci.org/shaarli/Shaarli) | 13 | [](https://travis-ci.org/shaarli/Shaarli) |
14 | • | 14 | • |
15 | [](https://github.com/shaarli/Shaarli) | 15 | [](https://github.com/shaarli/Shaarli) |
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php index 7fe3cb32..3aa21829 100644 --- a/application/ApplicationUtils.php +++ b/application/ApplicationUtils.php | |||
@@ -150,6 +150,8 @@ class ApplicationUtils | |||
150 | * @param string $minVersion minimum PHP required version | 150 | * @param string $minVersion minimum PHP required version |
151 | * @param string $curVersion current PHP version (use PHP_VERSION) | 151 | * @param string $curVersion current PHP version (use PHP_VERSION) |
152 | * | 152 | * |
153 | * @return bool true on success | ||
154 | * | ||
153 | * @throws Exception the PHP version is not supported | 155 | * @throws Exception the PHP version is not supported |
154 | */ | 156 | */ |
155 | public static function checkPHPVersion($minVersion, $curVersion) | 157 | public static function checkPHPVersion($minVersion, $curVersion) |
@@ -163,6 +165,7 @@ class ApplicationUtils | |||
163 | ); | 165 | ); |
164 | throw new Exception(sprintf($msg, $minVersion)); | 166 | throw new Exception(sprintf($msg, $minVersion)); |
165 | } | 167 | } |
168 | return true; | ||
166 | } | 169 | } |
167 | 170 | ||
168 | /** | 171 | /** |
diff --git a/application/History.php b/application/History.php index a5846652..4fd2f294 100644 --- a/application/History.php +++ b/application/History.php | |||
@@ -3,6 +3,7 @@ namespace Shaarli; | |||
3 | 3 | ||
4 | use DateTime; | 4 | use DateTime; |
5 | use Exception; | 5 | use Exception; |
6 | use Shaarli\Bookmark\Bookmark; | ||
6 | 7 | ||
7 | /** | 8 | /** |
8 | * Class History | 9 | * Class History |
@@ -20,7 +21,7 @@ use Exception; | |||
20 | * - UPDATED: link updated | 21 | * - UPDATED: link updated |
21 | * - DELETED: link deleted | 22 | * - DELETED: link deleted |
22 | * - SETTINGS: the settings have been updated through the UI. | 23 | * - SETTINGS: the settings have been updated through the UI. |
23 | * - IMPORT: bulk links import | 24 | * - IMPORT: bulk bookmarks import |
24 | * | 25 | * |
25 | * Note: new events are put at the beginning of the file and history array. | 26 | * Note: new events are put at the beginning of the file and history array. |
26 | */ | 27 | */ |
@@ -96,31 +97,31 @@ class History | |||
96 | /** | 97 | /** |
97 | * Add Event: new link. | 98 | * Add Event: new link. |
98 | * | 99 | * |
99 | * @param array $link Link data. | 100 | * @param Bookmark $link Link data. |
100 | */ | 101 | */ |
101 | public function addLink($link) | 102 | public function addLink($link) |
102 | { | 103 | { |
103 | $this->addEvent(self::CREATED, $link['id']); | 104 | $this->addEvent(self::CREATED, $link->getId()); |
104 | } | 105 | } |
105 | 106 | ||
106 | /** | 107 | /** |
107 | * Add Event: update existing link. | 108 | * Add Event: update existing link. |
108 | * | 109 | * |
109 | * @param array $link Link data. | 110 | * @param Bookmark $link Link data. |
110 | */ | 111 | */ |
111 | public function updateLink($link) | 112 | public function updateLink($link) |
112 | { | 113 | { |
113 | $this->addEvent(self::UPDATED, $link['id']); | 114 | $this->addEvent(self::UPDATED, $link->getId()); |
114 | } | 115 | } |
115 | 116 | ||
116 | /** | 117 | /** |
117 | * Add Event: delete existing link. | 118 | * Add Event: delete existing link. |
118 | * | 119 | * |
119 | * @param array $link Link data. | 120 | * @param Bookmark $link Link data. |
120 | */ | 121 | */ |
121 | public function deleteLink($link) | 122 | public function deleteLink($link) |
122 | { | 123 | { |
123 | $this->addEvent(self::DELETED, $link['id']); | 124 | $this->addEvent(self::DELETED, $link->getId()); |
124 | } | 125 | } |
125 | 126 | ||
126 | /** | 127 | /** |
@@ -134,7 +135,7 @@ class History | |||
134 | /** | 135 | /** |
135 | * Add Event: bulk import. | 136 | * Add Event: bulk import. |
136 | * | 137 | * |
137 | * Note: we don't store links add/update one by one since it can have a huge impact on performances. | 138 | * Note: we don't store bookmarks add/update one by one since it can have a huge impact on performances. |
138 | */ | 139 | */ |
139 | public function importLinks() | 140 | public function importLinks() |
140 | { | 141 | { |
diff --git a/application/Languages.php b/application/Languages.php index 5cda802e..d83e0765 100644 --- a/application/Languages.php +++ b/application/Languages.php | |||
@@ -179,9 +179,10 @@ class Languages | |||
179 | { | 179 | { |
180 | return [ | 180 | return [ |
181 | 'auto' => t('Automatic'), | 181 | 'auto' => t('Automatic'), |
182 | 'de' => t('German'), | ||
182 | 'en' => t('English'), | 183 | 'en' => t('English'), |
183 | 'fr' => t('French'), | 184 | 'fr' => t('French'), |
184 | 'de' => t('German'), | 185 | 'jp' => t('Japanese'), |
185 | ]; | 186 | ]; |
186 | } | 187 | } |
187 | } | 188 | } |
diff --git a/application/Router.php b/application/Router.php deleted file mode 100644 index d7187487..00000000 --- a/application/Router.php +++ /dev/null | |||
@@ -1,184 +0,0 @@ | |||
1 | <?php | ||
2 | namespace Shaarli; | ||
3 | |||
4 | /** | ||
5 | * Class Router | ||
6 | * | ||
7 | * (only displayable pages here) | ||
8 | */ | ||
9 | class Router | ||
10 | { | ||
11 | public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update'; | ||
12 | |||
13 | public static $PAGE_LOGIN = 'login'; | ||
14 | |||
15 | public static $PAGE_PICWALL = 'picwall'; | ||
16 | |||
17 | public static $PAGE_TAGCLOUD = 'tagcloud'; | ||
18 | |||
19 | public static $PAGE_TAGLIST = 'taglist'; | ||
20 | |||
21 | public static $PAGE_DAILY = 'daily'; | ||
22 | |||
23 | public static $PAGE_FEED_ATOM = 'atom'; | ||
24 | |||
25 | public static $PAGE_FEED_RSS = 'rss'; | ||
26 | |||
27 | public static $PAGE_TOOLS = 'tools'; | ||
28 | |||
29 | public static $PAGE_CHANGEPASSWORD = 'changepasswd'; | ||
30 | |||
31 | public static $PAGE_CONFIGURE = 'configure'; | ||
32 | |||
33 | public static $PAGE_CHANGETAG = 'changetag'; | ||
34 | |||
35 | public static $PAGE_ADDLINK = 'addlink'; | ||
36 | |||
37 | public static $PAGE_EDITLINK = 'edit_link'; | ||
38 | |||
39 | public static $PAGE_DELETELINK = 'delete_link'; | ||
40 | |||
41 | public static $PAGE_CHANGE_VISIBILITY = 'change_visibility'; | ||
42 | |||
43 | public static $PAGE_PINLINK = 'pin'; | ||
44 | |||
45 | public static $PAGE_EXPORT = 'export'; | ||
46 | |||
47 | public static $PAGE_IMPORT = 'import'; | ||
48 | |||
49 | public static $PAGE_OPENSEARCH = 'opensearch'; | ||
50 | |||
51 | public static $PAGE_LINKLIST = 'linklist'; | ||
52 | |||
53 | public static $PAGE_PLUGINSADMIN = 'pluginadmin'; | ||
54 | |||
55 | public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin'; | ||
56 | |||
57 | public static $PAGE_THUMBS_UPDATE = 'thumbs_update'; | ||
58 | |||
59 | public static $GET_TOKEN = 'token'; | ||
60 | |||
61 | /** | ||
62 | * Reproducing renderPage() if hell, to avoid regression. | ||
63 | * | ||
64 | * This highlights how bad this needs to be rewrite, | ||
65 | * but let's focus on plugins for now. | ||
66 | * | ||
67 | * @param string $query $_SERVER['QUERY_STRING']. | ||
68 | * @param array $get $_SERVER['GET']. | ||
69 | * @param bool $loggedIn true if authenticated user. | ||
70 | * | ||
71 | * @return string page found. | ||
72 | */ | ||
73 | public static function findPage($query, $get, $loggedIn) | ||
74 | { | ||
75 | $loggedIn = ($loggedIn === true) ? true : false; | ||
76 | |||
77 | if (empty($query) && !isset($get['edit_link']) && !isset($get['post'])) { | ||
78 | return self::$PAGE_LINKLIST; | ||
79 | } | ||
80 | |||
81 | if (startsWith($query, 'do=' . self::$PAGE_LOGIN) && $loggedIn === false) { | ||
82 | return self::$PAGE_LOGIN; | ||
83 | } | ||
84 | |||
85 | if (startsWith($query, 'do=' . self::$PAGE_PICWALL)) { | ||
86 | return self::$PAGE_PICWALL; | ||
87 | } | ||
88 | |||
89 | if (startsWith($query, 'do=' . self::$PAGE_TAGCLOUD)) { | ||
90 | return self::$PAGE_TAGCLOUD; | ||
91 | } | ||
92 | |||
93 | if (startsWith($query, 'do=' . self::$PAGE_TAGLIST)) { | ||
94 | return self::$PAGE_TAGLIST; | ||
95 | } | ||
96 | |||
97 | if (startsWith($query, 'do=' . self::$PAGE_OPENSEARCH)) { | ||
98 | return self::$PAGE_OPENSEARCH; | ||
99 | } | ||
100 | |||
101 | if (startsWith($query, 'do=' . self::$PAGE_DAILY)) { | ||
102 | return self::$PAGE_DAILY; | ||
103 | } | ||
104 | |||
105 | if (startsWith($query, 'do=' . self::$PAGE_FEED_ATOM)) { | ||
106 | return self::$PAGE_FEED_ATOM; | ||
107 | } | ||
108 | |||
109 | if (startsWith($query, 'do=' . self::$PAGE_FEED_RSS)) { | ||
110 | return self::$PAGE_FEED_RSS; | ||
111 | } | ||
112 | |||
113 | if (startsWith($query, 'do=' . self::$PAGE_THUMBS_UPDATE)) { | ||
114 | return self::$PAGE_THUMBS_UPDATE; | ||
115 | } | ||
116 | |||
117 | if (startsWith($query, 'do=' . self::$AJAX_THUMB_UPDATE)) { | ||
118 | return self::$AJAX_THUMB_UPDATE; | ||
119 | } | ||
120 | |||
121 | // At this point, only loggedin pages. | ||
122 | if (!$loggedIn) { | ||
123 | return self::$PAGE_LINKLIST; | ||
124 | } | ||
125 | |||
126 | if (startsWith($query, 'do=' . self::$PAGE_TOOLS)) { | ||
127 | return self::$PAGE_TOOLS; | ||
128 | } | ||
129 | |||
130 | if (startsWith($query, 'do=' . self::$PAGE_CHANGEPASSWORD)) { | ||
131 | return self::$PAGE_CHANGEPASSWORD; | ||
132 | } | ||
133 | |||
134 | if (startsWith($query, 'do=' . self::$PAGE_CONFIGURE)) { | ||
135 | return self::$PAGE_CONFIGURE; | ||
136 | } | ||
137 | |||
138 | if (startsWith($query, 'do=' . self::$PAGE_CHANGETAG)) { | ||
139 | return self::$PAGE_CHANGETAG; | ||
140 | } | ||
141 | |||
142 | if (startsWith($query, 'do=' . self::$PAGE_ADDLINK)) { | ||
143 | return self::$PAGE_ADDLINK; | ||
144 | } | ||
145 | |||
146 | if (isset($get['edit_link']) || isset($get['post'])) { | ||
147 | return self::$PAGE_EDITLINK; | ||
148 | } | ||
149 | |||
150 | if (isset($get['delete_link'])) { | ||
151 | return self::$PAGE_DELETELINK; | ||
152 | } | ||
153 | |||
154 | if (isset($get[self::$PAGE_CHANGE_VISIBILITY])) { | ||
155 | return self::$PAGE_CHANGE_VISIBILITY; | ||
156 | } | ||
157 | |||
158 | if (startsWith($query, 'do=' . self::$PAGE_PINLINK)) { | ||
159 | return self::$PAGE_PINLINK; | ||
160 | } | ||
161 | |||
162 | if (startsWith($query, 'do=' . self::$PAGE_EXPORT)) { | ||
163 | return self::$PAGE_EXPORT; | ||
164 | } | ||
165 | |||
166 | if (startsWith($query, 'do=' . self::$PAGE_IMPORT)) { | ||
167 | return self::$PAGE_IMPORT; | ||
168 | } | ||
169 | |||
170 | if (startsWith($query, 'do=' . self::$PAGE_PLUGINSADMIN)) { | ||
171 | return self::$PAGE_PLUGINSADMIN; | ||
172 | } | ||
173 | |||
174 | if (startsWith($query, 'do=' . self::$PAGE_SAVE_PLUGINSADMIN)) { | ||
175 | return self::$PAGE_SAVE_PLUGINSADMIN; | ||
176 | } | ||
177 | |||
178 | if (startsWith($query, 'do=' . self::$GET_TOKEN)) { | ||
179 | return self::$GET_TOKEN; | ||
180 | } | ||
181 | |||
182 | return self::$PAGE_LINKLIST; | ||
183 | } | ||
184 | } | ||
diff --git a/application/Thumbnailer.php b/application/Thumbnailer.php index d5f5ac28..5aec23c8 100644 --- a/application/Thumbnailer.php +++ b/application/Thumbnailer.php | |||
@@ -4,7 +4,6 @@ namespace Shaarli; | |||
4 | 4 | ||
5 | use Shaarli\Config\ConfigManager; | 5 | use Shaarli\Config\ConfigManager; |
6 | use WebThumbnailer\Application\ConfigManager as WTConfigManager; | 6 | use WebThumbnailer\Application\ConfigManager as WTConfigManager; |
7 | use WebThumbnailer\Exception\WebThumbnailerException; | ||
8 | use WebThumbnailer\WebThumbnailer; | 7 | use WebThumbnailer\WebThumbnailer; |
9 | 8 | ||
10 | /** | 9 | /** |
@@ -27,6 +26,7 @@ class Thumbnailer | |||
27 | 'instagram.com', | 26 | 'instagram.com', |
28 | 'pinterest.com', | 27 | 'pinterest.com', |
29 | 'pinterest.fr', | 28 | 'pinterest.fr', |
29 | 'soundcloud.com', | ||
30 | 'tumblr.com', | 30 | 'tumblr.com', |
31 | 'deviantart.com', | 31 | 'deviantart.com', |
32 | ]; | 32 | ]; |
@@ -89,7 +89,7 @@ class Thumbnailer | |||
89 | 89 | ||
90 | try { | 90 | try { |
91 | return $this->wt->thumbnail($url); | 91 | return $this->wt->thumbnail($url); |
92 | } catch (WebThumbnailerException $e) { | 92 | } catch (\Throwable $e) { |
93 | // Exceptions are only thrown in debug mode. | 93 | // Exceptions are only thrown in debug mode. |
94 | error_log(get_class($e) . ': ' . $e->getMessage()); | 94 | error_log(get_class($e) . ': ' . $e->getMessage()); |
95 | } | 95 | } |
diff --git a/application/Utils.php b/application/Utils.php index 925e1a22..bcfda65c 100644 --- a/application/Utils.php +++ b/application/Utils.php | |||
@@ -87,18 +87,22 @@ function endsWith($haystack, $needle, $case = true) | |||
87 | * | 87 | * |
88 | * @param mixed $input Data to escape: a single string or an array of strings. | 88 | * @param mixed $input Data to escape: a single string or an array of strings. |
89 | * | 89 | * |
90 | * @return string escaped. | 90 | * @return string|array escaped. |
91 | */ | 91 | */ |
92 | function escape($input) | 92 | function escape($input) |
93 | { | 93 | { |
94 | if (is_bool($input)) { | 94 | if (null === $input) { |
95 | return null; | ||
96 | } | ||
97 | |||
98 | if (is_bool($input) || is_int($input) || is_float($input) || $input instanceof DateTimeInterface) { | ||
95 | return $input; | 99 | return $input; |
96 | } | 100 | } |
97 | 101 | ||
98 | if (is_array($input)) { | 102 | if (is_array($input)) { |
99 | $out = array(); | 103 | $out = array(); |
100 | foreach ($input as $key => $value) { | 104 | foreach ($input as $key => $value) { |
101 | $out[$key] = escape($value); | 105 | $out[escape($key)] = escape($value); |
102 | } | 106 | } |
103 | return $out; | 107 | return $out; |
104 | } | 108 | } |
@@ -159,10 +163,10 @@ function checkDateFormat($format, $string) | |||
159 | */ | 163 | */ |
160 | function generateLocation($referer, $host, $loopTerms = array()) | 164 | function generateLocation($referer, $host, $loopTerms = array()) |
161 | { | 165 | { |
162 | $finalReferer = '?'; | 166 | $finalReferer = './?'; |
163 | 167 | ||
164 | // No referer if it contains any value in $loopCriteria. | 168 | // No referer if it contains any value in $loopCriteria. |
165 | foreach ($loopTerms as $value) { | 169 | foreach (array_filter($loopTerms) as $value) { |
166 | if (strpos($referer, $value) !== false) { | 170 | if (strpos($referer, $value) !== false) { |
167 | return $finalReferer; | 171 | return $finalReferer; |
168 | } | 172 | } |
@@ -294,15 +298,15 @@ function normalize_spaces($string) | |||
294 | * Requires php-intl to display international datetimes, | 298 | * Requires php-intl to display international datetimes, |
295 | * otherwise default format '%c' will be returned. | 299 | * otherwise default format '%c' will be returned. |
296 | * | 300 | * |
297 | * @param DateTime $date to format. | 301 | * @param DateTimeInterface $date to format. |
298 | * @param bool $time Displays time if true. | 302 | * @param bool $time Displays time if true. |
299 | * @param bool $intl Use international format if true. | 303 | * @param bool $intl Use international format if true. |
300 | * | 304 | * |
301 | * @return bool|string Formatted date, or false if the input is invalid. | 305 | * @return bool|string Formatted date, or false if the input is invalid. |
302 | */ | 306 | */ |
303 | function format_date($date, $time = true, $intl = true) | 307 | function format_date($date, $time = true, $intl = true) |
304 | { | 308 | { |
305 | if (! $date instanceof DateTime) { | 309 | if (! $date instanceof DateTimeInterface) { |
306 | return false; | 310 | return false; |
307 | } | 311 | } |
308 | 312 | ||
diff --git a/application/api/ApiMiddleware.php b/application/api/ApiMiddleware.php index 2d55bda6..f5b53b01 100644 --- a/application/api/ApiMiddleware.php +++ b/application/api/ApiMiddleware.php | |||
@@ -3,6 +3,7 @@ namespace Shaarli\Api; | |||
3 | 3 | ||
4 | use Shaarli\Api\Exceptions\ApiAuthorizationException; | 4 | use Shaarli\Api\Exceptions\ApiAuthorizationException; |
5 | use Shaarli\Api\Exceptions\ApiException; | 5 | use Shaarli\Api\Exceptions\ApiException; |
6 | use Shaarli\Bookmark\BookmarkFileService; | ||
6 | use Shaarli\Config\ConfigManager; | 7 | use Shaarli\Config\ConfigManager; |
7 | use Slim\Container; | 8 | use Slim\Container; |
8 | use Slim\Http\Request; | 9 | use Slim\Http\Request; |
@@ -70,7 +71,14 @@ class ApiMiddleware | |||
70 | $response = $e->getApiResponse(); | 71 | $response = $e->getApiResponse(); |
71 | } | 72 | } |
72 | 73 | ||
73 | return $response; | 74 | return $response |
75 | ->withHeader('Access-Control-Allow-Origin', '*') | ||
76 | ->withHeader( | ||
77 | 'Access-Control-Allow-Headers', | ||
78 | 'X-Requested-With, Content-Type, Accept, Origin, Authorization' | ||
79 | ) | ||
80 | ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') | ||
81 | ; | ||
74 | } | 82 | } |
75 | 83 | ||
76 | /** | 84 | /** |
@@ -99,7 +107,9 @@ class ApiMiddleware | |||
99 | */ | 107 | */ |
100 | protected function checkToken($request) | 108 | protected function checkToken($request) |
101 | { | 109 | { |
102 | if (! $request->hasHeader('Authorization')) { | 110 | if (!$request->hasHeader('Authorization') |
111 | && !isset($this->container->environment['REDIRECT_HTTP_AUTHORIZATION']) | ||
112 | ) { | ||
103 | throw new ApiAuthorizationException('JWT token not provided'); | 113 | throw new ApiAuthorizationException('JWT token not provided'); |
104 | } | 114 | } |
105 | 115 | ||
@@ -107,7 +117,11 @@ class ApiMiddleware | |||
107 | throw new ApiAuthorizationException('Token secret must be set in Shaarli\'s administration'); | 117 | throw new ApiAuthorizationException('Token secret must be set in Shaarli\'s administration'); |
108 | } | 118 | } |
109 | 119 | ||
110 | $authorization = $request->getHeaderLine('Authorization'); | 120 | if (isset($this->container->environment['REDIRECT_HTTP_AUTHORIZATION'])) { |
121 | $authorization = $this->container->environment['REDIRECT_HTTP_AUTHORIZATION']; | ||
122 | } else { | ||
123 | $authorization = $request->getHeaderLine('Authorization'); | ||
124 | } | ||
111 | 125 | ||
112 | if (! preg_match('/^Bearer (.*)/i', $authorization, $matches)) { | 126 | if (! preg_match('/^Bearer (.*)/i', $authorization, $matches)) { |
113 | throw new ApiAuthorizationException('Invalid JWT header'); | 127 | throw new ApiAuthorizationException('Invalid JWT header'); |
@@ -117,7 +131,7 @@ class ApiMiddleware | |||
117 | } | 131 | } |
118 | 132 | ||
119 | /** | 133 | /** |
120 | * Instantiate a new LinkDB including private links, | 134 | * Instantiate a new LinkDB including private bookmarks, |
121 | * and load in the Slim container. | 135 | * and load in the Slim container. |
122 | * | 136 | * |
123 | * FIXME! LinkDB could use a refactoring to avoid this trick. | 137 | * FIXME! LinkDB could use a refactoring to avoid this trick. |
@@ -126,10 +140,10 @@ class ApiMiddleware | |||
126 | */ | 140 | */ |
127 | protected function setLinkDb($conf) | 141 | protected function setLinkDb($conf) |
128 | { | 142 | { |
129 | $linkDb = new \Shaarli\Bookmark\LinkDB( | 143 | $linkDb = new BookmarkFileService( |
130 | $conf->get('resource.datastore'), | 144 | $conf, |
131 | true, | 145 | $this->container->get('history'), |
132 | $conf->get('privacy.hide_public_links') | 146 | true |
133 | ); | 147 | ); |
134 | $this->container['db'] = $linkDb; | 148 | $this->container['db'] = $linkDb; |
135 | } | 149 | } |
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php index 1e3ac02e..faebb8f5 100644 --- a/application/api/ApiUtils.php +++ b/application/api/ApiUtils.php | |||
@@ -2,6 +2,7 @@ | |||
2 | namespace Shaarli\Api; | 2 | namespace Shaarli\Api; |
3 | 3 | ||
4 | use Shaarli\Api\Exceptions\ApiAuthorizationException; | 4 | use Shaarli\Api\Exceptions\ApiAuthorizationException; |
5 | use Shaarli\Bookmark\Bookmark; | ||
5 | use Shaarli\Http\Base64Url; | 6 | use Shaarli\Http\Base64Url; |
6 | 7 | ||
7 | /** | 8 | /** |
@@ -15,6 +16,8 @@ class ApiUtils | |||
15 | * @param string $token JWT token extracted from the headers. | 16 | * @param string $token JWT token extracted from the headers. |
16 | * @param string $secret API secret set in the settings. | 17 | * @param string $secret API secret set in the settings. |
17 | * | 18 | * |
19 | * @return bool true on success | ||
20 | * | ||
18 | * @throws ApiAuthorizationException the token is not valid. | 21 | * @throws ApiAuthorizationException the token is not valid. |
19 | */ | 22 | */ |
20 | public static function validateJwtToken($token, $secret) | 23 | public static function validateJwtToken($token, $secret) |
@@ -45,33 +48,35 @@ class ApiUtils | |||
45 | ) { | 48 | ) { |
46 | throw new ApiAuthorizationException('Invalid JWT issued time'); | 49 | throw new ApiAuthorizationException('Invalid JWT issued time'); |
47 | } | 50 | } |
51 | |||
52 | return true; | ||
48 | } | 53 | } |
49 | 54 | ||
50 | /** | 55 | /** |
51 | * Format a Link for the REST API. | 56 | * Format a Link for the REST API. |
52 | * | 57 | * |
53 | * @param array $link Link data read from the datastore. | 58 | * @param Bookmark $bookmark Bookmark data read from the datastore. |
54 | * @param string $indexUrl Shaarli's index URL (used for relative URL). | 59 | * @param string $indexUrl Shaarli's index URL (used for relative URL). |
55 | * | 60 | * |
56 | * @return array Link data formatted for the REST API. | 61 | * @return array Link data formatted for the REST API. |
57 | */ | 62 | */ |
58 | public static function formatLink($link, $indexUrl) | 63 | public static function formatLink($bookmark, $indexUrl) |
59 | { | 64 | { |
60 | $out['id'] = $link['id']; | 65 | $out['id'] = $bookmark->getId(); |
61 | // Not an internal link | 66 | // Not an internal link |
62 | if (! is_note($link['url'])) { | 67 | if (! $bookmark->isNote()) { |
63 | $out['url'] = $link['url']; | 68 | $out['url'] = $bookmark->getUrl(); |
64 | } else { | 69 | } else { |
65 | $out['url'] = $indexUrl . $link['url']; | 70 | $out['url'] = rtrim($indexUrl, '/') . '/' . ltrim($bookmark->getUrl(), '/'); |
66 | } | 71 | } |
67 | $out['shorturl'] = $link['shorturl']; | 72 | $out['shorturl'] = $bookmark->getShortUrl(); |
68 | $out['title'] = $link['title']; | 73 | $out['title'] = $bookmark->getTitle(); |
69 | $out['description'] = $link['description']; | 74 | $out['description'] = $bookmark->getDescription(); |
70 | $out['tags'] = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY); | 75 | $out['tags'] = $bookmark->getTags(); |
71 | $out['private'] = $link['private'] == true; | 76 | $out['private'] = $bookmark->isPrivate(); |
72 | $out['created'] = $link['created']->format(\DateTime::ATOM); | 77 | $out['created'] = $bookmark->getCreated()->format(\DateTime::ATOM); |
73 | if (! empty($link['updated'])) { | 78 | if (! empty($bookmark->getUpdated())) { |
74 | $out['updated'] = $link['updated']->format(\DateTime::ATOM); | 79 | $out['updated'] = $bookmark->getUpdated()->format(\DateTime::ATOM); |
75 | } else { | 80 | } else { |
76 | $out['updated'] = ''; | 81 | $out['updated'] = ''; |
77 | } | 82 | } |
@@ -79,7 +84,7 @@ class ApiUtils | |||
79 | } | 84 | } |
80 | 85 | ||
81 | /** | 86 | /** |
82 | * Convert a link given through a request, to a valid link for LinkDB. | 87 | * Convert a link given through a request, to a valid Bookmark for the datastore. |
83 | * | 88 | * |
84 | * If no URL is provided, it will generate a local note URL. | 89 | * If no URL is provided, it will generate a local note URL. |
85 | * If no title is provided, it will use the URL as title. | 90 | * If no title is provided, it will use the URL as title. |
@@ -87,50 +92,42 @@ class ApiUtils | |||
87 | * @param array $input Request Link. | 92 | * @param array $input Request Link. |
88 | * @param bool $defaultPrivate Request Link. | 93 | * @param bool $defaultPrivate Request Link. |
89 | * | 94 | * |
90 | * @return array Formatted link. | 95 | * @return Bookmark instance. |
91 | */ | 96 | */ |
92 | public static function buildLinkFromRequest($input, $defaultPrivate) | 97 | public static function buildLinkFromRequest($input, $defaultPrivate) |
93 | { | 98 | { |
94 | $input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : ''; | 99 | $bookmark = new Bookmark(); |
100 | $url = ! empty($input['url']) ? cleanup_url($input['url']) : ''; | ||
95 | if (isset($input['private'])) { | 101 | if (isset($input['private'])) { |
96 | $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN); | 102 | $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN); |
97 | } else { | 103 | } else { |
98 | $private = $defaultPrivate; | 104 | $private = $defaultPrivate; |
99 | } | 105 | } |
100 | 106 | ||
101 | $link = [ | 107 | $bookmark->setTitle(! empty($input['title']) ? $input['title'] : ''); |
102 | 'title' => ! empty($input['title']) ? $input['title'] : $input['url'], | 108 | $bookmark->setUrl($url); |
103 | 'url' => $input['url'], | 109 | $bookmark->setDescription(! empty($input['description']) ? $input['description'] : ''); |
104 | 'description' => ! empty($input['description']) ? $input['description'] : '', | 110 | $bookmark->setTags(! empty($input['tags']) ? $input['tags'] : []); |
105 | 'tags' => ! empty($input['tags']) ? implode(' ', $input['tags']) : '', | 111 | $bookmark->setPrivate($private); |
106 | 'private' => $private, | 112 | |
107 | 'created' => new \DateTime(), | 113 | return $bookmark; |
108 | ]; | ||
109 | return $link; | ||
110 | } | 114 | } |
111 | 115 | ||
112 | /** | 116 | /** |
113 | * Update link fields using an updated link object. | 117 | * Update link fields using an updated link object. |
114 | * | 118 | * |
115 | * @param array $oldLink data | 119 | * @param Bookmark $oldLink data |
116 | * @param array $newLink data | 120 | * @param Bookmark $newLink data |
117 | * | 121 | * |
118 | * @return array $oldLink updated with $newLink values | 122 | * @return Bookmark $oldLink updated with $newLink values |
119 | */ | 123 | */ |
120 | public static function updateLink($oldLink, $newLink) | 124 | public static function updateLink($oldLink, $newLink) |
121 | { | 125 | { |
122 | foreach (['title', 'url', 'description', 'tags', 'private'] as $field) { | 126 | $oldLink->setTitle($newLink->getTitle()); |
123 | $oldLink[$field] = $newLink[$field]; | 127 | $oldLink->setUrl($newLink->getUrl()); |
124 | } | 128 | $oldLink->setDescription($newLink->getDescription()); |
125 | $oldLink['updated'] = new \DateTime(); | 129 | $oldLink->setTags($newLink->getTags()); |
126 | 130 | $oldLink->setPrivate($newLink->isPrivate()); | |
127 | if (empty($oldLink['url'])) { | ||
128 | $oldLink['url'] = '?' . $oldLink['shorturl']; | ||
129 | } | ||
130 | |||
131 | if (empty($oldLink['title'])) { | ||
132 | $oldLink['title'] = $oldLink['url']; | ||
133 | } | ||
134 | 131 | ||
135 | return $oldLink; | 132 | return $oldLink; |
136 | } | 133 | } |
@@ -139,7 +136,7 @@ class ApiUtils | |||
139 | * Format a Tag for the REST API. | 136 | * Format a Tag for the REST API. |
140 | * | 137 | * |
141 | * @param string $tag Tag name | 138 | * @param string $tag Tag name |
142 | * @param int $occurrences Number of links using this tag | 139 | * @param int $occurrences Number of bookmarks using this tag |
143 | * | 140 | * |
144 | * @return array Link data formatted for the REST API. | 141 | * @return array Link data formatted for the REST API. |
145 | */ | 142 | */ |
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php index a6e7cbab..c4b3d0c3 100644 --- a/application/api/controllers/ApiController.php +++ b/application/api/controllers/ApiController.php | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Api\Controllers; | 3 | namespace Shaarli\Api\Controllers; |
4 | 4 | ||
5 | use Shaarli\Bookmark\LinkDB; | 5 | use Shaarli\Bookmark\BookmarkServiceInterface; |
6 | use Shaarli\Config\ConfigManager; | 6 | use Shaarli\Config\ConfigManager; |
7 | use Slim\Container; | 7 | use Slim\Container; |
8 | 8 | ||
@@ -26,9 +26,9 @@ abstract class ApiController | |||
26 | protected $conf; | 26 | protected $conf; |
27 | 27 | ||
28 | /** | 28 | /** |
29 | * @var LinkDB | 29 | * @var BookmarkServiceInterface |
30 | */ | 30 | */ |
31 | protected $linkDb; | 31 | protected $bookmarkService; |
32 | 32 | ||
33 | /** | 33 | /** |
34 | * @var HistoryController | 34 | * @var HistoryController |
@@ -51,7 +51,7 @@ abstract class ApiController | |||
51 | { | 51 | { |
52 | $this->ci = $ci; | 52 | $this->ci = $ci; |
53 | $this->conf = $ci->get('conf'); | 53 | $this->conf = $ci->get('conf'); |
54 | $this->linkDb = $ci->get('db'); | 54 | $this->bookmarkService = $ci->get('db'); |
55 | $this->history = $ci->get('history'); | 55 | $this->history = $ci->get('history'); |
56 | if ($this->conf->get('dev.debug', false)) { | 56 | if ($this->conf->get('dev.debug', false)) { |
57 | $this->jsonStyle = JSON_PRETTY_PRINT; | 57 | $this->jsonStyle = JSON_PRETTY_PRINT; |
diff --git a/application/api/controllers/HistoryController.php b/application/api/controllers/HistoryController.php index 9afcfa26..505647a9 100644 --- a/application/api/controllers/HistoryController.php +++ b/application/api/controllers/HistoryController.php | |||
@@ -41,7 +41,7 @@ class HistoryController extends ApiController | |||
41 | throw new ApiBadParametersException('Invalid offset'); | 41 | throw new ApiBadParametersException('Invalid offset'); |
42 | } | 42 | } |
43 | 43 | ||
44 | // limit parameter is either a number of links or 'all' for everything. | 44 | // limit parameter is either a number of bookmarks or 'all' for everything. |
45 | $limit = $request->getParam('limit'); | 45 | $limit = $request->getParam('limit'); |
46 | if (empty($limit)) { | 46 | if (empty($limit)) { |
47 | $limit = count($history); | 47 | $limit = count($history); |
diff --git a/application/api/controllers/Info.php b/application/api/controllers/Info.php index f37dcae5..12f6b2f0 100644 --- a/application/api/controllers/Info.php +++ b/application/api/controllers/Info.php | |||
@@ -2,6 +2,7 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Api\Controllers; | 3 | namespace Shaarli\Api\Controllers; |
4 | 4 | ||
5 | use Shaarli\Bookmark\BookmarkFilter; | ||
5 | use Slim\Http\Request; | 6 | use Slim\Http\Request; |
6 | use Slim\Http\Response; | 7 | use Slim\Http\Response; |
7 | 8 | ||
@@ -26,8 +27,8 @@ class Info extends ApiController | |||
26 | public function getInfo($request, $response) | 27 | public function getInfo($request, $response) |
27 | { | 28 | { |
28 | $info = [ | 29 | $info = [ |
29 | 'global_counter' => count($this->linkDb), | 30 | 'global_counter' => $this->bookmarkService->count(), |
30 | 'private_counter' => count_private($this->linkDb), | 31 | 'private_counter' => $this->bookmarkService->count(BookmarkFilter::$PRIVATE), |
31 | 'settings' => array( | 32 | 'settings' => array( |
32 | 'title' => $this->conf->get('general.title', 'Shaarli'), | 33 | 'title' => $this->conf->get('general.title', 'Shaarli'), |
33 | 'header_link' => $this->conf->get('general.header_link', '?'), | 34 | 'header_link' => $this->conf->get('general.header_link', '?'), |
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php index ffcfd4c7..29247950 100644 --- a/application/api/controllers/Links.php +++ b/application/api/controllers/Links.php | |||
@@ -11,7 +11,7 @@ use Slim\Http\Response; | |||
11 | /** | 11 | /** |
12 | * Class Links | 12 | * Class Links |
13 | * | 13 | * |
14 | * REST API Controller: all services related to links collection. | 14 | * REST API Controller: all services related to bookmarks collection. |
15 | * | 15 | * |
16 | * @package Api\Controllers | 16 | * @package Api\Controllers |
17 | * @see http://shaarli.github.io/api-documentation/#links-links-collection | 17 | * @see http://shaarli.github.io/api-documentation/#links-links-collection |
@@ -19,12 +19,12 @@ use Slim\Http\Response; | |||
19 | class Links extends ApiController | 19 | class Links extends ApiController |
20 | { | 20 | { |
21 | /** | 21 | /** |
22 | * @var int Number of links returned if no limit is provided. | 22 | * @var int Number of bookmarks returned if no limit is provided. |
23 | */ | 23 | */ |
24 | public static $DEFAULT_LIMIT = 20; | 24 | public static $DEFAULT_LIMIT = 20; |
25 | 25 | ||
26 | /** | 26 | /** |
27 | * Retrieve a list of links, allowing different filters. | 27 | * Retrieve a list of bookmarks, allowing different filters. |
28 | * | 28 | * |
29 | * @param Request $request Slim request. | 29 | * @param Request $request Slim request. |
30 | * @param Response $response Slim response. | 30 | * @param Response $response Slim response. |
@@ -36,33 +36,32 @@ class Links extends ApiController | |||
36 | public function getLinks($request, $response) | 36 | public function getLinks($request, $response) |
37 | { | 37 | { |
38 | $private = $request->getParam('visibility'); | 38 | $private = $request->getParam('visibility'); |
39 | $links = $this->linkDb->filterSearch( | 39 | $bookmarks = $this->bookmarkService->search( |
40 | [ | 40 | [ |
41 | 'searchtags' => $request->getParam('searchtags', ''), | 41 | 'searchtags' => $request->getParam('searchtags', ''), |
42 | 'searchterm' => $request->getParam('searchterm', ''), | 42 | 'searchterm' => $request->getParam('searchterm', ''), |
43 | ], | 43 | ], |
44 | false, | ||
45 | $private | 44 | $private |
46 | ); | 45 | ); |
47 | 46 | ||
48 | // Return links from the {offset}th link, starting from 0. | 47 | // Return bookmarks from the {offset}th link, starting from 0. |
49 | $offset = $request->getParam('offset'); | 48 | $offset = $request->getParam('offset'); |
50 | if (! empty($offset) && ! ctype_digit($offset)) { | 49 | if (! empty($offset) && ! ctype_digit($offset)) { |
51 | throw new ApiBadParametersException('Invalid offset'); | 50 | throw new ApiBadParametersException('Invalid offset'); |
52 | } | 51 | } |
53 | $offset = ! empty($offset) ? intval($offset) : 0; | 52 | $offset = ! empty($offset) ? intval($offset) : 0; |
54 | if ($offset > count($links)) { | 53 | if ($offset > count($bookmarks)) { |
55 | return $response->withJson([], 200, $this->jsonStyle); | 54 | return $response->withJson([], 200, $this->jsonStyle); |
56 | } | 55 | } |
57 | 56 | ||
58 | // limit parameter is either a number of links or 'all' for everything. | 57 | // limit parameter is either a number of bookmarks or 'all' for everything. |
59 | $limit = $request->getParam('limit'); | 58 | $limit = $request->getParam('limit'); |
60 | if (empty($limit)) { | 59 | if (empty($limit)) { |
61 | $limit = self::$DEFAULT_LIMIT; | 60 | $limit = self::$DEFAULT_LIMIT; |
62 | } elseif (ctype_digit($limit)) { | 61 | } elseif (ctype_digit($limit)) { |
63 | $limit = intval($limit); | 62 | $limit = intval($limit); |
64 | } elseif ($limit === 'all') { | 63 | } elseif ($limit === 'all') { |
65 | $limit = count($links); | 64 | $limit = count($bookmarks); |
66 | } else { | 65 | } else { |
67 | throw new ApiBadParametersException('Invalid limit'); | 66 | throw new ApiBadParametersException('Invalid limit'); |
68 | } | 67 | } |
@@ -72,12 +71,12 @@ class Links extends ApiController | |||
72 | 71 | ||
73 | $out = []; | 72 | $out = []; |
74 | $index = 0; | 73 | $index = 0; |
75 | foreach ($links as $link) { | 74 | foreach ($bookmarks as $bookmark) { |
76 | if (count($out) >= $limit) { | 75 | if (count($out) >= $limit) { |
77 | break; | 76 | break; |
78 | } | 77 | } |
79 | if ($index++ >= $offset) { | 78 | if ($index++ >= $offset) { |
80 | $out[] = ApiUtils::formatLink($link, $indexUrl); | 79 | $out[] = ApiUtils::formatLink($bookmark, $indexUrl); |
81 | } | 80 | } |
82 | } | 81 | } |
83 | 82 | ||
@@ -97,11 +96,11 @@ class Links extends ApiController | |||
97 | */ | 96 | */ |
98 | public function getLink($request, $response, $args) | 97 | public function getLink($request, $response, $args) |
99 | { | 98 | { |
100 | if (!isset($this->linkDb[$args['id']])) { | 99 | if (!$this->bookmarkService->exists($args['id'])) { |
101 | throw new ApiLinkNotFoundException(); | 100 | throw new ApiLinkNotFoundException(); |
102 | } | 101 | } |
103 | $index = index_url($this->ci['environment']); | 102 | $index = index_url($this->ci['environment']); |
104 | $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index); | 103 | $out = ApiUtils::formatLink($this->bookmarkService->get($args['id']), $index); |
105 | 104 | ||
106 | return $response->withJson($out, 200, $this->jsonStyle); | 105 | return $response->withJson($out, 200, $this->jsonStyle); |
107 | } | 106 | } |
@@ -117,9 +116,11 @@ class Links extends ApiController | |||
117 | public function postLink($request, $response) | 116 | public function postLink($request, $response) |
118 | { | 117 | { |
119 | $data = $request->getParsedBody(); | 118 | $data = $request->getParsedBody(); |
120 | $link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); | 119 | $bookmark = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); |
121 | // duplicate by URL, return 409 Conflict | 120 | // duplicate by URL, return 409 Conflict |
122 | if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) { | 121 | if (! empty($bookmark->getUrl()) |
122 | && ! empty($dup = $this->bookmarkService->findByUrl($bookmark->getUrl())) | ||
123 | ) { | ||
123 | return $response->withJson( | 124 | return $response->withJson( |
124 | ApiUtils::formatLink($dup, index_url($this->ci['environment'])), | 125 | ApiUtils::formatLink($dup, index_url($this->ci['environment'])), |
125 | 409, | 126 | 409, |
@@ -127,23 +128,9 @@ class Links extends ApiController | |||
127 | ); | 128 | ); |
128 | } | 129 | } |
129 | 130 | ||
130 | $link['id'] = $this->linkDb->getNextId(); | 131 | $this->bookmarkService->add($bookmark); |
131 | $link['shorturl'] = link_small_hash($link['created'], $link['id']); | 132 | $out = ApiUtils::formatLink($bookmark, index_url($this->ci['environment'])); |
132 | 133 | $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $bookmark->getId()]); | |
133 | // note: general relative URL | ||
134 | if (empty($link['url'])) { | ||
135 | $link['url'] = '?' . $link['shorturl']; | ||
136 | } | ||
137 | |||
138 | if (empty($link['title'])) { | ||
139 | $link['title'] = $link['url']; | ||
140 | } | ||
141 | |||
142 | $this->linkDb[$link['id']] = $link; | ||
143 | $this->linkDb->save($this->conf->get('resource.page_cache')); | ||
144 | $this->history->addLink($link); | ||
145 | $out = ApiUtils::formatLink($link, index_url($this->ci['environment'])); | ||
146 | $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]); | ||
147 | return $response->withAddedHeader('Location', $redirect) | 134 | return $response->withAddedHeader('Location', $redirect) |
148 | ->withJson($out, 201, $this->jsonStyle); | 135 | ->withJson($out, 201, $this->jsonStyle); |
149 | } | 136 | } |
@@ -161,18 +148,18 @@ class Links extends ApiController | |||
161 | */ | 148 | */ |
162 | public function putLink($request, $response, $args) | 149 | public function putLink($request, $response, $args) |
163 | { | 150 | { |
164 | if (! isset($this->linkDb[$args['id']])) { | 151 | if (! $this->bookmarkService->exists($args['id'])) { |
165 | throw new ApiLinkNotFoundException(); | 152 | throw new ApiLinkNotFoundException(); |
166 | } | 153 | } |
167 | 154 | ||
168 | $index = index_url($this->ci['environment']); | 155 | $index = index_url($this->ci['environment']); |
169 | $data = $request->getParsedBody(); | 156 | $data = $request->getParsedBody(); |
170 | 157 | ||
171 | $requestLink = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); | 158 | $requestBookmark = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); |
172 | // duplicate URL on a different link, return 409 Conflict | 159 | // duplicate URL on a different link, return 409 Conflict |
173 | if (! empty($requestLink['url']) | 160 | if (! empty($requestBookmark->getUrl()) |
174 | && ! empty($dup = $this->linkDb->getLinkFromUrl($requestLink['url'])) | 161 | && ! empty($dup = $this->bookmarkService->findByUrl($requestBookmark->getUrl())) |
175 | && $dup['id'] != $args['id'] | 162 | && $dup->getId() != $args['id'] |
176 | ) { | 163 | ) { |
177 | return $response->withJson( | 164 | return $response->withJson( |
178 | ApiUtils::formatLink($dup, $index), | 165 | ApiUtils::formatLink($dup, $index), |
@@ -181,13 +168,11 @@ class Links extends ApiController | |||
181 | ); | 168 | ); |
182 | } | 169 | } |
183 | 170 | ||
184 | $responseLink = $this->linkDb[$args['id']]; | 171 | $responseBookmark = $this->bookmarkService->get($args['id']); |
185 | $responseLink = ApiUtils::updateLink($responseLink, $requestLink); | 172 | $responseBookmark = ApiUtils::updateLink($responseBookmark, $requestBookmark); |
186 | $this->linkDb[$responseLink['id']] = $responseLink; | 173 | $this->bookmarkService->set($responseBookmark); |
187 | $this->linkDb->save($this->conf->get('resource.page_cache')); | ||
188 | $this->history->updateLink($responseLink); | ||
189 | 174 | ||
190 | $out = ApiUtils::formatLink($responseLink, $index); | 175 | $out = ApiUtils::formatLink($responseBookmark, $index); |
191 | return $response->withJson($out, 200, $this->jsonStyle); | 176 | return $response->withJson($out, 200, $this->jsonStyle); |
192 | } | 177 | } |
193 | 178 | ||
@@ -204,13 +189,11 @@ class Links extends ApiController | |||
204 | */ | 189 | */ |
205 | public function deleteLink($request, $response, $args) | 190 | public function deleteLink($request, $response, $args) |
206 | { | 191 | { |
207 | if (! isset($this->linkDb[$args['id']])) { | 192 | if (! $this->bookmarkService->exists($args['id'])) { |
208 | throw new ApiLinkNotFoundException(); | 193 | throw new ApiLinkNotFoundException(); |
209 | } | 194 | } |
210 | $link = $this->linkDb[$args['id']]; | 195 | $bookmark = $this->bookmarkService->get($args['id']); |
211 | unset($this->linkDb[(int) $args['id']]); | 196 | $this->bookmarkService->remove($bookmark); |
212 | $this->linkDb->save($this->conf->get('resource.page_cache')); | ||
213 | $this->history->deleteLink($link); | ||
214 | 197 | ||
215 | return $response->withStatus(204); | 198 | return $response->withStatus(204); |
216 | } | 199 | } |
diff --git a/application/api/controllers/Tags.php b/application/api/controllers/Tags.php index 82f3ef74..e60e00a7 100644 --- a/application/api/controllers/Tags.php +++ b/application/api/controllers/Tags.php | |||
@@ -5,6 +5,7 @@ namespace Shaarli\Api\Controllers; | |||
5 | use Shaarli\Api\ApiUtils; | 5 | use Shaarli\Api\ApiUtils; |
6 | use Shaarli\Api\Exceptions\ApiBadParametersException; | 6 | use Shaarli\Api\Exceptions\ApiBadParametersException; |
7 | use Shaarli\Api\Exceptions\ApiTagNotFoundException; | 7 | use Shaarli\Api\Exceptions\ApiTagNotFoundException; |
8 | use Shaarli\Bookmark\BookmarkFilter; | ||
8 | use Slim\Http\Request; | 9 | use Slim\Http\Request; |
9 | use Slim\Http\Response; | 10 | use Slim\Http\Response; |
10 | 11 | ||
@@ -18,7 +19,7 @@ use Slim\Http\Response; | |||
18 | class Tags extends ApiController | 19 | class Tags extends ApiController |
19 | { | 20 | { |
20 | /** | 21 | /** |
21 | * @var int Number of links returned if no limit is provided. | 22 | * @var int Number of bookmarks returned if no limit is provided. |
22 | */ | 23 | */ |
23 | public static $DEFAULT_LIMIT = 'all'; | 24 | public static $DEFAULT_LIMIT = 'all'; |
24 | 25 | ||
@@ -35,7 +36,7 @@ class Tags extends ApiController | |||
35 | public function getTags($request, $response) | 36 | public function getTags($request, $response) |
36 | { | 37 | { |
37 | $visibility = $request->getParam('visibility'); | 38 | $visibility = $request->getParam('visibility'); |
38 | $tags = $this->linkDb->linksCountPerTag([], $visibility); | 39 | $tags = $this->bookmarkService->bookmarksCountPerTag([], $visibility); |
39 | 40 | ||
40 | // Return tags from the {offset}th tag, starting from 0. | 41 | // Return tags from the {offset}th tag, starting from 0. |
41 | $offset = $request->getParam('offset'); | 42 | $offset = $request->getParam('offset'); |
@@ -47,7 +48,7 @@ class Tags extends ApiController | |||
47 | return $response->withJson([], 200, $this->jsonStyle); | 48 | return $response->withJson([], 200, $this->jsonStyle); |
48 | } | 49 | } |
49 | 50 | ||
50 | // limit parameter is either a number of links or 'all' for everything. | 51 | // limit parameter is either a number of bookmarks or 'all' for everything. |
51 | $limit = $request->getParam('limit'); | 52 | $limit = $request->getParam('limit'); |
52 | if (empty($limit)) { | 53 | if (empty($limit)) { |
53 | $limit = self::$DEFAULT_LIMIT; | 54 | $limit = self::$DEFAULT_LIMIT; |
@@ -87,7 +88,7 @@ class Tags extends ApiController | |||
87 | */ | 88 | */ |
88 | public function getTag($request, $response, $args) | 89 | public function getTag($request, $response, $args) |
89 | { | 90 | { |
90 | $tags = $this->linkDb->linksCountPerTag(); | 91 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
91 | if (!isset($tags[$args['tagName']])) { | 92 | if (!isset($tags[$args['tagName']])) { |
92 | throw new ApiTagNotFoundException(); | 93 | throw new ApiTagNotFoundException(); |
93 | } | 94 | } |
@@ -111,7 +112,7 @@ class Tags extends ApiController | |||
111 | */ | 112 | */ |
112 | public function putTag($request, $response, $args) | 113 | public function putTag($request, $response, $args) |
113 | { | 114 | { |
114 | $tags = $this->linkDb->linksCountPerTag(); | 115 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
115 | if (! isset($tags[$args['tagName']])) { | 116 | if (! isset($tags[$args['tagName']])) { |
116 | throw new ApiTagNotFoundException(); | 117 | throw new ApiTagNotFoundException(); |
117 | } | 118 | } |
@@ -121,13 +122,19 @@ class Tags extends ApiController | |||
121 | throw new ApiBadParametersException('New tag name is required in the request body'); | 122 | throw new ApiBadParametersException('New tag name is required in the request body'); |
122 | } | 123 | } |
123 | 124 | ||
124 | $updated = $this->linkDb->renameTag($args['tagName'], $data['name']); | 125 | $bookmarks = $this->bookmarkService->search( |
125 | $this->linkDb->save($this->conf->get('resource.page_cache')); | 126 | ['searchtags' => $args['tagName']], |
126 | foreach ($updated as $link) { | 127 | BookmarkFilter::$ALL, |
127 | $this->history->updateLink($link); | 128 | true |
129 | ); | ||
130 | foreach ($bookmarks as $bookmark) { | ||
131 | $bookmark->renameTag($args['tagName'], $data['name']); | ||
132 | $this->bookmarkService->set($bookmark, false); | ||
133 | $this->history->updateLink($bookmark); | ||
128 | } | 134 | } |
135 | $this->bookmarkService->save(); | ||
129 | 136 | ||
130 | $tags = $this->linkDb->linksCountPerTag(); | 137 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
131 | $out = ApiUtils::formatTag($data['name'], $tags[$data['name']]); | 138 | $out = ApiUtils::formatTag($data['name'], $tags[$data['name']]); |
132 | return $response->withJson($out, 200, $this->jsonStyle); | 139 | return $response->withJson($out, 200, $this->jsonStyle); |
133 | } | 140 | } |
@@ -145,15 +152,22 @@ class Tags extends ApiController | |||
145 | */ | 152 | */ |
146 | public function deleteTag($request, $response, $args) | 153 | public function deleteTag($request, $response, $args) |
147 | { | 154 | { |
148 | $tags = $this->linkDb->linksCountPerTag(); | 155 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
149 | if (! isset($tags[$args['tagName']])) { | 156 | if (! isset($tags[$args['tagName']])) { |
150 | throw new ApiTagNotFoundException(); | 157 | throw new ApiTagNotFoundException(); |
151 | } | 158 | } |
152 | $updated = $this->linkDb->renameTag($args['tagName'], null); | 159 | |
153 | $this->linkDb->save($this->conf->get('resource.page_cache')); | 160 | $bookmarks = $this->bookmarkService->search( |
154 | foreach ($updated as $link) { | 161 | ['searchtags' => $args['tagName']], |
155 | $this->history->updateLink($link); | 162 | BookmarkFilter::$ALL, |
163 | true | ||
164 | ); | ||
165 | foreach ($bookmarks as $bookmark) { | ||
166 | $bookmark->deleteTag($args['tagName']); | ||
167 | $this->bookmarkService->set($bookmark, false); | ||
168 | $this->history->updateLink($bookmark); | ||
156 | } | 169 | } |
170 | $this->bookmarkService->save(); | ||
157 | 171 | ||
158 | return $response->withStatus(204); | 172 | return $response->withStatus(204); |
159 | } | 173 | } |
diff --git a/application/bookmark/Bookmark.php b/application/bookmark/Bookmark.php new file mode 100644 index 00000000..1beb8be2 --- /dev/null +++ b/application/bookmark/Bookmark.php | |||
@@ -0,0 +1,462 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | use DateTime; | ||
6 | use DateTimeInterface; | ||
7 | use Shaarli\Bookmark\Exception\InvalidBookmarkException; | ||
8 | |||
9 | /** | ||
10 | * Class Bookmark | ||
11 | * | ||
12 | * This class represent a single Bookmark with all its attributes. | ||
13 | * Every bookmark should manipulated using this, before being formatted. | ||
14 | * | ||
15 | * @package Shaarli\Bookmark | ||
16 | */ | ||
17 | class Bookmark | ||
18 | { | ||
19 | /** @var string Date format used in string (former ID format) */ | ||
20 | const LINK_DATE_FORMAT = 'Ymd_His'; | ||
21 | |||
22 | /** @var int Bookmark ID */ | ||
23 | protected $id; | ||
24 | |||
25 | /** @var string Permalink identifier */ | ||
26 | protected $shortUrl; | ||
27 | |||
28 | /** @var string Bookmark's URL - $shortUrl prefixed with `?` for notes */ | ||
29 | protected $url; | ||
30 | |||
31 | /** @var string Bookmark's title */ | ||
32 | protected $title; | ||
33 | |||
34 | /** @var string Raw bookmark's description */ | ||
35 | protected $description; | ||
36 | |||
37 | /** @var array List of bookmark's tags */ | ||
38 | protected $tags; | ||
39 | |||
40 | /** @var string|bool|null Thumbnail's URL - initialized at null, false if no thumbnail could be found */ | ||
41 | protected $thumbnail; | ||
42 | |||
43 | /** @var bool Set to true if the bookmark is set as sticky */ | ||
44 | protected $sticky; | ||
45 | |||
46 | /** @var DateTimeInterface Creation datetime */ | ||
47 | protected $created; | ||
48 | |||
49 | /** @var DateTimeInterface datetime */ | ||
50 | protected $updated; | ||
51 | |||
52 | /** @var bool True if the bookmark can only be seen while logged in */ | ||
53 | protected $private; | ||
54 | |||
55 | /** | ||
56 | * Initialize a link from array data. Especially useful to create a Bookmark from former link storage format. | ||
57 | * | ||
58 | * @param array $data | ||
59 | * | ||
60 | * @return $this | ||
61 | */ | ||
62 | public function fromArray($data) | ||
63 | { | ||
64 | $this->id = $data['id']; | ||
65 | $this->shortUrl = $data['shorturl']; | ||
66 | $this->url = $data['url']; | ||
67 | $this->title = $data['title']; | ||
68 | $this->description = $data['description']; | ||
69 | $this->thumbnail = isset($data['thumbnail']) ? $data['thumbnail'] : null; | ||
70 | $this->sticky = isset($data['sticky']) ? $data['sticky'] : false; | ||
71 | $this->created = $data['created']; | ||
72 | if (is_array($data['tags'])) { | ||
73 | $this->tags = $data['tags']; | ||
74 | } else { | ||
75 | $this->tags = preg_split('/\s+/', $data['tags'], -1, PREG_SPLIT_NO_EMPTY); | ||
76 | } | ||
77 | if (! empty($data['updated'])) { | ||
78 | $this->updated = $data['updated']; | ||
79 | } | ||
80 | $this->private = $data['private'] ? true : false; | ||
81 | |||
82 | return $this; | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * Make sure that the current instance of Bookmark is valid and can be saved into the data store. | ||
87 | * A valid link requires: | ||
88 | * - an integer ID | ||
89 | * - a short URL (for permalinks) | ||
90 | * - a creation date | ||
91 | * | ||
92 | * This function also initialize optional empty fields: | ||
93 | * - the URL with the permalink | ||
94 | * - the title with the URL | ||
95 | * | ||
96 | * @throws InvalidBookmarkException | ||
97 | */ | ||
98 | public function validate() | ||
99 | { | ||
100 | if ($this->id === null | ||
101 | || ! is_int($this->id) | ||
102 | || empty($this->shortUrl) | ||
103 | || empty($this->created) | ||
104 | || ! $this->created instanceof DateTimeInterface | ||
105 | ) { | ||
106 | throw new InvalidBookmarkException($this); | ||
107 | } | ||
108 | if (empty($this->url)) { | ||
109 | $this->url = '/shaare/'. $this->shortUrl; | ||
110 | } | ||
111 | if (empty($this->title)) { | ||
112 | $this->title = $this->url; | ||
113 | } | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * Set the Id. | ||
118 | * If they're not already initialized, this function also set: | ||
119 | * - created: with the current datetime | ||
120 | * - shortUrl: with a generated small hash from the date and the given ID | ||
121 | * | ||
122 | * @param int $id | ||
123 | * | ||
124 | * @return Bookmark | ||
125 | */ | ||
126 | public function setId($id) | ||
127 | { | ||
128 | $this->id = $id; | ||
129 | if (empty($this->created)) { | ||
130 | $this->created = new DateTime(); | ||
131 | } | ||
132 | if (empty($this->shortUrl)) { | ||
133 | $this->shortUrl = link_small_hash($this->created, $this->id); | ||
134 | } | ||
135 | |||
136 | return $this; | ||
137 | } | ||
138 | |||
139 | /** | ||
140 | * Get the Id. | ||
141 | * | ||
142 | * @return int | ||
143 | */ | ||
144 | public function getId() | ||
145 | { | ||
146 | return $this->id; | ||
147 | } | ||
148 | |||
149 | /** | ||
150 | * Get the ShortUrl. | ||
151 | * | ||
152 | * @return string | ||
153 | */ | ||
154 | public function getShortUrl() | ||
155 | { | ||
156 | return $this->shortUrl; | ||
157 | } | ||
158 | |||
159 | /** | ||
160 | * Get the Url. | ||
161 | * | ||
162 | * @return string | ||
163 | */ | ||
164 | public function getUrl() | ||
165 | { | ||
166 | return $this->url; | ||
167 | } | ||
168 | |||
169 | /** | ||
170 | * Get the Title. | ||
171 | * | ||
172 | * @return string | ||
173 | */ | ||
174 | public function getTitle() | ||
175 | { | ||
176 | return $this->title; | ||
177 | } | ||
178 | |||
179 | /** | ||
180 | * Get the Description. | ||
181 | * | ||
182 | * @return string | ||
183 | */ | ||
184 | public function getDescription() | ||
185 | { | ||
186 | return ! empty($this->description) ? $this->description : ''; | ||
187 | } | ||
188 | |||
189 | /** | ||
190 | * Get the Created. | ||
191 | * | ||
192 | * @return DateTimeInterface | ||
193 | */ | ||
194 | public function getCreated() | ||
195 | { | ||
196 | return $this->created; | ||
197 | } | ||
198 | |||
199 | /** | ||
200 | * Get the Updated. | ||
201 | * | ||
202 | * @return DateTimeInterface | ||
203 | */ | ||
204 | public function getUpdated() | ||
205 | { | ||
206 | return $this->updated; | ||
207 | } | ||
208 | |||
209 | /** | ||
210 | * Set the ShortUrl. | ||
211 | * | ||
212 | * @param string $shortUrl | ||
213 | * | ||
214 | * @return Bookmark | ||
215 | */ | ||
216 | public function setShortUrl($shortUrl) | ||
217 | { | ||
218 | $this->shortUrl = $shortUrl; | ||
219 | |||
220 | return $this; | ||
221 | } | ||
222 | |||
223 | /** | ||
224 | * Set the Url. | ||
225 | * | ||
226 | * @param string $url | ||
227 | * @param array $allowedProtocols | ||
228 | * | ||
229 | * @return Bookmark | ||
230 | */ | ||
231 | public function setUrl($url, $allowedProtocols = []) | ||
232 | { | ||
233 | $url = trim($url); | ||
234 | if (! empty($url)) { | ||
235 | $url = whitelist_protocols($url, $allowedProtocols); | ||
236 | } | ||
237 | $this->url = $url; | ||
238 | |||
239 | return $this; | ||
240 | } | ||
241 | |||
242 | /** | ||
243 | * Set the Title. | ||
244 | * | ||
245 | * @param string $title | ||
246 | * | ||
247 | * @return Bookmark | ||
248 | */ | ||
249 | public function setTitle($title) | ||
250 | { | ||
251 | $this->title = trim($title); | ||
252 | |||
253 | return $this; | ||
254 | } | ||
255 | |||
256 | /** | ||
257 | * Set the Description. | ||
258 | * | ||
259 | * @param string $description | ||
260 | * | ||
261 | * @return Bookmark | ||
262 | */ | ||
263 | public function setDescription($description) | ||
264 | { | ||
265 | $this->description = $description; | ||
266 | |||
267 | return $this; | ||
268 | } | ||
269 | |||
270 | /** | ||
271 | * Set the Created. | ||
272 | * Note: you shouldn't set this manually except for special cases (like bookmark import) | ||
273 | * | ||
274 | * @param DateTimeInterface $created | ||
275 | * | ||
276 | * @return Bookmark | ||
277 | */ | ||
278 | public function setCreated($created) | ||
279 | { | ||
280 | $this->created = $created; | ||
281 | |||
282 | return $this; | ||
283 | } | ||
284 | |||
285 | /** | ||
286 | * Set the Updated. | ||
287 | * | ||
288 | * @param DateTimeInterface $updated | ||
289 | * | ||
290 | * @return Bookmark | ||
291 | */ | ||
292 | public function setUpdated($updated) | ||
293 | { | ||
294 | $this->updated = $updated; | ||
295 | |||
296 | return $this; | ||
297 | } | ||
298 | |||
299 | /** | ||
300 | * Get the Private. | ||
301 | * | ||
302 | * @return bool | ||
303 | */ | ||
304 | public function isPrivate() | ||
305 | { | ||
306 | return $this->private ? true : false; | ||
307 | } | ||
308 | |||
309 | /** | ||
310 | * Set the Private. | ||
311 | * | ||
312 | * @param bool $private | ||
313 | * | ||
314 | * @return Bookmark | ||
315 | */ | ||
316 | public function setPrivate($private) | ||
317 | { | ||
318 | $this->private = $private ? true : false; | ||
319 | |||
320 | return $this; | ||
321 | } | ||
322 | |||
323 | /** | ||
324 | * Get the Tags. | ||
325 | * | ||
326 | * @return array | ||
327 | */ | ||
328 | public function getTags() | ||
329 | { | ||
330 | return is_array($this->tags) ? $this->tags : []; | ||
331 | } | ||
332 | |||
333 | /** | ||
334 | * Set the Tags. | ||
335 | * | ||
336 | * @param array $tags | ||
337 | * | ||
338 | * @return Bookmark | ||
339 | */ | ||
340 | public function setTags($tags) | ||
341 | { | ||
342 | $this->setTagsString(implode(' ', $tags)); | ||
343 | |||
344 | return $this; | ||
345 | } | ||
346 | |||
347 | /** | ||
348 | * Get the Thumbnail. | ||
349 | * | ||
350 | * @return string|bool|null Thumbnail's URL - initialized at null, false if no thumbnail could be found | ||
351 | */ | ||
352 | public function getThumbnail() | ||
353 | { | ||
354 | return !$this->isNote() ? $this->thumbnail : false; | ||
355 | } | ||
356 | |||
357 | /** | ||
358 | * Set the Thumbnail. | ||
359 | * | ||
360 | * @param string|bool $thumbnail Thumbnail's URL - false if no thumbnail could be found | ||
361 | * | ||
362 | * @return Bookmark | ||
363 | */ | ||
364 | public function setThumbnail($thumbnail) | ||
365 | { | ||
366 | $this->thumbnail = $thumbnail; | ||
367 | |||
368 | return $this; | ||
369 | } | ||
370 | |||
371 | /** | ||
372 | * Get the Sticky. | ||
373 | * | ||
374 | * @return bool | ||
375 | */ | ||
376 | public function isSticky() | ||
377 | { | ||
378 | return $this->sticky ? true : false; | ||
379 | } | ||
380 | |||
381 | /** | ||
382 | * Set the Sticky. | ||
383 | * | ||
384 | * @param bool $sticky | ||
385 | * | ||
386 | * @return Bookmark | ||
387 | */ | ||
388 | public function setSticky($sticky) | ||
389 | { | ||
390 | $this->sticky = $sticky ? true : false; | ||
391 | |||
392 | return $this; | ||
393 | } | ||
394 | |||
395 | /** | ||
396 | * @return string Bookmark's tags as a string, separated by a space | ||
397 | */ | ||
398 | public function getTagsString() | ||
399 | { | ||
400 | return implode(' ', $this->getTags()); | ||
401 | } | ||
402 | |||
403 | /** | ||
404 | * @return bool | ||
405 | */ | ||
406 | public function isNote() | ||
407 | { | ||
408 | // We check empty value to get a valid result if the link has not been saved yet | ||
409 | return empty($this->url) || startsWith($this->url, '/shaare/') || $this->url[0] === '?'; | ||
410 | } | ||
411 | |||
412 | /** | ||
413 | * Set tags from a string. | ||
414 | * Note: | ||
415 | * - tags must be separated whether by a space or a comma | ||
416 | * - multiple spaces will be removed | ||
417 | * - trailing dash in tags will be removed | ||
418 | * | ||
419 | * @param string $tags | ||
420 | * | ||
421 | * @return $this | ||
422 | */ | ||
423 | public function setTagsString($tags) | ||
424 | { | ||
425 | // Remove first '-' char in tags. | ||
426 | $tags = preg_replace('/(^| )\-/', '$1', $tags); | ||
427 | // Explode all tags separted by spaces or commas | ||
428 | $tags = preg_split('/[\s,]+/', $tags); | ||
429 | // Remove eventual empty values | ||
430 | $tags = array_values(array_filter($tags)); | ||
431 | |||
432 | $this->tags = $tags; | ||
433 | |||
434 | return $this; | ||
435 | } | ||
436 | |||
437 | /** | ||
438 | * Rename a tag in tags list. | ||
439 | * | ||
440 | * @param string $fromTag | ||
441 | * @param string $toTag | ||
442 | */ | ||
443 | public function renameTag($fromTag, $toTag) | ||
444 | { | ||
445 | if (($pos = array_search($fromTag, $this->tags)) !== false) { | ||
446 | $this->tags[$pos] = trim($toTag); | ||
447 | } | ||
448 | } | ||
449 | |||
450 | /** | ||
451 | * Delete a tag from tags list. | ||
452 | * | ||
453 | * @param string $tag | ||
454 | */ | ||
455 | public function deleteTag($tag) | ||
456 | { | ||
457 | if (($pos = array_search($tag, $this->tags)) !== false) { | ||
458 | unset($this->tags[$pos]); | ||
459 | $this->tags = array_values($this->tags); | ||
460 | } | ||
461 | } | ||
462 | } | ||
diff --git a/application/bookmark/BookmarkArray.php b/application/bookmark/BookmarkArray.php new file mode 100644 index 00000000..3bd5eb20 --- /dev/null +++ b/application/bookmark/BookmarkArray.php | |||
@@ -0,0 +1,260 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | use Shaarli\Bookmark\Exception\InvalidBookmarkException; | ||
6 | |||
7 | /** | ||
8 | * Class BookmarkArray | ||
9 | * | ||
10 | * Implementing ArrayAccess, this allows us to use the bookmark list | ||
11 | * as an array and iterate over it. | ||
12 | * | ||
13 | * @package Shaarli\Bookmark | ||
14 | */ | ||
15 | class BookmarkArray implements \Iterator, \Countable, \ArrayAccess | ||
16 | { | ||
17 | /** | ||
18 | * @var Bookmark[] | ||
19 | */ | ||
20 | protected $bookmarks; | ||
21 | |||
22 | /** | ||
23 | * @var array List of all bookmarks IDS mapped with their array offset. | ||
24 | * Map: id->offset. | ||
25 | */ | ||
26 | protected $ids; | ||
27 | |||
28 | /** | ||
29 | * @var int Position in the $this->keys array (for the Iterator interface) | ||
30 | */ | ||
31 | protected $position; | ||
32 | |||
33 | /** | ||
34 | * @var array List of offset keys (for the Iterator interface implementation) | ||
35 | */ | ||
36 | protected $keys; | ||
37 | |||
38 | /** | ||
39 | * @var array List of all recorded URLs (key=url, value=bookmark offset) | ||
40 | * for fast reserve search (url-->bookmark offset) | ||
41 | */ | ||
42 | protected $urls; | ||
43 | |||
44 | public function __construct() | ||
45 | { | ||
46 | $this->ids = []; | ||
47 | $this->bookmarks = []; | ||
48 | $this->keys = []; | ||
49 | $this->urls = []; | ||
50 | $this->position = 0; | ||
51 | } | ||
52 | |||
53 | /** | ||
54 | * Countable - Counts elements of an object | ||
55 | * | ||
56 | * @return int Number of bookmarks | ||
57 | */ | ||
58 | public function count() | ||
59 | { | ||
60 | return count($this->bookmarks); | ||
61 | } | ||
62 | |||
63 | /** | ||
64 | * ArrayAccess - Assigns a value to the specified offset | ||
65 | * | ||
66 | * @param int $offset Bookmark ID | ||
67 | * @param Bookmark $value instance | ||
68 | * | ||
69 | * @throws InvalidBookmarkException | ||
70 | */ | ||
71 | public function offsetSet($offset, $value) | ||
72 | { | ||
73 | if (! $value instanceof Bookmark | ||
74 | || $value->getId() === null || empty($value->getUrl()) | ||
75 | || ($offset !== null && ! is_int($offset)) || ! is_int($value->getId()) | ||
76 | || $offset !== null && $offset !== $value->getId() | ||
77 | ) { | ||
78 | throw new InvalidBookmarkException($value); | ||
79 | } | ||
80 | |||
81 | // If the bookmark exists, we reuse the real offset, otherwise new entry | ||
82 | if ($offset !== null) { | ||
83 | $existing = $this->getBookmarkOffset($offset); | ||
84 | } else { | ||
85 | $existing = $this->getBookmarkOffset($value->getId()); | ||
86 | } | ||
87 | |||
88 | if ($existing !== null) { | ||
89 | $offset = $existing; | ||
90 | } else { | ||
91 | $offset = count($this->bookmarks); | ||
92 | } | ||
93 | |||
94 | $this->bookmarks[$offset] = $value; | ||
95 | $this->urls[$value->getUrl()] = $offset; | ||
96 | $this->ids[$value->getId()] = $offset; | ||
97 | } | ||
98 | |||
99 | /** | ||
100 | * ArrayAccess - Whether or not an offset exists | ||
101 | * | ||
102 | * @param int $offset Bookmark ID | ||
103 | * | ||
104 | * @return bool true if it exists, false otherwise | ||
105 | */ | ||
106 | public function offsetExists($offset) | ||
107 | { | ||
108 | return array_key_exists($this->getBookmarkOffset($offset), $this->bookmarks); | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * ArrayAccess - Unsets an offset | ||
113 | * | ||
114 | * @param int $offset Bookmark ID | ||
115 | */ | ||
116 | public function offsetUnset($offset) | ||
117 | { | ||
118 | $realOffset = $this->getBookmarkOffset($offset); | ||
119 | $url = $this->bookmarks[$realOffset]->getUrl(); | ||
120 | unset($this->urls[$url]); | ||
121 | unset($this->ids[$offset]); | ||
122 | unset($this->bookmarks[$realOffset]); | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * ArrayAccess - Returns the value at specified offset | ||
127 | * | ||
128 | * @param int $offset Bookmark ID | ||
129 | * | ||
130 | * @return Bookmark|null The Bookmark if found, null otherwise | ||
131 | */ | ||
132 | public function offsetGet($offset) | ||
133 | { | ||
134 | $realOffset = $this->getBookmarkOffset($offset); | ||
135 | return isset($this->bookmarks[$realOffset]) ? $this->bookmarks[$realOffset] : null; | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Iterator - Returns the current element | ||
140 | * | ||
141 | * @return Bookmark corresponding to the current position | ||
142 | */ | ||
143 | public function current() | ||
144 | { | ||
145 | return $this[$this->keys[$this->position]]; | ||
146 | } | ||
147 | |||
148 | /** | ||
149 | * Iterator - Returns the key of the current element | ||
150 | * | ||
151 | * @return int Bookmark ID corresponding to the current position | ||
152 | */ | ||
153 | public function key() | ||
154 | { | ||
155 | return $this->keys[$this->position]; | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Iterator - Moves forward to next element | ||
160 | */ | ||
161 | public function next() | ||
162 | { | ||
163 | ++$this->position; | ||
164 | } | ||
165 | |||
166 | /** | ||
167 | * Iterator - Rewinds the Iterator to the first element | ||
168 | * | ||
169 | * Entries are sorted by date (latest first) | ||
170 | */ | ||
171 | public function rewind() | ||
172 | { | ||
173 | $this->keys = array_keys($this->ids); | ||
174 | $this->position = 0; | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * Iterator - Checks if current position is valid | ||
179 | * | ||
180 | * @return bool true if the current Bookmark ID exists, false otherwise | ||
181 | */ | ||
182 | public function valid() | ||
183 | { | ||
184 | return isset($this->keys[$this->position]); | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * Returns a bookmark offset in bookmarks array from its unique ID. | ||
189 | * | ||
190 | * @param int $id Persistent ID of a bookmark. | ||
191 | * | ||
192 | * @return int Real offset in local array, or null if doesn't exist. | ||
193 | */ | ||
194 | protected function getBookmarkOffset($id) | ||
195 | { | ||
196 | if (isset($this->ids[$id])) { | ||
197 | return $this->ids[$id]; | ||
198 | } | ||
199 | return null; | ||
200 | } | ||
201 | |||
202 | /** | ||
203 | * Return the next key for bookmark creation. | ||
204 | * E.g. If the last ID is 597, the next will be 598. | ||
205 | * | ||
206 | * @return int next ID. | ||
207 | */ | ||
208 | public function getNextId() | ||
209 | { | ||
210 | if (!empty($this->ids)) { | ||
211 | return max(array_keys($this->ids)) + 1; | ||
212 | } | ||
213 | return 0; | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * @param $url | ||
218 | * | ||
219 | * @return Bookmark|null | ||
220 | */ | ||
221 | public function getByUrl($url) | ||
222 | { | ||
223 | if (! empty($url) | ||
224 | && isset($this->urls[$url]) | ||
225 | && isset($this->bookmarks[$this->urls[$url]]) | ||
226 | ) { | ||
227 | return $this->bookmarks[$this->urls[$url]]; | ||
228 | } | ||
229 | return null; | ||
230 | } | ||
231 | |||
232 | /** | ||
233 | * Reorder links by creation date (newest first). | ||
234 | * | ||
235 | * Also update the urls and ids mapping arrays. | ||
236 | * | ||
237 | * @param string $order ASC|DESC | ||
238 | * @param bool $ignoreSticky If set to true, sticky bookmarks won't be first | ||
239 | */ | ||
240 | public function reorder(string $order = 'DESC', bool $ignoreSticky = false): void | ||
241 | { | ||
242 | $order = $order === 'ASC' ? -1 : 1; | ||
243 | // Reorder array by dates. | ||
244 | usort($this->bookmarks, function ($a, $b) use ($order, $ignoreSticky) { | ||
245 | /** @var $a Bookmark */ | ||
246 | /** @var $b Bookmark */ | ||
247 | if (false === $ignoreSticky && $a->isSticky() !== $b->isSticky()) { | ||
248 | return $a->isSticky() ? -1 : 1; | ||
249 | } | ||
250 | return $a->getCreated() < $b->getCreated() ? 1 * $order : -1 * $order; | ||
251 | }); | ||
252 | |||
253 | $this->urls = []; | ||
254 | $this->ids = []; | ||
255 | foreach ($this->bookmarks as $key => $bookmark) { | ||
256 | $this->urls[$bookmark->getUrl()] = $key; | ||
257 | $this->ids[$bookmark->getId()] = $key; | ||
258 | } | ||
259 | } | ||
260 | } | ||
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php new file mode 100644 index 00000000..c9ec2609 --- /dev/null +++ b/application/bookmark/BookmarkFileService.php | |||
@@ -0,0 +1,407 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | namespace Shaarli\Bookmark; | ||
5 | |||
6 | |||
7 | use Exception; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Bookmark\Exception\DatastoreNotInitializedException; | ||
10 | use Shaarli\Bookmark\Exception\EmptyDataStoreException; | ||
11 | use Shaarli\Config\ConfigManager; | ||
12 | use Shaarli\Formatter\BookmarkMarkdownFormatter; | ||
13 | use Shaarli\History; | ||
14 | use Shaarli\Legacy\LegacyLinkDB; | ||
15 | use Shaarli\Legacy\LegacyUpdater; | ||
16 | use Shaarli\Render\PageCacheManager; | ||
17 | use Shaarli\Updater\UpdaterUtils; | ||
18 | |||
19 | /** | ||
20 | * Class BookmarksService | ||
21 | * | ||
22 | * This is the entry point to manipulate the bookmark DB. | ||
23 | * It manipulates loads links from a file data store containing all bookmarks. | ||
24 | * | ||
25 | * It also triggers the legacy format (bookmarks as arrays) migration. | ||
26 | */ | ||
27 | class BookmarkFileService implements BookmarkServiceInterface | ||
28 | { | ||
29 | /** @var Bookmark[] instance */ | ||
30 | protected $bookmarks; | ||
31 | |||
32 | /** @var BookmarkIO instance */ | ||
33 | protected $bookmarksIO; | ||
34 | |||
35 | /** @var BookmarkFilter */ | ||
36 | protected $bookmarkFilter; | ||
37 | |||
38 | /** @var ConfigManager instance */ | ||
39 | protected $conf; | ||
40 | |||
41 | /** @var History instance */ | ||
42 | protected $history; | ||
43 | |||
44 | /** @var PageCacheManager instance */ | ||
45 | protected $pageCacheManager; | ||
46 | |||
47 | /** @var bool true for logged in users. Default value to retrieve private bookmarks. */ | ||
48 | protected $isLoggedIn; | ||
49 | |||
50 | /** | ||
51 | * @inheritDoc | ||
52 | */ | ||
53 | public function __construct(ConfigManager $conf, History $history, $isLoggedIn) | ||
54 | { | ||
55 | $this->conf = $conf; | ||
56 | $this->history = $history; | ||
57 | $this->pageCacheManager = new PageCacheManager($this->conf->get('resource.page_cache'), $isLoggedIn); | ||
58 | $this->bookmarksIO = new BookmarkIO($this->conf); | ||
59 | $this->isLoggedIn = $isLoggedIn; | ||
60 | |||
61 | if (!$this->isLoggedIn && $this->conf->get('privacy.hide_public_links', false)) { | ||
62 | $this->bookmarks = []; | ||
63 | } else { | ||
64 | try { | ||
65 | $this->bookmarks = $this->bookmarksIO->read(); | ||
66 | } catch (EmptyDataStoreException|DatastoreNotInitializedException $e) { | ||
67 | $this->bookmarks = new BookmarkArray(); | ||
68 | |||
69 | if ($this->isLoggedIn) { | ||
70 | // Datastore file does not exists, we initialize it with default bookmarks. | ||
71 | if ($e instanceof DatastoreNotInitializedException) { | ||
72 | $this->initialize(); | ||
73 | } else { | ||
74 | $this->save(); | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | |||
79 | if (! $this->bookmarks instanceof BookmarkArray) { | ||
80 | $this->migrate(); | ||
81 | exit( | ||
82 | 'Your data store has been migrated, please reload the page.'. PHP_EOL . | ||
83 | 'If this message keeps showing up, please delete data/updates.txt file.' | ||
84 | ); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | $this->bookmarkFilter = new BookmarkFilter($this->bookmarks); | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * @inheritDoc | ||
93 | */ | ||
94 | public function findByHash($hash) | ||
95 | { | ||
96 | $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash); | ||
97 | // PHP 7.3 introduced array_key_first() to avoid this hack | ||
98 | $first = reset($bookmark); | ||
99 | if (! $this->isLoggedIn && $first->isPrivate()) { | ||
100 | throw new Exception('Not authorized'); | ||
101 | } | ||
102 | |||
103 | return $first; | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * @inheritDoc | ||
108 | */ | ||
109 | public function findByUrl($url) | ||
110 | { | ||
111 | return $this->bookmarks->getByUrl($url); | ||
112 | } | ||
113 | |||
114 | /** | ||
115 | * @inheritDoc | ||
116 | */ | ||
117 | public function search( | ||
118 | $request = [], | ||
119 | $visibility = null, | ||
120 | $caseSensitive = false, | ||
121 | $untaggedOnly = false, | ||
122 | bool $ignoreSticky = false | ||
123 | ) { | ||
124 | if ($visibility === null) { | ||
125 | $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC; | ||
126 | } | ||
127 | |||
128 | // Filter bookmark database according to parameters. | ||
129 | $searchtags = isset($request['searchtags']) ? $request['searchtags'] : ''; | ||
130 | $searchterm = isset($request['searchterm']) ? $request['searchterm'] : ''; | ||
131 | |||
132 | if ($ignoreSticky) { | ||
133 | $this->bookmarks->reorder('DESC', true); | ||
134 | } | ||
135 | |||
136 | return $this->bookmarkFilter->filter( | ||
137 | BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT, | ||
138 | [$searchtags, $searchterm], | ||
139 | $caseSensitive, | ||
140 | $visibility, | ||
141 | $untaggedOnly | ||
142 | ); | ||
143 | } | ||
144 | |||
145 | /** | ||
146 | * @inheritDoc | ||
147 | */ | ||
148 | public function get($id, $visibility = null) | ||
149 | { | ||
150 | if (! isset($this->bookmarks[$id])) { | ||
151 | throw new BookmarkNotFoundException(); | ||
152 | } | ||
153 | |||
154 | if ($visibility === null) { | ||
155 | $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC; | ||
156 | } | ||
157 | |||
158 | $bookmark = $this->bookmarks[$id]; | ||
159 | if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private') | ||
160 | || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public') | ||
161 | ) { | ||
162 | throw new Exception('Unauthorized'); | ||
163 | } | ||
164 | |||
165 | return $bookmark; | ||
166 | } | ||
167 | |||
168 | /** | ||
169 | * @inheritDoc | ||
170 | */ | ||
171 | public function set($bookmark, $save = true) | ||
172 | { | ||
173 | if (true !== $this->isLoggedIn) { | ||
174 | throw new Exception(t('You\'re not authorized to alter the datastore')); | ||
175 | } | ||
176 | if (! $bookmark instanceof Bookmark) { | ||
177 | throw new Exception(t('Provided data is invalid')); | ||
178 | } | ||
179 | if (! isset($this->bookmarks[$bookmark->getId()])) { | ||
180 | throw new BookmarkNotFoundException(); | ||
181 | } | ||
182 | $bookmark->validate(); | ||
183 | |||
184 | $bookmark->setUpdated(new \DateTime()); | ||
185 | $this->bookmarks[$bookmark->getId()] = $bookmark; | ||
186 | if ($save === true) { | ||
187 | $this->save(); | ||
188 | $this->history->updateLink($bookmark); | ||
189 | } | ||
190 | return $this->bookmarks[$bookmark->getId()]; | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * @inheritDoc | ||
195 | */ | ||
196 | public function add($bookmark, $save = true) | ||
197 | { | ||
198 | if (true !== $this->isLoggedIn) { | ||
199 | throw new Exception(t('You\'re not authorized to alter the datastore')); | ||
200 | } | ||
201 | if (! $bookmark instanceof Bookmark) { | ||
202 | throw new Exception(t('Provided data is invalid')); | ||
203 | } | ||
204 | if (! empty($bookmark->getId())) { | ||
205 | throw new Exception(t('This bookmarks already exists')); | ||
206 | } | ||
207 | $bookmark->setId($this->bookmarks->getNextId()); | ||
208 | $bookmark->validate(); | ||
209 | |||
210 | $this->bookmarks[$bookmark->getId()] = $bookmark; | ||
211 | if ($save === true) { | ||
212 | $this->save(); | ||
213 | $this->history->addLink($bookmark); | ||
214 | } | ||
215 | return $this->bookmarks[$bookmark->getId()]; | ||
216 | } | ||
217 | |||
218 | /** | ||
219 | * @inheritDoc | ||
220 | */ | ||
221 | public function addOrSet($bookmark, $save = true) | ||
222 | { | ||
223 | if (true !== $this->isLoggedIn) { | ||
224 | throw new Exception(t('You\'re not authorized to alter the datastore')); | ||
225 | } | ||
226 | if (! $bookmark instanceof Bookmark) { | ||
227 | throw new Exception('Provided data is invalid'); | ||
228 | } | ||
229 | if ($bookmark->getId() === null) { | ||
230 | return $this->add($bookmark, $save); | ||
231 | } | ||
232 | return $this->set($bookmark, $save); | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * @inheritDoc | ||
237 | */ | ||
238 | public function remove($bookmark, $save = true) | ||
239 | { | ||
240 | if (true !== $this->isLoggedIn) { | ||
241 | throw new Exception(t('You\'re not authorized to alter the datastore')); | ||
242 | } | ||
243 | if (! $bookmark instanceof Bookmark) { | ||
244 | throw new Exception(t('Provided data is invalid')); | ||
245 | } | ||
246 | if (! isset($this->bookmarks[$bookmark->getId()])) { | ||
247 | throw new BookmarkNotFoundException(); | ||
248 | } | ||
249 | |||
250 | unset($this->bookmarks[$bookmark->getId()]); | ||
251 | if ($save === true) { | ||
252 | $this->save(); | ||
253 | $this->history->deleteLink($bookmark); | ||
254 | } | ||
255 | } | ||
256 | |||
257 | /** | ||
258 | * @inheritDoc | ||
259 | */ | ||
260 | public function exists($id, $visibility = null) | ||
261 | { | ||
262 | if (! isset($this->bookmarks[$id])) { | ||
263 | return false; | ||
264 | } | ||
265 | |||
266 | if ($visibility === null) { | ||
267 | $visibility = $this->isLoggedIn ? 'all' : 'public'; | ||
268 | } | ||
269 | |||
270 | $bookmark = $this->bookmarks[$id]; | ||
271 | if (($bookmark->isPrivate() && $visibility != 'all' && $visibility != 'private') | ||
272 | || (! $bookmark->isPrivate() && $visibility != 'all' && $visibility != 'public') | ||
273 | ) { | ||
274 | return false; | ||
275 | } | ||
276 | |||
277 | return true; | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * @inheritDoc | ||
282 | */ | ||
283 | public function count($visibility = null) | ||
284 | { | ||
285 | return count($this->search([], $visibility)); | ||
286 | } | ||
287 | |||
288 | /** | ||
289 | * @inheritDoc | ||
290 | */ | ||
291 | public function save() | ||
292 | { | ||
293 | if (true !== $this->isLoggedIn) { | ||
294 | // TODO: raise an Exception instead | ||
295 | die('You are not authorized to change the database.'); | ||
296 | } | ||
297 | |||
298 | $this->bookmarks->reorder(); | ||
299 | $this->bookmarksIO->write($this->bookmarks); | ||
300 | $this->pageCacheManager->invalidateCaches(); | ||
301 | } | ||
302 | |||
303 | /** | ||
304 | * @inheritDoc | ||
305 | */ | ||
306 | public function bookmarksCountPerTag($filteringTags = [], $visibility = null) | ||
307 | { | ||
308 | $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility); | ||
309 | $tags = []; | ||
310 | $caseMapping = []; | ||
311 | foreach ($bookmarks as $bookmark) { | ||
312 | foreach ($bookmark->getTags() as $tag) { | ||
313 | if (empty($tag) | ||
314 | || (! $this->isLoggedIn && startsWith($tag, '.')) | ||
315 | || $tag === BookmarkMarkdownFormatter::NO_MD_TAG | ||
316 | || in_array($tag, $filteringTags, true) | ||
317 | ) { | ||
318 | continue; | ||
319 | } | ||
320 | |||
321 | // The first case found will be displayed. | ||
322 | if (!isset($caseMapping[strtolower($tag)])) { | ||
323 | $caseMapping[strtolower($tag)] = $tag; | ||
324 | $tags[$caseMapping[strtolower($tag)]] = 0; | ||
325 | } | ||
326 | $tags[$caseMapping[strtolower($tag)]]++; | ||
327 | } | ||
328 | } | ||
329 | |||
330 | /* | ||
331 | * Formerly used arsort(), which doesn't define the sort behaviour for equal values. | ||
332 | * Also, this function doesn't produce the same result between PHP 5.6 and 7. | ||
333 | * | ||
334 | * So we now use array_multisort() to sort tags by DESC occurrences, | ||
335 | * then ASC alphabetically for equal values. | ||
336 | * | ||
337 | * @see https://github.com/shaarli/Shaarli/issues/1142 | ||
338 | */ | ||
339 | $keys = array_keys($tags); | ||
340 | $tmpTags = array_combine($keys, $keys); | ||
341 | array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags); | ||
342 | return $tags; | ||
343 | } | ||
344 | |||
345 | /** | ||
346 | * @inheritDoc | ||
347 | */ | ||
348 | public function days() | ||
349 | { | ||
350 | $bookmarkDays = []; | ||
351 | foreach ($this->search() as $bookmark) { | ||
352 | $bookmarkDays[$bookmark->getCreated()->format('Ymd')] = 0; | ||
353 | } | ||
354 | $bookmarkDays = array_keys($bookmarkDays); | ||
355 | sort($bookmarkDays); | ||
356 | |||
357 | return $bookmarkDays; | ||
358 | } | ||
359 | |||
360 | /** | ||
361 | * @inheritDoc | ||
362 | */ | ||
363 | public function filterDay($request) | ||
364 | { | ||
365 | $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC; | ||
366 | |||
367 | return $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_DAY, $request, false, $visibility); | ||
368 | } | ||
369 | |||
370 | /** | ||
371 | * @inheritDoc | ||
372 | */ | ||
373 | public function initialize() | ||
374 | { | ||
375 | $initializer = new BookmarkInitializer($this); | ||
376 | $initializer->initialize(); | ||
377 | |||
378 | if (true === $this->isLoggedIn) { | ||
379 | $this->save(); | ||
380 | } | ||
381 | } | ||
382 | |||
383 | /** | ||
384 | * Handles migration to the new database format (BookmarksArray). | ||
385 | */ | ||
386 | protected function migrate() | ||
387 | { | ||
388 | $bookmarkDb = new LegacyLinkDB( | ||
389 | $this->conf->get('resource.datastore'), | ||
390 | true, | ||
391 | false | ||
392 | ); | ||
393 | $updater = new LegacyUpdater( | ||
394 | UpdaterUtils::read_updates_file($this->conf->get('resource.updates')), | ||
395 | $bookmarkDb, | ||
396 | $this->conf, | ||
397 | true | ||
398 | ); | ||
399 | $newUpdates = $updater->update(); | ||
400 | if (! empty($newUpdates)) { | ||
401 | UpdaterUtils::write_updates_file( | ||
402 | $this->conf->get('resource.updates'), | ||
403 | $updater->getDoneUpdates() | ||
404 | ); | ||
405 | } | ||
406 | } | ||
407 | } | ||
diff --git a/application/bookmark/BookmarkFilter.php b/application/bookmark/BookmarkFilter.php new file mode 100644 index 00000000..6636bbfe --- /dev/null +++ b/application/bookmark/BookmarkFilter.php | |||
@@ -0,0 +1,473 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | use Exception; | ||
6 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
7 | |||
8 | /** | ||
9 | * Class LinkFilter. | ||
10 | * | ||
11 | * Perform search and filter operation on link data list. | ||
12 | */ | ||
13 | class BookmarkFilter | ||
14 | { | ||
15 | /** | ||
16 | * @var string permalinks. | ||
17 | */ | ||
18 | public static $FILTER_HASH = 'permalink'; | ||
19 | |||
20 | /** | ||
21 | * @var string text search. | ||
22 | */ | ||
23 | public static $FILTER_TEXT = 'fulltext'; | ||
24 | |||
25 | /** | ||
26 | * @var string tag filter. | ||
27 | */ | ||
28 | public static $FILTER_TAG = 'tags'; | ||
29 | |||
30 | /** | ||
31 | * @var string filter by day. | ||
32 | */ | ||
33 | public static $FILTER_DAY = 'FILTER_DAY'; | ||
34 | |||
35 | /** | ||
36 | * @var string filter by day. | ||
37 | */ | ||
38 | public static $DEFAULT = 'NO_FILTER'; | ||
39 | |||
40 | /** @var string Visibility: all */ | ||
41 | public static $ALL = 'all'; | ||
42 | |||
43 | /** @var string Visibility: public */ | ||
44 | public static $PUBLIC = 'public'; | ||
45 | |||
46 | /** @var string Visibility: private */ | ||
47 | public static $PRIVATE = 'private'; | ||
48 | |||
49 | /** | ||
50 | * @var string Allowed characters for hashtags (regex syntax). | ||
51 | */ | ||
52 | public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}'; | ||
53 | |||
54 | /** | ||
55 | * @var Bookmark[] all available bookmarks. | ||
56 | */ | ||
57 | private $bookmarks; | ||
58 | |||
59 | /** | ||
60 | * @param Bookmark[] $bookmarks initialization. | ||
61 | */ | ||
62 | public function __construct($bookmarks) | ||
63 | { | ||
64 | $this->bookmarks = $bookmarks; | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * Filter bookmarks according to parameters. | ||
69 | * | ||
70 | * @param string $type Type of filter (eg. tags, permalink, etc.). | ||
71 | * @param mixed $request Filter content. | ||
72 | * @param bool $casesensitive Optional: Perform case sensitive filter if true. | ||
73 | * @param string $visibility Optional: return only all/private/public bookmarks | ||
74 | * @param bool $untaggedonly Optional: return only untagged bookmarks. Applies only if $type includes FILTER_TAG | ||
75 | * | ||
76 | * @return Bookmark[] filtered bookmark list. | ||
77 | * | ||
78 | * @throws BookmarkNotFoundException | ||
79 | */ | ||
80 | public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false) | ||
81 | { | ||
82 | if (!in_array($visibility, ['all', 'public', 'private'])) { | ||
83 | $visibility = 'all'; | ||
84 | } | ||
85 | |||
86 | switch ($type) { | ||
87 | case self::$FILTER_HASH: | ||
88 | return $this->filterSmallHash($request); | ||
89 | case self::$FILTER_TAG | self::$FILTER_TEXT: // == "vuotext" | ||
90 | $noRequest = empty($request) || (empty($request[0]) && empty($request[1])); | ||
91 | if ($noRequest) { | ||
92 | if ($untaggedonly) { | ||
93 | return $this->filterUntagged($visibility); | ||
94 | } | ||
95 | return $this->noFilter($visibility); | ||
96 | } | ||
97 | if ($untaggedonly) { | ||
98 | $filtered = $this->filterUntagged($visibility); | ||
99 | } else { | ||
100 | $filtered = $this->bookmarks; | ||
101 | } | ||
102 | if (!empty($request[0])) { | ||
103 | $filtered = (new BookmarkFilter($filtered))->filterTags($request[0], $casesensitive, $visibility); | ||
104 | } | ||
105 | if (!empty($request[1])) { | ||
106 | $filtered = (new BookmarkFilter($filtered))->filterFulltext($request[1], $visibility); | ||
107 | } | ||
108 | return $filtered; | ||
109 | case self::$FILTER_TEXT: | ||
110 | return $this->filterFulltext($request, $visibility); | ||
111 | case self::$FILTER_TAG: | ||
112 | if ($untaggedonly) { | ||
113 | return $this->filterUntagged($visibility); | ||
114 | } else { | ||
115 | return $this->filterTags($request, $casesensitive, $visibility); | ||
116 | } | ||
117 | case self::$FILTER_DAY: | ||
118 | return $this->filterDay($request, $visibility); | ||
119 | default: | ||
120 | return $this->noFilter($visibility); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * Unknown filter, but handle private only. | ||
126 | * | ||
127 | * @param string $visibility Optional: return only all/private/public bookmarks | ||
128 | * | ||
129 | * @return Bookmark[] filtered bookmarks. | ||
130 | */ | ||
131 | private function noFilter($visibility = 'all') | ||
132 | { | ||
133 | if ($visibility === 'all') { | ||
134 | return $this->bookmarks; | ||
135 | } | ||
136 | |||
137 | $out = array(); | ||
138 | foreach ($this->bookmarks as $key => $value) { | ||
139 | if ($value->isPrivate() && $visibility === 'private') { | ||
140 | $out[$key] = $value; | ||
141 | } elseif (!$value->isPrivate() && $visibility === 'public') { | ||
142 | $out[$key] = $value; | ||
143 | } | ||
144 | } | ||
145 | |||
146 | return $out; | ||
147 | } | ||
148 | |||
149 | /** | ||
150 | * Returns the shaare corresponding to a smallHash. | ||
151 | * | ||
152 | * @param string $smallHash permalink hash. | ||
153 | * | ||
154 | * @return array $filtered array containing permalink data. | ||
155 | * | ||
156 | * @throws \Shaarli\Bookmark\Exception\BookmarkNotFoundException if the smallhash doesn't match any link. | ||
157 | */ | ||
158 | private function filterSmallHash($smallHash) | ||
159 | { | ||
160 | foreach ($this->bookmarks as $key => $l) { | ||
161 | if ($smallHash == $l->getShortUrl()) { | ||
162 | // Yes, this is ugly and slow | ||
163 | return [$key => $l]; | ||
164 | } | ||
165 | } | ||
166 | |||
167 | throw new BookmarkNotFoundException(); | ||
168 | } | ||
169 | |||
170 | /** | ||
171 | * Returns the list of bookmarks corresponding to a full-text search | ||
172 | * | ||
173 | * Searches: | ||
174 | * - in the URLs, title and description; | ||
175 | * - are case-insensitive; | ||
176 | * - terms surrounded by quotes " are exact terms search. | ||
177 | * - terms starting with a dash - are excluded (except exact terms). | ||
178 | * | ||
179 | * Example: | ||
180 | * print_r($mydb->filterFulltext('hollandais')); | ||
181 | * | ||
182 | * mb_convert_case($val, MB_CASE_LOWER, 'UTF-8') | ||
183 | * - allows to perform searches on Unicode text | ||
184 | * - see https://github.com/shaarli/Shaarli/issues/75 for examples | ||
185 | * | ||
186 | * @param string $searchterms search query. | ||
187 | * @param string $visibility Optional: return only all/private/public bookmarks. | ||
188 | * | ||
189 | * @return array search results. | ||
190 | */ | ||
191 | private function filterFulltext($searchterms, $visibility = 'all') | ||
192 | { | ||
193 | if (empty($searchterms)) { | ||
194 | return $this->noFilter($visibility); | ||
195 | } | ||
196 | |||
197 | $filtered = array(); | ||
198 | $search = mb_convert_case(html_entity_decode($searchterms), MB_CASE_LOWER, 'UTF-8'); | ||
199 | $exactRegex = '/"([^"]+)"/'; | ||
200 | // Retrieve exact search terms. | ||
201 | preg_match_all($exactRegex, $search, $exactSearch); | ||
202 | $exactSearch = array_values(array_filter($exactSearch[1])); | ||
203 | |||
204 | // Remove exact search terms to get AND terms search. | ||
205 | $explodedSearchAnd = explode(' ', trim(preg_replace($exactRegex, '', $search))); | ||
206 | $explodedSearchAnd = array_values(array_filter($explodedSearchAnd)); | ||
207 | |||
208 | // Filter excluding terms and update andSearch. | ||
209 | $excludeSearch = array(); | ||
210 | $andSearch = array(); | ||
211 | foreach ($explodedSearchAnd as $needle) { | ||
212 | if ($needle[0] == '-' && strlen($needle) > 1) { | ||
213 | $excludeSearch[] = substr($needle, 1); | ||
214 | } else { | ||
215 | $andSearch[] = $needle; | ||
216 | } | ||
217 | } | ||
218 | |||
219 | // Iterate over every stored link. | ||
220 | foreach ($this->bookmarks as $id => $link) { | ||
221 | // ignore non private bookmarks when 'privatonly' is on. | ||
222 | if ($visibility !== 'all') { | ||
223 | if (!$link->isPrivate() && $visibility === 'private') { | ||
224 | continue; | ||
225 | } elseif ($link->isPrivate() && $visibility === 'public') { | ||
226 | continue; | ||
227 | } | ||
228 | } | ||
229 | |||
230 | // Concatenate link fields to search across fields. | ||
231 | // Adds a '\' separator for exact search terms. | ||
232 | $content = mb_convert_case($link->getTitle(), MB_CASE_LOWER, 'UTF-8') .'\\'; | ||
233 | $content .= mb_convert_case($link->getDescription(), MB_CASE_LOWER, 'UTF-8') .'\\'; | ||
234 | $content .= mb_convert_case($link->getUrl(), MB_CASE_LOWER, 'UTF-8') .'\\'; | ||
235 | $content .= mb_convert_case($link->getTagsString(), MB_CASE_LOWER, 'UTF-8') .'\\'; | ||
236 | |||
237 | // Be optimistic | ||
238 | $found = true; | ||
239 | |||
240 | // First, we look for exact term search | ||
241 | for ($i = 0; $i < count($exactSearch) && $found; $i++) { | ||
242 | $found = strpos($content, $exactSearch[$i]) !== false; | ||
243 | } | ||
244 | |||
245 | // Iterate over keywords, if keyword is not found, | ||
246 | // no need to check for the others. We want all or nothing. | ||
247 | for ($i = 0; $i < count($andSearch) && $found; $i++) { | ||
248 | $found = strpos($content, $andSearch[$i]) !== false; | ||
249 | } | ||
250 | |||
251 | // Exclude terms. | ||
252 | for ($i = 0; $i < count($excludeSearch) && $found; $i++) { | ||
253 | $found = strpos($content, $excludeSearch[$i]) === false; | ||
254 | } | ||
255 | |||
256 | if ($found) { | ||
257 | $filtered[$id] = $link; | ||
258 | } | ||
259 | } | ||
260 | |||
261 | return $filtered; | ||
262 | } | ||
263 | |||
264 | /** | ||
265 | * generate a regex fragment out of a tag | ||
266 | * | ||
267 | * @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard | ||
268 | * | ||
269 | * @return string generated regex fragment | ||
270 | */ | ||
271 | private static function tag2regex($tag) | ||
272 | { | ||
273 | $len = strlen($tag); | ||
274 | if (!$len || $tag === "-" || $tag === "*") { | ||
275 | // nothing to search, return empty regex | ||
276 | return ''; | ||
277 | } | ||
278 | if ($tag[0] === "-") { | ||
279 | // query is negated | ||
280 | $i = 1; // use offset to start after '-' character | ||
281 | $regex = '(?!'; // create negative lookahead | ||
282 | } else { | ||
283 | $i = 0; // start at first character | ||
284 | $regex = '(?='; // use positive lookahead | ||
285 | } | ||
286 | $regex .= '.*(?:^| )'; // before tag may only be a space or the beginning | ||
287 | // iterate over string, separating it into placeholder and content | ||
288 | for (; $i < $len; $i++) { | ||
289 | if ($tag[$i] === '*') { | ||
290 | // placeholder found | ||
291 | $regex .= '[^ ]*?'; | ||
292 | } else { | ||
293 | // regular characters | ||
294 | $offset = strpos($tag, '*', $i); | ||
295 | if ($offset === false) { | ||
296 | // no placeholder found, set offset to end of string | ||
297 | $offset = $len; | ||
298 | } | ||
299 | // subtract one, as we want to get before the placeholder or end of string | ||
300 | $offset -= 1; | ||
301 | // we got a tag name that we want to search for. escape any regex characters to prevent conflicts. | ||
302 | $regex .= preg_quote(substr($tag, $i, $offset - $i + 1), '/'); | ||
303 | // move $i on | ||
304 | $i = $offset; | ||
305 | } | ||
306 | } | ||
307 | $regex .= '(?:$| ))'; // after the tag may only be a space or the end | ||
308 | return $regex; | ||
309 | } | ||
310 | |||
311 | /** | ||
312 | * Returns the list of bookmarks associated with a given list of tags | ||
313 | * | ||
314 | * You can specify one or more tags, separated by space or a comma, e.g. | ||
315 | * print_r($mydb->filterTags('linux programming')); | ||
316 | * | ||
317 | * @param string $tags list of tags separated by commas or blank spaces. | ||
318 | * @param bool $casesensitive ignore case if false. | ||
319 | * @param string $visibility Optional: return only all/private/public bookmarks. | ||
320 | * | ||
321 | * @return array filtered bookmarks. | ||
322 | */ | ||
323 | public function filterTags($tags, $casesensitive = false, $visibility = 'all') | ||
324 | { | ||
325 | // get single tags (we may get passed an array, even though the docs say different) | ||
326 | $inputTags = $tags; | ||
327 | if (!is_array($tags)) { | ||
328 | // we got an input string, split tags | ||
329 | $inputTags = preg_split('/(?:\s+)|,/', $inputTags, -1, PREG_SPLIT_NO_EMPTY); | ||
330 | } | ||
331 | |||
332 | if (!count($inputTags)) { | ||
333 | // no input tags | ||
334 | return $this->noFilter($visibility); | ||
335 | } | ||
336 | |||
337 | // If we only have public visibility, we can't look for hidden tags | ||
338 | if ($visibility === self::$PUBLIC) { | ||
339 | $inputTags = array_values(array_filter($inputTags, function ($tag) { | ||
340 | return ! startsWith($tag, '.'); | ||
341 | })); | ||
342 | |||
343 | if (empty($inputTags)) { | ||
344 | return []; | ||
345 | } | ||
346 | } | ||
347 | |||
348 | // build regex from all tags | ||
349 | $re = '/^' . implode(array_map("self::tag2regex", $inputTags)) . '.*$/'; | ||
350 | if (!$casesensitive) { | ||
351 | // make regex case insensitive | ||
352 | $re .= 'i'; | ||
353 | } | ||
354 | |||
355 | // create resulting array | ||
356 | $filtered = []; | ||
357 | |||
358 | // iterate over each link | ||
359 | foreach ($this->bookmarks as $key => $link) { | ||
360 | // check level of visibility | ||
361 | // ignore non private bookmarks when 'privateonly' is on. | ||
362 | if ($visibility !== 'all') { | ||
363 | if (!$link->isPrivate() && $visibility === 'private') { | ||
364 | continue; | ||
365 | } elseif ($link->isPrivate() && $visibility === 'public') { | ||
366 | continue; | ||
367 | } | ||
368 | } | ||
369 | $search = $link->getTagsString(); // build search string, start with tags of current link | ||
370 | if (strlen(trim($link->getDescription())) && strpos($link->getDescription(), '#') !== false) { | ||
371 | // description given and at least one possible tag found | ||
372 | $descTags = array(); | ||
373 | // find all tags in the form of #tag in the description | ||
374 | preg_match_all( | ||
375 | '/(?<![' . self::$HASHTAG_CHARS . '])#([' . self::$HASHTAG_CHARS . ']+?)\b/sm', | ||
376 | $link->getDescription(), | ||
377 | $descTags | ||
378 | ); | ||
379 | if (count($descTags[1])) { | ||
380 | // there were some tags in the description, add them to the search string | ||
381 | $search .= ' ' . implode(' ', $descTags[1]); | ||
382 | } | ||
383 | }; | ||
384 | // match regular expression with search string | ||
385 | if (!preg_match($re, $search)) { | ||
386 | // this entry does _not_ match our regex | ||
387 | continue; | ||
388 | } | ||
389 | $filtered[$key] = $link; | ||
390 | } | ||
391 | return $filtered; | ||
392 | } | ||
393 | |||
394 | /** | ||
395 | * Return only bookmarks without any tag. | ||
396 | * | ||
397 | * @param string $visibility return only all/private/public bookmarks. | ||
398 | * | ||
399 | * @return array filtered bookmarks. | ||
400 | */ | ||
401 | public function filterUntagged($visibility) | ||
402 | { | ||
403 | $filtered = []; | ||
404 | foreach ($this->bookmarks as $key => $link) { | ||
405 | if ($visibility !== 'all') { | ||
406 | if (!$link->isPrivate() && $visibility === 'private') { | ||
407 | continue; | ||
408 | } elseif ($link->isPrivate() && $visibility === 'public') { | ||
409 | continue; | ||
410 | } | ||
411 | } | ||
412 | |||
413 | if (empty(trim($link->getTagsString()))) { | ||
414 | $filtered[$key] = $link; | ||
415 | } | ||
416 | } | ||
417 | |||
418 | return $filtered; | ||
419 | } | ||
420 | |||
421 | /** | ||
422 | * Returns the list of articles for a given day, chronologically sorted | ||
423 | * | ||
424 | * Day must be in the form 'YYYYMMDD' (e.g. '20120125'), e.g. | ||
425 | * print_r($mydb->filterDay('20120125')); | ||
426 | * | ||
427 | * @param string $day day to filter. | ||
428 | * @param string $visibility return only all/private/public bookmarks. | ||
429 | |||
430 | * @return array all link matching given day. | ||
431 | * | ||
432 | * @throws Exception if date format is invalid. | ||
433 | */ | ||
434 | public function filterDay($day, $visibility) | ||
435 | { | ||
436 | if (!checkDateFormat('Ymd', $day)) { | ||
437 | throw new Exception('Invalid date format'); | ||
438 | } | ||
439 | |||
440 | $filtered = []; | ||
441 | foreach ($this->bookmarks as $key => $bookmark) { | ||
442 | if ($visibility === static::$PUBLIC && $bookmark->isPrivate()) { | ||
443 | continue; | ||
444 | } | ||
445 | |||
446 | if ($bookmark->getCreated()->format('Ymd') == $day) { | ||
447 | $filtered[$key] = $bookmark; | ||
448 | } | ||
449 | } | ||
450 | |||
451 | // sort by date ASC | ||
452 | return array_reverse($filtered, true); | ||
453 | } | ||
454 | |||
455 | /** | ||
456 | * Convert a list of tags (str) to an array. Also | ||
457 | * - handle case sensitivity. | ||
458 | * - accepts spaces commas as separator. | ||
459 | * | ||
460 | * @param string $tags string containing a list of tags. | ||
461 | * @param bool $casesensitive will convert everything to lowercase if false. | ||
462 | * | ||
463 | * @return array filtered tags string. | ||
464 | */ | ||
465 | public static function tagsStrToArray($tags, $casesensitive) | ||
466 | { | ||
467 | // We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek) | ||
468 | $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8'); | ||
469 | $tagsOut = str_replace(',', ' ', $tagsOut); | ||
470 | |||
471 | return preg_split('/\s+/', $tagsOut, -1, PREG_SPLIT_NO_EMPTY); | ||
472 | } | ||
473 | } | ||
diff --git a/application/bookmark/BookmarkIO.php b/application/bookmark/BookmarkIO.php new file mode 100644 index 00000000..6bf7f365 --- /dev/null +++ b/application/bookmark/BookmarkIO.php | |||
@@ -0,0 +1,108 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | use Shaarli\Bookmark\Exception\DatastoreNotInitializedException; | ||
6 | use Shaarli\Bookmark\Exception\EmptyDataStoreException; | ||
7 | use Shaarli\Bookmark\Exception\NotWritableDataStoreException; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | |||
10 | /** | ||
11 | * Class BookmarkIO | ||
12 | * | ||
13 | * This class performs read/write operation to the file data store. | ||
14 | * Used by BookmarkFileService. | ||
15 | * | ||
16 | * @package Shaarli\Bookmark | ||
17 | */ | ||
18 | class BookmarkIO | ||
19 | { | ||
20 | /** | ||
21 | * @var string Datastore file path | ||
22 | */ | ||
23 | protected $datastore; | ||
24 | |||
25 | /** | ||
26 | * @var ConfigManager instance | ||
27 | */ | ||
28 | protected $conf; | ||
29 | |||
30 | /** | ||
31 | * string Datastore PHP prefix | ||
32 | */ | ||
33 | protected static $phpPrefix = '<?php /* '; | ||
34 | |||
35 | /** | ||
36 | * string Datastore PHP suffix | ||
37 | */ | ||
38 | protected static $phpSuffix = ' */ ?>'; | ||
39 | |||
40 | /** | ||
41 | * LinksIO constructor. | ||
42 | * | ||
43 | * @param ConfigManager $conf instance | ||
44 | */ | ||
45 | public function __construct($conf) | ||
46 | { | ||
47 | $this->conf = $conf; | ||
48 | $this->datastore = $conf->get('resource.datastore'); | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Reads database from disk to memory | ||
53 | * | ||
54 | * @return BookmarkArray instance | ||
55 | * | ||
56 | * @throws NotWritableDataStoreException Data couldn't be loaded | ||
57 | * @throws EmptyDataStoreException Datastore file exists but does not contain any bookmark | ||
58 | * @throws DatastoreNotInitializedException File does not exists | ||
59 | */ | ||
60 | public function read() | ||
61 | { | ||
62 | if (! file_exists($this->datastore)) { | ||
63 | throw new DatastoreNotInitializedException(); | ||
64 | } | ||
65 | |||
66 | if (!is_writable($this->datastore)) { | ||
67 | throw new NotWritableDataStoreException($this->datastore); | ||
68 | } | ||
69 | |||
70 | // Note that gzinflate is faster than gzuncompress. | ||
71 | // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 | ||
72 | $links = unserialize(gzinflate(base64_decode( | ||
73 | substr(file_get_contents($this->datastore), | ||
74 | strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); | ||
75 | |||
76 | if (empty($links)) { | ||
77 | if (filesize($this->datastore) > 100) { | ||
78 | throw new NotWritableDataStoreException($this->datastore); | ||
79 | } | ||
80 | throw new EmptyDataStoreException(); | ||
81 | } | ||
82 | |||
83 | return $links; | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * Saves the database from memory to disk | ||
88 | * | ||
89 | * @param BookmarkArray $links instance. | ||
90 | * | ||
91 | * @throws NotWritableDataStoreException the datastore is not writable | ||
92 | */ | ||
93 | public function write($links) | ||
94 | { | ||
95 | if (is_file($this->datastore) && !is_writeable($this->datastore)) { | ||
96 | // The datastore exists but is not writeable | ||
97 | throw new NotWritableDataStoreException($this->datastore); | ||
98 | } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) { | ||
99 | // The datastore does not exist and its parent directory is not writeable | ||
100 | throw new NotWritableDataStoreException(dirname($this->datastore)); | ||
101 | } | ||
102 | |||
103 | file_put_contents( | ||
104 | $this->datastore, | ||
105 | self::$phpPrefix.base64_encode(gzdeflate(serialize($links))).self::$phpSuffix | ||
106 | ); | ||
107 | } | ||
108 | } | ||
diff --git a/application/bookmark/BookmarkInitializer.php b/application/bookmark/BookmarkInitializer.php new file mode 100644 index 00000000..815047e3 --- /dev/null +++ b/application/bookmark/BookmarkInitializer.php | |||
@@ -0,0 +1,110 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | /** | ||
6 | * Class BookmarkInitializer | ||
7 | * | ||
8 | * This class is used to initialized default bookmarks after a fresh install of Shaarli. | ||
9 | * It should be only called if the datastore file does not exist(users might want to delete the default bookmarks). | ||
10 | * | ||
11 | * To prevent data corruption, it does not overwrite existing bookmarks, | ||
12 | * even though there should not be any. | ||
13 | * | ||
14 | * @package Shaarli\Bookmark | ||
15 | */ | ||
16 | class BookmarkInitializer | ||
17 | { | ||
18 | /** @var BookmarkServiceInterface */ | ||
19 | protected $bookmarkService; | ||
20 | |||
21 | /** | ||
22 | * BookmarkInitializer constructor. | ||
23 | * | ||
24 | * @param BookmarkServiceInterface $bookmarkService | ||
25 | */ | ||
26 | public function __construct($bookmarkService) | ||
27 | { | ||
28 | $this->bookmarkService = $bookmarkService; | ||
29 | } | ||
30 | |||
31 | /** | ||
32 | * Initialize the data store with default bookmarks | ||
33 | */ | ||
34 | public function initialize() | ||
35 | { | ||
36 | $bookmark = new Bookmark(); | ||
37 | $bookmark->setTitle('quicksilver (loop) on Vimeo ' . t('(private bookmark with thumbnail demo)')); | ||
38 | $bookmark->setUrl('https://vimeo.com/153493904'); | ||
39 | $bookmark->setDescription(t( | ||
40 | 'Shaarli will automatically pick up the thumbnail for links to a variety of websites. | ||
41 | |||
42 | Explore your new Shaarli instance by trying out controls and menus. | ||
43 | Visit the project on [Github](https://github.com/shaarli/Shaarli) or [the documentation](https://shaarli.readthedocs.io/en/master/) to learn more about Shaarli. | ||
44 | |||
45 | Now you can edit or delete the default shaares. | ||
46 | ' | ||
47 | )); | ||
48 | $bookmark->setTagsString('shaarli help thumbnail'); | ||
49 | $bookmark->setPrivate(true); | ||
50 | $this->bookmarkService->add($bookmark, false); | ||
51 | |||
52 | $bookmark = new Bookmark(); | ||
53 | $bookmark->setTitle(t('Note: Shaare descriptions')); | ||
54 | $bookmark->setDescription(t( | ||
55 | 'Adding a shaare without entering a URL creates a text-only "note" post such as this one. | ||
56 | This note is private, so you are the only one able to see it while logged in. | ||
57 | |||
58 | You can use this to keep notes, post articles, code snippets, and much more. | ||
59 | |||
60 | The Markdown formatting setting allows you to format your notes and bookmark description: | ||
61 | |||
62 | ### Title headings | ||
63 | |||
64 | #### Multiple headings levels | ||
65 | * bullet lists | ||
66 | * _italic_ text | ||
67 | * **bold** text | ||
68 | * ~~strike through~~ text | ||
69 | * `code` blocks | ||
70 | * images | ||
71 | * [links](https://en.wikipedia.org/wiki/Markdown) | ||
72 | |||
73 | Markdown also supports tables: | ||
74 | |||
75 | | Name | Type | Color | Qty | | ||
76 | | ------- | --------- | ------ | ----- | | ||
77 | | Orange | Fruit | Orange | 126 | | ||
78 | | Apple | Fruit | Any | 62 | | ||
79 | | Lemon | Fruit | Yellow | 30 | | ||
80 | | Carrot | Vegetable | Red | 14 | | ||
81 | ' | ||
82 | )); | ||
83 | $bookmark->setTagsString('shaarli help'); | ||
84 | $bookmark->setPrivate(true); | ||
85 | $this->bookmarkService->add($bookmark, false); | ||
86 | |||
87 | $bookmark = new Bookmark(); | ||
88 | $bookmark->setTitle( | ||
89 | 'Shaarli - ' . t('The personal, minimalist, super-fast, database free, bookmarking service') | ||
90 | ); | ||
91 | $bookmark->setDescription(t( | ||
92 | 'Welcome to Shaarli! | ||
93 | |||
94 | Shaarli allows you to bookmark your favorite pages, and share them with others or store them privately. | ||
95 | You can add a description to your bookmarks, such as this one, and tag them. | ||
96 | |||
97 | Create a new shaare by clicking the `+Shaare` button, or using any of the recommended tools (browser extension, mobile app, bookmarklet, REST API, etc.). | ||
98 | |||
99 | You can easily retrieve your links, even with thousands of them, using the internal search engine, or search through tags (e.g. this Shaare is tagged with `shaarli` and `help`). | ||
100 | Hashtags such as #shaarli #help are also supported. | ||
101 | You can also filter the available [RSS feed](/feed/atom) and picture wall by tag or plaintext search. | ||
102 | |||
103 | We hope that you will enjoy using Shaarli, maintained with â¤ï¸ by the community! | ||
104 | Feel free to open [an issue](https://github.com/shaarli/Shaarli/issues) if you have a suggestion or encounter an issue. | ||
105 | ' | ||
106 | )); | ||
107 | $bookmark->setTagsString('shaarli help'); | ||
108 | $this->bookmarkService->add($bookmark, false); | ||
109 | } | ||
110 | } | ||
diff --git a/application/bookmark/BookmarkServiceInterface.php b/application/bookmark/BookmarkServiceInterface.php new file mode 100644 index 00000000..b9b483eb --- /dev/null +++ b/application/bookmark/BookmarkServiceInterface.php | |||
@@ -0,0 +1,186 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | |||
6 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
7 | use Shaarli\Bookmark\Exception\NotWritableDataStoreException; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\History; | ||
10 | |||
11 | /** | ||
12 | * Class BookmarksService | ||
13 | * | ||
14 | * This is the entry point to manipulate the bookmark DB. | ||
15 | */ | ||
16 | interface BookmarkServiceInterface | ||
17 | { | ||
18 | /** | ||
19 | * BookmarksService constructor. | ||
20 | * | ||
21 | * @param ConfigManager $conf instance | ||
22 | * @param History $history instance | ||
23 | * @param bool $isLoggedIn true if the current user is logged in | ||
24 | */ | ||
25 | public function __construct(ConfigManager $conf, History $history, $isLoggedIn); | ||
26 | |||
27 | /** | ||
28 | * Find a bookmark by hash | ||
29 | * | ||
30 | * @param string $hash | ||
31 | * | ||
32 | * @return mixed | ||
33 | * | ||
34 | * @throws \Exception | ||
35 | */ | ||
36 | public function findByHash($hash); | ||
37 | |||
38 | /** | ||
39 | * @param $url | ||
40 | * | ||
41 | * @return Bookmark|null | ||
42 | */ | ||
43 | public function findByUrl($url); | ||
44 | |||
45 | /** | ||
46 | * Search bookmarks | ||
47 | * | ||
48 | * @param mixed $request | ||
49 | * @param string $visibility | ||
50 | * @param bool $caseSensitive | ||
51 | * @param bool $untaggedOnly | ||
52 | * @param bool $ignoreSticky | ||
53 | * | ||
54 | * @return Bookmark[] | ||
55 | */ | ||
56 | public function search( | ||
57 | $request = [], | ||
58 | $visibility = null, | ||
59 | $caseSensitive = false, | ||
60 | $untaggedOnly = false, | ||
61 | bool $ignoreSticky = false | ||
62 | ); | ||
63 | |||
64 | /** | ||
65 | * Get a single bookmark by its ID. | ||
66 | * | ||
67 | * @param int $id Bookmark ID | ||
68 | * @param string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an | ||
69 | * exception | ||
70 | * | ||
71 | * @return Bookmark | ||
72 | * | ||
73 | * @throws BookmarkNotFoundException | ||
74 | * @throws \Exception | ||
75 | */ | ||
76 | public function get($id, $visibility = null); | ||
77 | |||
78 | /** | ||
79 | * Updates an existing bookmark (depending on its ID). | ||
80 | * | ||
81 | * @param Bookmark $bookmark | ||
82 | * @param bool $save Writes to the datastore if set to true | ||
83 | * | ||
84 | * @return Bookmark Updated bookmark | ||
85 | * | ||
86 | * @throws BookmarkNotFoundException | ||
87 | * @throws \Exception | ||
88 | */ | ||
89 | public function set($bookmark, $save = true); | ||
90 | |||
91 | /** | ||
92 | * Adds a new bookmark (the ID must be empty). | ||
93 | * | ||
94 | * @param Bookmark $bookmark | ||
95 | * @param bool $save Writes to the datastore if set to true | ||
96 | * | ||
97 | * @return Bookmark new bookmark | ||
98 | * | ||
99 | * @throws \Exception | ||
100 | */ | ||
101 | public function add($bookmark, $save = true); | ||
102 | |||
103 | /** | ||
104 | * Adds or updates a bookmark depending on its ID: | ||
105 | * - a Bookmark without ID will be added | ||
106 | * - a Bookmark with an existing ID will be updated | ||
107 | * | ||
108 | * @param Bookmark $bookmark | ||
109 | * @param bool $save | ||
110 | * | ||
111 | * @return Bookmark | ||
112 | * | ||
113 | * @throws \Exception | ||
114 | */ | ||
115 | public function addOrSet($bookmark, $save = true); | ||
116 | |||
117 | /** | ||
118 | * Deletes a bookmark. | ||
119 | * | ||
120 | * @param Bookmark $bookmark | ||
121 | * @param bool $save | ||
122 | * | ||
123 | * @throws \Exception | ||
124 | */ | ||
125 | public function remove($bookmark, $save = true); | ||
126 | |||
127 | /** | ||
128 | * Get a single bookmark by its ID. | ||
129 | * | ||
130 | * @param int $id Bookmark ID | ||
131 | * @param string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an | ||
132 | * exception | ||
133 | * | ||
134 | * @return bool | ||
135 | */ | ||
136 | public function exists($id, $visibility = null); | ||
137 | |||
138 | /** | ||
139 | * Return the number of available bookmarks for given visibility. | ||
140 | * | ||
141 | * @param string $visibility public|private|all | ||
142 | * | ||
143 | * @return int Number of bookmarks | ||
144 | */ | ||
145 | public function count($visibility = null); | ||
146 | |||
147 | /** | ||
148 | * Write the datastore. | ||
149 | * | ||
150 | * @throws NotWritableDataStoreException | ||
151 | */ | ||
152 | public function save(); | ||
153 | |||
154 | /** | ||
155 | * Returns the list tags appearing in the bookmarks with the given tags | ||
156 | * | ||
157 | * @param array $filteringTags tags selecting the bookmarks to consider | ||
158 | * @param string $visibility process only all/private/public bookmarks | ||
159 | * | ||
160 | * @return array tag => bookmarksCount | ||
161 | */ | ||
162 | public function bookmarksCountPerTag($filteringTags = [], $visibility = 'all'); | ||
163 | |||
164 | /** | ||
165 | * Returns the list of days containing articles (oldest first) | ||
166 | * | ||
167 | * @return array containing days (in format YYYYMMDD). | ||
168 | */ | ||
169 | public function days(); | ||
170 | |||
171 | /** | ||
172 | * Returns the list of articles for a given day. | ||
173 | * | ||
174 | * @param string $request day to filter. Format: YYYYMMDD. | ||
175 | * | ||
176 | * @return Bookmark[] list of shaare found. | ||
177 | * | ||
178 | * @throws BookmarkNotFoundException | ||
179 | */ | ||
180 | public function filterDay($request); | ||
181 | |||
182 | /** | ||
183 | * Creates the default database after a fresh install. | ||
184 | */ | ||
185 | public function initialize(); | ||
186 | } | ||
diff --git a/application/bookmark/LinkUtils.php b/application/bookmark/LinkUtils.php index 77eb2d95..e7af4d55 100644 --- a/application/bookmark/LinkUtils.php +++ b/application/bookmark/LinkUtils.php | |||
@@ -1,112 +1,6 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | use Shaarli\Bookmark\LinkDB; | 3 | use Shaarli\Bookmark\Bookmark; |
4 | |||
5 | /** | ||
6 | * Get cURL callback function for CURLOPT_WRITEFUNCTION | ||
7 | * | ||
8 | * @param string $charset to extract from the downloaded page (reference) | ||
9 | * @param string $title to extract from the downloaded page (reference) | ||
10 | * @param string $description to extract from the downloaded page (reference) | ||
11 | * @param string $keywords to extract from the downloaded page (reference) | ||
12 | * @param bool $retrieveDescription Automatically tries to retrieve description and keywords from HTML content | ||
13 | * @param string $curlGetInfo Optionally overrides curl_getinfo function | ||
14 | * | ||
15 | * @return Closure | ||
16 | */ | ||
17 | function get_curl_download_callback( | ||
18 | &$charset, | ||
19 | &$title, | ||
20 | &$description, | ||
21 | &$keywords, | ||
22 | $retrieveDescription, | ||
23 | $curlGetInfo = 'curl_getinfo' | ||
24 | ) { | ||
25 | $isRedirected = false; | ||
26 | $currentChunk = 0; | ||
27 | $foundChunk = null; | ||
28 | |||
29 | /** | ||
30 | * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download). | ||
31 | * | ||
32 | * While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text' | ||
33 | * Then we extract the title and the charset and stop the download when it's done. | ||
34 | * | ||
35 | * @param resource $ch cURL resource | ||
36 | * @param string $data chunk of data being downloaded | ||
37 | * | ||
38 | * @return int|bool length of $data or false if we need to stop the download | ||
39 | */ | ||
40 | return function (&$ch, $data) use ( | ||
41 | $retrieveDescription, | ||
42 | $curlGetInfo, | ||
43 | &$charset, | ||
44 | &$title, | ||
45 | &$description, | ||
46 | &$keywords, | ||
47 | &$isRedirected, | ||
48 | &$currentChunk, | ||
49 | &$foundChunk | ||
50 | ) { | ||
51 | $currentChunk++; | ||
52 | $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE); | ||
53 | if (!empty($responseCode) && in_array($responseCode, [301, 302])) { | ||
54 | $isRedirected = true; | ||
55 | return strlen($data); | ||
56 | } | ||
57 | if (!empty($responseCode) && $responseCode !== 200) { | ||
58 | return false; | ||
59 | } | ||
60 | // After a redirection, the content type will keep the previous request value | ||
61 | // until it finds the next content-type header. | ||
62 | if (! $isRedirected || strpos(strtolower($data), 'content-type') !== false) { | ||
63 | $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE); | ||
64 | } | ||
65 | if (!empty($contentType) && strpos($contentType, 'text/html') === false) { | ||
66 | return false; | ||
67 | } | ||
68 | if (!empty($contentType) && empty($charset)) { | ||
69 | $charset = header_extract_charset($contentType); | ||
70 | } | ||
71 | if (empty($charset)) { | ||
72 | $charset = html_extract_charset($data); | ||
73 | } | ||
74 | if (empty($title)) { | ||
75 | $title = html_extract_title($data); | ||
76 | $foundChunk = ! empty($title) ? $currentChunk : $foundChunk; | ||
77 | } | ||
78 | if ($retrieveDescription && empty($description)) { | ||
79 | $description = html_extract_tag('description', $data); | ||
80 | $foundChunk = ! empty($description) ? $currentChunk : $foundChunk; | ||
81 | } | ||
82 | if ($retrieveDescription && empty($keywords)) { | ||
83 | $keywords = html_extract_tag('keywords', $data); | ||
84 | if (! empty($keywords)) { | ||
85 | $foundChunk = $currentChunk; | ||
86 | // Keywords use the format tag1, tag2 multiple words, tag | ||
87 | // So we format them to match Shaarli's separator and glue multiple words with '-' | ||
88 | $keywords = implode(' ', array_map(function($keyword) { | ||
89 | return implode('-', preg_split('/\s+/', trim($keyword))); | ||
90 | }, explode(',', $keywords))); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | // We got everything we want, stop the download. | ||
95 | // If we already found either the title, description or keywords, | ||
96 | // it's highly unlikely that we'll found the other metas further than | ||
97 | // in the same chunk of data or the next one. So we also stop the download after that. | ||
98 | if ((!empty($responseCode) && !empty($contentType) && !empty($charset)) && $foundChunk !== null | ||
99 | && (! $retrieveDescription | ||
100 | || $foundChunk < $currentChunk | ||
101 | || (!empty($title) && !empty($description) && !empty($keywords)) | ||
102 | ) | ||
103 | ) { | ||
104 | return false; | ||
105 | } | ||
106 | |||
107 | return strlen($data); | ||
108 | }; | ||
109 | } | ||
110 | 4 | ||
111 | /** | 5 | /** |
112 | * Extract title from an HTML document. | 6 | * Extract title from an HTML document. |
@@ -132,7 +26,7 @@ function html_extract_title($html) | |||
132 | */ | 26 | */ |
133 | function header_extract_charset($header) | 27 | function header_extract_charset($header) |
134 | { | 28 | { |
135 | preg_match('/charset="?([^; ]+)/i', $header, $match); | 29 | preg_match('/charset=["\']?([^; "\']+)/i', $header, $match); |
136 | if (! empty($match[1])) { | 30 | if (! empty($match[1])) { |
137 | return strtolower(trim($match[1])); | 31 | return strtolower(trim($match[1])); |
138 | } | 32 | } |
@@ -188,30 +82,11 @@ function html_extract_tag($tag, $html) | |||
188 | } | 82 | } |
189 | 83 | ||
190 | /** | 84 | /** |
191 | * Count private links in given linklist. | 85 | * In a string, converts URLs to clickable bookmarks. |
192 | * | ||
193 | * @param array|Countable $links Linklist. | ||
194 | * | ||
195 | * @return int Number of private links. | ||
196 | */ | ||
197 | function count_private($links) | ||
198 | { | ||
199 | $cpt = 0; | ||
200 | foreach ($links as $link) { | ||
201 | if ($link['private']) { | ||
202 | $cpt += 1; | ||
203 | } | ||
204 | } | ||
205 | |||
206 | return $cpt; | ||
207 | } | ||
208 | |||
209 | /** | ||
210 | * In a string, converts URLs to clickable links. | ||
211 | * | 86 | * |
212 | * @param string $text input string. | 87 | * @param string $text input string. |
213 | * | 88 | * |
214 | * @return string returns $text with all links converted to HTML links. | 89 | * @return string returns $text with all bookmarks converted to HTML bookmarks. |
215 | * | 90 | * |
216 | * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 | 91 | * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 |
217 | */ | 92 | */ |
@@ -239,7 +114,7 @@ function hashtag_autolink($description, $indexUrl = '') | |||
239 | * \p{Mn} - any non marking space (accents, umlauts, etc) | 114 | * \p{Mn} - any non marking space (accents, umlauts, etc) |
240 | */ | 115 | */ |
241 | $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; | 116 | $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; |
242 | $replacement = '$1<a href="'. $indexUrl .'?addtag=$2" title="Hashtag $2">#$2</a>'; | 117 | $replacement = '$1<a href="'. $indexUrl .'./add-tag/$2" title="Hashtag $2">#$2</a>'; |
243 | return preg_replace($regex, $replacement, $description); | 118 | return preg_replace($regex, $replacement, $description); |
244 | } | 119 | } |
245 | 120 | ||
@@ -279,7 +154,7 @@ function format_description($description, $indexUrl = '') | |||
279 | */ | 154 | */ |
280 | function link_small_hash($date, $id) | 155 | function link_small_hash($date, $id) |
281 | { | 156 | { |
282 | return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id); | 157 | return smallHash($date->format(Bookmark::LINK_DATE_FORMAT) . $id); |
283 | } | 158 | } |
284 | 159 | ||
285 | /** | 160 | /** |
diff --git a/application/bookmark/exception/LinkNotFoundException.php b/application/bookmark/exception/BookmarkNotFoundException.php index f9414428..827a3d35 100644 --- a/application/bookmark/exception/LinkNotFoundException.php +++ b/application/bookmark/exception/BookmarkNotFoundException.php | |||
@@ -3,7 +3,7 @@ namespace Shaarli\Bookmark\Exception; | |||
3 | 3 | ||
4 | use Exception; | 4 | use Exception; |
5 | 5 | ||
6 | class LinkNotFoundException extends Exception | 6 | class BookmarkNotFoundException extends Exception |
7 | { | 7 | { |
8 | /** | 8 | /** |
9 | * LinkNotFoundException constructor. | 9 | * LinkNotFoundException constructor. |
diff --git a/application/bookmark/exception/DatastoreNotInitializedException.php b/application/bookmark/exception/DatastoreNotInitializedException.php new file mode 100644 index 00000000..f495049d --- /dev/null +++ b/application/bookmark/exception/DatastoreNotInitializedException.php | |||
@@ -0,0 +1,10 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Bookmark\Exception; | ||
6 | |||
7 | class DatastoreNotInitializedException extends \Exception | ||
8 | { | ||
9 | |||
10 | } | ||
diff --git a/application/bookmark/exception/EmptyDataStoreException.php b/application/bookmark/exception/EmptyDataStoreException.php new file mode 100644 index 00000000..cd48c1e6 --- /dev/null +++ b/application/bookmark/exception/EmptyDataStoreException.php | |||
@@ -0,0 +1,7 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | namespace Shaarli\Bookmark\Exception; | ||
5 | |||
6 | |||
7 | class EmptyDataStoreException extends \Exception {} | ||
diff --git a/application/bookmark/exception/InvalidBookmarkException.php b/application/bookmark/exception/InvalidBookmarkException.php new file mode 100644 index 00000000..10c84a6d --- /dev/null +++ b/application/bookmark/exception/InvalidBookmarkException.php | |||
@@ -0,0 +1,30 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark\Exception; | ||
4 | |||
5 | use Shaarli\Bookmark\Bookmark; | ||
6 | |||
7 | class InvalidBookmarkException extends \Exception | ||
8 | { | ||
9 | public function __construct($bookmark) | ||
10 | { | ||
11 | if ($bookmark instanceof Bookmark) { | ||
12 | if ($bookmark->getCreated() instanceof \DateTime) { | ||
13 | $created = $bookmark->getCreated()->format(\DateTime::ATOM); | ||
14 | } elseif (empty($bookmark->getCreated())) { | ||
15 | $created = ''; | ||
16 | } else { | ||
17 | $created = 'Not a DateTime object'; | ||
18 | } | ||
19 | $this->message = 'This bookmark is not valid'. PHP_EOL; | ||
20 | $this->message .= ' - ID: '. $bookmark->getId() . PHP_EOL; | ||
21 | $this->message .= ' - Title: '. $bookmark->getTitle() . PHP_EOL; | ||
22 | $this->message .= ' - Url: '. $bookmark->getUrl() . PHP_EOL; | ||
23 | $this->message .= ' - ShortUrl: '. $bookmark->getShortUrl() . PHP_EOL; | ||
24 | $this->message .= ' - Created: '. $created . PHP_EOL; | ||
25 | } else { | ||
26 | $this->message = 'The provided data is not a bookmark'. PHP_EOL; | ||
27 | $this->message .= var_export($bookmark, true); | ||
28 | } | ||
29 | } | ||
30 | } | ||
diff --git a/application/bookmark/exception/NotWritableDataStoreException.php b/application/bookmark/exception/NotWritableDataStoreException.php new file mode 100644 index 00000000..95f34b50 --- /dev/null +++ b/application/bookmark/exception/NotWritableDataStoreException.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | namespace Shaarli\Bookmark\Exception; | ||
5 | |||
6 | |||
7 | class NotWritableDataStoreException extends \Exception | ||
8 | { | ||
9 | /** | ||
10 | * NotReadableDataStore constructor. | ||
11 | * | ||
12 | * @param string $dataStore file path | ||
13 | */ | ||
14 | public function __construct($dataStore) | ||
15 | { | ||
16 | $this->message = 'Couldn\'t load data from the data store file "'. $dataStore .'". '. | ||
17 | 'Your data might be corrupted, or your file isn\'t readable.'; | ||
18 | } | ||
19 | } | ||
diff --git a/application/config/ConfigJson.php b/application/config/ConfigJson.php index 4509357c..c0c0dab9 100644 --- a/application/config/ConfigJson.php +++ b/application/config/ConfigJson.php | |||
@@ -46,7 +46,7 @@ class ConfigJson implements ConfigIO | |||
46 | // JSON_PRETTY_PRINT is available from PHP 5.4. | 46 | // JSON_PRETTY_PRINT is available from PHP 5.4. |
47 | $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; | 47 | $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; |
48 | $data = self::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix(); | 48 | $data = self::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix(); |
49 | if (!file_put_contents($filepath, $data)) { | 49 | if (empty($filepath) || !file_put_contents($filepath, $data)) { |
50 | throw new \Shaarli\Exceptions\IOException( | 50 | throw new \Shaarli\Exceptions\IOException( |
51 | $filepath, | 51 | $filepath, |
52 | t('Shaarli could not create the config file. '. | 52 | t('Shaarli could not create the config file. '. |
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php index c95e6800..4c98be30 100644 --- a/application/config/ConfigManager.php +++ b/application/config/ConfigManager.php | |||
@@ -3,6 +3,7 @@ namespace Shaarli\Config; | |||
3 | 3 | ||
4 | use Shaarli\Config\Exception\MissingFieldConfigException; | 4 | use Shaarli\Config\Exception\MissingFieldConfigException; |
5 | use Shaarli\Config\Exception\UnauthorizedConfigException; | 5 | use Shaarli\Config\Exception\UnauthorizedConfigException; |
6 | use Shaarli\Thumbnailer; | ||
6 | 7 | ||
7 | /** | 8 | /** |
8 | * Class ConfigManager | 9 | * Class ConfigManager |
@@ -361,7 +362,7 @@ class ConfigManager | |||
361 | $this->setEmpty('security.open_shaarli', false); | 362 | $this->setEmpty('security.open_shaarli', false); |
362 | $this->setEmpty('security.allowed_protocols', ['ftp', 'ftps', 'magnet']); | 363 | $this->setEmpty('security.allowed_protocols', ['ftp', 'ftps', 'magnet']); |
363 | 364 | ||
364 | $this->setEmpty('general.header_link', '?'); | 365 | $this->setEmpty('general.header_link', '/'); |
365 | $this->setEmpty('general.links_per_page', 20); | 366 | $this->setEmpty('general.links_per_page', 20); |
366 | $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS); | 367 | $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS); |
367 | $this->setEmpty('general.default_note_title', 'Note: '); | 368 | $this->setEmpty('general.default_note_title', 'Note: '); |
@@ -381,6 +382,7 @@ class ConfigManager | |||
381 | // default state of the 'remember me' checkbox of the login form | 382 | // default state of the 'remember me' checkbox of the login form |
382 | $this->setEmpty('privacy.remember_user_default', true); | 383 | $this->setEmpty('privacy.remember_user_default', true); |
383 | 384 | ||
385 | $this->setEmpty('thumbnails.mode', Thumbnailer::MODE_ALL); | ||
384 | $this->setEmpty('thumbnails.width', '125'); | 386 | $this->setEmpty('thumbnails.width', '125'); |
385 | $this->setEmpty('thumbnails.height', '90'); | 387 | $this->setEmpty('thumbnails.height', '90'); |
386 | 388 | ||
@@ -389,6 +391,8 @@ class ConfigManager | |||
389 | $this->setEmpty('translation.extensions', []); | 391 | $this->setEmpty('translation.extensions', []); |
390 | 392 | ||
391 | $this->setEmpty('plugins', array()); | 393 | $this->setEmpty('plugins', array()); |
394 | |||
395 | $this->setEmpty('formatter', 'markdown'); | ||
392 | } | 396 | } |
393 | 397 | ||
394 | /** | 398 | /** |
diff --git a/application/config/ConfigPlugin.php b/application/config/ConfigPlugin.php index dbb24937..ea8dfbda 100644 --- a/application/config/ConfigPlugin.php +++ b/application/config/ConfigPlugin.php | |||
@@ -1,6 +1,7 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | use Shaarli\Config\Exception\PluginConfigOrderException; | 3 | use Shaarli\Config\Exception\PluginConfigOrderException; |
4 | use Shaarli\Plugin\PluginManager; | ||
4 | 5 | ||
5 | /** | 6 | /** |
6 | * Plugin configuration helper functions. | 7 | * Plugin configuration helper functions. |
@@ -19,6 +20,20 @@ use Shaarli\Config\Exception\PluginConfigOrderException; | |||
19 | */ | 20 | */ |
20 | function save_plugin_config($formData) | 21 | function save_plugin_config($formData) |
21 | { | 22 | { |
23 | // We can only save existing plugins | ||
24 | $directories = str_replace( | ||
25 | PluginManager::$PLUGINS_PATH . '/', | ||
26 | '', | ||
27 | glob(PluginManager::$PLUGINS_PATH . '/*') | ||
28 | ); | ||
29 | $formData = array_filter( | ||
30 | $formData, | ||
31 | function ($value, string $key) use ($directories) { | ||
32 | return startsWith($key, 'order') || in_array($key, $directories); | ||
33 | }, | ||
34 | ARRAY_FILTER_USE_BOTH | ||
35 | ); | ||
36 | |||
22 | // Make sure there are no duplicates in orders. | 37 | // Make sure there are no duplicates in orders. |
23 | if (!validate_plugin_order($formData)) { | 38 | if (!validate_plugin_order($formData)) { |
24 | throw new PluginConfigOrderException(); | 39 | throw new PluginConfigOrderException(); |
@@ -69,7 +84,7 @@ function validate_plugin_order($formData) | |||
69 | $orders = array(); | 84 | $orders = array(); |
70 | foreach ($formData as $key => $value) { | 85 | foreach ($formData as $key => $value) { |
71 | // No duplicate order allowed. | 86 | // No duplicate order allowed. |
72 | if (in_array($value, $orders)) { | 87 | if (in_array($value, $orders, true)) { |
73 | return false; | 88 | return false; |
74 | } | 89 | } |
75 | 90 | ||
diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php new file mode 100644 index 00000000..55bb51b5 --- /dev/null +++ b/application/container/ContainerBuilder.php | |||
@@ -0,0 +1,165 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Container; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkFileService; | ||
8 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\Feed\FeedBuilder; | ||
11 | use Shaarli\Formatter\FormatterFactory; | ||
12 | use Shaarli\Front\Controller\Visitor\ErrorController; | ||
13 | use Shaarli\Front\Controller\Visitor\ErrorNotFoundController; | ||
14 | use Shaarli\History; | ||
15 | use Shaarli\Http\HttpAccess; | ||
16 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
17 | use Shaarli\Plugin\PluginManager; | ||
18 | use Shaarli\Render\PageBuilder; | ||
19 | use Shaarli\Render\PageCacheManager; | ||
20 | use Shaarli\Security\CookieManager; | ||
21 | use Shaarli\Security\LoginManager; | ||
22 | use Shaarli\Security\SessionManager; | ||
23 | use Shaarli\Thumbnailer; | ||
24 | use Shaarli\Updater\Updater; | ||
25 | use Shaarli\Updater\UpdaterUtils; | ||
26 | |||
27 | /** | ||
28 | * Class ContainerBuilder | ||
29 | * | ||
30 | * Helper used to build a Slim container instance with Shaarli's object dependencies. | ||
31 | * Note that most injected objects MUST be added as closures, to let the container instantiate | ||
32 | * only the objects it requires during the execution. | ||
33 | * | ||
34 | * @package Container | ||
35 | */ | ||
36 | class ContainerBuilder | ||
37 | { | ||
38 | /** @var ConfigManager */ | ||
39 | protected $conf; | ||
40 | |||
41 | /** @var SessionManager */ | ||
42 | protected $session; | ||
43 | |||
44 | /** @var CookieManager */ | ||
45 | protected $cookieManager; | ||
46 | |||
47 | /** @var LoginManager */ | ||
48 | protected $login; | ||
49 | |||
50 | /** @var string|null */ | ||
51 | protected $basePath = null; | ||
52 | |||
53 | public function __construct( | ||
54 | ConfigManager $conf, | ||
55 | SessionManager $session, | ||
56 | CookieManager $cookieManager, | ||
57 | LoginManager $login | ||
58 | ) { | ||
59 | $this->conf = $conf; | ||
60 | $this->session = $session; | ||
61 | $this->login = $login; | ||
62 | $this->cookieManager = $cookieManager; | ||
63 | } | ||
64 | |||
65 | public function build(): ShaarliContainer | ||
66 | { | ||
67 | $container = new ShaarliContainer(); | ||
68 | |||
69 | $container['conf'] = $this->conf; | ||
70 | $container['sessionManager'] = $this->session; | ||
71 | $container['cookieManager'] = $this->cookieManager; | ||
72 | $container['loginManager'] = $this->login; | ||
73 | $container['basePath'] = $this->basePath; | ||
74 | |||
75 | $container['plugins'] = function (ShaarliContainer $container): PluginManager { | ||
76 | return new PluginManager($container->conf); | ||
77 | }; | ||
78 | |||
79 | $container['history'] = function (ShaarliContainer $container): History { | ||
80 | return new History($container->conf->get('resource.history')); | ||
81 | }; | ||
82 | |||
83 | $container['bookmarkService'] = function (ShaarliContainer $container): BookmarkServiceInterface { | ||
84 | return new BookmarkFileService( | ||
85 | $container->conf, | ||
86 | $container->history, | ||
87 | $container->loginManager->isLoggedIn() | ||
88 | ); | ||
89 | }; | ||
90 | |||
91 | $container['pageBuilder'] = function (ShaarliContainer $container): PageBuilder { | ||
92 | return new PageBuilder( | ||
93 | $container->conf, | ||
94 | $container->sessionManager->getSession(), | ||
95 | $container->bookmarkService, | ||
96 | $container->sessionManager->generateToken(), | ||
97 | $container->loginManager->isLoggedIn() | ||
98 | ); | ||
99 | }; | ||
100 | |||
101 | $container['pluginManager'] = function (ShaarliContainer $container): PluginManager { | ||
102 | $pluginManager = new PluginManager($container->conf); | ||
103 | |||
104 | $pluginManager->load($container->conf->get('general.enabled_plugins')); | ||
105 | |||
106 | return $pluginManager; | ||
107 | }; | ||
108 | |||
109 | $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { | ||
110 | return new FormatterFactory( | ||
111 | $container->conf, | ||
112 | $container->loginManager->isLoggedIn() | ||
113 | ); | ||
114 | }; | ||
115 | |||
116 | $container['pageCacheManager'] = function (ShaarliContainer $container): PageCacheManager { | ||
117 | return new PageCacheManager( | ||
118 | $container->conf->get('resource.page_cache'), | ||
119 | $container->loginManager->isLoggedIn() | ||
120 | ); | ||
121 | }; | ||
122 | |||
123 | $container['feedBuilder'] = function (ShaarliContainer $container): FeedBuilder { | ||
124 | return new FeedBuilder( | ||
125 | $container->bookmarkService, | ||
126 | $container->formatterFactory->getFormatter(), | ||
127 | $container->environment, | ||
128 | $container->loginManager->isLoggedIn() | ||
129 | ); | ||
130 | }; | ||
131 | |||
132 | $container['thumbnailer'] = function (ShaarliContainer $container): Thumbnailer { | ||
133 | return new Thumbnailer($container->conf); | ||
134 | }; | ||
135 | |||
136 | $container['httpAccess'] = function (): HttpAccess { | ||
137 | return new HttpAccess(); | ||
138 | }; | ||
139 | |||
140 | $container['netscapeBookmarkUtils'] = function (ShaarliContainer $container): NetscapeBookmarkUtils { | ||
141 | return new NetscapeBookmarkUtils($container->bookmarkService, $container->conf, $container->history); | ||
142 | }; | ||
143 | |||
144 | $container['updater'] = function (ShaarliContainer $container): Updater { | ||
145 | return new Updater( | ||
146 | UpdaterUtils::read_updates_file($container->conf->get('resource.updates')), | ||
147 | $container->bookmarkService, | ||
148 | $container->conf, | ||
149 | $container->loginManager->isLoggedIn() | ||
150 | ); | ||
151 | }; | ||
152 | |||
153 | $container['notFoundHandler'] = function (ShaarliContainer $container): ErrorNotFoundController { | ||
154 | return new ErrorNotFoundController($container); | ||
155 | }; | ||
156 | $container['errorHandler'] = function (ShaarliContainer $container): ErrorController { | ||
157 | return new ErrorController($container); | ||
158 | }; | ||
159 | $container['phpErrorHandler'] = function (ShaarliContainer $container): ErrorController { | ||
160 | return new ErrorController($container); | ||
161 | }; | ||
162 | |||
163 | return $container; | ||
164 | } | ||
165 | } | ||
diff --git a/application/container/ShaarliContainer.php b/application/container/ShaarliContainer.php new file mode 100644 index 00000000..66e669aa --- /dev/null +++ b/application/container/ShaarliContainer.php | |||
@@ -0,0 +1,51 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Container; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Feed\FeedBuilder; | ||
10 | use Shaarli\Formatter\FormatterFactory; | ||
11 | use Shaarli\History; | ||
12 | use Shaarli\Http\HttpAccess; | ||
13 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
14 | use Shaarli\Plugin\PluginManager; | ||
15 | use Shaarli\Render\PageBuilder; | ||
16 | use Shaarli\Render\PageCacheManager; | ||
17 | use Shaarli\Security\CookieManager; | ||
18 | use Shaarli\Security\LoginManager; | ||
19 | use Shaarli\Security\SessionManager; | ||
20 | use Shaarli\Thumbnailer; | ||
21 | use Shaarli\Updater\Updater; | ||
22 | use Slim\Container; | ||
23 | |||
24 | /** | ||
25 | * Extension of Slim container to document the injected objects. | ||
26 | * | ||
27 | * @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`) | ||
28 | * @property BookmarkServiceInterface $bookmarkService | ||
29 | * @property CookieManager $cookieManager | ||
30 | * @property ConfigManager $conf | ||
31 | * @property mixed[] $environment $_SERVER automatically injected by Slim | ||
32 | * @property callable $errorHandler Overrides default Slim exception display | ||
33 | * @property FeedBuilder $feedBuilder | ||
34 | * @property FormatterFactory $formatterFactory | ||
35 | * @property History $history | ||
36 | * @property HttpAccess $httpAccess | ||
37 | * @property LoginManager $loginManager | ||
38 | * @property NetscapeBookmarkUtils $netscapeBookmarkUtils | ||
39 | * @property callable $notFoundHandler Overrides default Slim exception display | ||
40 | * @property PageBuilder $pageBuilder | ||
41 | * @property PageCacheManager $pageCacheManager | ||
42 | * @property callable $phpErrorHandler Overrides default Slim PHP error display | ||
43 | * @property PluginManager $pluginManager | ||
44 | * @property SessionManager $sessionManager | ||
45 | * @property Thumbnailer $thumbnailer | ||
46 | * @property Updater $updater | ||
47 | */ | ||
48 | class ShaarliContainer extends Container | ||
49 | { | ||
50 | |||
51 | } | ||
diff --git a/application/feed/Cache.php b/application/feed/Cache.php deleted file mode 100644 index e5d43e61..00000000 --- a/application/feed/Cache.php +++ /dev/null | |||
@@ -1,38 +0,0 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Cache utilities | ||
4 | */ | ||
5 | |||
6 | /** | ||
7 | * Purges all cached pages | ||
8 | * | ||
9 | * @param string $pageCacheDir page cache directory | ||
10 | * | ||
11 | * @return mixed an error string if the directory is missing | ||
12 | */ | ||
13 | function purgeCachedPages($pageCacheDir) | ||
14 | { | ||
15 | if (! is_dir($pageCacheDir)) { | ||
16 | $error = sprintf(t('Cannot purge %s: no directory'), $pageCacheDir); | ||
17 | error_log($error); | ||
18 | return $error; | ||
19 | } | ||
20 | |||
21 | array_map('unlink', glob($pageCacheDir.'/*.cache')); | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Invalidates caches when the database is changed or the user logs out. | ||
26 | * | ||
27 | * @param string $pageCacheDir page cache directory | ||
28 | */ | ||
29 | function invalidateCaches($pageCacheDir) | ||
30 | { | ||
31 | // Purge cache attached to session. | ||
32 | if (isset($_SESSION['tags'])) { | ||
33 | unset($_SESSION['tags']); | ||
34 | } | ||
35 | |||
36 | // Purge page cache shared by sessions. | ||
37 | purgeCachedPages($pageCacheDir); | ||
38 | } | ||
diff --git a/application/feed/FeedBuilder.php b/application/feed/FeedBuilder.php index 7c859474..f6def630 100644 --- a/application/feed/FeedBuilder.php +++ b/application/feed/FeedBuilder.php | |||
@@ -2,6 +2,9 @@ | |||
2 | namespace Shaarli\Feed; | 2 | namespace Shaarli\Feed; |
3 | 3 | ||
4 | use DateTime; | 4 | use DateTime; |
5 | use Shaarli\Bookmark\Bookmark; | ||
6 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
7 | use Shaarli\Formatter\BookmarkFormatter; | ||
5 | 8 | ||
6 | /** | 9 | /** |
7 | * FeedBuilder class. | 10 | * FeedBuilder class. |
@@ -26,37 +29,30 @@ class FeedBuilder | |||
26 | public static $DEFAULT_LANGUAGE = 'en-en'; | 29 | public static $DEFAULT_LANGUAGE = 'en-en'; |
27 | 30 | ||
28 | /** | 31 | /** |
29 | * @var int Number of links to display in a feed by default. | 32 | * @var int Number of bookmarks to display in a feed by default. |
30 | */ | 33 | */ |
31 | public static $DEFAULT_NB_LINKS = 50; | 34 | public static $DEFAULT_NB_LINKS = 50; |
32 | 35 | ||
33 | /** | 36 | /** |
34 | * @var \Shaarli\Bookmark\LinkDB instance. | 37 | * @var BookmarkServiceInterface instance. |
35 | */ | 38 | */ |
36 | protected $linkDB; | 39 | protected $linkDB; |
37 | 40 | ||
38 | /** | 41 | /** |
39 | * @var string RSS or ATOM feed. | 42 | * @var BookmarkFormatter instance. |
40 | */ | 43 | */ |
41 | protected $feedType; | 44 | protected $formatter; |
42 | 45 | ||
43 | /** | 46 | /** @var mixed[] $_SERVER */ |
44 | * @var array $_SERVER | ||
45 | */ | ||
46 | protected $serverInfo; | 47 | protected $serverInfo; |
47 | 48 | ||
48 | /** | 49 | /** |
49 | * @var array $_GET | ||
50 | */ | ||
51 | protected $userInput; | ||
52 | |||
53 | /** | ||
54 | * @var boolean True if the user is currently logged in, false otherwise. | 50 | * @var boolean True if the user is currently logged in, false otherwise. |
55 | */ | 51 | */ |
56 | protected $isLoggedIn; | 52 | protected $isLoggedIn; |
57 | 53 | ||
58 | /** | 54 | /** |
59 | * @var boolean Use permalinks instead of direct links if true. | 55 | * @var boolean Use permalinks instead of direct bookmarks if true. |
60 | */ | 56 | */ |
61 | protected $usePermalinks; | 57 | protected $usePermalinks; |
62 | 58 | ||
@@ -69,7 +65,6 @@ class FeedBuilder | |||
69 | * @var string server locale. | 65 | * @var string server locale. |
70 | */ | 66 | */ |
71 | protected $locale; | 67 | protected $locale; |
72 | |||
73 | /** | 68 | /** |
74 | * @var DateTime Latest item date. | 69 | * @var DateTime Latest item date. |
75 | */ | 70 | */ |
@@ -78,38 +73,38 @@ class FeedBuilder | |||
78 | /** | 73 | /** |
79 | * Feed constructor. | 74 | * Feed constructor. |
80 | * | 75 | * |
81 | * @param \Shaarli\Bookmark\LinkDB $linkDB LinkDB instance. | 76 | * @param BookmarkServiceInterface $linkDB LinkDB instance. |
82 | * @param string $feedType Type of feed. | 77 | * @param BookmarkFormatter $formatter instance. |
83 | * @param array $serverInfo $_SERVER. | 78 | * @param array $serverInfo $_SERVER. |
84 | * @param array $userInput $_GET. | 79 | * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise. |
85 | * @param boolean $isLoggedIn True if the user is currently logged in, | ||
86 | * false otherwise. | ||
87 | */ | 80 | */ |
88 | public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLoggedIn) | 81 | public function __construct($linkDB, $formatter, $serverInfo, $isLoggedIn) |
89 | { | 82 | { |
90 | $this->linkDB = $linkDB; | 83 | $this->linkDB = $linkDB; |
91 | $this->feedType = $feedType; | 84 | $this->formatter = $formatter; |
92 | $this->serverInfo = $serverInfo; | 85 | $this->serverInfo = $serverInfo; |
93 | $this->userInput = $userInput; | ||
94 | $this->isLoggedIn = $isLoggedIn; | 86 | $this->isLoggedIn = $isLoggedIn; |
95 | } | 87 | } |
96 | 88 | ||
97 | /** | 89 | /** |
98 | * Build data for feed templates. | 90 | * Build data for feed templates. |
99 | * | 91 | * |
92 | * @param string $feedType Type of feed (RSS/ATOM). | ||
93 | * @param array $userInput $_GET. | ||
94 | * | ||
100 | * @return array Formatted data for feeds templates. | 95 | * @return array Formatted data for feeds templates. |
101 | */ | 96 | */ |
102 | public function buildData() | 97 | public function buildData(string $feedType, ?array $userInput) |
103 | { | 98 | { |
104 | // Search for untagged links | 99 | // Search for untagged bookmarks |
105 | if (isset($this->userInput['searchtags']) && empty($this->userInput['searchtags'])) { | 100 | if (isset($this->userInput['searchtags']) && empty($userInput['searchtags'])) { |
106 | $this->userInput['searchtags'] = false; | 101 | $userInput['searchtags'] = false; |
107 | } | 102 | } |
108 | 103 | ||
109 | // Optionally filter the results: | 104 | // Optionally filter the results: |
110 | $linksToDisplay = $this->linkDB->filterSearch($this->userInput); | 105 | $linksToDisplay = $this->linkDB->search($userInput, null, false, false, true); |
111 | 106 | ||
112 | $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay)); | 107 | $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay), $userInput); |
113 | 108 | ||
114 | // Can't use array_keys() because $link is a LinkDB instance and not a real array. | 109 | // Can't use array_keys() because $link is a LinkDB instance and not a real array. |
115 | $keys = array(); | 110 | $keys = array(); |
@@ -118,17 +113,18 @@ class FeedBuilder | |||
118 | } | 113 | } |
119 | 114 | ||
120 | $pageaddr = escape(index_url($this->serverInfo)); | 115 | $pageaddr = escape(index_url($this->serverInfo)); |
116 | $this->formatter->addContextData('index_url', $pageaddr); | ||
121 | $linkDisplayed = array(); | 117 | $linkDisplayed = array(); |
122 | for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) { | 118 | for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) { |
123 | $linkDisplayed[$keys[$i]] = $this->buildItem($linksToDisplay[$keys[$i]], $pageaddr); | 119 | $linkDisplayed[$keys[$i]] = $this->buildItem($feedType, $linksToDisplay[$keys[$i]], $pageaddr); |
124 | } | 120 | } |
125 | 121 | ||
126 | $data['language'] = $this->getTypeLanguage(); | 122 | $data['language'] = $this->getTypeLanguage($feedType); |
127 | $data['last_update'] = $this->getLatestDateFormatted(); | 123 | $data['last_update'] = $this->getLatestDateFormatted($feedType); |
128 | $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; | 124 | $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; |
129 | // Remove leading slash from REQUEST_URI. | 125 | // Remove leading path from REQUEST_URI (already contained in $pageaddr). |
130 | $data['self_link'] = escape(server_url($this->serverInfo)) | 126 | $requestUri = preg_replace('#(.*?/)(feed.*)#', '$2', escape($this->serverInfo['REQUEST_URI'])); |
131 | . escape($this->serverInfo['REQUEST_URI']); | 127 | $data['self_link'] = $pageaddr . $requestUri; |
132 | $data['index_url'] = $pageaddr; | 128 | $data['index_url'] = $pageaddr; |
133 | $data['usepermalinks'] = $this->usePermalinks === true; | 129 | $data['usepermalinks'] = $this->usePermalinks === true; |
134 | $data['links'] = $linkDisplayed; | 130 | $data['links'] = $linkDisplayed; |
@@ -137,56 +133,7 @@ class FeedBuilder | |||
137 | } | 133 | } |
138 | 134 | ||
139 | /** | 135 | /** |
140 | * Build a feed item (one per shaare). | 136 | * Set this to true to use permalinks instead of direct bookmarks. |
141 | * | ||
142 | * @param array $link Single link array extracted from LinkDB. | ||
143 | * @param string $pageaddr Index URL. | ||
144 | * | ||
145 | * @return array Link array with feed attributes. | ||
146 | */ | ||
147 | protected function buildItem($link, $pageaddr) | ||
148 | { | ||
149 | $link['guid'] = $pageaddr . '?' . $link['shorturl']; | ||
150 | // Prepend the root URL for notes | ||
151 | if (is_note($link['url'])) { | ||
152 | $link['url'] = $pageaddr . $link['url']; | ||
153 | } | ||
154 | if ($this->usePermalinks === true) { | ||
155 | $permalink = '<a href="' . $link['url'] . '" title="' . t('Direct link') . '">' . t('Direct link') . '</a>'; | ||
156 | } else { | ||
157 | $permalink = '<a href="' . $link['guid'] . '" title="' . t('Permalink') . '">' . t('Permalink') . '</a>'; | ||
158 | } | ||
159 | $link['description'] = format_description($link['description'], $pageaddr); | ||
160 | $link['description'] .= PHP_EOL . '<br>— ' . $permalink; | ||
161 | |||
162 | $pubDate = $link['created']; | ||
163 | $link['pub_iso_date'] = $this->getIsoDate($pubDate); | ||
164 | |||
165 | // atom:entry elements MUST contain exactly one atom:updated element. | ||
166 | if (!empty($link['updated'])) { | ||
167 | $upDate = $link['updated']; | ||
168 | $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM); | ||
169 | } else { | ||
170 | $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM); | ||
171 | } | ||
172 | |||
173 | // Save the more recent item. | ||
174 | if (empty($this->latestDate) || $this->latestDate < $pubDate) { | ||
175 | $this->latestDate = $pubDate; | ||
176 | } | ||
177 | if (!empty($upDate) && $this->latestDate < $upDate) { | ||
178 | $this->latestDate = $upDate; | ||
179 | } | ||
180 | |||
181 | $taglist = array_filter(explode(' ', $link['tags']), 'strlen'); | ||
182 | uasort($taglist, 'strcasecmp'); | ||
183 | $link['taglist'] = $taglist; | ||
184 | |||
185 | return $link; | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * Set this to true to use permalinks instead of direct links. | ||
190 | * | 137 | * |
191 | * @param boolean $usePermalinks true to force permalinks. | 138 | * @param boolean $usePermalinks true to force permalinks. |
192 | */ | 139 | */ |
@@ -216,21 +163,63 @@ class FeedBuilder | |||
216 | } | 163 | } |
217 | 164 | ||
218 | /** | 165 | /** |
166 | * Build a feed item (one per shaare). | ||
167 | * | ||
168 | * @param string $feedType Type of feed (RSS/ATOM). | ||
169 | * @param Bookmark $link Single link array extracted from LinkDB. | ||
170 | * @param string $pageaddr Index URL. | ||
171 | * | ||
172 | * @return array Link array with feed attributes. | ||
173 | */ | ||
174 | protected function buildItem(string $feedType, $link, $pageaddr) | ||
175 | { | ||
176 | $data = $this->formatter->format($link); | ||
177 | $data['guid'] = rtrim($pageaddr, '/') . '/shaare/' . $data['shorturl']; | ||
178 | if ($this->usePermalinks === true) { | ||
179 | $permalink = '<a href="'. $data['url'] .'" title="'. t('Direct link') .'">'. t('Direct link') .'</a>'; | ||
180 | } else { | ||
181 | $permalink = '<a href="'. $data['guid'] .'" title="'. t('Permalink') .'">'. t('Permalink') .'</a>'; | ||
182 | } | ||
183 | $data['description'] .= PHP_EOL . PHP_EOL . '<br>— ' . $permalink; | ||
184 | |||
185 | $data['pub_iso_date'] = $this->getIsoDate($feedType, $data['created']); | ||
186 | |||
187 | // atom:entry elements MUST contain exactly one atom:updated element. | ||
188 | if (!empty($link->getUpdated())) { | ||
189 | $data['up_iso_date'] = $this->getIsoDate($feedType, $data['updated'], DateTime::ATOM); | ||
190 | } else { | ||
191 | $data['up_iso_date'] = $this->getIsoDate($feedType, $data['created'], DateTime::ATOM); | ||
192 | } | ||
193 | |||
194 | // Save the more recent item. | ||
195 | if (empty($this->latestDate) || $this->latestDate < $data['created']) { | ||
196 | $this->latestDate = $data['created']; | ||
197 | } | ||
198 | if (!empty($data['updated']) && $this->latestDate < $data['updated']) { | ||
199 | $this->latestDate = $data['updated']; | ||
200 | } | ||
201 | |||
202 | return $data; | ||
203 | } | ||
204 | |||
205 | /** | ||
219 | * Get the language according to the feed type, based on the locale: | 206 | * Get the language according to the feed type, based on the locale: |
220 | * | 207 | * |
221 | * - RSS format: en-us (default: 'en-en'). | 208 | * - RSS format: en-us (default: 'en-en'). |
222 | * - ATOM format: fr (default: 'en'). | 209 | * - ATOM format: fr (default: 'en'). |
223 | * | 210 | * |
211 | * @param string $feedType Type of feed (RSS/ATOM). | ||
212 | * | ||
224 | * @return string The language. | 213 | * @return string The language. |
225 | */ | 214 | */ |
226 | public function getTypeLanguage() | 215 | protected function getTypeLanguage(string $feedType) |
227 | { | 216 | { |
228 | // Use the locale do define the language, if available. | 217 | // Use the locale do define the language, if available. |
229 | if (!empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) { | 218 | if (!empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) { |
230 | $length = ($this->feedType === self::$FEED_RSS) ? 5 : 2; | 219 | $length = ($feedType === self::$FEED_RSS) ? 5 : 2; |
231 | return str_replace('_', '-', substr($this->locale, 0, $length)); | 220 | return str_replace('_', '-', substr($this->locale, 0, $length)); |
232 | } | 221 | } |
233 | return ($this->feedType === self::$FEED_RSS) ? 'en-en' : 'en'; | 222 | return ($feedType === self::$FEED_RSS) ? 'en-en' : 'en'; |
234 | } | 223 | } |
235 | 224 | ||
236 | /** | 225 | /** |
@@ -238,32 +227,35 @@ class FeedBuilder | |||
238 | * | 227 | * |
239 | * Return an empty string if invalid DateTime is passed. | 228 | * Return an empty string if invalid DateTime is passed. |
240 | * | 229 | * |
230 | * @param string $feedType Type of feed (RSS/ATOM). | ||
231 | * | ||
241 | * @return string Formatted date. | 232 | * @return string Formatted date. |
242 | */ | 233 | */ |
243 | protected function getLatestDateFormatted() | 234 | protected function getLatestDateFormatted(string $feedType) |
244 | { | 235 | { |
245 | if (empty($this->latestDate) || !$this->latestDate instanceof DateTime) { | 236 | if (empty($this->latestDate) || !$this->latestDate instanceof DateTime) { |
246 | return ''; | 237 | return ''; |
247 | } | 238 | } |
248 | 239 | ||
249 | $type = ($this->feedType == self::$FEED_RSS) ? DateTime::RSS : DateTime::ATOM; | 240 | $type = ($feedType == self::$FEED_RSS) ? DateTime::RSS : DateTime::ATOM; |
250 | return $this->latestDate->format($type); | 241 | return $this->latestDate->format($type); |
251 | } | 242 | } |
252 | 243 | ||
253 | /** | 244 | /** |
254 | * Get ISO date from DateTime according to feed type. | 245 | * Get ISO date from DateTime according to feed type. |
255 | * | 246 | * |
247 | * @param string $feedType Type of feed (RSS/ATOM). | ||
256 | * @param DateTime $date Date to format. | 248 | * @param DateTime $date Date to format. |
257 | * @param string|bool $format Force format. | 249 | * @param string|bool $format Force format. |
258 | * | 250 | * |
259 | * @return string Formatted date. | 251 | * @return string Formatted date. |
260 | */ | 252 | */ |
261 | protected function getIsoDate(DateTime $date, $format = false) | 253 | protected function getIsoDate(string $feedType, DateTime $date, $format = false) |
262 | { | 254 | { |
263 | if ($format !== false) { | 255 | if ($format !== false) { |
264 | return $date->format($format); | 256 | return $date->format($format); |
265 | } | 257 | } |
266 | if ($this->feedType == self::$FEED_RSS) { | 258 | if ($feedType == self::$FEED_RSS) { |
267 | return $date->format(DateTime::RSS); | 259 | return $date->format(DateTime::RSS); |
268 | } | 260 | } |
269 | return $date->format(DateTime::ATOM); | 261 | return $date->format(DateTime::ATOM); |
@@ -273,23 +265,24 @@ class FeedBuilder | |||
273 | * Returns the number of link to display according to 'nb' user input parameter. | 265 | * Returns the number of link to display according to 'nb' user input parameter. |
274 | * | 266 | * |
275 | * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS. | 267 | * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS. |
276 | * If 'nb' is set to 'all', display all filtered links (max parameter). | 268 | * If 'nb' is set to 'all', display all filtered bookmarks (max parameter). |
277 | * | 269 | * |
278 | * @param int $max maximum number of links to display. | 270 | * @param int $max maximum number of bookmarks to display. |
271 | * @param array $userInput $_GET. | ||
279 | * | 272 | * |
280 | * @return int number of links to display. | 273 | * @return int number of bookmarks to display. |
281 | */ | 274 | */ |
282 | public function getNbLinks($max) | 275 | protected function getNbLinks($max, ?array $userInput) |
283 | { | 276 | { |
284 | if (empty($this->userInput['nb'])) { | 277 | if (empty($userInput['nb'])) { |
285 | return self::$DEFAULT_NB_LINKS; | 278 | return self::$DEFAULT_NB_LINKS; |
286 | } | 279 | } |
287 | 280 | ||
288 | if ($this->userInput['nb'] == 'all') { | 281 | if ($userInput['nb'] == 'all') { |
289 | return $max; | 282 | return $max; |
290 | } | 283 | } |
291 | 284 | ||
292 | $intNb = intval($this->userInput['nb']); | 285 | $intNb = intval($userInput['nb']); |
293 | if (!is_int($intNb) || $intNb == 0) { | 286 | if (!is_int($intNb) || $intNb == 0) { |
294 | return self::$DEFAULT_NB_LINKS; | 287 | return self::$DEFAULT_NB_LINKS; |
295 | } | 288 | } |
diff --git a/application/formatter/BookmarkDefaultFormatter.php b/application/formatter/BookmarkDefaultFormatter.php new file mode 100644 index 00000000..9d4a0fa0 --- /dev/null +++ b/application/formatter/BookmarkDefaultFormatter.php | |||
@@ -0,0 +1,87 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | /** | ||
6 | * Class BookmarkDefaultFormatter | ||
7 | * | ||
8 | * Default bookmark formatter. | ||
9 | * Escape values for HTML display and automatically add link to URL and hashtags. | ||
10 | * | ||
11 | * @package Shaarli\Formatter | ||
12 | */ | ||
13 | class BookmarkDefaultFormatter extends BookmarkFormatter | ||
14 | { | ||
15 | /** | ||
16 | * @inheritdoc | ||
17 | */ | ||
18 | public function formatTitle($bookmark) | ||
19 | { | ||
20 | return escape($bookmark->getTitle()); | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * @inheritdoc | ||
25 | */ | ||
26 | public function formatDescription($bookmark) | ||
27 | { | ||
28 | $indexUrl = ! empty($this->contextData['index_url']) ? $this->contextData['index_url'] : ''; | ||
29 | return format_description(escape($bookmark->getDescription()), $indexUrl); | ||
30 | } | ||
31 | |||
32 | /** | ||
33 | * @inheritdoc | ||
34 | */ | ||
35 | protected function formatTagList($bookmark) | ||
36 | { | ||
37 | return escape(parent::formatTagList($bookmark)); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * @inheritdoc | ||
42 | */ | ||
43 | public function formatTagString($bookmark) | ||
44 | { | ||
45 | return implode(' ', $this->formatTagList($bookmark)); | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * @inheritdoc | ||
50 | */ | ||
51 | public function formatUrl($bookmark) | ||
52 | { | ||
53 | if ($bookmark->isNote() && isset($this->contextData['index_url'])) { | ||
54 | return rtrim($this->contextData['index_url'], '/') . '/' . escape(ltrim($bookmark->getUrl(), '/')); | ||
55 | } | ||
56 | |||
57 | return escape($bookmark->getUrl()); | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * @inheritdoc | ||
62 | */ | ||
63 | protected function formatRealUrl($bookmark) | ||
64 | { | ||
65 | if ($bookmark->isNote()) { | ||
66 | if (isset($this->contextData['index_url'])) { | ||
67 | $prefix = rtrim($this->contextData['index_url'], '/') . '/'; | ||
68 | } | ||
69 | |||
70 | if (isset($this->contextData['base_path'])) { | ||
71 | $prefix = rtrim($this->contextData['base_path'], '/') . '/'; | ||
72 | } | ||
73 | |||
74 | return escape($prefix ?? '') . escape(ltrim($bookmark->getUrl(), '/')); | ||
75 | } | ||
76 | |||
77 | return escape($bookmark->getUrl()); | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * @inheritdoc | ||
82 | */ | ||
83 | protected function formatThumbnail($bookmark) | ||
84 | { | ||
85 | return escape($bookmark->getThumbnail()); | ||
86 | } | ||
87 | } | ||
diff --git a/application/formatter/BookmarkFormatter.php b/application/formatter/BookmarkFormatter.php new file mode 100644 index 00000000..0042dafe --- /dev/null +++ b/application/formatter/BookmarkFormatter.php | |||
@@ -0,0 +1,313 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use DateTime; | ||
6 | use Shaarli\Bookmark\Bookmark; | ||
7 | use Shaarli\Config\ConfigManager; | ||
8 | |||
9 | /** | ||
10 | * Class BookmarkFormatter | ||
11 | * | ||
12 | * Abstract class processing all bookmark attributes through methods designed to be overridden. | ||
13 | * | ||
14 | * @package Shaarli\Formatter | ||
15 | */ | ||
16 | abstract class BookmarkFormatter | ||
17 | { | ||
18 | /** | ||
19 | * @var ConfigManager | ||
20 | */ | ||
21 | protected $conf; | ||
22 | |||
23 | /** @var bool */ | ||
24 | protected $isLoggedIn; | ||
25 | |||
26 | /** | ||
27 | * @var array Additional parameters than can be used for specific formatting | ||
28 | * e.g. index_url for Feed formatting | ||
29 | */ | ||
30 | protected $contextData = []; | ||
31 | |||
32 | /** | ||
33 | * LinkDefaultFormatter constructor. | ||
34 | * @param ConfigManager $conf | ||
35 | */ | ||
36 | public function __construct(ConfigManager $conf, bool $isLoggedIn) | ||
37 | { | ||
38 | $this->conf = $conf; | ||
39 | $this->isLoggedIn = $isLoggedIn; | ||
40 | } | ||
41 | |||
42 | /** | ||
43 | * Convert a Bookmark into an array usable by templates and plugins. | ||
44 | * | ||
45 | * All Bookmark attributes are formatted through a format method | ||
46 | * that can be overridden in a formatter extending this class. | ||
47 | * | ||
48 | * @param Bookmark $bookmark instance | ||
49 | * | ||
50 | * @return array formatted representation of a Bookmark | ||
51 | */ | ||
52 | public function format($bookmark) | ||
53 | { | ||
54 | $out['id'] = $this->formatId($bookmark); | ||
55 | $out['shorturl'] = $this->formatShortUrl($bookmark); | ||
56 | $out['url'] = $this->formatUrl($bookmark); | ||
57 | $out['real_url'] = $this->formatRealUrl($bookmark); | ||
58 | $out['title'] = $this->formatTitle($bookmark); | ||
59 | $out['description'] = $this->formatDescription($bookmark); | ||
60 | $out['thumbnail'] = $this->formatThumbnail($bookmark); | ||
61 | $out['urlencoded_taglist'] = $this->formatUrlEncodedTagList($bookmark); | ||
62 | $out['taglist'] = $this->formatTagList($bookmark); | ||
63 | $out['urlencoded_tags'] = $this->formatUrlEncodedTagString($bookmark); | ||
64 | $out['tags'] = $this->formatTagString($bookmark); | ||
65 | $out['sticky'] = $bookmark->isSticky(); | ||
66 | $out['private'] = $bookmark->isPrivate(); | ||
67 | $out['class'] = $this->formatClass($bookmark); | ||
68 | $out['created'] = $this->formatCreated($bookmark); | ||
69 | $out['updated'] = $this->formatUpdated($bookmark); | ||
70 | $out['timestamp'] = $this->formatCreatedTimestamp($bookmark); | ||
71 | $out['updated_timestamp'] = $this->formatUpdatedTimestamp($bookmark); | ||
72 | return $out; | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Add additional data available to formatters. | ||
77 | * This is used for example to add `index_url` in description's links. | ||
78 | * | ||
79 | * @param string $key Context data key | ||
80 | * @param string $value Context data value | ||
81 | */ | ||
82 | public function addContextData($key, $value) | ||
83 | { | ||
84 | $this->contextData[$key] = $value; | ||
85 | |||
86 | return $this; | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Format ID | ||
91 | * | ||
92 | * @param Bookmark $bookmark instance | ||
93 | * | ||
94 | * @return int formatted ID | ||
95 | */ | ||
96 | protected function formatId($bookmark) | ||
97 | { | ||
98 | return $bookmark->getId(); | ||
99 | } | ||
100 | |||
101 | /** | ||
102 | * Format ShortUrl | ||
103 | * | ||
104 | * @param Bookmark $bookmark instance | ||
105 | * | ||
106 | * @return string formatted ShortUrl | ||
107 | */ | ||
108 | protected function formatShortUrl($bookmark) | ||
109 | { | ||
110 | return $bookmark->getShortUrl(); | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Format Url | ||
115 | * | ||
116 | * @param Bookmark $bookmark instance | ||
117 | * | ||
118 | * @return string formatted Url | ||
119 | */ | ||
120 | protected function formatUrl($bookmark) | ||
121 | { | ||
122 | return $bookmark->getUrl(); | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * Format RealUrl | ||
127 | * Legacy: identical to Url | ||
128 | * | ||
129 | * @param Bookmark $bookmark instance | ||
130 | * | ||
131 | * @return string formatted RealUrl | ||
132 | */ | ||
133 | protected function formatRealUrl($bookmark) | ||
134 | { | ||
135 | return $this->formatUrl($bookmark); | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Format Title | ||
140 | * | ||
141 | * @param Bookmark $bookmark instance | ||
142 | * | ||
143 | * @return string formatted Title | ||
144 | */ | ||
145 | protected function formatTitle($bookmark) | ||
146 | { | ||
147 | return $bookmark->getTitle(); | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Format Description | ||
152 | * | ||
153 | * @param Bookmark $bookmark instance | ||
154 | * | ||
155 | * @return string formatted Description | ||
156 | */ | ||
157 | protected function formatDescription($bookmark) | ||
158 | { | ||
159 | return $bookmark->getDescription(); | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * Format Thumbnail | ||
164 | * | ||
165 | * @param Bookmark $bookmark instance | ||
166 | * | ||
167 | * @return string formatted Thumbnail | ||
168 | */ | ||
169 | protected function formatThumbnail($bookmark) | ||
170 | { | ||
171 | return $bookmark->getThumbnail(); | ||
172 | } | ||
173 | |||
174 | /** | ||
175 | * Format Tags | ||
176 | * | ||
177 | * @param Bookmark $bookmark instance | ||
178 | * | ||
179 | * @return array formatted Tags | ||
180 | */ | ||
181 | protected function formatTagList($bookmark) | ||
182 | { | ||
183 | return $this->filterTagList($bookmark->getTags()); | ||
184 | } | ||
185 | |||
186 | /** | ||
187 | * Format Url Encoded Tags | ||
188 | * | ||
189 | * @param Bookmark $bookmark instance | ||
190 | * | ||
191 | * @return array formatted Tags | ||
192 | */ | ||
193 | protected function formatUrlEncodedTagList($bookmark) | ||
194 | { | ||
195 | return array_map('urlencode', $this->filterTagList($bookmark->getTags())); | ||
196 | } | ||
197 | |||
198 | /** | ||
199 | * Format TagString | ||
200 | * | ||
201 | * @param Bookmark $bookmark instance | ||
202 | * | ||
203 | * @return string formatted TagString | ||
204 | */ | ||
205 | protected function formatTagString($bookmark) | ||
206 | { | ||
207 | return implode(' ', $this->formatTagList($bookmark)); | ||
208 | } | ||
209 | |||
210 | /** | ||
211 | * Format TagString | ||
212 | * | ||
213 | * @param Bookmark $bookmark instance | ||
214 | * | ||
215 | * @return string formatted TagString | ||
216 | */ | ||
217 | protected function formatUrlEncodedTagString($bookmark) | ||
218 | { | ||
219 | return implode(' ', $this->formatUrlEncodedTagList($bookmark)); | ||
220 | } | ||
221 | |||
222 | /** | ||
223 | * Format Class | ||
224 | * Used to add specific CSS class for a link | ||
225 | * | ||
226 | * @param Bookmark $bookmark instance | ||
227 | * | ||
228 | * @return string formatted Class | ||
229 | */ | ||
230 | protected function formatClass($bookmark) | ||
231 | { | ||
232 | return $bookmark->isPrivate() ? 'private' : ''; | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * Format Created | ||
237 | * | ||
238 | * @param Bookmark $bookmark instance | ||
239 | * | ||
240 | * @return DateTime instance | ||
241 | */ | ||
242 | protected function formatCreated(Bookmark $bookmark) | ||
243 | { | ||
244 | return $bookmark->getCreated(); | ||
245 | } | ||
246 | |||
247 | /** | ||
248 | * Format Updated | ||
249 | * | ||
250 | * @param Bookmark $bookmark instance | ||
251 | * | ||
252 | * @return DateTime instance | ||
253 | */ | ||
254 | protected function formatUpdated(Bookmark $bookmark) | ||
255 | { | ||
256 | return $bookmark->getUpdated(); | ||
257 | } | ||
258 | |||
259 | /** | ||
260 | * Format CreatedTimestamp | ||
261 | * | ||
262 | * @param Bookmark $bookmark instance | ||
263 | * | ||
264 | * @return int formatted CreatedTimestamp | ||
265 | */ | ||
266 | protected function formatCreatedTimestamp(Bookmark $bookmark) | ||
267 | { | ||
268 | if (! empty($bookmark->getCreated())) { | ||
269 | return $bookmark->getCreated()->getTimestamp(); | ||
270 | } | ||
271 | return 0; | ||
272 | } | ||
273 | |||
274 | /** | ||
275 | * Format UpdatedTimestamp | ||
276 | * | ||
277 | * @param Bookmark $bookmark instance | ||
278 | * | ||
279 | * @return int formatted UpdatedTimestamp | ||
280 | */ | ||
281 | protected function formatUpdatedTimestamp(Bookmark $bookmark) | ||
282 | { | ||
283 | if (! empty($bookmark->getUpdated())) { | ||
284 | return $bookmark->getUpdated()->getTimestamp(); | ||
285 | } | ||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | /** | ||
290 | * Format tag list, e.g. remove private tags if the user is not logged in. | ||
291 | * | ||
292 | * @param array $tags | ||
293 | * | ||
294 | * @return array | ||
295 | */ | ||
296 | protected function filterTagList(array $tags): array | ||
297 | { | ||
298 | if ($this->isLoggedIn === true) { | ||
299 | return $tags; | ||
300 | } | ||
301 | |||
302 | $out = []; | ||
303 | foreach ($tags as $tag) { | ||
304 | if (strpos($tag, '.') === 0) { | ||
305 | continue; | ||
306 | } | ||
307 | |||
308 | $out[] = $tag; | ||
309 | } | ||
310 | |||
311 | return $out; | ||
312 | } | ||
313 | } | ||
diff --git a/application/formatter/BookmarkMarkdownFormatter.php b/application/formatter/BookmarkMarkdownFormatter.php new file mode 100644 index 00000000..5d244d4c --- /dev/null +++ b/application/formatter/BookmarkMarkdownFormatter.php | |||
@@ -0,0 +1,206 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use Shaarli\Config\ConfigManager; | ||
6 | |||
7 | /** | ||
8 | * Class BookmarkMarkdownFormatter | ||
9 | * | ||
10 | * Format bookmark description into Markdown format. | ||
11 | * | ||
12 | * @package Shaarli\Formatter | ||
13 | */ | ||
14 | class BookmarkMarkdownFormatter extends BookmarkDefaultFormatter | ||
15 | { | ||
16 | /** | ||
17 | * When this tag is present in a bookmark, its description should not be processed with Markdown | ||
18 | */ | ||
19 | const NO_MD_TAG = 'nomarkdown'; | ||
20 | |||
21 | /** @var \Parsedown instance */ | ||
22 | protected $parsedown; | ||
23 | |||
24 | /** @var bool used to escape HTML in Markdown or not. | ||
25 | * It MUST be set to true for shared instance as HTML content can | ||
26 | * introduce XSS vulnerabilities. | ||
27 | */ | ||
28 | protected $escape; | ||
29 | |||
30 | /** | ||
31 | * @var array List of allowed protocols for links inside bookmark's description. | ||
32 | */ | ||
33 | protected $allowedProtocols; | ||
34 | |||
35 | /** | ||
36 | * LinkMarkdownFormatter constructor. | ||
37 | * | ||
38 | * @param ConfigManager $conf instance | ||
39 | * @param bool $isLoggedIn | ||
40 | */ | ||
41 | public function __construct(ConfigManager $conf, bool $isLoggedIn) | ||
42 | { | ||
43 | parent::__construct($conf, $isLoggedIn); | ||
44 | |||
45 | $this->parsedown = new \Parsedown(); | ||
46 | $this->escape = $conf->get('security.markdown_escape', true); | ||
47 | $this->allowedProtocols = $conf->get('security.allowed_protocols', []); | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * @inheritdoc | ||
52 | */ | ||
53 | public function formatDescription($bookmark) | ||
54 | { | ||
55 | if (in_array(self::NO_MD_TAG, $bookmark->getTags())) { | ||
56 | return parent::formatDescription($bookmark); | ||
57 | } | ||
58 | |||
59 | $processedDescription = $bookmark->getDescription(); | ||
60 | $processedDescription = $this->filterProtocols($processedDescription); | ||
61 | $processedDescription = $this->formatHashTags($processedDescription); | ||
62 | $processedDescription = $this->reverseEscapedHtml($processedDescription); | ||
63 | $processedDescription = $this->parsedown | ||
64 | ->setMarkupEscaped($this->escape) | ||
65 | ->setBreaksEnabled(true) | ||
66 | ->text($processedDescription); | ||
67 | $processedDescription = $this->sanitizeHtml($processedDescription); | ||
68 | |||
69 | if (!empty($processedDescription)) { | ||
70 | $processedDescription = '<div class="markdown">'. $processedDescription . '</div>'; | ||
71 | } | ||
72 | |||
73 | return $processedDescription; | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * Remove the NO markdown tag if it is present | ||
78 | * | ||
79 | * @inheritdoc | ||
80 | */ | ||
81 | protected function formatTagList($bookmark) | ||
82 | { | ||
83 | $out = parent::formatTagList($bookmark); | ||
84 | if ($this->isLoggedIn === false && ($pos = array_search(self::NO_MD_TAG, $out)) !== false) { | ||
85 | unset($out[$pos]); | ||
86 | return array_values($out); | ||
87 | } | ||
88 | return $out; | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * Replace not whitelisted protocols with http:// in given description. | ||
93 | * Also adds `index_url` to relative links if it's specified | ||
94 | * | ||
95 | * @param string $description input description text. | ||
96 | * | ||
97 | * @return string $description without malicious link. | ||
98 | */ | ||
99 | protected function filterProtocols($description) | ||
100 | { | ||
101 | $allowedProtocols = $this->allowedProtocols; | ||
102 | $indexUrl = ! empty($this->contextData['index_url']) ? $this->contextData['index_url'] : ''; | ||
103 | |||
104 | return preg_replace_callback( | ||
105 | '#]\((.*?)\)#is', | ||
106 | function ($match) use ($allowedProtocols, $indexUrl) { | ||
107 | $link = startsWith($match[1], '?') || startsWith($match[1], '/') ? $indexUrl : ''; | ||
108 | $link .= whitelist_protocols($match[1], $allowedProtocols); | ||
109 | return ']('. $link.')'; | ||
110 | }, | ||
111 | $description | ||
112 | ); | ||
113 | } | ||
114 | |||
115 | /** | ||
116 | * Replace hashtag in Markdown links format | ||
117 | * E.g. `#hashtag` becomes `[#hashtag](./add-tag/hashtag)` | ||
118 | * It includes the index URL if specified. | ||
119 | * | ||
120 | * @param string $description | ||
121 | * | ||
122 | * @return string | ||
123 | */ | ||
124 | protected function formatHashTags($description) | ||
125 | { | ||
126 | $indexUrl = ! empty($this->contextData['index_url']) ? $this->contextData['index_url'] : ''; | ||
127 | |||
128 | /* | ||
129 | * To support unicode: http://stackoverflow.com/a/35498078/1484919 | ||
130 | * \p{Pc} - to match underscore | ||
131 | * \p{N} - numeric character in any script | ||
132 | * \p{L} - letter from any language | ||
133 | * \p{Mn} - any non marking space (accents, umlauts, etc) | ||
134 | */ | ||
135 | $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; | ||
136 | $replacement = '$1[#$2]('. $indexUrl .'./add-tag/$2)'; | ||
137 | |||
138 | $descriptionLines = explode(PHP_EOL, $description); | ||
139 | $descriptionOut = ''; | ||
140 | $codeBlockOn = false; | ||
141 | $lineCount = 0; | ||
142 | |||
143 | foreach ($descriptionLines as $descriptionLine) { | ||
144 | // Detect line of code: starting with 4 spaces, | ||
145 | // except lists which can start with +/*/- or `2.` after spaces. | ||
146 | $codeLineOn = preg_match('/^ +(?=[^\+\*\-])(?=(?!\d\.).)/', $descriptionLine) > 0; | ||
147 | // Detect and toggle block of code | ||
148 | if (!$codeBlockOn) { | ||
149 | $codeBlockOn = preg_match('/^```/', $descriptionLine) > 0; | ||
150 | } elseif (preg_match('/^```/', $descriptionLine) > 0) { | ||
151 | $codeBlockOn = false; | ||
152 | } | ||
153 | |||
154 | if (!$codeBlockOn && !$codeLineOn) { | ||
155 | $descriptionLine = preg_replace($regex, $replacement, $descriptionLine); | ||
156 | } | ||
157 | |||
158 | $descriptionOut .= $descriptionLine; | ||
159 | if ($lineCount++ < count($descriptionLines) - 1) { | ||
160 | $descriptionOut .= PHP_EOL; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | return $descriptionOut; | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * Remove dangerous HTML tags (tags, iframe, etc.). | ||
169 | * Doesn't affect <code> content (already escaped by Parsedown). | ||
170 | * | ||
171 | * @param string $description input description text. | ||
172 | * | ||
173 | * @return string given string escaped. | ||
174 | */ | ||
175 | protected function sanitizeHtml($description) | ||
176 | { | ||
177 | $escapeTags = array( | ||
178 | 'script', | ||
179 | 'style', | ||
180 | 'link', | ||
181 | 'iframe', | ||
182 | 'frameset', | ||
183 | 'frame', | ||
184 | ); | ||
185 | foreach ($escapeTags as $tag) { | ||
186 | $description = preg_replace_callback( | ||
187 | '#<\s*'. $tag .'[^>]*>(.*</\s*'. $tag .'[^>]*>)?#is', | ||
188 | function ($match) { | ||
189 | return escape($match[0]); | ||
190 | }, | ||
191 | $description | ||
192 | ); | ||
193 | } | ||
194 | $description = preg_replace( | ||
195 | '#(<[^>]+\s)on[a-z]*="?[^ "]*"?#is', | ||
196 | '$1', | ||
197 | $description | ||
198 | ); | ||
199 | return $description; | ||
200 | } | ||
201 | |||
202 | protected function reverseEscapedHtml($description) | ||
203 | { | ||
204 | return unescape($description); | ||
205 | } | ||
206 | } | ||
diff --git a/application/formatter/BookmarkRawFormatter.php b/application/formatter/BookmarkRawFormatter.php new file mode 100644 index 00000000..bc372273 --- /dev/null +++ b/application/formatter/BookmarkRawFormatter.php | |||
@@ -0,0 +1,13 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | /** | ||
6 | * Class BookmarkRawFormatter | ||
7 | * | ||
8 | * Used to retrieve bookmarks as array with raw values. | ||
9 | * Warning: Do NOT use this for HTML content as it can introduce XSS vulnerabilities. | ||
10 | * | ||
11 | * @package Shaarli\Formatter | ||
12 | */ | ||
13 | class BookmarkRawFormatter extends BookmarkFormatter {} | ||
diff --git a/application/formatter/FormatterFactory.php b/application/formatter/FormatterFactory.php new file mode 100644 index 00000000..a029579f --- /dev/null +++ b/application/formatter/FormatterFactory.php | |||
@@ -0,0 +1,51 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use Shaarli\Config\ConfigManager; | ||
6 | |||
7 | /** | ||
8 | * Class FormatterFactory | ||
9 | * | ||
10 | * Helper class used to instantiate the proper BookmarkFormatter. | ||
11 | * | ||
12 | * @package Shaarli\Formatter | ||
13 | */ | ||
14 | class FormatterFactory | ||
15 | { | ||
16 | /** @var ConfigManager instance */ | ||
17 | protected $conf; | ||
18 | |||
19 | /** @var bool */ | ||
20 | protected $isLoggedIn; | ||
21 | |||
22 | /** | ||
23 | * FormatterFactory constructor. | ||
24 | * | ||
25 | * @param ConfigManager $conf | ||
26 | * @param bool $isLoggedIn | ||
27 | */ | ||
28 | public function __construct(ConfigManager $conf, bool $isLoggedIn) | ||
29 | { | ||
30 | $this->conf = $conf; | ||
31 | $this->isLoggedIn = $isLoggedIn; | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Instanciate a BookmarkFormatter depending on the configuration or provided formatter type. | ||
36 | * | ||
37 | * @param string|null $type force a specific type regardless of the configuration | ||
38 | * | ||
39 | * @return BookmarkFormatter instance. | ||
40 | */ | ||
41 | public function getFormatter(string $type = null): BookmarkFormatter | ||
42 | { | ||
43 | $type = $type ? $type : $this->conf->get('formatter', 'default'); | ||
44 | $className = '\\Shaarli\\Formatter\\Bookmark'. ucfirst($type) .'Formatter'; | ||
45 | if (!class_exists($className)) { | ||
46 | $className = '\\Shaarli\\Formatter\\BookmarkDefaultFormatter'; | ||
47 | } | ||
48 | |||
49 | return new $className($this->conf, $this->isLoggedIn); | ||
50 | } | ||
51 | } | ||
diff --git a/application/front/ShaarliAdminMiddleware.php b/application/front/ShaarliAdminMiddleware.php new file mode 100644 index 00000000..35ce4a3b --- /dev/null +++ b/application/front/ShaarliAdminMiddleware.php | |||
@@ -0,0 +1,27 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Front; | ||
4 | |||
5 | use Slim\Http\Request; | ||
6 | use Slim\Http\Response; | ||
7 | |||
8 | /** | ||
9 | * Middleware used for controller requiring to be authenticated. | ||
10 | * It extends ShaarliMiddleware, and just make sure that the user is authenticated. | ||
11 | * Otherwise, it redirects to the login page. | ||
12 | */ | ||
13 | class ShaarliAdminMiddleware extends ShaarliMiddleware | ||
14 | { | ||
15 | public function __invoke(Request $request, Response $response, callable $next): Response | ||
16 | { | ||
17 | $this->initBasePath($request); | ||
18 | |||
19 | if (true !== $this->container->loginManager->isLoggedIn()) { | ||
20 | $returnUrl = urlencode($this->container->environment['REQUEST_URI']); | ||
21 | |||
22 | return $response->withRedirect($this->container->basePath . '/login?returnurl=' . $returnUrl); | ||
23 | } | ||
24 | |||
25 | return parent::__invoke($request, $response, $next); | ||
26 | } | ||
27 | } | ||
diff --git a/application/front/ShaarliMiddleware.php b/application/front/ShaarliMiddleware.php new file mode 100644 index 00000000..d1aa1399 --- /dev/null +++ b/application/front/ShaarliMiddleware.php | |||
@@ -0,0 +1,114 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Front; | ||
4 | |||
5 | use Shaarli\Container\ShaarliContainer; | ||
6 | use Shaarli\Front\Exception\UnauthorizedException; | ||
7 | use Slim\Http\Request; | ||
8 | use Slim\Http\Response; | ||
9 | |||
10 | /** | ||
11 | * Class ShaarliMiddleware | ||
12 | * | ||
13 | * This will be called before accessing any Shaarli controller. | ||
14 | */ | ||
15 | class ShaarliMiddleware | ||
16 | { | ||
17 | /** @var ShaarliContainer contains all Shaarli DI */ | ||
18 | protected $container; | ||
19 | |||
20 | public function __construct(ShaarliContainer $container) | ||
21 | { | ||
22 | $this->container = $container; | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Middleware execution: | ||
27 | * - run updates | ||
28 | * - if not logged in open shaarli, redirect to login | ||
29 | * - execute the controller | ||
30 | * - return the response | ||
31 | * | ||
32 | * In case of error, the error template will be displayed with the exception message. | ||
33 | * | ||
34 | * @param Request $request Slim request | ||
35 | * @param Response $response Slim response | ||
36 | * @param callable $next Next action | ||
37 | * | ||
38 | * @return Response response. | ||
39 | */ | ||
40 | public function __invoke(Request $request, Response $response, callable $next): Response | ||
41 | { | ||
42 | $this->initBasePath($request); | ||
43 | |||
44 | try { | ||
45 | if (!is_file($this->container->conf->getConfigFileExt()) | ||
46 | && !in_array($next->getName(), ['displayInstall', 'saveInstall'], true) | ||
47 | ) { | ||
48 | return $response->withRedirect($this->container->basePath . '/install'); | ||
49 | } | ||
50 | |||
51 | $this->runUpdates(); | ||
52 | $this->checkOpenShaarli($request, $response, $next); | ||
53 | |||
54 | return $next($request, $response); | ||
55 | } catch (UnauthorizedException $e) { | ||
56 | $returnUrl = urlencode($this->container->environment['REQUEST_URI']); | ||
57 | |||
58 | return $response->withRedirect($this->container->basePath . '/login?returnurl=' . $returnUrl); | ||
59 | } | ||
60 | // Other exceptions are handled by ErrorController | ||
61 | } | ||
62 | |||
63 | /** | ||
64 | * Run the updater for every requests processed while logged in. | ||
65 | */ | ||
66 | protected function runUpdates(): void | ||
67 | { | ||
68 | if ($this->container->loginManager->isLoggedIn() !== true) { | ||
69 | return; | ||
70 | } | ||
71 | |||
72 | $this->container->updater->setBasePath($this->container->basePath); | ||
73 | $newUpdates = $this->container->updater->update(); | ||
74 | if (!empty($newUpdates)) { | ||
75 | $this->container->updater->writeUpdates( | ||
76 | $this->container->conf->get('resource.updates'), | ||
77 | $this->container->updater->getDoneUpdates() | ||
78 | ); | ||
79 | |||
80 | $this->container->pageCacheManager->invalidateCaches(); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Access is denied to most pages with `hide_public_links` + `force_login` settings. | ||
86 | */ | ||
87 | protected function checkOpenShaarli(Request $request, Response $response, callable $next): bool | ||
88 | { | ||
89 | if (// if the user isn't logged in | ||
90 | !$this->container->loginManager->isLoggedIn() | ||
91 | // and Shaarli doesn't have public content... | ||
92 | && $this->container->conf->get('privacy.hide_public_links') | ||
93 | // and is configured to enforce the login | ||
94 | && $this->container->conf->get('privacy.force_login') | ||
95 | // and the current page isn't already the login page | ||
96 | // and the user is not requesting a feed (which would lead to a different content-type as expected) | ||
97 | && !in_array($next->getName(), ['login', 'processLogin', 'atom', 'rss'], true) | ||
98 | ) { | ||
99 | throw new UnauthorizedException(); | ||
100 | } | ||
101 | |||
102 | return true; | ||
103 | } | ||
104 | |||
105 | /** | ||
106 | * Initialize the URL base path if it hasn't been defined yet. | ||
107 | */ | ||
108 | protected function initBasePath(Request $request): void | ||
109 | { | ||
110 | if (null === $this->container->basePath) { | ||
111 | $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/'); | ||
112 | } | ||
113 | } | ||
114 | } | ||
diff --git a/application/front/controller/admin/ConfigureController.php b/application/front/controller/admin/ConfigureController.php new file mode 100644 index 00000000..e675fcca --- /dev/null +++ b/application/front/controller/admin/ConfigureController.php | |||
@@ -0,0 +1,126 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Languages; | ||
8 | use Shaarli\Render\TemplatePage; | ||
9 | use Shaarli\Render\ThemeUtils; | ||
10 | use Shaarli\Thumbnailer; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | use Throwable; | ||
14 | |||
15 | /** | ||
16 | * Class ConfigureController | ||
17 | * | ||
18 | * Slim controller used to handle Shaarli configuration page (display + save new config). | ||
19 | */ | ||
20 | class ConfigureController extends ShaarliAdminController | ||
21 | { | ||
22 | /** | ||
23 | * GET /admin/configure - Displays the configuration page | ||
24 | */ | ||
25 | public function index(Request $request, Response $response): Response | ||
26 | { | ||
27 | $this->assignView('title', $this->container->conf->get('general.title', 'Shaarli')); | ||
28 | $this->assignView('theme', $this->container->conf->get('resource.theme')); | ||
29 | $this->assignView( | ||
30 | 'theme_available', | ||
31 | ThemeUtils::getThemes($this->container->conf->get('resource.raintpl_tpl')) | ||
32 | ); | ||
33 | $this->assignView('formatter_available', ['default', 'markdown']); | ||
34 | list($continents, $cities) = generateTimeZoneData( | ||
35 | timezone_identifiers_list(), | ||
36 | $this->container->conf->get('general.timezone') | ||
37 | ); | ||
38 | $this->assignView('continents', $continents); | ||
39 | $this->assignView('cities', $cities); | ||
40 | $this->assignView('retrieve_description', $this->container->conf->get('general.retrieve_description', false)); | ||
41 | $this->assignView('private_links_default', $this->container->conf->get('privacy.default_private_links', false)); | ||
42 | $this->assignView( | ||
43 | 'session_protection_disabled', | ||
44 | $this->container->conf->get('security.session_protection_disabled', false) | ||
45 | ); | ||
46 | $this->assignView('enable_rss_permalinks', $this->container->conf->get('feed.rss_permalinks', false)); | ||
47 | $this->assignView('enable_update_check', $this->container->conf->get('updates.check_updates', true)); | ||
48 | $this->assignView('hide_public_links', $this->container->conf->get('privacy.hide_public_links', false)); | ||
49 | $this->assignView('api_enabled', $this->container->conf->get('api.enabled', true)); | ||
50 | $this->assignView('api_secret', $this->container->conf->get('api.secret')); | ||
51 | $this->assignView('languages', Languages::getAvailableLanguages()); | ||
52 | $this->assignView('gd_enabled', extension_loaded('gd')); | ||
53 | $this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)); | ||
54 | $this->assignView('pagetitle', t('Configure') .' - '. $this->container->conf->get('general.title', 'Shaarli')); | ||
55 | |||
56 | return $response->write($this->render(TemplatePage::CONFIGURE)); | ||
57 | } | ||
58 | |||
59 | /** | ||
60 | * POST /admin/configure - Update Shaarli's configuration | ||
61 | */ | ||
62 | public function save(Request $request, Response $response): Response | ||
63 | { | ||
64 | $this->checkToken($request); | ||
65 | |||
66 | $continent = $request->getParam('continent'); | ||
67 | $city = $request->getParam('city'); | ||
68 | $tz = 'UTC'; | ||
69 | if (null !== $continent && null !== $city && isTimeZoneValid($continent, $city)) { | ||
70 | $tz = $continent . '/' . $city; | ||
71 | } | ||
72 | |||
73 | $this->container->conf->set('general.timezone', $tz); | ||
74 | $this->container->conf->set('general.title', escape($request->getParam('title'))); | ||
75 | $this->container->conf->set('general.header_link', escape($request->getParam('titleLink'))); | ||
76 | $this->container->conf->set('general.retrieve_description', !empty($request->getParam('retrieveDescription'))); | ||
77 | $this->container->conf->set('resource.theme', escape($request->getParam('theme'))); | ||
78 | $this->container->conf->set( | ||
79 | 'security.session_protection_disabled', | ||
80 | !empty($request->getParam('disablesessionprotection')) | ||
81 | ); | ||
82 | $this->container->conf->set( | ||
83 | 'privacy.default_private_links', | ||
84 | !empty($request->getParam('privateLinkByDefault')) | ||
85 | ); | ||
86 | $this->container->conf->set('feed.rss_permalinks', !empty($request->getParam('enableRssPermalinks'))); | ||
87 | $this->container->conf->set('updates.check_updates', !empty($request->getParam('updateCheck'))); | ||
88 | $this->container->conf->set('privacy.hide_public_links', !empty($request->getParam('hidePublicLinks'))); | ||
89 | $this->container->conf->set('api.enabled', !empty($request->getParam('enableApi'))); | ||
90 | $this->container->conf->set('api.secret', escape($request->getParam('apiSecret'))); | ||
91 | $this->container->conf->set('formatter', escape($request->getParam('formatter'))); | ||
92 | |||
93 | if (!empty($request->getParam('language'))) { | ||
94 | $this->container->conf->set('translation.language', escape($request->getParam('language'))); | ||
95 | } | ||
96 | |||
97 | $thumbnailsMode = extension_loaded('gd') ? $request->getParam('enableThumbnails') : Thumbnailer::MODE_NONE; | ||
98 | if ($thumbnailsMode !== Thumbnailer::MODE_NONE | ||
99 | && $thumbnailsMode !== $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) | ||
100 | ) { | ||
101 | $this->saveWarningMessage( | ||
102 | t('You have enabled or changed thumbnails mode.') . | ||
103 | '<a href="'. $this->container->basePath .'/admin/thumbnails">' . t('Please synchronize them.') .'</a>' | ||
104 | ); | ||
105 | } | ||
106 | $this->container->conf->set('thumbnails.mode', $thumbnailsMode); | ||
107 | |||
108 | try { | ||
109 | $this->container->conf->write($this->container->loginManager->isLoggedIn()); | ||
110 | $this->container->history->updateSettings(); | ||
111 | $this->container->pageCacheManager->invalidateCaches(); | ||
112 | } catch (Throwable $e) { | ||
113 | $this->assignView('message', t('Error while writing config file after configuration update.')); | ||
114 | |||
115 | if ($this->container->conf->get('dev.debug', false)) { | ||
116 | $this->assignView('stacktrace', $e->getMessage() . PHP_EOL . $e->getTraceAsString()); | ||
117 | } | ||
118 | |||
119 | return $response->write($this->render('error')); | ||
120 | } | ||
121 | |||
122 | $this->saveSuccessMessage(t('Configuration was saved.')); | ||
123 | |||
124 | return $this->redirect($response, '/admin/configure'); | ||
125 | } | ||
126 | } | ||
diff --git a/application/front/controller/admin/ExportController.php b/application/front/controller/admin/ExportController.php new file mode 100644 index 00000000..2be957fa --- /dev/null +++ b/application/front/controller/admin/ExportController.php | |||
@@ -0,0 +1,80 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use DateTime; | ||
8 | use Shaarli\Bookmark\Bookmark; | ||
9 | use Shaarli\Render\TemplatePage; | ||
10 | use Slim\Http\Request; | ||
11 | use Slim\Http\Response; | ||
12 | |||
13 | /** | ||
14 | * Class ExportController | ||
15 | * | ||
16 | * Slim controller used to display Shaarli data export page, | ||
17 | * and process the bookmarks export as a Netscape Bookmarks file. | ||
18 | */ | ||
19 | class ExportController extends ShaarliAdminController | ||
20 | { | ||
21 | /** | ||
22 | * GET /admin/export - Display export page | ||
23 | */ | ||
24 | public function index(Request $request, Response $response): Response | ||
25 | { | ||
26 | $this->assignView('pagetitle', t('Export') .' - '. $this->container->conf->get('general.title', 'Shaarli')); | ||
27 | |||
28 | return $response->write($this->render(TemplatePage::EXPORT)); | ||
29 | } | ||
30 | |||
31 | /** | ||
32 | * POST /admin/export - Process export, and serve download file named | ||
33 | * bookmarks_(all|private|public)_datetime.html | ||
34 | */ | ||
35 | public function export(Request $request, Response $response): Response | ||
36 | { | ||
37 | $this->checkToken($request); | ||
38 | |||
39 | $selection = $request->getParam('selection'); | ||
40 | |||
41 | if (empty($selection)) { | ||
42 | $this->saveErrorMessage(t('Please select an export mode.')); | ||
43 | |||
44 | return $this->redirect($response, '/admin/export'); | ||
45 | } | ||
46 | |||
47 | $prependNoteUrl = filter_var($request->getParam('prepend_note_url') ?? false, FILTER_VALIDATE_BOOLEAN); | ||
48 | |||
49 | try { | ||
50 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
51 | |||
52 | $this->assignView( | ||
53 | 'links', | ||
54 | $this->container->netscapeBookmarkUtils->filterAndFormat( | ||
55 | $formatter, | ||
56 | $selection, | ||
57 | $prependNoteUrl, | ||
58 | index_url($this->container->environment) | ||
59 | ) | ||
60 | ); | ||
61 | } catch (\Exception $exc) { | ||
62 | $this->saveErrorMessage($exc->getMessage()); | ||
63 | |||
64 | return $this->redirect($response, '/admin/export'); | ||
65 | } | ||
66 | |||
67 | $now = new DateTime(); | ||
68 | $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8'); | ||
69 | $response = $response->withHeader( | ||
70 | 'Content-disposition', | ||
71 | 'attachment; filename=bookmarks_'.$selection.'_'.$now->format(Bookmark::LINK_DATE_FORMAT).'.html' | ||
72 | ); | ||
73 | |||
74 | $this->assignView('date', $now->format(DateTime::RFC822)); | ||
75 | $this->assignView('eol', PHP_EOL); | ||
76 | $this->assignView('selection', $selection); | ||
77 | |||
78 | return $response->write($this->render(TemplatePage::NETSCAPE_EXPORT_BOOKMARKS)); | ||
79 | } | ||
80 | } | ||
diff --git a/application/front/controller/admin/ImportController.php b/application/front/controller/admin/ImportController.php new file mode 100644 index 00000000..758d5ef9 --- /dev/null +++ b/application/front/controller/admin/ImportController.php | |||
@@ -0,0 +1,82 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Psr\Http\Message\UploadedFileInterface; | ||
8 | use Shaarli\Render\TemplatePage; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Class ImportController | ||
14 | * | ||
15 | * Slim controller used to display Shaarli data import page, | ||
16 | * and import bookmarks from Netscape Bookmarks file. | ||
17 | */ | ||
18 | class ImportController extends ShaarliAdminController | ||
19 | { | ||
20 | /** | ||
21 | * GET /admin/import - Display import page | ||
22 | */ | ||
23 | public function index(Request $request, Response $response): Response | ||
24 | { | ||
25 | $this->assignView( | ||
26 | 'maxfilesize', | ||
27 | get_max_upload_size( | ||
28 | ini_get('post_max_size'), | ||
29 | ini_get('upload_max_filesize'), | ||
30 | false | ||
31 | ) | ||
32 | ); | ||
33 | $this->assignView( | ||
34 | 'maxfilesizeHuman', | ||
35 | get_max_upload_size( | ||
36 | ini_get('post_max_size'), | ||
37 | ini_get('upload_max_filesize'), | ||
38 | true | ||
39 | ) | ||
40 | ); | ||
41 | $this->assignView('pagetitle', t('Import') .' - '. $this->container->conf->get('general.title', 'Shaarli')); | ||
42 | |||
43 | return $response->write($this->render(TemplatePage::IMPORT)); | ||
44 | } | ||
45 | |||
46 | /** | ||
47 | * POST /admin/import - Process import file provided and create bookmarks | ||
48 | */ | ||
49 | public function import(Request $request, Response $response): Response | ||
50 | { | ||
51 | $this->checkToken($request); | ||
52 | |||
53 | $file = ($request->getUploadedFiles() ?? [])['filetoupload'] ?? null; | ||
54 | if (!$file instanceof UploadedFileInterface) { | ||
55 | $this->saveErrorMessage(t('No import file provided.')); | ||
56 | |||
57 | return $this->redirect($response, '/admin/import'); | ||
58 | } | ||
59 | |||
60 | |||
61 | // Import bookmarks from an uploaded file | ||
62 | if (0 === $file->getSize()) { | ||
63 | // The file is too big or some form field may be missing. | ||
64 | $msg = sprintf( | ||
65 | t( | ||
66 | 'The file you are trying to upload is probably bigger than what this webserver can accept' | ||
67 | .' (%s). Please upload in smaller chunks.' | ||
68 | ), | ||
69 | get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')) | ||
70 | ); | ||
71 | $this->saveErrorMessage($msg); | ||
72 | |||
73 | return $this->redirect($response, '/admin/import'); | ||
74 | } | ||
75 | |||
76 | $status = $this->container->netscapeBookmarkUtils->import($request->getParams(), $file); | ||
77 | |||
78 | $this->saveSuccessMessage($status); | ||
79 | |||
80 | return $this->redirect($response, '/admin/import'); | ||
81 | } | ||
82 | } | ||
diff --git a/application/front/controller/admin/LogoutController.php b/application/front/controller/admin/LogoutController.php new file mode 100644 index 00000000..28165129 --- /dev/null +++ b/application/front/controller/admin/LogoutController.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Security\CookieManager; | ||
8 | use Shaarli\Security\LoginManager; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Class LogoutController | ||
14 | * | ||
15 | * Slim controller used to logout the user. | ||
16 | * It invalidates page cache and terminate the user session. Then it redirects to the homepage. | ||
17 | */ | ||
18 | class LogoutController extends ShaarliAdminController | ||
19 | { | ||
20 | public function index(Request $request, Response $response): Response | ||
21 | { | ||
22 | $this->container->pageCacheManager->invalidateCaches(); | ||
23 | $this->container->sessionManager->logout(); | ||
24 | $this->container->cookieManager->setCookieParameter( | ||
25 | CookieManager::STAY_SIGNED_IN, | ||
26 | 'false', | ||
27 | 0, | ||
28 | $this->container->basePath . '/' | ||
29 | ); | ||
30 | |||
31 | return $this->redirect($response, '/'); | ||
32 | } | ||
33 | } | ||
diff --git a/application/front/controller/admin/ManageShaareController.php b/application/front/controller/admin/ManageShaareController.php new file mode 100644 index 00000000..bb083486 --- /dev/null +++ b/application/front/controller/admin/ManageShaareController.php | |||
@@ -0,0 +1,371 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Formatter\BookmarkMarkdownFormatter; | ||
10 | use Shaarli\Render\TemplatePage; | ||
11 | use Shaarli\Thumbnailer; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | /** | ||
16 | * Class PostBookmarkController | ||
17 | * | ||
18 | * Slim controller used to handle Shaarli create or edit bookmarks. | ||
19 | */ | ||
20 | class ManageShaareController extends ShaarliAdminController | ||
21 | { | ||
22 | /** | ||
23 | * GET /admin/add-shaare - Displays the form used to create a new bookmark from an URL | ||
24 | */ | ||
25 | public function addShaare(Request $request, Response $response): Response | ||
26 | { | ||
27 | $this->assignView( | ||
28 | 'pagetitle', | ||
29 | t('Shaare a new link') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
30 | ); | ||
31 | |||
32 | return $response->write($this->render(TemplatePage::ADDLINK)); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * GET /admin/shaare - Displays the bookmark form for creation. | ||
37 | * Note that if the URL is found in existing bookmarks, then it will be in edit mode. | ||
38 | */ | ||
39 | public function displayCreateForm(Request $request, Response $response): Response | ||
40 | { | ||
41 | $url = cleanup_url($request->getParam('post')); | ||
42 | |||
43 | $linkIsNew = false; | ||
44 | // Check if URL is not already in database (in this case, we will edit the existing link) | ||
45 | $bookmark = $this->container->bookmarkService->findByUrl($url); | ||
46 | if (null === $bookmark) { | ||
47 | $linkIsNew = true; | ||
48 | // Get shaare data if it was provided in URL (e.g.: by the bookmarklet). | ||
49 | $title = $request->getParam('title'); | ||
50 | $description = $request->getParam('description'); | ||
51 | $tags = $request->getParam('tags'); | ||
52 | $private = filter_var($request->getParam('private'), FILTER_VALIDATE_BOOLEAN); | ||
53 | |||
54 | // If this is an HTTP(S) link, we try go get the page to extract | ||
55 | // the title (otherwise we will to straight to the edit form.) | ||
56 | if (empty($title) && strpos(get_url_scheme($url) ?: '', 'http') !== false) { | ||
57 | $retrieveDescription = $this->container->conf->get('general.retrieve_description'); | ||
58 | // Short timeout to keep the application responsive | ||
59 | // The callback will fill $charset and $title with data from the downloaded page. | ||
60 | $this->container->httpAccess->getHttpResponse( | ||
61 | $url, | ||
62 | $this->container->conf->get('general.download_timeout', 30), | ||
63 | $this->container->conf->get('general.download_max_size', 4194304), | ||
64 | $this->container->httpAccess->getCurlDownloadCallback( | ||
65 | $charset, | ||
66 | $title, | ||
67 | $description, | ||
68 | $tags, | ||
69 | $retrieveDescription | ||
70 | ) | ||
71 | ); | ||
72 | if (! empty($title) && strtolower($charset) !== 'utf-8' && mb_check_encoding($charset)) { | ||
73 | $title = mb_convert_encoding($title, 'utf-8', $charset); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | if (empty($url) && empty($title)) { | ||
78 | $title = $this->container->conf->get('general.default_note_title', t('Note: ')); | ||
79 | } | ||
80 | |||
81 | $link = [ | ||
82 | 'title' => $title, | ||
83 | 'url' => $url ?? '', | ||
84 | 'description' => $description ?? '', | ||
85 | 'tags' => $tags ?? '', | ||
86 | 'private' => $private, | ||
87 | ]; | ||
88 | } else { | ||
89 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
90 | $link = $formatter->format($bookmark); | ||
91 | } | ||
92 | |||
93 | return $this->displayForm($link, $linkIsNew, $request, $response); | ||
94 | } | ||
95 | |||
96 | /** | ||
97 | * GET /admin/shaare/{id} - Displays the bookmark form in edition mode. | ||
98 | */ | ||
99 | public function displayEditForm(Request $request, Response $response, array $args): Response | ||
100 | { | ||
101 | $id = $args['id'] ?? ''; | ||
102 | try { | ||
103 | if (false === ctype_digit($id)) { | ||
104 | throw new BookmarkNotFoundException(); | ||
105 | } | ||
106 | $bookmark = $this->container->bookmarkService->get((int) $id); // Read database | ||
107 | } catch (BookmarkNotFoundException $e) { | ||
108 | $this->saveErrorMessage(sprintf( | ||
109 | t('Bookmark with identifier %s could not be found.'), | ||
110 | $id | ||
111 | )); | ||
112 | |||
113 | return $this->redirect($response, '/'); | ||
114 | } | ||
115 | |||
116 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
117 | $link = $formatter->format($bookmark); | ||
118 | |||
119 | return $this->displayForm($link, false, $request, $response); | ||
120 | } | ||
121 | |||
122 | /** | ||
123 | * POST /admin/shaare | ||
124 | */ | ||
125 | public function save(Request $request, Response $response): Response | ||
126 | { | ||
127 | $this->checkToken($request); | ||
128 | |||
129 | // lf_id should only be present if the link exists. | ||
130 | $id = $request->getParam('lf_id') !== null ? intval(escape($request->getParam('lf_id'))) : null; | ||
131 | if (null !== $id && true === $this->container->bookmarkService->exists($id)) { | ||
132 | // Edit | ||
133 | $bookmark = $this->container->bookmarkService->get($id); | ||
134 | } else { | ||
135 | // New link | ||
136 | $bookmark = new Bookmark(); | ||
137 | } | ||
138 | |||
139 | $bookmark->setTitle($request->getParam('lf_title')); | ||
140 | $bookmark->setDescription($request->getParam('lf_description')); | ||
141 | $bookmark->setUrl($request->getParam('lf_url'), $this->container->conf->get('security.allowed_protocols', [])); | ||
142 | $bookmark->setPrivate(filter_var($request->getParam('lf_private'), FILTER_VALIDATE_BOOLEAN)); | ||
143 | $bookmark->setTagsString($request->getParam('lf_tags')); | ||
144 | |||
145 | if ($this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE | ||
146 | && false === $bookmark->isNote() | ||
147 | ) { | ||
148 | $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); | ||
149 | } | ||
150 | $this->container->bookmarkService->addOrSet($bookmark, false); | ||
151 | |||
152 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
153 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
154 | $data = $formatter->format($bookmark); | ||
155 | $this->executePageHooks('save_link', $data); | ||
156 | |||
157 | $bookmark->fromArray($data); | ||
158 | $this->container->bookmarkService->set($bookmark); | ||
159 | |||
160 | // If we are called from the bookmarklet, we must close the popup: | ||
161 | if ($request->getParam('source') === 'bookmarklet') { | ||
162 | return $response->write('<script>self.close();</script>'); | ||
163 | } | ||
164 | |||
165 | if (!empty($request->getParam('returnurl'))) { | ||
166 | $this->container->environment['HTTP_REFERER'] = escape($request->getParam('returnurl')); | ||
167 | } | ||
168 | |||
169 | return $this->redirectFromReferer( | ||
170 | $request, | ||
171 | $response, | ||
172 | ['/admin/add-shaare', '/admin/shaare'], ['addlink', 'post', 'edit_link'], | ||
173 | $bookmark->getShortUrl() | ||
174 | ); | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * GET /admin/shaare/delete - Delete one or multiple bookmarks (depending on `id` query parameter). | ||
179 | */ | ||
180 | public function deleteBookmark(Request $request, Response $response): Response | ||
181 | { | ||
182 | $this->checkToken($request); | ||
183 | |||
184 | $ids = escape(trim($request->getParam('id') ?? '')); | ||
185 | if (empty($ids) || strpos($ids, ' ') !== false) { | ||
186 | // multiple, space-separated ids provided | ||
187 | $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit')); | ||
188 | } else { | ||
189 | $ids = [$ids]; | ||
190 | } | ||
191 | |||
192 | // assert at least one id is given | ||
193 | if (0 === count($ids)) { | ||
194 | $this->saveErrorMessage(t('Invalid bookmark ID provided.')); | ||
195 | |||
196 | return $this->redirectFromReferer($request, $response, [], ['delete-shaare']); | ||
197 | } | ||
198 | |||
199 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
200 | $count = 0; | ||
201 | foreach ($ids as $id) { | ||
202 | try { | ||
203 | $bookmark = $this->container->bookmarkService->get((int) $id); | ||
204 | } catch (BookmarkNotFoundException $e) { | ||
205 | $this->saveErrorMessage(sprintf( | ||
206 | t('Bookmark with identifier %s could not be found.'), | ||
207 | $id | ||
208 | )); | ||
209 | |||
210 | continue; | ||
211 | } | ||
212 | |||
213 | $data = $formatter->format($bookmark); | ||
214 | $this->executePageHooks('delete_link', $data); | ||
215 | $this->container->bookmarkService->remove($bookmark, false); | ||
216 | ++ $count; | ||
217 | } | ||
218 | |||
219 | if ($count > 0) { | ||
220 | $this->container->bookmarkService->save(); | ||
221 | } | ||
222 | |||
223 | // If we are called from the bookmarklet, we must close the popup: | ||
224 | if ($request->getParam('source') === 'bookmarklet') { | ||
225 | return $response->write('<script>self.close();</script>'); | ||
226 | } | ||
227 | |||
228 | // Don't redirect to where we were previously because the datastore has changed. | ||
229 | return $this->redirect($response, '/'); | ||
230 | } | ||
231 | |||
232 | /** | ||
233 | * GET /admin/shaare/visibility | ||
234 | * | ||
235 | * Change visibility (public/private) of one or multiple bookmarks (depending on `id` query parameter). | ||
236 | */ | ||
237 | public function changeVisibility(Request $request, Response $response): Response | ||
238 | { | ||
239 | $this->checkToken($request); | ||
240 | |||
241 | $ids = trim(escape($request->getParam('id') ?? '')); | ||
242 | if (empty($ids) || strpos($ids, ' ') !== false) { | ||
243 | // multiple, space-separated ids provided | ||
244 | $ids = array_values(array_filter(preg_split('/\s+/', $ids), 'ctype_digit')); | ||
245 | } else { | ||
246 | // only a single id provided | ||
247 | $ids = [$ids]; | ||
248 | } | ||
249 | |||
250 | // assert at least one id is given | ||
251 | if (0 === count($ids)) { | ||
252 | $this->saveErrorMessage(t('Invalid bookmark ID provided.')); | ||
253 | |||
254 | return $this->redirectFromReferer($request, $response, [], ['change_visibility']); | ||
255 | } | ||
256 | |||
257 | // assert that the visibility is valid | ||
258 | $visibility = $request->getParam('newVisibility'); | ||
259 | if (null === $visibility || false === in_array($visibility, ['public', 'private'], true)) { | ||
260 | $this->saveErrorMessage(t('Invalid visibility provided.')); | ||
261 | |||
262 | return $this->redirectFromReferer($request, $response, [], ['change_visibility']); | ||
263 | } else { | ||
264 | $isPrivate = $visibility === 'private'; | ||
265 | } | ||
266 | |||
267 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
268 | $count = 0; | ||
269 | |||
270 | foreach ($ids as $id) { | ||
271 | try { | ||
272 | $bookmark = $this->container->bookmarkService->get((int) $id); | ||
273 | } catch (BookmarkNotFoundException $e) { | ||
274 | $this->saveErrorMessage(sprintf( | ||
275 | t('Bookmark with identifier %s could not be found.'), | ||
276 | $id | ||
277 | )); | ||
278 | |||
279 | continue; | ||
280 | } | ||
281 | |||
282 | $bookmark->setPrivate($isPrivate); | ||
283 | |||
284 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
285 | $data = $formatter->format($bookmark); | ||
286 | $this->executePageHooks('save_link', $data); | ||
287 | $bookmark->fromArray($data); | ||
288 | |||
289 | $this->container->bookmarkService->set($bookmark, false); | ||
290 | ++$count; | ||
291 | } | ||
292 | |||
293 | if ($count > 0) { | ||
294 | $this->container->bookmarkService->save(); | ||
295 | } | ||
296 | |||
297 | return $this->redirectFromReferer($request, $response, ['/visibility'], ['change_visibility']); | ||
298 | } | ||
299 | |||
300 | /** | ||
301 | * GET /admin/shaare/{id}/pin - Pin or unpin a bookmark. | ||
302 | */ | ||
303 | public function pinBookmark(Request $request, Response $response, array $args): Response | ||
304 | { | ||
305 | $this->checkToken($request); | ||
306 | |||
307 | $id = $args['id'] ?? ''; | ||
308 | try { | ||
309 | if (false === ctype_digit($id)) { | ||
310 | throw new BookmarkNotFoundException(); | ||
311 | } | ||
312 | $bookmark = $this->container->bookmarkService->get((int) $id); // Read database | ||
313 | } catch (BookmarkNotFoundException $e) { | ||
314 | $this->saveErrorMessage(sprintf( | ||
315 | t('Bookmark with identifier %s could not be found.'), | ||
316 | $id | ||
317 | )); | ||
318 | |||
319 | return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']); | ||
320 | } | ||
321 | |||
322 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
323 | |||
324 | $bookmark->setSticky(!$bookmark->isSticky()); | ||
325 | |||
326 | // To preserve backward compatibility with 3rd parties, plugins still use arrays | ||
327 | $data = $formatter->format($bookmark); | ||
328 | $this->executePageHooks('save_link', $data); | ||
329 | $bookmark->fromArray($data); | ||
330 | |||
331 | $this->container->bookmarkService->set($bookmark); | ||
332 | |||
333 | return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']); | ||
334 | } | ||
335 | |||
336 | /** | ||
337 | * Helper function used to display the shaare form whether it's a new or existing bookmark. | ||
338 | * | ||
339 | * @param array $link data used in template, either from parameters or from the data store | ||
340 | */ | ||
341 | protected function displayForm(array $link, bool $isNew, Request $request, Response $response): Response | ||
342 | { | ||
343 | $tags = $this->container->bookmarkService->bookmarksCountPerTag(); | ||
344 | if ($this->container->conf->get('formatter') === 'markdown') { | ||
345 | $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1; | ||
346 | } | ||
347 | |||
348 | $data = escape([ | ||
349 | 'link' => $link, | ||
350 | 'link_is_new' => $isNew, | ||
351 | 'http_referer' => $this->container->environment['HTTP_REFERER'] ?? '', | ||
352 | 'source' => $request->getParam('source') ?? '', | ||
353 | 'tags' => $tags, | ||
354 | 'default_private_links' => $this->container->conf->get('privacy.default_private_links', false), | ||
355 | ]); | ||
356 | |||
357 | $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK); | ||
358 | |||
359 | foreach ($data as $key => $value) { | ||
360 | $this->assignView($key, $value); | ||
361 | } | ||
362 | |||
363 | $editLabel = false === $isNew ? t('Edit') .' ' : ''; | ||
364 | $this->assignView( | ||
365 | 'pagetitle', | ||
366 | $editLabel . t('Shaare') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
367 | ); | ||
368 | |||
369 | return $response->write($this->render(TemplatePage::EDIT_LINK)); | ||
370 | } | ||
371 | } | ||
diff --git a/application/front/controller/admin/ManageTagController.php b/application/front/controller/admin/ManageTagController.php new file mode 100644 index 00000000..2065c3e2 --- /dev/null +++ b/application/front/controller/admin/ManageTagController.php | |||
@@ -0,0 +1,88 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkFilter; | ||
8 | use Shaarli\Render\TemplatePage; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Class ManageTagController | ||
14 | * | ||
15 | * Slim controller used to handle Shaarli manage tags page (rename and delete tags). | ||
16 | */ | ||
17 | class ManageTagController extends ShaarliAdminController | ||
18 | { | ||
19 | /** | ||
20 | * GET /admin/tags - Displays the manage tags page | ||
21 | */ | ||
22 | public function index(Request $request, Response $response): Response | ||
23 | { | ||
24 | $fromTag = $request->getParam('fromtag') ?? ''; | ||
25 | |||
26 | $this->assignView('fromtag', escape($fromTag)); | ||
27 | $this->assignView( | ||
28 | 'pagetitle', | ||
29 | t('Manage tags') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
30 | ); | ||
31 | |||
32 | return $response->write($this->render(TemplatePage::CHANGE_TAG)); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * POST /admin/tags - Update or delete provided tag | ||
37 | */ | ||
38 | public function save(Request $request, Response $response): Response | ||
39 | { | ||
40 | $this->checkToken($request); | ||
41 | |||
42 | $isDelete = null !== $request->getParam('deletetag') && null === $request->getParam('renametag'); | ||
43 | |||
44 | $fromTag = trim($request->getParam('fromtag') ?? ''); | ||
45 | $toTag = trim($request->getParam('totag') ?? ''); | ||
46 | |||
47 | if (0 === strlen($fromTag) || false === $isDelete && 0 === strlen($toTag)) { | ||
48 | $this->saveWarningMessage(t('Invalid tags provided.')); | ||
49 | |||
50 | return $this->redirect($response, '/admin/tags'); | ||
51 | } | ||
52 | |||
53 | // TODO: move this to bookmark service | ||
54 | $count = 0; | ||
55 | $bookmarks = $this->container->bookmarkService->search(['searchtags' => $fromTag], BookmarkFilter::$ALL, true); | ||
56 | foreach ($bookmarks as $bookmark) { | ||
57 | if (false === $isDelete) { | ||
58 | $bookmark->renameTag($fromTag, $toTag); | ||
59 | } else { | ||
60 | $bookmark->deleteTag($fromTag); | ||
61 | } | ||
62 | |||
63 | $this->container->bookmarkService->set($bookmark, false); | ||
64 | $this->container->history->updateLink($bookmark); | ||
65 | $count++; | ||
66 | } | ||
67 | |||
68 | $this->container->bookmarkService->save(); | ||
69 | |||
70 | if (true === $isDelete) { | ||
71 | $alert = sprintf( | ||
72 | t('The tag was removed from %d bookmark.', 'The tag was removed from %d bookmarks.', $count), | ||
73 | $count | ||
74 | ); | ||
75 | } else { | ||
76 | $alert = sprintf( | ||
77 | t('The tag was renamed in %d bookmark.', 'The tag was renamed in %d bookmarks.', $count), | ||
78 | $count | ||
79 | ); | ||
80 | } | ||
81 | |||
82 | $this->saveSuccessMessage($alert); | ||
83 | |||
84 | $redirect = true === $isDelete ? '/admin/tags' : '/?searchtags='. urlencode($toTag); | ||
85 | |||
86 | return $this->redirect($response, $redirect); | ||
87 | } | ||
88 | } | ||
diff --git a/application/front/controller/admin/PasswordController.php b/application/front/controller/admin/PasswordController.php new file mode 100644 index 00000000..5ec0d24b --- /dev/null +++ b/application/front/controller/admin/PasswordController.php | |||
@@ -0,0 +1,101 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Container\ShaarliContainer; | ||
8 | use Shaarli\Front\Exception\OpenShaarliPasswordException; | ||
9 | use Shaarli\Front\Exception\ShaarliFrontException; | ||
10 | use Shaarli\Render\TemplatePage; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | use Throwable; | ||
14 | |||
15 | /** | ||
16 | * Class PasswordController | ||
17 | * | ||
18 | * Slim controller used to handle passwords update. | ||
19 | */ | ||
20 | class PasswordController extends ShaarliAdminController | ||
21 | { | ||
22 | public function __construct(ShaarliContainer $container) | ||
23 | { | ||
24 | parent::__construct($container); | ||
25 | |||
26 | $this->assignView( | ||
27 | 'pagetitle', | ||
28 | t('Change password') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
29 | ); | ||
30 | } | ||
31 | |||
32 | /** | ||
33 | * GET /admin/password - Displays the change password template | ||
34 | */ | ||
35 | public function index(Request $request, Response $response): Response | ||
36 | { | ||
37 | return $response->write($this->render(TemplatePage::CHANGE_PASSWORD)); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * POST /admin/password - Change admin password - existing and new passwords need to be provided. | ||
42 | */ | ||
43 | public function change(Request $request, Response $response): Response | ||
44 | { | ||
45 | $this->checkToken($request); | ||
46 | |||
47 | if ($this->container->conf->get('security.open_shaarli', false)) { | ||
48 | throw new OpenShaarliPasswordException(); | ||
49 | } | ||
50 | |||
51 | $oldPassword = $request->getParam('oldpassword'); | ||
52 | $newPassword = $request->getParam('setpassword'); | ||
53 | |||
54 | if (empty($newPassword) || empty($oldPassword)) { | ||
55 | $this->saveErrorMessage(t('You must provide the current and new password to change it.')); | ||
56 | |||
57 | return $response | ||
58 | ->withStatus(400) | ||
59 | ->write($this->render(TemplatePage::CHANGE_PASSWORD)) | ||
60 | ; | ||
61 | } | ||
62 | |||
63 | // Make sure old password is correct. | ||
64 | $oldHash = sha1( | ||
65 | $oldPassword . | ||
66 | $this->container->conf->get('credentials.login') . | ||
67 | $this->container->conf->get('credentials.salt') | ||
68 | ); | ||
69 | |||
70 | if ($oldHash !== $this->container->conf->get('credentials.hash')) { | ||
71 | $this->saveErrorMessage(t('The old password is not correct.')); | ||
72 | |||
73 | return $response | ||
74 | ->withStatus(400) | ||
75 | ->write($this->render(TemplatePage::CHANGE_PASSWORD)) | ||
76 | ; | ||
77 | } | ||
78 | |||
79 | // Save new password | ||
80 | // Salt renders rainbow-tables attacks useless. | ||
81 | $this->container->conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); | ||
82 | $this->container->conf->set( | ||
83 | 'credentials.hash', | ||
84 | sha1( | ||
85 | $newPassword | ||
86 | . $this->container->conf->get('credentials.login') | ||
87 | . $this->container->conf->get('credentials.salt') | ||
88 | ) | ||
89 | ); | ||
90 | |||
91 | try { | ||
92 | $this->container->conf->write($this->container->loginManager->isLoggedIn()); | ||
93 | } catch (Throwable $e) { | ||
94 | throw new ShaarliFrontException($e->getMessage(), 500, $e); | ||
95 | } | ||
96 | |||
97 | $this->saveSuccessMessage(t('Your password has been changed')); | ||
98 | |||
99 | return $response->write($this->render(TemplatePage::CHANGE_PASSWORD)); | ||
100 | } | ||
101 | } | ||
diff --git a/application/front/controller/admin/PluginsController.php b/application/front/controller/admin/PluginsController.php new file mode 100644 index 00000000..8e059681 --- /dev/null +++ b/application/front/controller/admin/PluginsController.php | |||
@@ -0,0 +1,85 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Exception; | ||
8 | use Shaarli\Render\TemplatePage; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Class PluginsController | ||
14 | * | ||
15 | * Slim controller used to handle Shaarli plugins configuration page (display + save new config). | ||
16 | */ | ||
17 | class PluginsController extends ShaarliAdminController | ||
18 | { | ||
19 | /** | ||
20 | * GET /admin/plugins - Displays the configuration page | ||
21 | */ | ||
22 | public function index(Request $request, Response $response): Response | ||
23 | { | ||
24 | $pluginMeta = $this->container->pluginManager->getPluginsMeta(); | ||
25 | |||
26 | // Split plugins into 2 arrays: ordered enabled plugins and disabled. | ||
27 | $enabledPlugins = array_filter($pluginMeta, function ($v) { | ||
28 | return ($v['order'] ?? false) !== false; | ||
29 | }); | ||
30 | $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $this->container->conf->get('plugins', [])); | ||
31 | uasort( | ||
32 | $enabledPlugins, | ||
33 | function ($a, $b) { | ||
34 | return $a['order'] - $b['order']; | ||
35 | } | ||
36 | ); | ||
37 | $disabledPlugins = array_filter($pluginMeta, function ($v) { | ||
38 | return ($v['order'] ?? false) === false; | ||
39 | }); | ||
40 | |||
41 | $this->assignView('enabledPlugins', $enabledPlugins); | ||
42 | $this->assignView('disabledPlugins', $disabledPlugins); | ||
43 | $this->assignView( | ||
44 | 'pagetitle', | ||
45 | t('Plugin Administration') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
46 | ); | ||
47 | |||
48 | return $response->write($this->render(TemplatePage::PLUGINS_ADMIN)); | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * POST /admin/plugins - Update Shaarli's configuration | ||
53 | */ | ||
54 | public function save(Request $request, Response $response): Response | ||
55 | { | ||
56 | $this->checkToken($request); | ||
57 | |||
58 | try { | ||
59 | $parameters = $request->getParams() ?? []; | ||
60 | |||
61 | $this->executePageHooks('save_plugin_parameters', $parameters); | ||
62 | |||
63 | if (isset($parameters['parameters_form'])) { | ||
64 | unset($parameters['parameters_form']); | ||
65 | unset($parameters['token']); | ||
66 | foreach ($parameters as $param => $value) { | ||
67 | $this->container->conf->set('plugins.'. $param, escape($value)); | ||
68 | } | ||
69 | } else { | ||
70 | $this->container->conf->set('general.enabled_plugins', save_plugin_config($parameters)); | ||
71 | } | ||
72 | |||
73 | $this->container->conf->write($this->container->loginManager->isLoggedIn()); | ||
74 | $this->container->history->updateSettings(); | ||
75 | |||
76 | $this->saveSuccessMessage(t('Setting successfully saved.')); | ||
77 | } catch (Exception $e) { | ||
78 | $this->saveErrorMessage( | ||
79 | t('Error while saving plugin configuration: ') . PHP_EOL . $e->getMessage() | ||
80 | ); | ||
81 | } | ||
82 | |||
83 | return $this->redirect($response, '/admin/plugins'); | ||
84 | } | ||
85 | } | ||
diff --git a/application/front/controller/admin/SessionFilterController.php b/application/front/controller/admin/SessionFilterController.php new file mode 100644 index 00000000..d9a7a2e0 --- /dev/null +++ b/application/front/controller/admin/SessionFilterController.php | |||
@@ -0,0 +1,50 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkFilter; | ||
8 | use Shaarli\Security\SessionManager; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Class SessionFilterController | ||
14 | * | ||
15 | * Slim controller used to handle filters stored in the user session, such as visibility, etc. | ||
16 | */ | ||
17 | class SessionFilterController extends ShaarliAdminController | ||
18 | { | ||
19 | /** | ||
20 | * GET /admin/visibility: allows to display only public or only private bookmarks in linklist | ||
21 | */ | ||
22 | public function visibility(Request $request, Response $response, array $args): Response | ||
23 | { | ||
24 | if (false === $this->container->loginManager->isLoggedIn()) { | ||
25 | return $this->redirectFromReferer($request, $response, ['visibility']); | ||
26 | } | ||
27 | |||
28 | $newVisibility = $args['visibility'] ?? null; | ||
29 | if (false === in_array($newVisibility, [BookmarkFilter::$PRIVATE, BookmarkFilter::$PUBLIC], true)) { | ||
30 | $newVisibility = null; | ||
31 | } | ||
32 | |||
33 | $currentVisibility = $this->container->sessionManager->getSessionParameter(SessionManager::KEY_VISIBILITY); | ||
34 | |||
35 | // Visibility not set or not already expected value, set expected value, otherwise reset it | ||
36 | if ($newVisibility !== null && (null === $currentVisibility || $currentVisibility !== $newVisibility)) { | ||
37 | // See only public bookmarks | ||
38 | $this->container->sessionManager->setSessionParameter( | ||
39 | SessionManager::KEY_VISIBILITY, | ||
40 | $newVisibility | ||
41 | ); | ||
42 | } else { | ||
43 | $this->container->sessionManager->deleteSessionParameter(SessionManager::KEY_VISIBILITY); | ||
44 | } | ||
45 | |||
46 | return $this->redirectFromReferer($request, $response, ['visibility']); | ||
47 | } | ||
48 | |||
49 | |||
50 | } | ||
diff --git a/application/front/controller/admin/ShaarliAdminController.php b/application/front/controller/admin/ShaarliAdminController.php new file mode 100644 index 00000000..c26c9cbe --- /dev/null +++ b/application/front/controller/admin/ShaarliAdminController.php | |||
@@ -0,0 +1,71 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Front\Controller\Visitor\ShaarliVisitorController; | ||
8 | use Shaarli\Front\Exception\WrongTokenException; | ||
9 | use Shaarli\Security\SessionManager; | ||
10 | use Slim\Http\Request; | ||
11 | |||
12 | /** | ||
13 | * Class ShaarliAdminController | ||
14 | * | ||
15 | * All admin controllers (for logged in users) MUST extend this abstract class. | ||
16 | * It makes sure that the user is properly logged in, and otherwise throw an exception | ||
17 | * which will redirect to the login page. | ||
18 | * | ||
19 | * @package Shaarli\Front\Controller\Admin | ||
20 | */ | ||
21 | abstract class ShaarliAdminController extends ShaarliVisitorController | ||
22 | { | ||
23 | /** | ||
24 | * Any persistent action to the config or data store must check the XSRF token validity. | ||
25 | */ | ||
26 | protected function checkToken(Request $request): bool | ||
27 | { | ||
28 | if (!$this->container->sessionManager->checkToken($request->getParam('token'))) { | ||
29 | throw new WrongTokenException(); | ||
30 | } | ||
31 | |||
32 | return true; | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Save a SUCCESS message in user session, which will be displayed on any template page. | ||
37 | */ | ||
38 | protected function saveSuccessMessage(string $message): void | ||
39 | { | ||
40 | $this->saveMessage(SessionManager::KEY_SUCCESS_MESSAGES, $message); | ||
41 | } | ||
42 | |||
43 | /** | ||
44 | * Save a WARNING message in user session, which will be displayed on any template page. | ||
45 | */ | ||
46 | protected function saveWarningMessage(string $message): void | ||
47 | { | ||
48 | $this->saveMessage(SessionManager::KEY_WARNING_MESSAGES, $message); | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Save an ERROR message in user session, which will be displayed on any template page. | ||
53 | */ | ||
54 | protected function saveErrorMessage(string $message): void | ||
55 | { | ||
56 | $this->saveMessage(SessionManager::KEY_ERROR_MESSAGES, $message); | ||
57 | } | ||
58 | |||
59 | /** | ||
60 | * Use the sessionManager to save the provided message using the proper type. | ||
61 | * | ||
62 | * @param string $type successed/warnings/errors | ||
63 | */ | ||
64 | protected function saveMessage(string $type, string $message): void | ||
65 | { | ||
66 | $messages = $this->container->sessionManager->getSessionParameter($type) ?? []; | ||
67 | $messages[] = $message; | ||
68 | |||
69 | $this->container->sessionManager->setSessionParameter($type, $messages); | ||
70 | } | ||
71 | } | ||
diff --git a/application/front/controller/admin/ThumbnailsController.php b/application/front/controller/admin/ThumbnailsController.php new file mode 100644 index 00000000..81c87ed0 --- /dev/null +++ b/application/front/controller/admin/ThumbnailsController.php | |||
@@ -0,0 +1,65 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
8 | use Shaarli\Render\TemplatePage; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Class ToolsController | ||
14 | * | ||
15 | * Slim controller used to handle thumbnails update. | ||
16 | */ | ||
17 | class ThumbnailsController extends ShaarliAdminController | ||
18 | { | ||
19 | /** | ||
20 | * GET /admin/thumbnails - Display thumbnails update page | ||
21 | */ | ||
22 | public function index(Request $request, Response $response): Response | ||
23 | { | ||
24 | $ids = []; | ||
25 | foreach ($this->container->bookmarkService->search() as $bookmark) { | ||
26 | // A note or not HTTP(S) | ||
27 | if ($bookmark->isNote() || !startsWith(strtolower($bookmark->getUrl()), 'http')) { | ||
28 | continue; | ||
29 | } | ||
30 | |||
31 | $ids[] = $bookmark->getId(); | ||
32 | } | ||
33 | |||
34 | $this->assignView('ids', $ids); | ||
35 | $this->assignView( | ||
36 | 'pagetitle', | ||
37 | t('Thumbnails update') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
38 | ); | ||
39 | |||
40 | return $response->write($this->render(TemplatePage::THUMBNAILS)); | ||
41 | } | ||
42 | |||
43 | /** | ||
44 | * PATCH /admin/shaare/{id}/thumbnail-update - Route for AJAX calls | ||
45 | */ | ||
46 | public function ajaxUpdate(Request $request, Response $response, array $args): Response | ||
47 | { | ||
48 | $id = $args['id'] ?? null; | ||
49 | |||
50 | if (false === ctype_digit($id)) { | ||
51 | return $response->withStatus(400); | ||
52 | } | ||
53 | |||
54 | try { | ||
55 | $bookmark = $this->container->bookmarkService->get($id); | ||
56 | } catch (BookmarkNotFoundException $e) { | ||
57 | return $response->withStatus(404); | ||
58 | } | ||
59 | |||
60 | $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); | ||
61 | $this->container->bookmarkService->set($bookmark); | ||
62 | |||
63 | return $response->withJson($this->container->formatterFactory->getFormatter('raw')->format($bookmark)); | ||
64 | } | ||
65 | } | ||
diff --git a/application/front/controller/admin/TokenController.php b/application/front/controller/admin/TokenController.php new file mode 100644 index 00000000..08d68d0a --- /dev/null +++ b/application/front/controller/admin/TokenController.php | |||
@@ -0,0 +1,26 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Slim\Http\Request; | ||
8 | use Slim\Http\Response; | ||
9 | |||
10 | /** | ||
11 | * Class TokenController | ||
12 | * | ||
13 | * Endpoint used to retrieve a XSRF token. Useful for AJAX requests. | ||
14 | */ | ||
15 | class TokenController extends ShaarliAdminController | ||
16 | { | ||
17 | /** | ||
18 | * GET /admin/token | ||
19 | */ | ||
20 | public function getToken(Request $request, Response $response): Response | ||
21 | { | ||
22 | $response = $response->withHeader('Content-Type', 'text/plain'); | ||
23 | |||
24 | return $response->write($this->container->sessionManager->generateToken()); | ||
25 | } | ||
26 | } | ||
diff --git a/application/front/controller/admin/ToolsController.php b/application/front/controller/admin/ToolsController.php new file mode 100644 index 00000000..a87f20d2 --- /dev/null +++ b/application/front/controller/admin/ToolsController.php | |||
@@ -0,0 +1,35 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Render\TemplatePage; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | /** | ||
12 | * Class ToolsController | ||
13 | * | ||
14 | * Slim controller used to display the tools page. | ||
15 | */ | ||
16 | class ToolsController extends ShaarliAdminController | ||
17 | { | ||
18 | public function index(Request $request, Response $response): Response | ||
19 | { | ||
20 | $data = [ | ||
21 | 'pageabsaddr' => index_url($this->container->environment), | ||
22 | 'sslenabled' => is_https($this->container->environment), | ||
23 | ]; | ||
24 | |||
25 | $this->executePageHooks('render_tools', $data, TemplatePage::TOOLS); | ||
26 | |||
27 | foreach ($data as $key => $value) { | ||
28 | $this->assignView($key, $value); | ||
29 | } | ||
30 | |||
31 | $this->assignView('pagetitle', t('Tools') .' - '. $this->container->conf->get('general.title', 'Shaarli')); | ||
32 | |||
33 | return $response->write($this->render(TemplatePage::TOOLS)); | ||
34 | } | ||
35 | } | ||
diff --git a/application/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php new file mode 100644 index 00000000..18368751 --- /dev/null +++ b/application/front/controller/visitor/BookmarkListController.php | |||
@@ -0,0 +1,241 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Legacy\LegacyController; | ||
10 | use Shaarli\Legacy\UnknowLegacyRouteException; | ||
11 | use Shaarli\Render\TemplatePage; | ||
12 | use Shaarli\Thumbnailer; | ||
13 | use Slim\Http\Request; | ||
14 | use Slim\Http\Response; | ||
15 | |||
16 | /** | ||
17 | * Class BookmarkListController | ||
18 | * | ||
19 | * Slim controller used to render the bookmark list, the home page of Shaarli. | ||
20 | * It also displays permalinks, and process legacy routes based on GET parameters. | ||
21 | */ | ||
22 | class BookmarkListController extends ShaarliVisitorController | ||
23 | { | ||
24 | /** | ||
25 | * GET / - Displays the bookmark list, with optional filter parameters. | ||
26 | */ | ||
27 | public function index(Request $request, Response $response): Response | ||
28 | { | ||
29 | $legacyResponse = $this->processLegacyController($request, $response); | ||
30 | if (null !== $legacyResponse) { | ||
31 | return $legacyResponse; | ||
32 | } | ||
33 | |||
34 | $formatter = $this->container->formatterFactory->getFormatter(); | ||
35 | $formatter->addContextData('base_path', $this->container->basePath); | ||
36 | |||
37 | $searchTags = normalize_spaces($request->getParam('searchtags') ?? ''); | ||
38 | $searchTerm = escape(normalize_spaces($request->getParam('searchterm') ?? ''));; | ||
39 | |||
40 | // Filter bookmarks according search parameters. | ||
41 | $visibility = $this->container->sessionManager->getSessionParameter('visibility'); | ||
42 | $search = [ | ||
43 | 'searchtags' => $searchTags, | ||
44 | 'searchterm' => $searchTerm, | ||
45 | ]; | ||
46 | $linksToDisplay = $this->container->bookmarkService->search( | ||
47 | $search, | ||
48 | $visibility, | ||
49 | false, | ||
50 | !!$this->container->sessionManager->getSessionParameter('untaggedonly') | ||
51 | ) ?? []; | ||
52 | |||
53 | // ---- Handle paging. | ||
54 | $keys = []; | ||
55 | foreach ($linksToDisplay as $key => $value) { | ||
56 | $keys[] = $key; | ||
57 | } | ||
58 | |||
59 | $linksPerPage = $this->container->sessionManager->getSessionParameter('LINKS_PER_PAGE', 20) ?: 20; | ||
60 | |||
61 | // Select articles according to paging. | ||
62 | $pageCount = (int) ceil(count($keys) / $linksPerPage) ?: 1; | ||
63 | $page = (int) $request->getParam('page') ?? 1; | ||
64 | $page = $page < 1 ? 1 : $page; | ||
65 | $page = $page > $pageCount ? $pageCount : $page; | ||
66 | |||
67 | // Start index. | ||
68 | $i = ($page - 1) * $linksPerPage; | ||
69 | $end = $i + $linksPerPage; | ||
70 | |||
71 | $linkDisp = []; | ||
72 | $save = false; | ||
73 | while ($i < $end && $i < count($keys)) { | ||
74 | $save = $this->updateThumbnail($linksToDisplay[$keys[$i]], false) || $save; | ||
75 | $link = $formatter->format($linksToDisplay[$keys[$i]]); | ||
76 | |||
77 | $linkDisp[$keys[$i]] = $link; | ||
78 | $i++; | ||
79 | } | ||
80 | |||
81 | if ($save) { | ||
82 | $this->container->bookmarkService->save(); | ||
83 | } | ||
84 | |||
85 | // Compute paging navigation | ||
86 | $searchtagsUrl = $searchTags === '' ? '' : '&searchtags=' . urlencode($searchTags); | ||
87 | $searchtermUrl = $searchTerm === '' ? '' : '&searchterm=' . urlencode($searchTerm); | ||
88 | |||
89 | $previous_page_url = ''; | ||
90 | if ($i !== count($keys)) { | ||
91 | $previous_page_url = '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl; | ||
92 | } | ||
93 | $next_page_url = ''; | ||
94 | if ($page > 1) { | ||
95 | $next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl; | ||
96 | } | ||
97 | |||
98 | // Fill all template fields. | ||
99 | $data = array_merge( | ||
100 | $this->initializeTemplateVars(), | ||
101 | [ | ||
102 | 'previous_page_url' => $previous_page_url, | ||
103 | 'next_page_url' => $next_page_url, | ||
104 | 'page_current' => $page, | ||
105 | 'page_max' => $pageCount, | ||
106 | 'result_count' => count($linksToDisplay), | ||
107 | 'search_term' => escape($searchTerm), | ||
108 | 'search_tags' => escape($searchTags), | ||
109 | 'search_tags_url' => array_map('urlencode', explode(' ', $searchTags)), | ||
110 | 'visibility' => $visibility, | ||
111 | 'links' => $linkDisp, | ||
112 | ] | ||
113 | ); | ||
114 | |||
115 | if (!empty($searchTerm) || !empty($searchTags)) { | ||
116 | $data['pagetitle'] = t('Search: '); | ||
117 | $data['pagetitle'] .= ! empty($searchTerm) ? $searchTerm . ' ' : ''; | ||
118 | $bracketWrap = function ($tag) { | ||
119 | return '[' . $tag . ']'; | ||
120 | }; | ||
121 | $data['pagetitle'] .= ! empty($searchTags) | ||
122 | ? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchTags))) . ' ' | ||
123 | : ''; | ||
124 | $data['pagetitle'] .= '- '; | ||
125 | } | ||
126 | |||
127 | $data['pagetitle'] = ($data['pagetitle'] ?? '') . $this->container->conf->get('general.title', 'Shaarli'); | ||
128 | |||
129 | $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST); | ||
130 | $this->assignAllView($data); | ||
131 | |||
132 | return $response->write($this->render(TemplatePage::LINKLIST)); | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * GET /shaare/{hash} - Display a single shaare | ||
137 | */ | ||
138 | public function permalink(Request $request, Response $response, array $args): Response | ||
139 | { | ||
140 | try { | ||
141 | $bookmark = $this->container->bookmarkService->findByHash($args['hash']); | ||
142 | } catch (BookmarkNotFoundException $e) { | ||
143 | $this->assignView('error_message', $e->getMessage()); | ||
144 | |||
145 | return $response->write($this->render(TemplatePage::ERROR_404)); | ||
146 | } | ||
147 | |||
148 | $this->updateThumbnail($bookmark); | ||
149 | |||
150 | $formatter = $this->container->formatterFactory->getFormatter(); | ||
151 | $formatter->addContextData('base_path', $this->container->basePath); | ||
152 | |||
153 | $data = array_merge( | ||
154 | $this->initializeTemplateVars(), | ||
155 | [ | ||
156 | 'pagetitle' => $bookmark->getTitle() .' - '. $this->container->conf->get('general.title', 'Shaarli'), | ||
157 | 'links' => [$formatter->format($bookmark)], | ||
158 | ] | ||
159 | ); | ||
160 | |||
161 | $this->executePageHooks('render_linklist', $data, TemplatePage::LINKLIST); | ||
162 | $this->assignAllView($data); | ||
163 | |||
164 | return $response->write($this->render(TemplatePage::LINKLIST)); | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * Update the thumbnail of a single bookmark if necessary. | ||
169 | */ | ||
170 | protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool | ||
171 | { | ||
172 | // Logged in, thumbnails enabled, not a note, is HTTP | ||
173 | // and (never retrieved yet or no valid cache file) | ||
174 | if ($this->container->loginManager->isLoggedIn() | ||
175 | && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE | ||
176 | && false !== $bookmark->getThumbnail() | ||
177 | && !$bookmark->isNote() | ||
178 | && (null === $bookmark->getThumbnail() || !is_file($bookmark->getThumbnail())) | ||
179 | && startsWith(strtolower($bookmark->getUrl()), 'http') | ||
180 | ) { | ||
181 | $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); | ||
182 | $this->container->bookmarkService->set($bookmark, $writeDatastore); | ||
183 | |||
184 | return true; | ||
185 | } | ||
186 | |||
187 | return false; | ||
188 | } | ||
189 | |||
190 | /** | ||
191 | * @return string[] Default template variables without values. | ||
192 | */ | ||
193 | protected function initializeTemplateVars(): array | ||
194 | { | ||
195 | return [ | ||
196 | 'previous_page_url' => '', | ||
197 | 'next_page_url' => '', | ||
198 | 'page_max' => '', | ||
199 | 'search_tags' => '', | ||
200 | 'result_count' => '', | ||
201 | ]; | ||
202 | } | ||
203 | |||
204 | /** | ||
205 | * Process legacy routes if necessary. They used query parameters. | ||
206 | * If no legacy routes is passed, return null. | ||
207 | */ | ||
208 | protected function processLegacyController(Request $request, Response $response): ?Response | ||
209 | { | ||
210 | // Legacy smallhash filter | ||
211 | $queryString = $this->container->environment['QUERY_STRING'] ?? null; | ||
212 | if (null !== $queryString && 1 === preg_match('/^([a-zA-Z0-9-_@]{6})($|&|#)/', $queryString, $match)) { | ||
213 | return $this->redirect($response, '/shaare/' . $match[1]); | ||
214 | } | ||
215 | |||
216 | // Legacy controllers (mostly used for redirections) | ||
217 | if (null !== $request->getQueryParam('do')) { | ||
218 | $legacyController = new LegacyController($this->container); | ||
219 | |||
220 | try { | ||
221 | return $legacyController->process($request, $response, $request->getQueryParam('do')); | ||
222 | } catch (UnknowLegacyRouteException $e) { | ||
223 | // We ignore legacy 404 | ||
224 | return null; | ||
225 | } | ||
226 | } | ||
227 | |||
228 | // Legacy GET admin routes | ||
229 | $legacyGetRoutes = array_intersect( | ||
230 | LegacyController::LEGACY_GET_ROUTES, | ||
231 | array_keys($request->getQueryParams() ?? []) | ||
232 | ); | ||
233 | if (1 === count($legacyGetRoutes)) { | ||
234 | $legacyController = new LegacyController($this->container); | ||
235 | |||
236 | return $legacyController->process($request, $response, $legacyGetRoutes[0]); | ||
237 | } | ||
238 | |||
239 | return null; | ||
240 | } | ||
241 | } | ||
diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php new file mode 100644 index 00000000..07617cf1 --- /dev/null +++ b/application/front/controller/visitor/DailyController.php | |||
@@ -0,0 +1,192 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use DateTime; | ||
8 | use DateTimeImmutable; | ||
9 | use Shaarli\Bookmark\Bookmark; | ||
10 | use Shaarli\Render\TemplatePage; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | |||
14 | /** | ||
15 | * Class DailyController | ||
16 | * | ||
17 | * Slim controller used to render the daily page. | ||
18 | */ | ||
19 | class DailyController extends ShaarliVisitorController | ||
20 | { | ||
21 | public static $DAILY_RSS_NB_DAYS = 8; | ||
22 | |||
23 | /** | ||
24 | * Controller displaying all bookmarks published in a single day. | ||
25 | * It take a `day` date query parameter (format YYYYMMDD). | ||
26 | */ | ||
27 | public function index(Request $request, Response $response): Response | ||
28 | { | ||
29 | $day = $request->getQueryParam('day') ?? date('Ymd'); | ||
30 | |||
31 | $availableDates = $this->container->bookmarkService->days(); | ||
32 | $nbAvailableDates = count($availableDates); | ||
33 | $index = array_search($day, $availableDates); | ||
34 | |||
35 | if ($index === false) { | ||
36 | // no bookmarks for day, but at least one day with bookmarks | ||
37 | $day = $availableDates[$nbAvailableDates - 1] ?? $day; | ||
38 | $previousDay = $availableDates[$nbAvailableDates - 2] ?? ''; | ||
39 | } else { | ||
40 | $previousDay = $availableDates[$index - 1] ?? ''; | ||
41 | $nextDay = $availableDates[$index + 1] ?? ''; | ||
42 | } | ||
43 | |||
44 | if ($day === date('Ymd')) { | ||
45 | $this->assignView('dayDesc', t('Today')); | ||
46 | } elseif ($day === date('Ymd', strtotime('-1 days'))) { | ||
47 | $this->assignView('dayDesc', t('Yesterday')); | ||
48 | } | ||
49 | |||
50 | try { | ||
51 | $linksToDisplay = $this->container->bookmarkService->filterDay($day); | ||
52 | } catch (\Exception $exc) { | ||
53 | $linksToDisplay = []; | ||
54 | } | ||
55 | |||
56 | $formatter = $this->container->formatterFactory->getFormatter(); | ||
57 | $formatter->addContextData('base_path', $this->container->basePath); | ||
58 | // We pre-format some fields for proper output. | ||
59 | foreach ($linksToDisplay as $key => $bookmark) { | ||
60 | $linksToDisplay[$key] = $formatter->format($bookmark); | ||
61 | // This page is a bit specific, we need raw description to calculate the length | ||
62 | $linksToDisplay[$key]['formatedDescription'] = $linksToDisplay[$key]['description']; | ||
63 | $linksToDisplay[$key]['description'] = $bookmark->getDescription(); | ||
64 | } | ||
65 | |||
66 | $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); | ||
67 | $data = [ | ||
68 | 'linksToDisplay' => $linksToDisplay, | ||
69 | 'day' => $dayDate->getTimestamp(), | ||
70 | 'dayDate' => $dayDate, | ||
71 | 'previousday' => $previousDay ?? '', | ||
72 | 'nextday' => $nextDay ?? '', | ||
73 | ]; | ||
74 | |||
75 | // Hooks are called before column construction so that plugins don't have to deal with columns. | ||
76 | $this->executePageHooks('render_daily', $data, TemplatePage::DAILY); | ||
77 | |||
78 | $data['cols'] = $this->calculateColumns($data['linksToDisplay']); | ||
79 | |||
80 | $this->assignAllView($data); | ||
81 | |||
82 | $mainTitle = $this->container->conf->get('general.title', 'Shaarli'); | ||
83 | $this->assignView( | ||
84 | 'pagetitle', | ||
85 | t('Daily') .' - '. format_date($dayDate, false) . ' - ' . $mainTitle | ||
86 | ); | ||
87 | |||
88 | return $response->write($this->render(TemplatePage::DAILY)); | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * Daily RSS feed: 1 RSS entry per day giving all the bookmarks on that day. | ||
93 | * Gives the last 7 days (which have bookmarks). | ||
94 | * This RSS feed cannot be filtered and does not trigger plugins yet. | ||
95 | */ | ||
96 | public function rss(Request $request, Response $response): Response | ||
97 | { | ||
98 | $response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8'); | ||
99 | |||
100 | $pageUrl = page_url($this->container->environment); | ||
101 | $cache = $this->container->pageCacheManager->getCachePage($pageUrl); | ||
102 | |||
103 | $cached = $cache->cachedVersion(); | ||
104 | if (!empty($cached)) { | ||
105 | return $response->write($cached); | ||
106 | } | ||
107 | |||
108 | $days = []; | ||
109 | foreach ($this->container->bookmarkService->search() as $bookmark) { | ||
110 | $day = $bookmark->getCreated()->format('Ymd'); | ||
111 | |||
112 | // Stop iterating after DAILY_RSS_NB_DAYS entries | ||
113 | if (count($days) === static::$DAILY_RSS_NB_DAYS && !isset($days[$day])) { | ||
114 | break; | ||
115 | } | ||
116 | |||
117 | $days[$day][] = $bookmark; | ||
118 | } | ||
119 | |||
120 | // Build the RSS feed. | ||
121 | $indexUrl = escape(index_url($this->container->environment)); | ||
122 | |||
123 | $formatter = $this->container->formatterFactory->getFormatter(); | ||
124 | $formatter->addContextData('index_url', $indexUrl); | ||
125 | |||
126 | $dataPerDay = []; | ||
127 | |||
128 | /** @var Bookmark[] $bookmarks */ | ||
129 | foreach ($days as $day => $bookmarks) { | ||
130 | $dayDatetime = DateTimeImmutable::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); | ||
131 | $dataPerDay[$day] = [ | ||
132 | 'date' => $dayDatetime, | ||
133 | 'date_rss' => $dayDatetime->format(DateTime::RSS), | ||
134 | 'date_human' => format_date($dayDatetime, false, true), | ||
135 | 'absolute_url' => $indexUrl . 'daily?day=' . $day, | ||
136 | 'links' => [], | ||
137 | ]; | ||
138 | |||
139 | foreach ($bookmarks as $key => $bookmark) { | ||
140 | $dataPerDay[$day]['links'][$key] = $formatter->format($bookmark); | ||
141 | |||
142 | // Make permalink URL absolute | ||
143 | if ($bookmark->isNote()) { | ||
144 | $dataPerDay[$day]['links'][$key]['url'] = $indexUrl . $bookmark->getUrl(); | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | |||
149 | $this->assignView('title', $this->container->conf->get('general.title', 'Shaarli')); | ||
150 | $this->assignView('index_url', $indexUrl); | ||
151 | $this->assignView('page_url', $pageUrl); | ||
152 | $this->assignView('hide_timestamps', $this->container->conf->get('privacy.hide_timestamps', false)); | ||
153 | $this->assignView('days', $dataPerDay); | ||
154 | |||
155 | $rssContent = $this->render(TemplatePage::DAILY_RSS); | ||
156 | |||
157 | $cache->cache($rssContent); | ||
158 | |||
159 | return $response->write($rssContent); | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * We need to spread the articles on 3 columns. | ||
164 | * did not want to use a JavaScript lib like http://masonry.desandro.com/ | ||
165 | * so I manually spread entries with a simple method: I roughly evaluate the | ||
166 | * height of a div according to title and description length. | ||
167 | */ | ||
168 | protected function calculateColumns(array $links): array | ||
169 | { | ||
170 | // Entries to display, for each column. | ||
171 | $columns = [[], [], []]; | ||
172 | // Rough estimate of columns fill. | ||
173 | $fill = [0, 0, 0]; | ||
174 | foreach ($links as $link) { | ||
175 | // Roughly estimate length of entry (by counting characters) | ||
176 | // Title: 30 chars = 1 line. 1 line is 30 pixels height. | ||
177 | // Description: 836 characters gives roughly 342 pixel height. | ||
178 | // This is not perfect, but it's usually OK. | ||
179 | $length = strlen($link['title'] ?? '') + (342 * strlen($link['description'] ?? '')) / 836; | ||
180 | if (! empty($link['thumbnail'])) { | ||
181 | $length += 100; // 1 thumbnails roughly takes 100 pixels height. | ||
182 | } | ||
183 | // Then put in column which is the less filled: | ||
184 | $smallest = min($fill); // find smallest value in array. | ||
185 | $index = array_search($smallest, $fill); // find index of this smallest value. | ||
186 | array_push($columns[$index], $link); // Put entry in this column. | ||
187 | $fill[$index] += $length; | ||
188 | } | ||
189 | |||
190 | return $columns; | ||
191 | } | ||
192 | } | ||
diff --git a/application/front/controller/visitor/ErrorController.php b/application/front/controller/visitor/ErrorController.php new file mode 100644 index 00000000..10aa84c8 --- /dev/null +++ b/application/front/controller/visitor/ErrorController.php | |||
@@ -0,0 +1,45 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Front\Exception\ShaarliFrontException; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | /** | ||
12 | * Controller used to render the error page, with a provided exception. | ||
13 | * It is actually used as a Slim error handler. | ||
14 | */ | ||
15 | class ErrorController extends ShaarliVisitorController | ||
16 | { | ||
17 | public function __invoke(Request $request, Response $response, \Throwable $throwable): Response | ||
18 | { | ||
19 | // Unknown error encountered | ||
20 | $this->container->pageBuilder->reset(); | ||
21 | |||
22 | if ($throwable instanceof ShaarliFrontException) { | ||
23 | // Functional error | ||
24 | $this->assignView('message', nl2br($throwable->getMessage())); | ||
25 | |||
26 | $response = $response->withStatus($throwable->getCode()); | ||
27 | } else { | ||
28 | // Internal error (any other Throwable) | ||
29 | if ($this->container->conf->get('dev.debug', false)) { | ||
30 | $this->assignView('message', $throwable->getMessage()); | ||
31 | $this->assignView( | ||
32 | 'stacktrace', | ||
33 | nl2br(get_class($throwable) .': '. PHP_EOL . $throwable->getTraceAsString()) | ||
34 | ); | ||
35 | } else { | ||
36 | $this->assignView('message', t('An unexpected error occurred.')); | ||
37 | } | ||
38 | |||
39 | $response = $response->withStatus(500); | ||
40 | } | ||
41 | |||
42 | |||
43 | return $response->write($this->render('error')); | ||
44 | } | ||
45 | } | ||
diff --git a/application/front/controller/visitor/ErrorNotFoundController.php b/application/front/controller/visitor/ErrorNotFoundController.php new file mode 100644 index 00000000..758dd83b --- /dev/null +++ b/application/front/controller/visitor/ErrorNotFoundController.php | |||
@@ -0,0 +1,29 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Slim\Http\Request; | ||
8 | use Slim\Http\Response; | ||
9 | |||
10 | /** | ||
11 | * Controller used to render the 404 error page. | ||
12 | */ | ||
13 | class ErrorNotFoundController extends ShaarliVisitorController | ||
14 | { | ||
15 | public function __invoke(Request $request, Response $response): Response | ||
16 | { | ||
17 | // Request from the API | ||
18 | if (false !== strpos($request->getRequestTarget(), '/api/v1')) { | ||
19 | return $response->withStatus(404); | ||
20 | } | ||
21 | |||
22 | // This is required because the middleware is ignored if the route is not found. | ||
23 | $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/'); | ||
24 | |||
25 | $this->assignView('error_message', t('Requested page could not be found.')); | ||
26 | |||
27 | return $response->withStatus(404)->write($this->render('404')); | ||
28 | } | ||
29 | } | ||
diff --git a/application/front/controller/visitor/FeedController.php b/application/front/controller/visitor/FeedController.php new file mode 100644 index 00000000..8d8b546a --- /dev/null +++ b/application/front/controller/visitor/FeedController.php | |||
@@ -0,0 +1,58 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Feed\FeedBuilder; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | /** | ||
12 | * Class FeedController | ||
13 | * | ||
14 | * Slim controller handling ATOM and RSS feed. | ||
15 | */ | ||
16 | class FeedController extends ShaarliVisitorController | ||
17 | { | ||
18 | public function atom(Request $request, Response $response): Response | ||
19 | { | ||
20 | return $this->processRequest(FeedBuilder::$FEED_ATOM, $request, $response); | ||
21 | } | ||
22 | |||
23 | public function rss(Request $request, Response $response): Response | ||
24 | { | ||
25 | return $this->processRequest(FeedBuilder::$FEED_RSS, $request, $response); | ||
26 | } | ||
27 | |||
28 | protected function processRequest(string $feedType, Request $request, Response $response): Response | ||
29 | { | ||
30 | $response = $response->withHeader('Content-Type', 'application/'. $feedType .'+xml; charset=utf-8'); | ||
31 | |||
32 | $pageUrl = page_url($this->container->environment); | ||
33 | $cache = $this->container->pageCacheManager->getCachePage($pageUrl); | ||
34 | |||
35 | $cached = $cache->cachedVersion(); | ||
36 | if (!empty($cached)) { | ||
37 | return $response->write($cached); | ||
38 | } | ||
39 | |||
40 | // Generate data. | ||
41 | $this->container->feedBuilder->setLocale(strtolower(setlocale(LC_COLLATE, 0))); | ||
42 | $this->container->feedBuilder->setHideDates($this->container->conf->get('privacy.hide_timestamps', false)); | ||
43 | $this->container->feedBuilder->setUsePermalinks( | ||
44 | null !== $request->getParam('permalinks') || !$this->container->conf->get('feed.rss_permalinks') | ||
45 | ); | ||
46 | |||
47 | $data = $this->container->feedBuilder->buildData($feedType, $request->getParams()); | ||
48 | |||
49 | $this->executePageHooks('render_feed', $data, 'feed.' . $feedType); | ||
50 | $this->assignAllView($data); | ||
51 | |||
52 | $content = $this->render('feed.' . $feedType); | ||
53 | |||
54 | $cache->cache($content); | ||
55 | |||
56 | return $response->write($content); | ||
57 | } | ||
58 | } | ||
diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php new file mode 100644 index 00000000..7cb32777 --- /dev/null +++ b/application/front/controller/visitor/InstallController.php | |||
@@ -0,0 +1,165 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\ApplicationUtils; | ||
8 | use Shaarli\Container\ShaarliContainer; | ||
9 | use Shaarli\Front\Exception\AlreadyInstalledException; | ||
10 | use Shaarli\Front\Exception\ResourcePermissionException; | ||
11 | use Shaarli\Languages; | ||
12 | use Shaarli\Security\SessionManager; | ||
13 | use Slim\Http\Request; | ||
14 | use Slim\Http\Response; | ||
15 | |||
16 | /** | ||
17 | * Slim controller used to render install page, and create initial configuration file. | ||
18 | */ | ||
19 | class InstallController extends ShaarliVisitorController | ||
20 | { | ||
21 | public const SESSION_TEST_KEY = 'session_tested'; | ||
22 | public const SESSION_TEST_VALUE = 'Working'; | ||
23 | |||
24 | public function __construct(ShaarliContainer $container) | ||
25 | { | ||
26 | parent::__construct($container); | ||
27 | |||
28 | if (is_file($this->container->conf->getConfigFileExt())) { | ||
29 | throw new AlreadyInstalledException(); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Display the install template page. | ||
35 | * Also test file permissions and sessions beforehand. | ||
36 | */ | ||
37 | public function index(Request $request, Response $response): Response | ||
38 | { | ||
39 | // Before installation, we'll make sure that permissions are set properly, and sessions are working. | ||
40 | $this->checkPermissions(); | ||
41 | |||
42 | if (static::SESSION_TEST_VALUE | ||
43 | !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY) | ||
44 | ) { | ||
45 | $this->container->sessionManager->setSessionParameter(static::SESSION_TEST_KEY, static::SESSION_TEST_VALUE); | ||
46 | |||
47 | return $this->redirect($response, '/install/session-test'); | ||
48 | } | ||
49 | |||
50 | [$continents, $cities] = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); | ||
51 | |||
52 | $this->assignView('continents', $continents); | ||
53 | $this->assignView('cities', $cities); | ||
54 | $this->assignView('languages', Languages::getAvailableLanguages()); | ||
55 | |||
56 | return $response->write($this->render('install')); | ||
57 | } | ||
58 | |||
59 | /** | ||
60 | * Route checking that the session parameter has been properly saved between two distinct requests. | ||
61 | * If the session parameter is preserved, redirect to install template page, otherwise displays error. | ||
62 | */ | ||
63 | public function sessionTest(Request $request, Response $response): Response | ||
64 | { | ||
65 | // This part makes sure sessions works correctly. | ||
66 | // (Because on some hosts, session.save_path may not be set correctly, | ||
67 | // or we may not have write access to it.) | ||
68 | if (static::SESSION_TEST_VALUE | ||
69 | !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY) | ||
70 | ) { | ||
71 | // Step 2: Check if data in session is correct. | ||
72 | $msg = t( | ||
73 | '<pre>Sessions do not seem to work correctly on your server.<br>'. | ||
74 | 'Make sure the variable "session.save_path" is set correctly in your PHP config, '. | ||
75 | 'and that you have write access to it.<br>'. | ||
76 | 'It currently points to %s.<br>'. | ||
77 | 'On some browsers, accessing your server via a hostname like \'localhost\' '. | ||
78 | 'or any custom hostname without a dot causes cookie storage to fail. '. | ||
79 | 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>' | ||
80 | ); | ||
81 | $msg = sprintf($msg, $this->container->sessionManager->getSavePath()); | ||
82 | |||
83 | $this->assignView('message', $msg); | ||
84 | |||
85 | return $response->write($this->render('error')); | ||
86 | } | ||
87 | |||
88 | return $this->redirect($response, '/install'); | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * Save installation form and initialize config file and datastore if necessary. | ||
93 | */ | ||
94 | public function save(Request $request, Response $response): Response | ||
95 | { | ||
96 | $timezone = 'UTC'; | ||
97 | if (!empty($request->getParam('continent')) | ||
98 | && !empty($request->getParam('city')) | ||
99 | && isTimeZoneValid($request->getParam('continent'), $request->getParam('city')) | ||
100 | ) { | ||
101 | $timezone = $request->getParam('continent') . '/' . $request->getParam('city'); | ||
102 | } | ||
103 | $this->container->conf->set('general.timezone', $timezone); | ||
104 | |||
105 | $login = $request->getParam('setlogin'); | ||
106 | $this->container->conf->set('credentials.login', $login); | ||
107 | $salt = sha1(uniqid('', true) .'_'. mt_rand()); | ||
108 | $this->container->conf->set('credentials.salt', $salt); | ||
109 | $this->container->conf->set('credentials.hash', sha1($request->getParam('setpassword') . $login . $salt)); | ||
110 | |||
111 | if (!empty($request->getParam('title'))) { | ||
112 | $this->container->conf->set('general.title', escape($request->getParam('title'))); | ||
113 | } else { | ||
114 | $this->container->conf->set( | ||
115 | 'general.title', | ||
116 | 'Shared bookmarks on '.escape(index_url($this->container->environment)) | ||
117 | ); | ||
118 | } | ||
119 | |||
120 | $this->container->conf->set('translation.language', escape($request->getParam('language'))); | ||
121 | $this->container->conf->set('updates.check_updates', !empty($request->getParam('updateCheck'))); | ||
122 | $this->container->conf->set('api.enabled', !empty($request->getParam('enableApi'))); | ||
123 | $this->container->conf->set( | ||
124 | 'api.secret', | ||
125 | generate_api_secret( | ||
126 | $this->container->conf->get('credentials.login'), | ||
127 | $this->container->conf->get('credentials.salt') | ||
128 | ) | ||
129 | ); | ||
130 | $this->container->conf->set('general.header_link', $this->container->basePath . '/'); | ||
131 | |||
132 | try { | ||
133 | // Everything is ok, let's create config file. | ||
134 | $this->container->conf->write($this->container->loginManager->isLoggedIn()); | ||
135 | } catch (\Exception $e) { | ||
136 | $this->assignView('message', t('Error while writing config file after configuration update.')); | ||
137 | $this->assignView('stacktrace', $e->getMessage() . PHP_EOL . $e->getTraceAsString()); | ||
138 | |||
139 | return $response->write($this->render('error')); | ||
140 | } | ||
141 | |||
142 | $this->container->sessionManager->setSessionParameter( | ||
143 | SessionManager::KEY_SUCCESS_MESSAGES, | ||
144 | [t('Shaarli is now configured. Please login and start shaaring your bookmarks!')] | ||
145 | ); | ||
146 | |||
147 | return $this->redirect($response, '/login'); | ||
148 | } | ||
149 | |||
150 | protected function checkPermissions(): bool | ||
151 | { | ||
152 | // Ensure Shaarli has proper access to its resources | ||
153 | $errors = ApplicationUtils::checkResourcePermissions($this->container->conf); | ||
154 | if (empty($errors)) { | ||
155 | return true; | ||
156 | } | ||
157 | |||
158 | $message = t('Insufficient permissions:') . PHP_EOL; | ||
159 | foreach ($errors as $error) { | ||
160 | $message .= PHP_EOL . $error; | ||
161 | } | ||
162 | |||
163 | throw new ResourcePermissionException($message); | ||
164 | } | ||
165 | } | ||
diff --git a/application/front/controller/visitor/LoginController.php b/application/front/controller/visitor/LoginController.php new file mode 100644 index 00000000..121ba40b --- /dev/null +++ b/application/front/controller/visitor/LoginController.php | |||
@@ -0,0 +1,154 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Front\Exception\CantLoginException; | ||
8 | use Shaarli\Front\Exception\LoginBannedException; | ||
9 | use Shaarli\Front\Exception\WrongTokenException; | ||
10 | use Shaarli\Render\TemplatePage; | ||
11 | use Shaarli\Security\CookieManager; | ||
12 | use Shaarli\Security\SessionManager; | ||
13 | use Slim\Http\Request; | ||
14 | use Slim\Http\Response; | ||
15 | |||
16 | /** | ||
17 | * Class LoginController | ||
18 | * | ||
19 | * Slim controller used to render the login page. | ||
20 | * | ||
21 | * The login page is not available if the user is banned | ||
22 | * or if open shaarli setting is enabled. | ||
23 | */ | ||
24 | class LoginController extends ShaarliVisitorController | ||
25 | { | ||
26 | /** | ||
27 | * GET /login - Display the login page. | ||
28 | */ | ||
29 | public function index(Request $request, Response $response): Response | ||
30 | { | ||
31 | try { | ||
32 | $this->checkLoginState(); | ||
33 | } catch (CantLoginException $e) { | ||
34 | return $this->redirect($response, '/'); | ||
35 | } | ||
36 | |||
37 | if ($request->getParam('login') !== null) { | ||
38 | $this->assignView('username', escape($request->getParam('login'))); | ||
39 | } | ||
40 | |||
41 | $returnUrl = $request->getParam('returnurl') ?? $this->container->environment['HTTP_REFERER'] ?? null; | ||
42 | |||
43 | $this | ||
44 | ->assignView('returnurl', escape($returnUrl)) | ||
45 | ->assignView('remember_user_default', $this->container->conf->get('privacy.remember_user_default', true)) | ||
46 | ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli')) | ||
47 | ; | ||
48 | |||
49 | return $response->write($this->render(TemplatePage::LOGIN)); | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * POST /login - Process login | ||
54 | */ | ||
55 | public function login(Request $request, Response $response): Response | ||
56 | { | ||
57 | if (!$this->container->sessionManager->checkToken($request->getParam('token'))) { | ||
58 | throw new WrongTokenException(); | ||
59 | } | ||
60 | |||
61 | try { | ||
62 | $this->checkLoginState(); | ||
63 | } catch (CantLoginException $e) { | ||
64 | return $this->redirect($response, '/'); | ||
65 | } | ||
66 | |||
67 | if (!$this->container->loginManager->checkCredentials( | ||
68 | $this->container->environment['REMOTE_ADDR'], | ||
69 | client_ip_id($this->container->environment), | ||
70 | $request->getParam('login'), | ||
71 | $request->getParam('password') | ||
72 | ) | ||
73 | ) { | ||
74 | $this->container->loginManager->handleFailedLogin($this->container->environment); | ||
75 | |||
76 | $this->container->sessionManager->setSessionParameter( | ||
77 | SessionManager::KEY_ERROR_MESSAGES, | ||
78 | [t('Wrong login/password.')] | ||
79 | ); | ||
80 | |||
81 | // Call controller directly instead of unnecessary redirection | ||
82 | return $this->index($request, $response); | ||
83 | } | ||
84 | |||
85 | $this->container->loginManager->handleSuccessfulLogin($this->container->environment); | ||
86 | |||
87 | $cookiePath = $this->container->basePath . '/'; | ||
88 | $expirationTime = $this->saveLongLastingSession($request, $cookiePath); | ||
89 | $this->renewUserSession($cookiePath, $expirationTime); | ||
90 | |||
91 | // Force referer from given return URL | ||
92 | $this->container->environment['HTTP_REFERER'] = $request->getParam('returnurl'); | ||
93 | |||
94 | return $this->redirectFromReferer($request, $response, ['login', 'install']); | ||
95 | } | ||
96 | |||
97 | /** | ||
98 | * Make sure that the user is allowed to login and/or displaying the login page: | ||
99 | * - not already logged in | ||
100 | * - not open shaarli | ||
101 | * - not banned | ||
102 | */ | ||
103 | protected function checkLoginState(): bool | ||
104 | { | ||
105 | if ($this->container->loginManager->isLoggedIn() | ||
106 | || $this->container->conf->get('security.open_shaarli', false) | ||
107 | ) { | ||
108 | throw new CantLoginException(); | ||
109 | } | ||
110 | |||
111 | if (true !== $this->container->loginManager->canLogin($this->container->environment)) { | ||
112 | throw new LoginBannedException(); | ||
113 | } | ||
114 | |||
115 | return true; | ||
116 | } | ||
117 | |||
118 | /** | ||
119 | * @return int Session duration in seconds | ||
120 | */ | ||
121 | protected function saveLongLastingSession(Request $request, string $cookiePath): int | ||
122 | { | ||
123 | if (empty($request->getParam('longlastingsession'))) { | ||
124 | // Standard session expiration (=when browser closes) | ||
125 | $expirationTime = 0; | ||
126 | } else { | ||
127 | // Keep the session cookie even after the browser closes | ||
128 | $this->container->sessionManager->setStaySignedIn(true); | ||
129 | $expirationTime = $this->container->sessionManager->extendSession(); | ||
130 | } | ||
131 | |||
132 | $this->container->cookieManager->setCookieParameter( | ||
133 | CookieManager::STAY_SIGNED_IN, | ||
134 | $this->container->loginManager->getStaySignedInToken(), | ||
135 | $expirationTime, | ||
136 | $cookiePath | ||
137 | ); | ||
138 | |||
139 | return $expirationTime; | ||
140 | } | ||
141 | |||
142 | protected function renewUserSession(string $cookiePath, int $expirationTime): void | ||
143 | { | ||
144 | // Send cookie with the new expiration date to the browser | ||
145 | $this->container->sessionManager->destroy(); | ||
146 | $this->container->sessionManager->cookieParameters( | ||
147 | $expirationTime, | ||
148 | $cookiePath, | ||
149 | $this->container->environment['SERVER_NAME'] | ||
150 | ); | ||
151 | $this->container->sessionManager->start(); | ||
152 | $this->container->sessionManager->regenerateId(true); | ||
153 | } | ||
154 | } | ||
diff --git a/application/front/controller/visitor/OpenSearchController.php b/application/front/controller/visitor/OpenSearchController.php new file mode 100644 index 00000000..36d60acf --- /dev/null +++ b/application/front/controller/visitor/OpenSearchController.php | |||
@@ -0,0 +1,27 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Render\TemplatePage; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | /** | ||
12 | * Class OpenSearchController | ||
13 | * | ||
14 | * Slim controller used to render open search template. | ||
15 | * This allows to add Shaarli as a search engine within the browser. | ||
16 | */ | ||
17 | class OpenSearchController extends ShaarliVisitorController | ||
18 | { | ||
19 | public function index(Request $request, Response $response): Response | ||
20 | { | ||
21 | $response = $response->withHeader('Content-Type', 'application/opensearchdescription+xml; charset=utf-8'); | ||
22 | |||
23 | $this->assignView('serverurl', index_url($this->container->environment)); | ||
24 | |||
25 | return $response->write($this->render(TemplatePage::OPEN_SEARCH)); | ||
26 | } | ||
27 | } | ||
diff --git a/application/front/controller/visitor/PictureWallController.php b/application/front/controller/visitor/PictureWallController.php new file mode 100644 index 00000000..3c57f8dd --- /dev/null +++ b/application/front/controller/visitor/PictureWallController.php | |||
@@ -0,0 +1,54 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Front\Exception\ThumbnailsDisabledException; | ||
8 | use Shaarli\Render\TemplatePage; | ||
9 | use Shaarli\Thumbnailer; | ||
10 | use Slim\Http\Request; | ||
11 | use Slim\Http\Response; | ||
12 | |||
13 | /** | ||
14 | * Class PicturesWallController | ||
15 | * | ||
16 | * Slim controller used to render the pictures wall page. | ||
17 | * If thumbnails mode is set to NONE, we just render the template without any image. | ||
18 | */ | ||
19 | class PictureWallController extends ShaarliVisitorController | ||
20 | { | ||
21 | public function index(Request $request, Response $response): Response | ||
22 | { | ||
23 | if ($this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) { | ||
24 | throw new ThumbnailsDisabledException(); | ||
25 | } | ||
26 | |||
27 | $this->assignView( | ||
28 | 'pagetitle', | ||
29 | t('Picture wall') .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
30 | ); | ||
31 | |||
32 | // Optionally filter the results: | ||
33 | $links = $this->container->bookmarkService->search($request->getQueryParams()); | ||
34 | $linksToDisplay = []; | ||
35 | |||
36 | // Get only bookmarks which have a thumbnail. | ||
37 | // Note: we do not retrieve thumbnails here, the request is too heavy. | ||
38 | $formatter = $this->container->formatterFactory->getFormatter('raw'); | ||
39 | foreach ($links as $key => $link) { | ||
40 | if (!empty($link->getThumbnail())) { | ||
41 | $linksToDisplay[] = $formatter->format($link); | ||
42 | } | ||
43 | } | ||
44 | |||
45 | $data = ['linksToDisplay' => $linksToDisplay]; | ||
46 | $this->executePageHooks('render_picwall', $data, TemplatePage::PICTURE_WALL); | ||
47 | |||
48 | foreach ($data as $key => $value) { | ||
49 | $this->assignView($key, $value); | ||
50 | } | ||
51 | |||
52 | return $response->write($this->render(TemplatePage::PICTURE_WALL)); | ||
53 | } | ||
54 | } | ||
diff --git a/application/front/controller/visitor/PublicSessionFilterController.php b/application/front/controller/visitor/PublicSessionFilterController.php new file mode 100644 index 00000000..1a66362d --- /dev/null +++ b/application/front/controller/visitor/PublicSessionFilterController.php | |||
@@ -0,0 +1,46 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Security\SessionManager; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | /** | ||
12 | * Slim controller used to handle filters stored in the visitor session, links per page, etc. | ||
13 | */ | ||
14 | class PublicSessionFilterController extends ShaarliVisitorController | ||
15 | { | ||
16 | /** | ||
17 | * GET /links-per-page: set the number of bookmarks to display per page in homepage | ||
18 | */ | ||
19 | public function linksPerPage(Request $request, Response $response): Response | ||
20 | { | ||
21 | $linksPerPage = $request->getParam('nb') ?? null; | ||
22 | if (null === $linksPerPage || false === is_numeric($linksPerPage)) { | ||
23 | $linksPerPage = $this->container->conf->get('general.links_per_page', 20); | ||
24 | } | ||
25 | |||
26 | $this->container->sessionManager->setSessionParameter( | ||
27 | SessionManager::KEY_LINKS_PER_PAGE, | ||
28 | abs(intval($linksPerPage)) | ||
29 | ); | ||
30 | |||
31 | return $this->redirectFromReferer($request, $response, ['linksperpage'], ['nb']); | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * GET /untagged-only: allows to display only bookmarks without any tag | ||
36 | */ | ||
37 | public function untaggedOnly(Request $request, Response $response): Response | ||
38 | { | ||
39 | $this->container->sessionManager->setSessionParameter( | ||
40 | SessionManager::KEY_UNTAGGED_ONLY, | ||
41 | empty($this->container->sessionManager->getSessionParameter(SessionManager::KEY_UNTAGGED_ONLY)) | ||
42 | ); | ||
43 | |||
44 | return $this->redirectFromReferer($request, $response, ['untaggedonly', 'untagged-only']); | ||
45 | } | ||
46 | } | ||
diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php new file mode 100644 index 00000000..55c075a2 --- /dev/null +++ b/application/front/controller/visitor/ShaarliVisitorController.php | |||
@@ -0,0 +1,180 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkFilter; | ||
8 | use Shaarli\Container\ShaarliContainer; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Class ShaarliVisitorController | ||
14 | * | ||
15 | * All controllers accessible by visitors (non logged in users) should extend this abstract class. | ||
16 | * Contains a few helper function for template rendering, plugins, etc. | ||
17 | * | ||
18 | * @package Shaarli\Front\Controller\Visitor | ||
19 | */ | ||
20 | abstract class ShaarliVisitorController | ||
21 | { | ||
22 | /** @var ShaarliContainer */ | ||
23 | protected $container; | ||
24 | |||
25 | /** @param ShaarliContainer $container Slim container (extended for attribute completion). */ | ||
26 | public function __construct(ShaarliContainer $container) | ||
27 | { | ||
28 | $this->container = $container; | ||
29 | } | ||
30 | |||
31 | /** | ||
32 | * Assign variables to RainTPL template through the PageBuilder. | ||
33 | * | ||
34 | * @param mixed $value Value to assign to the template | ||
35 | */ | ||
36 | protected function assignView(string $name, $value): self | ||
37 | { | ||
38 | $this->container->pageBuilder->assign($name, $value); | ||
39 | |||
40 | return $this; | ||
41 | } | ||
42 | |||
43 | /** | ||
44 | * Assign variables to RainTPL template through the PageBuilder. | ||
45 | * | ||
46 | * @param mixed $data Values to assign to the template and their keys | ||
47 | */ | ||
48 | protected function assignAllView(array $data): self | ||
49 | { | ||
50 | foreach ($data as $key => $value) { | ||
51 | $this->assignView($key, $value); | ||
52 | } | ||
53 | |||
54 | return $this; | ||
55 | } | ||
56 | |||
57 | protected function render(string $template): string | ||
58 | { | ||
59 | $this->assignView('linkcount', $this->container->bookmarkService->count(BookmarkFilter::$ALL)); | ||
60 | $this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
61 | |||
62 | $this->executeDefaultHooks($template); | ||
63 | |||
64 | $this->assignView('plugin_errors', $this->container->pluginManager->getErrors()); | ||
65 | |||
66 | return $this->container->pageBuilder->render($template, $this->container->basePath); | ||
67 | } | ||
68 | |||
69 | /** | ||
70 | * Call plugin hooks for header, footer and includes, specifying which page will be rendered. | ||
71 | * Then assign generated data to RainTPL. | ||
72 | */ | ||
73 | protected function executeDefaultHooks(string $template): void | ||
74 | { | ||
75 | $common_hooks = [ | ||
76 | 'includes', | ||
77 | 'header', | ||
78 | 'footer', | ||
79 | ]; | ||
80 | |||
81 | $parameters = $this->buildPluginParameters($template); | ||
82 | |||
83 | foreach ($common_hooks as $name) { | ||
84 | $pluginData = []; | ||
85 | $this->container->pluginManager->executeHooks( | ||
86 | 'render_' . $name, | ||
87 | $pluginData, | ||
88 | $parameters | ||
89 | ); | ||
90 | $this->assignView('plugins_' . $name, $pluginData); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | protected function executePageHooks(string $hook, array &$data, string $template = null): void | ||
95 | { | ||
96 | $this->container->pluginManager->executeHooks( | ||
97 | $hook, | ||
98 | $data, | ||
99 | $this->buildPluginParameters($template) | ||
100 | ); | ||
101 | } | ||
102 | |||
103 | protected function buildPluginParameters(?string $template): array | ||
104 | { | ||
105 | return [ | ||
106 | 'target' => $template, | ||
107 | 'loggedin' => $this->container->loginManager->isLoggedIn(), | ||
108 | 'basePath' => $this->container->basePath, | ||
109 | 'bookmarkService' => $this->container->bookmarkService | ||
110 | ]; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Simple helper which prepend the base path to redirect path. | ||
115 | * | ||
116 | * @param Response $response | ||
117 | * @param string $path Absolute path, e.g.: `/`, or `/admin/shaare/123` regardless of install directory | ||
118 | * | ||
119 | * @return Response updated | ||
120 | */ | ||
121 | protected function redirect(Response $response, string $path): Response | ||
122 | { | ||
123 | return $response->withRedirect($this->container->basePath . $path); | ||
124 | } | ||
125 | |||
126 | /** | ||
127 | * Generates a redirection to the previous page, based on the HTTP_REFERER. | ||
128 | * It fails back to the home page. | ||
129 | * | ||
130 | * @param array $loopTerms Terms to remove from path and query string to prevent direction loop. | ||
131 | * @param array $clearParams List of parameter to remove from the query string of the referrer. | ||
132 | */ | ||
133 | protected function redirectFromReferer( | ||
134 | Request $request, | ||
135 | Response $response, | ||
136 | array $loopTerms = [], | ||
137 | array $clearParams = [], | ||
138 | string $anchor = null | ||
139 | ): Response { | ||
140 | $defaultPath = $this->container->basePath . '/'; | ||
141 | $referer = $this->container->environment['HTTP_REFERER'] ?? null; | ||
142 | |||
143 | if (null !== $referer) { | ||
144 | $currentUrl = parse_url($referer); | ||
145 | // If the referer is not related to Shaarli instance, redirect to default | ||
146 | if (isset($currentUrl['host']) | ||
147 | && strpos(index_url($this->container->environment), $currentUrl['host']) === false | ||
148 | ) { | ||
149 | return $response->withRedirect($defaultPath); | ||
150 | } | ||
151 | |||
152 | parse_str($currentUrl['query'] ?? '', $params); | ||
153 | $path = $currentUrl['path'] ?? $defaultPath; | ||
154 | } else { | ||
155 | $params = []; | ||
156 | $path = $defaultPath; | ||
157 | } | ||
158 | |||
159 | // Prevent redirection loop | ||
160 | if (isset($currentUrl)) { | ||
161 | foreach ($clearParams as $value) { | ||
162 | unset($params[$value]); | ||
163 | } | ||
164 | |||
165 | $checkQuery = implode('', array_keys($params)); | ||
166 | foreach ($loopTerms as $value) { | ||
167 | if (strpos($path . $checkQuery, $value) !== false) { | ||
168 | $params = []; | ||
169 | $path = $defaultPath; | ||
170 | break; | ||
171 | } | ||
172 | } | ||
173 | } | ||
174 | |||
175 | $queryString = count($params) > 0 ? '?'. http_build_query($params) : ''; | ||
176 | $anchor = $anchor ? '#' . $anchor : ''; | ||
177 | |||
178 | return $response->withRedirect($path . $queryString . $anchor); | ||
179 | } | ||
180 | } | ||
diff --git a/application/front/controller/visitor/TagCloudController.php b/application/front/controller/visitor/TagCloudController.php new file mode 100644 index 00000000..76ed7690 --- /dev/null +++ b/application/front/controller/visitor/TagCloudController.php | |||
@@ -0,0 +1,121 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Slim\Http\Request; | ||
8 | use Slim\Http\Response; | ||
9 | |||
10 | /** | ||
11 | * Class TagCloud | ||
12 | * | ||
13 | * Slim controller used to render the tag cloud and tag list pages. | ||
14 | */ | ||
15 | class TagCloudController extends ShaarliVisitorController | ||
16 | { | ||
17 | protected const TYPE_CLOUD = 'cloud'; | ||
18 | protected const TYPE_LIST = 'list'; | ||
19 | |||
20 | /** | ||
21 | * Display the tag cloud through the template engine. | ||
22 | * This controller a few filters: | ||
23 | * - Visibility stored in the session for logged in users | ||
24 | * - `searchtags` query parameter: will return tags associated with filter in at least one bookmark | ||
25 | */ | ||
26 | public function cloud(Request $request, Response $response): Response | ||
27 | { | ||
28 | return $this->processRequest(static::TYPE_CLOUD, $request, $response); | ||
29 | } | ||
30 | |||
31 | /** | ||
32 | * Display the tag list through the template engine. | ||
33 | * This controller a few filters: | ||
34 | * - Visibility stored in the session for logged in users | ||
35 | * - `searchtags` query parameter: will return tags associated with filter in at least one bookmark | ||
36 | * - `sort` query parameters: | ||
37 | * + `usage` (default): most used tags first | ||
38 | * + `alpha`: alphabetical order | ||
39 | */ | ||
40 | public function list(Request $request, Response $response): Response | ||
41 | { | ||
42 | return $this->processRequest(static::TYPE_LIST, $request, $response); | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Process the request for both tag cloud and tag list endpoints. | ||
47 | */ | ||
48 | protected function processRequest(string $type, Request $request, Response $response): Response | ||
49 | { | ||
50 | if ($this->container->loginManager->isLoggedIn() === true) { | ||
51 | $visibility = $this->container->sessionManager->getSessionParameter('visibility'); | ||
52 | } | ||
53 | |||
54 | $sort = $request->getQueryParam('sort'); | ||
55 | $searchTags = $request->getQueryParam('searchtags'); | ||
56 | $filteringTags = $searchTags !== null ? explode(' ', $searchTags) : []; | ||
57 | |||
58 | $tags = $this->container->bookmarkService->bookmarksCountPerTag($filteringTags, $visibility ?? null); | ||
59 | |||
60 | if (static::TYPE_CLOUD === $type || 'alpha' === $sort) { | ||
61 | // TODO: the sorting should be handled by bookmarkService instead of the controller | ||
62 | alphabetical_sort($tags, false, true); | ||
63 | } | ||
64 | |||
65 | if (static::TYPE_CLOUD === $type) { | ||
66 | $tags = $this->formatTagsForCloud($tags); | ||
67 | } | ||
68 | |||
69 | $tagsUrl = []; | ||
70 | foreach ($tags as $tag => $value) { | ||
71 | $tagsUrl[escape($tag)] = urlencode((string) $tag); | ||
72 | } | ||
73 | |||
74 | $searchTags = implode(' ', escape($filteringTags)); | ||
75 | $searchTagsUrl = urlencode(implode(' ', $filteringTags)); | ||
76 | $data = [ | ||
77 | 'search_tags' => escape($searchTags), | ||
78 | 'search_tags_url' => $searchTagsUrl, | ||
79 | 'tags' => escape($tags), | ||
80 | 'tags_url' => $tagsUrl, | ||
81 | ]; | ||
82 | $this->executePageHooks('render_tag' . $type, $data, 'tag.' . $type); | ||
83 | $this->assignAllView($data); | ||
84 | |||
85 | $searchTags = !empty($searchTags) ? $searchTags .' - ' : ''; | ||
86 | $this->assignView( | ||
87 | 'pagetitle', | ||
88 | $searchTags . t('Tag '. $type) .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
89 | ); | ||
90 | |||
91 | return $response->write($this->render('tag.' . $type)); | ||
92 | } | ||
93 | |||
94 | /** | ||
95 | * Format the tags array for the tag cloud template. | ||
96 | * | ||
97 | * @param array<string, int> $tags List of tags as key with count as value | ||
98 | * | ||
99 | * @return mixed[] List of tags as key, with count and expected font size in a subarray | ||
100 | */ | ||
101 | protected function formatTagsForCloud(array $tags): array | ||
102 | { | ||
103 | // We sort tags alphabetically, then choose a font size according to count. | ||
104 | // First, find max value. | ||
105 | $maxCount = count($tags) > 0 ? max($tags) : 0; | ||
106 | $logMaxCount = $maxCount > 1 ? log($maxCount, 30) : 1; | ||
107 | $tagList = []; | ||
108 | foreach ($tags as $key => $value) { | ||
109 | // Tag font size scaling: | ||
110 | // default 15 and 30 logarithm bases affect scaling, | ||
111 | // 2.2 and 0.8 are arbitrary font sizes in em. | ||
112 | $size = log($value, 15) / $logMaxCount * 2.2 + 0.8; | ||
113 | $tagList[$key] = [ | ||
114 | 'count' => $value, | ||
115 | 'size' => number_format($size, 2, '.', ''), | ||
116 | ]; | ||
117 | } | ||
118 | |||
119 | return $tagList; | ||
120 | } | ||
121 | } | ||
diff --git a/application/front/controller/visitor/TagController.php b/application/front/controller/visitor/TagController.php new file mode 100644 index 00000000..de4e7ea2 --- /dev/null +++ b/application/front/controller/visitor/TagController.php | |||
@@ -0,0 +1,118 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Slim\Http\Request; | ||
8 | use Slim\Http\Response; | ||
9 | |||
10 | /** | ||
11 | * Class TagController | ||
12 | * | ||
13 | * Slim controller handle tags. | ||
14 | */ | ||
15 | class TagController extends ShaarliVisitorController | ||
16 | { | ||
17 | /** | ||
18 | * Add another tag in the current search through an HTTP redirection. | ||
19 | * | ||
20 | * @param array $args Should contain `newTag` key as tag to add to current search | ||
21 | */ | ||
22 | public function addTag(Request $request, Response $response, array $args): Response | ||
23 | { | ||
24 | $newTag = $args['newTag'] ?? null; | ||
25 | $referer = $this->container->environment['HTTP_REFERER'] ?? null; | ||
26 | |||
27 | // In case browser does not send HTTP_REFERER, we search a single tag | ||
28 | if (null === $referer) { | ||
29 | if (null !== $newTag) { | ||
30 | return $this->redirect($response, '/?searchtags='. urlencode($newTag)); | ||
31 | } | ||
32 | |||
33 | return $this->redirect($response, '/'); | ||
34 | } | ||
35 | |||
36 | $currentUrl = parse_url($referer); | ||
37 | parse_str($currentUrl['query'] ?? '', $params); | ||
38 | |||
39 | if (null === $newTag) { | ||
40 | return $response->withRedirect(($currentUrl['path'] ?? './') .'?'. http_build_query($params)); | ||
41 | } | ||
42 | |||
43 | // Prevent redirection loop | ||
44 | if (isset($params['addtag'])) { | ||
45 | unset($params['addtag']); | ||
46 | } | ||
47 | |||
48 | // Check if this tag is already in the search query and ignore it if it is. | ||
49 | // Each tag is always separated by a space | ||
50 | $currentTags = isset($params['searchtags']) ? explode(' ', $params['searchtags']) : []; | ||
51 | |||
52 | $addtag = true; | ||
53 | foreach ($currentTags as $value) { | ||
54 | if ($value === $newTag) { | ||
55 | $addtag = false; | ||
56 | break; | ||
57 | } | ||
58 | } | ||
59 | |||
60 | // Append the tag if necessary | ||
61 | if (true === $addtag) { | ||
62 | $currentTags[] = trim($newTag); | ||
63 | } | ||
64 | |||
65 | $params['searchtags'] = trim(implode(' ', $currentTags)); | ||
66 | |||
67 | // We also remove page (keeping the same page has no sense, since the results are different) | ||
68 | unset($params['page']); | ||
69 | |||
70 | return $response->withRedirect(($currentUrl['path'] ?? './') .'?'. http_build_query($params)); | ||
71 | } | ||
72 | |||
73 | /** | ||
74 | * Remove a tag from the current search through an HTTP redirection. | ||
75 | * | ||
76 | * @param array $args Should contain `tag` key as tag to remove from current search | ||
77 | */ | ||
78 | public function removeTag(Request $request, Response $response, array $args): Response | ||
79 | { | ||
80 | $referer = $this->container->environment['HTTP_REFERER'] ?? null; | ||
81 | |||
82 | // If the referrer is not provided, we can update the search, so we failback on the bookmark list | ||
83 | if (empty($referer)) { | ||
84 | return $this->redirect($response, '/'); | ||
85 | } | ||
86 | |||
87 | $tagToRemove = $args['tag'] ?? null; | ||
88 | $currentUrl = parse_url($referer); | ||
89 | parse_str($currentUrl['query'] ?? '', $params); | ||
90 | |||
91 | if (null === $tagToRemove) { | ||
92 | return $response->withRedirect(($currentUrl['path'] ?? './') .'?'. http_build_query($params)); | ||
93 | } | ||
94 | |||
95 | // Prevent redirection loop | ||
96 | if (isset($params['removetag'])) { | ||
97 | unset($params['removetag']); | ||
98 | } | ||
99 | |||
100 | if (isset($params['searchtags'])) { | ||
101 | $tags = explode(' ', $params['searchtags']); | ||
102 | // Remove value from array $tags. | ||
103 | $tags = array_diff($tags, [$tagToRemove]); | ||
104 | $params['searchtags'] = implode(' ', $tags); | ||
105 | |||
106 | if (empty($params['searchtags'])) { | ||
107 | unset($params['searchtags']); | ||
108 | } | ||
109 | |||
110 | // We also remove page (keeping the same page has no sense, since the results are different) | ||
111 | unset($params['page']); | ||
112 | } | ||
113 | |||
114 | $queryParams = count($params) > 0 ? '?' . http_build_query($params) : ''; | ||
115 | |||
116 | return $response->withRedirect(($currentUrl['path'] ?? './') . $queryParams); | ||
117 | } | ||
118 | } | ||
diff --git a/application/front/exceptions/AlreadyInstalledException.php b/application/front/exceptions/AlreadyInstalledException.php new file mode 100644 index 00000000..4add86cf --- /dev/null +++ b/application/front/exceptions/AlreadyInstalledException.php | |||
@@ -0,0 +1,15 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | class AlreadyInstalledException extends ShaarliFrontException | ||
8 | { | ||
9 | public function __construct() | ||
10 | { | ||
11 | $message = t('Shaarli has already been installed. Login to edit the configuration.'); | ||
12 | |||
13 | parent::__construct($message, 401); | ||
14 | } | ||
15 | } | ||
diff --git a/application/front/exceptions/CantLoginException.php b/application/front/exceptions/CantLoginException.php new file mode 100644 index 00000000..cd16635d --- /dev/null +++ b/application/front/exceptions/CantLoginException.php | |||
@@ -0,0 +1,10 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | class CantLoginException extends \Exception | ||
8 | { | ||
9 | |||
10 | } | ||
diff --git a/application/front/exceptions/LoginBannedException.php b/application/front/exceptions/LoginBannedException.php new file mode 100644 index 00000000..79d0ea15 --- /dev/null +++ b/application/front/exceptions/LoginBannedException.php | |||
@@ -0,0 +1,15 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | class LoginBannedException extends ShaarliFrontException | ||
8 | { | ||
9 | public function __construct() | ||
10 | { | ||
11 | $message = t('You have been banned after too many failed login attempts. Try again later.'); | ||
12 | |||
13 | parent::__construct($message, 401); | ||
14 | } | ||
15 | } | ||
diff --git a/application/front/exceptions/OpenShaarliPasswordException.php b/application/front/exceptions/OpenShaarliPasswordException.php new file mode 100644 index 00000000..a6f0b3ae --- /dev/null +++ b/application/front/exceptions/OpenShaarliPasswordException.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | /** | ||
8 | * Class OpenShaarliPasswordException | ||
9 | * | ||
10 | * Raised if the user tries to change the admin password on an open shaarli instance. | ||
11 | */ | ||
12 | class OpenShaarliPasswordException extends ShaarliFrontException | ||
13 | { | ||
14 | public function __construct() | ||
15 | { | ||
16 | parent::__construct(t('You are not supposed to change a password on an Open Shaarli.'), 403); | ||
17 | } | ||
18 | } | ||
diff --git a/application/front/exceptions/ResourcePermissionException.php b/application/front/exceptions/ResourcePermissionException.php new file mode 100644 index 00000000..8fbf03b9 --- /dev/null +++ b/application/front/exceptions/ResourcePermissionException.php | |||
@@ -0,0 +1,13 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | class ResourcePermissionException extends ShaarliFrontException | ||
8 | { | ||
9 | public function __construct(string $message) | ||
10 | { | ||
11 | parent::__construct($message, 500); | ||
12 | } | ||
13 | } | ||
diff --git a/application/front/exceptions/ShaarliFrontException.php b/application/front/exceptions/ShaarliFrontException.php new file mode 100644 index 00000000..73847e6d --- /dev/null +++ b/application/front/exceptions/ShaarliFrontException.php | |||
@@ -0,0 +1,23 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | use Throwable; | ||
8 | |||
9 | /** | ||
10 | * Class ShaarliException | ||
11 | * | ||
12 | * Exception class used to defined any custom exception thrown during front rendering. | ||
13 | * | ||
14 | * @package Front\Exception | ||
15 | */ | ||
16 | class ShaarliFrontException extends \Exception | ||
17 | { | ||
18 | /** Override parent constructor to force $message and $httpCode parameters to be set. */ | ||
19 | public function __construct(string $message, int $httpCode, Throwable $previous = null) | ||
20 | { | ||
21 | parent::__construct($message, $httpCode, $previous); | ||
22 | } | ||
23 | } | ||
diff --git a/application/front/exceptions/ThumbnailsDisabledException.php b/application/front/exceptions/ThumbnailsDisabledException.php new file mode 100644 index 00000000..0ed337f5 --- /dev/null +++ b/application/front/exceptions/ThumbnailsDisabledException.php | |||
@@ -0,0 +1,15 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | class ThumbnailsDisabledException extends ShaarliFrontException | ||
8 | { | ||
9 | public function __construct() | ||
10 | { | ||
11 | $message = t('Picture wall unavailable (thumbnails are disabled).'); | ||
12 | |||
13 | parent::__construct($message, 400); | ||
14 | } | ||
15 | } | ||
diff --git a/application/front/exceptions/UnauthorizedException.php b/application/front/exceptions/UnauthorizedException.php new file mode 100644 index 00000000..4231094a --- /dev/null +++ b/application/front/exceptions/UnauthorizedException.php | |||
@@ -0,0 +1,15 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | /** | ||
8 | * Class UnauthorizedException | ||
9 | * | ||
10 | * Exception raised if the user tries to access a ShaarliAdminController while logged out. | ||
11 | */ | ||
12 | class UnauthorizedException extends \Exception | ||
13 | { | ||
14 | |||
15 | } | ||
diff --git a/application/front/exceptions/WrongTokenException.php b/application/front/exceptions/WrongTokenException.php new file mode 100644 index 00000000..42002720 --- /dev/null +++ b/application/front/exceptions/WrongTokenException.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | /** | ||
8 | * Class OpenShaarliPasswordException | ||
9 | * | ||
10 | * Raised if the user tries to perform an action with an invalid XSRF token. | ||
11 | */ | ||
12 | class WrongTokenException extends ShaarliFrontException | ||
13 | { | ||
14 | public function __construct() | ||
15 | { | ||
16 | parent::__construct(t('Wrong token.'), 403); | ||
17 | } | ||
18 | } | ||
diff --git a/application/http/HttpAccess.php b/application/http/HttpAccess.php new file mode 100644 index 00000000..81d9e076 --- /dev/null +++ b/application/http/HttpAccess.php | |||
@@ -0,0 +1,39 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Http; | ||
6 | |||
7 | /** | ||
8 | * Class HttpAccess | ||
9 | * | ||
10 | * This is mostly an OOP wrapper for HTTP functions defined in `HttpUtils`. | ||
11 | * It is used as dependency injection in Shaarli's container. | ||
12 | * | ||
13 | * @package Shaarli\Http | ||
14 | */ | ||
15 | class HttpAccess | ||
16 | { | ||
17 | public function getHttpResponse($url, $timeout = 30, $maxBytes = 4194304, $curlWriteFunction = null) | ||
18 | { | ||
19 | return get_http_response($url, $timeout, $maxBytes, $curlWriteFunction); | ||
20 | } | ||
21 | |||
22 | public function getCurlDownloadCallback( | ||
23 | &$charset, | ||
24 | &$title, | ||
25 | &$description, | ||
26 | &$keywords, | ||
27 | $retrieveDescription, | ||
28 | $curlGetInfo = 'curl_getinfo' | ||
29 | ) { | ||
30 | return get_curl_download_callback( | ||
31 | $charset, | ||
32 | $title, | ||
33 | $description, | ||
34 | $keywords, | ||
35 | $retrieveDescription, | ||
36 | $curlGetInfo | ||
37 | ); | ||
38 | } | ||
39 | } | ||
diff --git a/application/http/HttpUtils.php b/application/http/HttpUtils.php index 2ea9195d..9f414073 100644 --- a/application/http/HttpUtils.php +++ b/application/http/HttpUtils.php | |||
@@ -369,7 +369,11 @@ function server_url($server) | |||
369 | */ | 369 | */ |
370 | function index_url($server) | 370 | function index_url($server) |
371 | { | 371 | { |
372 | $scriptname = $server['SCRIPT_NAME']; | 372 | if (defined('SHAARLI_ROOT_URL') && null !== SHAARLI_ROOT_URL) { |
373 | return rtrim(SHAARLI_ROOT_URL, '/') . '/'; | ||
374 | } | ||
375 | |||
376 | $scriptname = !empty($server['SCRIPT_NAME']) ? $server['SCRIPT_NAME'] : '/'; | ||
373 | if (endsWith($scriptname, 'index.php')) { | 377 | if (endsWith($scriptname, 'index.php')) { |
374 | $scriptname = substr($scriptname, 0, -9); | 378 | $scriptname = substr($scriptname, 0, -9); |
375 | } | 379 | } |
@@ -377,7 +381,7 @@ function index_url($server) | |||
377 | } | 381 | } |
378 | 382 | ||
379 | /** | 383 | /** |
380 | * Returns the absolute URL of the current script, with the query | 384 | * Returns the absolute URL of the current script, with current route and query |
381 | * | 385 | * |
382 | * If the resource is "index.php", then it is removed (for better-looking URLs) | 386 | * If the resource is "index.php", then it is removed (for better-looking URLs) |
383 | * | 387 | * |
@@ -387,10 +391,17 @@ function index_url($server) | |||
387 | */ | 391 | */ |
388 | function page_url($server) | 392 | function page_url($server) |
389 | { | 393 | { |
394 | $scriptname = $server['SCRIPT_NAME'] ?? ''; | ||
395 | if (endsWith($scriptname, 'index.php')) { | ||
396 | $scriptname = substr($scriptname, 0, -9); | ||
397 | } | ||
398 | |||
399 | $route = preg_replace('@^' . $scriptname . '@', '', $server['REQUEST_URI'] ?? ''); | ||
390 | if (! empty($server['QUERY_STRING'])) { | 400 | if (! empty($server['QUERY_STRING'])) { |
391 | return index_url($server).'?'.$server['QUERY_STRING']; | 401 | return index_url($server) . $route . '?' . $server['QUERY_STRING']; |
392 | } | 402 | } |
393 | return index_url($server); | 403 | |
404 | return index_url($server) . $route; | ||
394 | } | 405 | } |
395 | 406 | ||
396 | /** | 407 | /** |
@@ -477,3 +488,109 @@ function is_https($server) | |||
477 | 488 | ||
478 | return ! empty($server['HTTPS']); | 489 | return ! empty($server['HTTPS']); |
479 | } | 490 | } |
491 | |||
492 | /** | ||
493 | * Get cURL callback function for CURLOPT_WRITEFUNCTION | ||
494 | * | ||
495 | * @param string $charset to extract from the downloaded page (reference) | ||
496 | * @param string $title to extract from the downloaded page (reference) | ||
497 | * @param string $description to extract from the downloaded page (reference) | ||
498 | * @param string $keywords to extract from the downloaded page (reference) | ||
499 | * @param bool $retrieveDescription Automatically tries to retrieve description and keywords from HTML content | ||
500 | * @param string $curlGetInfo Optionally overrides curl_getinfo function | ||
501 | * | ||
502 | * @return Closure | ||
503 | */ | ||
504 | function get_curl_download_callback( | ||
505 | &$charset, | ||
506 | &$title, | ||
507 | &$description, | ||
508 | &$keywords, | ||
509 | $retrieveDescription, | ||
510 | $curlGetInfo = 'curl_getinfo' | ||
511 | ) { | ||
512 | $isRedirected = false; | ||
513 | $currentChunk = 0; | ||
514 | $foundChunk = null; | ||
515 | |||
516 | /** | ||
517 | * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download). | ||
518 | * | ||
519 | * While downloading the remote page, we check that the HTTP code is 200 and content type is 'html/text' | ||
520 | * Then we extract the title and the charset and stop the download when it's done. | ||
521 | * | ||
522 | * @param resource $ch cURL resource | ||
523 | * @param string $data chunk of data being downloaded | ||
524 | * | ||
525 | * @return int|bool length of $data or false if we need to stop the download | ||
526 | */ | ||
527 | return function (&$ch, $data) use ( | ||
528 | $retrieveDescription, | ||
529 | $curlGetInfo, | ||
530 | &$charset, | ||
531 | &$title, | ||
532 | &$description, | ||
533 | &$keywords, | ||
534 | &$isRedirected, | ||
535 | &$currentChunk, | ||
536 | &$foundChunk | ||
537 | ) { | ||
538 | $currentChunk++; | ||
539 | $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE); | ||
540 | if (!empty($responseCode) && in_array($responseCode, [301, 302])) { | ||
541 | $isRedirected = true; | ||
542 | return strlen($data); | ||
543 | } | ||
544 | if (!empty($responseCode) && $responseCode !== 200) { | ||
545 | return false; | ||
546 | } | ||
547 | // After a redirection, the content type will keep the previous request value | ||
548 | // until it finds the next content-type header. | ||
549 | if (! $isRedirected || strpos(strtolower($data), 'content-type') !== false) { | ||
550 | $contentType = $curlGetInfo($ch, CURLINFO_CONTENT_TYPE); | ||
551 | } | ||
552 | if (!empty($contentType) && strpos($contentType, 'text/html') === false) { | ||
553 | return false; | ||
554 | } | ||
555 | if (!empty($contentType) && empty($charset)) { | ||
556 | $charset = header_extract_charset($contentType); | ||
557 | } | ||
558 | if (empty($charset)) { | ||
559 | $charset = html_extract_charset($data); | ||
560 | } | ||
561 | if (empty($title)) { | ||
562 | $title = html_extract_title($data); | ||
563 | $foundChunk = ! empty($title) ? $currentChunk : $foundChunk; | ||
564 | } | ||
565 | if ($retrieveDescription && empty($description)) { | ||
566 | $description = html_extract_tag('description', $data); | ||
567 | $foundChunk = ! empty($description) ? $currentChunk : $foundChunk; | ||
568 | } | ||
569 | if ($retrieveDescription && empty($keywords)) { | ||
570 | $keywords = html_extract_tag('keywords', $data); | ||
571 | if (! empty($keywords)) { | ||
572 | $foundChunk = $currentChunk; | ||
573 | // Keywords use the format tag1, tag2 multiple words, tag | ||
574 | // So we format them to match Shaarli's separator and glue multiple words with '-' | ||
575 | $keywords = implode(' ', array_map(function($keyword) { | ||
576 | return implode('-', preg_split('/\s+/', trim($keyword))); | ||
577 | }, explode(',', $keywords))); | ||
578 | } | ||
579 | } | ||
580 | |||
581 | // We got everything we want, stop the download. | ||
582 | // If we already found either the title, description or keywords, | ||
583 | // it's highly unlikely that we'll found the other metas further than | ||
584 | // in the same chunk of data or the next one. So we also stop the download after that. | ||
585 | if ((!empty($responseCode) && !empty($contentType) && !empty($charset)) && $foundChunk !== null | ||
586 | && (! $retrieveDescription | ||
587 | || $foundChunk < $currentChunk | ||
588 | || (!empty($title) && !empty($description) && !empty($keywords)) | ||
589 | ) | ||
590 | ) { | ||
591 | return false; | ||
592 | } | ||
593 | |||
594 | return strlen($data); | ||
595 | }; | ||
596 | } | ||
diff --git a/application/http/UrlUtils.php b/application/http/UrlUtils.php index 4bc84b82..e8d1a283 100644 --- a/application/http/UrlUtils.php +++ b/application/http/UrlUtils.php | |||
@@ -73,7 +73,7 @@ function add_trailing_slash($url) | |||
73 | */ | 73 | */ |
74 | function whitelist_protocols($url, $protocols) | 74 | function whitelist_protocols($url, $protocols) |
75 | { | 75 | { |
76 | if (startsWith($url, '?') || startsWith($url, '/')) { | 76 | if (startsWith($url, '?') || startsWith($url, '/') || startsWith($url, '#')) { |
77 | return $url; | 77 | return $url; |
78 | } | 78 | } |
79 | $protocols = array_merge(['http', 'https'], $protocols); | 79 | $protocols = array_merge(['http', 'https'], $protocols); |
diff --git a/application/legacy/LegacyController.php b/application/legacy/LegacyController.php new file mode 100644 index 00000000..826604e7 --- /dev/null +++ b/application/legacy/LegacyController.php | |||
@@ -0,0 +1,162 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Legacy; | ||
6 | |||
7 | use Shaarli\Feed\FeedBuilder; | ||
8 | use Shaarli\Front\Controller\Visitor\ShaarliVisitorController; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * We use this to maintain legacy routes, and redirect requests to the corresponding Slim route. | ||
14 | * Only public routes, and both `?addlink` and `?post` were kept here. | ||
15 | * Other routes will just display the linklist. | ||
16 | * | ||
17 | * @deprecated | ||
18 | */ | ||
19 | class LegacyController extends ShaarliVisitorController | ||
20 | { | ||
21 | /** @var string[] Both `?post` and `?addlink` do not use `?do=` format. */ | ||
22 | public const LEGACY_GET_ROUTES = [ | ||
23 | 'post', | ||
24 | 'addlink', | ||
25 | ]; | ||
26 | |||
27 | /** | ||
28 | * This method will call `$action` method, which will redirect to corresponding Slim route. | ||
29 | */ | ||
30 | public function process(Request $request, Response $response, string $action): Response | ||
31 | { | ||
32 | if (!method_exists($this, $action)) { | ||
33 | throw new UnknowLegacyRouteException(); | ||
34 | } | ||
35 | |||
36 | return $this->{$action}($request, $response); | ||
37 | } | ||
38 | |||
39 | /** Legacy route: ?post= */ | ||
40 | public function post(Request $request, Response $response): Response | ||
41 | { | ||
42 | $route = '/admin/shaare'; | ||
43 | $buildParameters = function (?array $parameters, bool $encode) { | ||
44 | if ($encode) { | ||
45 | $parameters = array_map('urlencode', $parameters); | ||
46 | } | ||
47 | |||
48 | return count($parameters) > 0 ? '?' . http_build_query($parameters) : ''; | ||
49 | }; | ||
50 | |||
51 | |||
52 | if (!$this->container->loginManager->isLoggedIn()) { | ||
53 | $parameters = $buildParameters($request->getQueryParams(), true); | ||
54 | return $this->redirect($response, '/login?returnurl='. $this->getBasePath() . $route . $parameters); | ||
55 | } | ||
56 | |||
57 | $parameters = $buildParameters($request->getQueryParams(), false); | ||
58 | |||
59 | return $this->redirect($response, $route . $parameters); | ||
60 | } | ||
61 | |||
62 | /** Legacy route: ?addlink= */ | ||
63 | protected function addlink(Request $request, Response $response): Response | ||
64 | { | ||
65 | $route = '/admin/add-shaare'; | ||
66 | |||
67 | if (!$this->container->loginManager->isLoggedIn()) { | ||
68 | return $this->redirect($response, '/login?returnurl=' . $this->getBasePath() . $route); | ||
69 | } | ||
70 | |||
71 | return $this->redirect($response, $route); | ||
72 | } | ||
73 | |||
74 | /** Legacy route: ?do=login */ | ||
75 | protected function login(Request $request, Response $response): Response | ||
76 | { | ||
77 | $returnUrl = $request->getQueryParam('returnurl'); | ||
78 | |||
79 | return $this->redirect($response, '/login' . ($returnUrl ? '?returnurl=' . $returnUrl : '')); | ||
80 | } | ||
81 | |||
82 | /** Legacy route: ?do=logout */ | ||
83 | protected function logout(Request $request, Response $response): Response | ||
84 | { | ||
85 | return $this->redirect($response, '/admin/logout'); | ||
86 | } | ||
87 | |||
88 | /** Legacy route: ?do=picwall */ | ||
89 | protected function picwall(Request $request, Response $response): Response | ||
90 | { | ||
91 | return $this->redirect($response, '/picture-wall'); | ||
92 | } | ||
93 | |||
94 | /** Legacy route: ?do=tagcloud */ | ||
95 | protected function tagcloud(Request $request, Response $response): Response | ||
96 | { | ||
97 | return $this->redirect($response, '/tags/cloud'); | ||
98 | } | ||
99 | |||
100 | /** Legacy route: ?do=taglist */ | ||
101 | protected function taglist(Request $request, Response $response): Response | ||
102 | { | ||
103 | return $this->redirect($response, '/tags/list'); | ||
104 | } | ||
105 | |||
106 | /** Legacy route: ?do=daily */ | ||
107 | protected function daily(Request $request, Response $response): Response | ||
108 | { | ||
109 | $dayParam = !empty($request->getParam('day')) ? '?day=' . escape($request->getParam('day')) : ''; | ||
110 | |||
111 | return $this->redirect($response, '/daily' . $dayParam); | ||
112 | } | ||
113 | |||
114 | /** Legacy route: ?do=rss */ | ||
115 | protected function rss(Request $request, Response $response): Response | ||
116 | { | ||
117 | return $this->feed($request, $response, FeedBuilder::$FEED_RSS); | ||
118 | } | ||
119 | |||
120 | /** Legacy route: ?do=atom */ | ||
121 | protected function atom(Request $request, Response $response): Response | ||
122 | { | ||
123 | return $this->feed($request, $response, FeedBuilder::$FEED_ATOM); | ||
124 | } | ||
125 | |||
126 | /** Legacy route: ?do=opensearch */ | ||
127 | protected function opensearch(Request $request, Response $response): Response | ||
128 | { | ||
129 | return $this->redirect($response, '/open-search'); | ||
130 | } | ||
131 | |||
132 | /** Legacy route: ?do=dailyrss */ | ||
133 | protected function dailyrss(Request $request, Response $response): Response | ||
134 | { | ||
135 | return $this->redirect($response, '/daily-rss'); | ||
136 | } | ||
137 | |||
138 | /** Legacy route: ?do=feed */ | ||
139 | protected function feed(Request $request, Response $response, string $feedType): Response | ||
140 | { | ||
141 | $parameters = count($request->getQueryParams()) > 0 ? '?' . http_build_query($request->getQueryParams()) : ''; | ||
142 | |||
143 | return $this->redirect($response, '/feed/' . $feedType . $parameters); | ||
144 | } | ||
145 | |||
146 | /** Legacy route: ?do=configure */ | ||
147 | protected function configure(Request $request, Response $response): Response | ||
148 | { | ||
149 | $route = '/admin/configure'; | ||
150 | |||
151 | if (!$this->container->loginManager->isLoggedIn()) { | ||
152 | return $this->redirect($response, '/login?returnurl=' . $this->getBasePath() . $route); | ||
153 | } | ||
154 | |||
155 | return $this->redirect($response, $route); | ||
156 | } | ||
157 | |||
158 | protected function getBasePath(): string | ||
159 | { | ||
160 | return $this->container->basePath ?: ''; | ||
161 | } | ||
162 | } | ||
diff --git a/application/bookmark/LinkDB.php b/application/legacy/LegacyLinkDB.php index 76ba95f0..7bf76fd4 100644 --- a/application/bookmark/LinkDB.php +++ b/application/legacy/LegacyLinkDB.php | |||
@@ -1,17 +1,18 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | namespace Shaarli\Bookmark; | 3 | namespace Shaarli\Legacy; |
4 | 4 | ||
5 | use ArrayAccess; | 5 | use ArrayAccess; |
6 | use Countable; | 6 | use Countable; |
7 | use DateTime; | 7 | use DateTime; |
8 | use Iterator; | 8 | use Iterator; |
9 | use Shaarli\Bookmark\Exception\LinkNotFoundException; | 9 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; |
10 | use Shaarli\Exceptions\IOException; | 10 | use Shaarli\Exceptions\IOException; |
11 | use Shaarli\FileUtils; | 11 | use Shaarli\FileUtils; |
12 | use Shaarli\Render\PageCacheManager; | ||
12 | 13 | ||
13 | /** | 14 | /** |
14 | * Data storage for links. | 15 | * Data storage for bookmarks. |
15 | * | 16 | * |
16 | * This object behaves like an associative array. | 17 | * This object behaves like an associative array. |
17 | * | 18 | * |
@@ -29,8 +30,8 @@ use Shaarli\FileUtils; | |||
29 | * - private: Is this link private? 0=no, other value=yes | 30 | * - private: Is this link private? 0=no, other value=yes |
30 | * - tags: tags attached to this entry (separated by spaces) | 31 | * - tags: tags attached to this entry (separated by spaces) |
31 | * - title Title of the link | 32 | * - title Title of the link |
32 | * - url URL of the link. Used for displayable links. | 33 | * - url URL of the link. Used for displayable bookmarks. |
33 | * Can be absolute or relative in the database but the relative links | 34 | * Can be absolute or relative in the database but the relative bookmarks |
34 | * will be converted to absolute ones in templates. | 35 | * will be converted to absolute ones in templates. |
35 | * - real_url Raw URL in stored in the DB (absolute or relative). | 36 | * - real_url Raw URL in stored in the DB (absolute or relative). |
36 | * - shorturl Permalink smallhash | 37 | * - shorturl Permalink smallhash |
@@ -49,11 +50,13 @@ use Shaarli\FileUtils; | |||
49 | * Example: | 50 | * Example: |
50 | * - DB: link #1 (2010-01-01) link #2 (2016-01-01) | 51 | * - DB: link #1 (2010-01-01) link #2 (2016-01-01) |
51 | * - Order: #2 #1 | 52 | * - Order: #2 #1 |
52 | * - Import links containing: link #3 (2013-01-01) | 53 | * - Import bookmarks containing: link #3 (2013-01-01) |
53 | * - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01) | 54 | * - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01) |
54 | * - Real order: #2 #3 #1 | 55 | * - Real order: #2 #3 #1 |
56 | * | ||
57 | * @deprecated | ||
55 | */ | 58 | */ |
56 | class LinkDB implements Iterator, Countable, ArrayAccess | 59 | class LegacyLinkDB implements Iterator, Countable, ArrayAccess |
57 | { | 60 | { |
58 | // Links are stored as a PHP serialized string | 61 | // Links are stored as a PHP serialized string |
59 | private $datastore; | 62 | private $datastore; |
@@ -61,7 +64,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
61 | // Link date storage format | 64 | // Link date storage format |
62 | const LINK_DATE_FORMAT = 'Ymd_His'; | 65 | const LINK_DATE_FORMAT = 'Ymd_His'; |
63 | 66 | ||
64 | // List of links (associative array) | 67 | // List of bookmarks (associative array) |
65 | // - key: link date (e.g. "20110823_124546"), | 68 | // - key: link date (e.g. "20110823_124546"), |
66 | // - value: associative array (keys: title, description...) | 69 | // - value: associative array (keys: title, description...) |
67 | private $links; | 70 | private $links; |
@@ -71,7 +74,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
71 | private $urls; | 74 | private $urls; |
72 | 75 | ||
73 | /** | 76 | /** |
74 | * @var array List of all links IDS mapped with their array offset. | 77 | * @var array List of all bookmarks IDS mapped with their array offset. |
75 | * Map: id->offset. | 78 | * Map: id->offset. |
76 | */ | 79 | */ |
77 | protected $ids; | 80 | protected $ids; |
@@ -82,10 +85,10 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
82 | // Position in the $this->keys array (for the Iterator interface) | 85 | // Position in the $this->keys array (for the Iterator interface) |
83 | private $position; | 86 | private $position; |
84 | 87 | ||
85 | // Is the user logged in? (used to filter private links) | 88 | // Is the user logged in? (used to filter private bookmarks) |
86 | private $loggedIn; | 89 | private $loggedIn; |
87 | 90 | ||
88 | // Hide public links | 91 | // Hide public bookmarks |
89 | private $hidePublicLinks; | 92 | private $hidePublicLinks; |
90 | 93 | ||
91 | /** | 94 | /** |
@@ -95,7 +98,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
95 | * | 98 | * |
96 | * @param string $datastore datastore file path. | 99 | * @param string $datastore datastore file path. |
97 | * @param boolean $isLoggedIn is the user logged in? | 100 | * @param boolean $isLoggedIn is the user logged in? |
98 | * @param boolean $hidePublicLinks if true all links are private. | 101 | * @param boolean $hidePublicLinks if true all bookmarks are private. |
99 | */ | 102 | */ |
100 | public function __construct( | 103 | public function __construct( |
101 | $datastore, | 104 | $datastore, |
@@ -280,7 +283,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
280 | */ | 283 | */ |
281 | private function read() | 284 | private function read() |
282 | { | 285 | { |
283 | // Public links are hidden and user not logged in => nothing to show | 286 | // Public bookmarks are hidden and user not logged in => nothing to show |
284 | if ($this->hidePublicLinks && !$this->loggedIn) { | 287 | if ($this->hidePublicLinks && !$this->loggedIn) { |
285 | $this->links = array(); | 288 | $this->links = array(); |
286 | return; | 289 | return; |
@@ -310,7 +313,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
310 | 313 | ||
311 | $link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false; | 314 | $link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false; |
312 | 315 | ||
313 | // To be able to load links before running the update, and prepare the update | 316 | // To be able to load bookmarks before running the update, and prepare the update |
314 | if (!isset($link['created'])) { | 317 | if (!isset($link['created'])) { |
315 | $link['id'] = $link['linkdate']; | 318 | $link['id'] = $link['linkdate']; |
316 | $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']); | 319 | $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']); |
@@ -350,7 +353,8 @@ You use the community supported version of the original Shaarli project, by Seba | |||
350 | 353 | ||
351 | $this->write(); | 354 | $this->write(); |
352 | 355 | ||
353 | invalidateCaches($pageCacheDir); | 356 | $pageCacheManager = new PageCacheManager($pageCacheDir, $this->loggedIn); |
357 | $pageCacheManager->invalidateCaches(); | ||
354 | } | 358 | } |
355 | 359 | ||
356 | /** | 360 | /** |
@@ -375,13 +379,13 @@ You use the community supported version of the original Shaarli project, by Seba | |||
375 | * | 379 | * |
376 | * @return array $filtered array containing permalink data. | 380 | * @return array $filtered array containing permalink data. |
377 | * | 381 | * |
378 | * @throws LinkNotFoundException if the smallhash is malformed or doesn't match any link. | 382 | * @throws BookmarkNotFoundException if the smallhash is malformed or doesn't match any link. |
379 | */ | 383 | */ |
380 | public function filterHash($request) | 384 | public function filterHash($request) |
381 | { | 385 | { |
382 | $request = substr($request, 0, 6); | 386 | $request = substr($request, 0, 6); |
383 | $linkFilter = new LinkFilter($this->links); | 387 | $linkFilter = new LegacyLinkFilter($this->links); |
384 | return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request); | 388 | return $linkFilter->filter(LegacyLinkFilter::$FILTER_HASH, $request); |
385 | } | 389 | } |
386 | 390 | ||
387 | /** | 391 | /** |
@@ -393,21 +397,21 @@ You use the community supported version of the original Shaarli project, by Seba | |||
393 | */ | 397 | */ |
394 | public function filterDay($request) | 398 | public function filterDay($request) |
395 | { | 399 | { |
396 | $linkFilter = new LinkFilter($this->links); | 400 | $linkFilter = new LegacyLinkFilter($this->links); |
397 | return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request); | 401 | return $linkFilter->filter(LegacyLinkFilter::$FILTER_DAY, $request); |
398 | } | 402 | } |
399 | 403 | ||
400 | /** | 404 | /** |
401 | * Filter links according to search parameters. | 405 | * Filter bookmarks according to search parameters. |
402 | * | 406 | * |
403 | * @param array $filterRequest Search request content. Supported keys: | 407 | * @param array $filterRequest Search request content. Supported keys: |
404 | * - searchtags: list of tags | 408 | * - searchtags: list of tags |
405 | * - searchterm: term search | 409 | * - searchterm: term search |
406 | * @param bool $casesensitive Optional: Perform case sensitive filter | 410 | * @param bool $casesensitive Optional: Perform case sensitive filter |
407 | * @param string $visibility return only all/private/public links | 411 | * @param string $visibility return only all/private/public bookmarks |
408 | * @param bool $untaggedonly return only untagged links | 412 | * @param bool $untaggedonly return only untagged bookmarks |
409 | * | 413 | * |
410 | * @return array filtered links, all links if no suitable filter was provided. | 414 | * @return array filtered bookmarks, all bookmarks if no suitable filter was provided. |
411 | */ | 415 | */ |
412 | public function filterSearch( | 416 | public function filterSearch( |
413 | $filterRequest = array(), | 417 | $filterRequest = array(), |
@@ -420,19 +424,19 @@ You use the community supported version of the original Shaarli project, by Seba | |||
420 | $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; | 424 | $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; |
421 | $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; | 425 | $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; |
422 | 426 | ||
423 | // Search tags + fullsearch - blank string parameter will return all links. | 427 | // Search tags + fullsearch - blank string parameter will return all bookmarks. |
424 | $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; // == "vuotext" | 428 | $type = LegacyLinkFilter::$FILTER_TAG | LegacyLinkFilter::$FILTER_TEXT; // == "vuotext" |
425 | $request = [$searchtags, $searchterm]; | 429 | $request = [$searchtags, $searchterm]; |
426 | 430 | ||
427 | $linkFilter = new LinkFilter($this); | 431 | $linkFilter = new LegacyLinkFilter($this); |
428 | return $linkFilter->filter($type, $request, $casesensitive, $visibility, $untaggedonly); | 432 | return $linkFilter->filter($type, $request, $casesensitive, $visibility, $untaggedonly); |
429 | } | 433 | } |
430 | 434 | ||
431 | /** | 435 | /** |
432 | * Returns the list tags appearing in the links with the given tags | 436 | * Returns the list tags appearing in the bookmarks with the given tags |
433 | * | 437 | * |
434 | * @param array $filteringTags tags selecting the links to consider | 438 | * @param array $filteringTags tags selecting the bookmarks to consider |
435 | * @param string $visibility process only all/private/public links | 439 | * @param string $visibility process only all/private/public bookmarks |
436 | * | 440 | * |
437 | * @return array tag => linksCount | 441 | * @return array tag => linksCount |
438 | */ | 442 | */ |
@@ -471,12 +475,12 @@ You use the community supported version of the original Shaarli project, by Seba | |||
471 | } | 475 | } |
472 | 476 | ||
473 | /** | 477 | /** |
474 | * Rename or delete a tag across all links. | 478 | * Rename or delete a tag across all bookmarks. |
475 | * | 479 | * |
476 | * @param string $from Tag to rename | 480 | * @param string $from Tag to rename |
477 | * @param string $to New tag. If none is provided, the from tag will be deleted | 481 | * @param string $to New tag. If none is provided, the from tag will be deleted |
478 | * | 482 | * |
479 | * @return array|bool List of altered links or false on error | 483 | * @return array|bool List of altered bookmarks or false on error |
480 | */ | 484 | */ |
481 | public function renameTag($from, $to) | 485 | public function renameTag($from, $to) |
482 | { | 486 | { |
@@ -519,7 +523,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
519 | } | 523 | } |
520 | 524 | ||
521 | /** | 525 | /** |
522 | * Reorder links by creation date (newest first). | 526 | * Reorder bookmarks by creation date (newest first). |
523 | * | 527 | * |
524 | * Also update the urls and ids mapping arrays. | 528 | * Also update the urls and ids mapping arrays. |
525 | * | 529 | * |
@@ -533,6 +537,9 @@ You use the community supported version of the original Shaarli project, by Seba | |||
533 | if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) { | 537 | if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) { |
534 | return $a['sticky'] ? -1 : 1; | 538 | return $a['sticky'] ? -1 : 1; |
535 | } | 539 | } |
540 | if ($a['created'] == $b['created']) { | ||
541 | return $a['id'] < $b['id'] ? 1 * $order : -1 * $order; | ||
542 | } | ||
536 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; | 543 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; |
537 | }); | 544 | }); |
538 | 545 | ||
@@ -559,7 +566,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
559 | } | 566 | } |
560 | 567 | ||
561 | /** | 568 | /** |
562 | * Returns a link offset in links array from its unique ID. | 569 | * Returns a link offset in bookmarks array from its unique ID. |
563 | * | 570 | * |
564 | * @param int $id Persistent ID of a link. | 571 | * @param int $id Persistent ID of a link. |
565 | * | 572 | * |
diff --git a/application/bookmark/LinkFilter.php b/application/legacy/LegacyLinkFilter.php index 9b966307..7cf93d60 100644 --- a/application/bookmark/LinkFilter.php +++ b/application/legacy/LegacyLinkFilter.php | |||
@@ -1,16 +1,18 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | namespace Shaarli\Bookmark; | 3 | namespace Shaarli\Legacy; |
4 | 4 | ||
5 | use Exception; | 5 | use Exception; |
6 | use Shaarli\Bookmark\Exception\LinkNotFoundException; | 6 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; |
7 | 7 | ||
8 | /** | 8 | /** |
9 | * Class LinkFilter. | 9 | * Class LinkFilter. |
10 | * | 10 | * |
11 | * Perform search and filter operation on link data list. | 11 | * Perform search and filter operation on link data list. |
12 | * | ||
13 | * @deprecated | ||
12 | */ | 14 | */ |
13 | class LinkFilter | 15 | class LegacyLinkFilter |
14 | { | 16 | { |
15 | /** | 17 | /** |
16 | * @var string permalinks. | 18 | * @var string permalinks. |
@@ -38,12 +40,12 @@ class LinkFilter | |||
38 | public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}'; | 40 | public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}'; |
39 | 41 | ||
40 | /** | 42 | /** |
41 | * @var LinkDB all available links. | 43 | * @var LegacyLinkDB all available links. |
42 | */ | 44 | */ |
43 | private $links; | 45 | private $links; |
44 | 46 | ||
45 | /** | 47 | /** |
46 | * @param LinkDB $links initialization. | 48 | * @param LegacyLinkDB $links initialization. |
47 | */ | 49 | */ |
48 | public function __construct($links) | 50 | public function __construct($links) |
49 | { | 51 | { |
@@ -84,10 +86,10 @@ class LinkFilter | |||
84 | $filtered = $this->links; | 86 | $filtered = $this->links; |
85 | } | 87 | } |
86 | if (!empty($request[0])) { | 88 | if (!empty($request[0])) { |
87 | $filtered = (new LinkFilter($filtered))->filterTags($request[0], $casesensitive, $visibility); | 89 | $filtered = (new LegacyLinkFilter($filtered))->filterTags($request[0], $casesensitive, $visibility); |
88 | } | 90 | } |
89 | if (!empty($request[1])) { | 91 | if (!empty($request[1])) { |
90 | $filtered = (new LinkFilter($filtered))->filterFulltext($request[1], $visibility); | 92 | $filtered = (new LegacyLinkFilter($filtered))->filterFulltext($request[1], $visibility); |
91 | } | 93 | } |
92 | return $filtered; | 94 | return $filtered; |
93 | case self::$FILTER_TEXT: | 95 | case self::$FILTER_TEXT: |
@@ -137,7 +139,7 @@ class LinkFilter | |||
137 | * | 139 | * |
138 | * @return array $filtered array containing permalink data. | 140 | * @return array $filtered array containing permalink data. |
139 | * | 141 | * |
140 | * @throws \Shaarli\Bookmark\Exception\LinkNotFoundException if the smallhash doesn't match any link. | 142 | * @throws BookmarkNotFoundException if the smallhash doesn't match any link. |
141 | */ | 143 | */ |
142 | private function filterSmallHash($smallHash) | 144 | private function filterSmallHash($smallHash) |
143 | { | 145 | { |
@@ -151,7 +153,7 @@ class LinkFilter | |||
151 | } | 153 | } |
152 | 154 | ||
153 | if (empty($filtered)) { | 155 | if (empty($filtered)) { |
154 | throw new LinkNotFoundException(); | 156 | throw new BookmarkNotFoundException(); |
155 | } | 157 | } |
156 | 158 | ||
157 | return $filtered; | 159 | return $filtered; |
diff --git a/application/legacy/LegacyRouter.php b/application/legacy/LegacyRouter.php new file mode 100644 index 00000000..0449c7e1 --- /dev/null +++ b/application/legacy/LegacyRouter.php | |||
@@ -0,0 +1,63 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Legacy; | ||
4 | |||
5 | /** | ||
6 | * Class Router | ||
7 | * | ||
8 | * (only displayable pages here) | ||
9 | * | ||
10 | * @deprecated | ||
11 | */ | ||
12 | class LegacyRouter | ||
13 | { | ||
14 | public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update'; | ||
15 | |||
16 | public static $PAGE_LOGIN = 'login'; | ||
17 | |||
18 | public static $PAGE_PICWALL = 'picwall'; | ||
19 | |||
20 | public static $PAGE_TAGCLOUD = 'tag.cloud'; | ||
21 | |||
22 | public static $PAGE_TAGLIST = 'tag.list'; | ||
23 | |||
24 | public static $PAGE_DAILY = 'daily'; | ||
25 | |||
26 | public static $PAGE_FEED_ATOM = 'feed.atom'; | ||
27 | |||
28 | public static $PAGE_FEED_RSS = 'feed.rss'; | ||
29 | |||
30 | public static $PAGE_TOOLS = 'tools'; | ||
31 | |||
32 | public static $PAGE_CHANGEPASSWORD = 'changepasswd'; | ||
33 | |||
34 | public static $PAGE_CONFIGURE = 'configure'; | ||
35 | |||
36 | public static $PAGE_CHANGETAG = 'changetag'; | ||
37 | |||
38 | public static $PAGE_ADDLINK = 'addlink'; | ||
39 | |||
40 | public static $PAGE_EDITLINK = 'editlink'; | ||
41 | |||
42 | public static $PAGE_DELETELINK = 'delete_link'; | ||
43 | |||
44 | public static $PAGE_CHANGE_VISIBILITY = 'change_visibility'; | ||
45 | |||
46 | public static $PAGE_PINLINK = 'pin'; | ||
47 | |||
48 | public static $PAGE_EXPORT = 'export'; | ||
49 | |||
50 | public static $PAGE_IMPORT = 'import'; | ||
51 | |||
52 | public static $PAGE_OPENSEARCH = 'opensearch'; | ||
53 | |||
54 | public static $PAGE_LINKLIST = 'linklist'; | ||
55 | |||
56 | public static $PAGE_PLUGINSADMIN = 'pluginadmin'; | ||
57 | |||
58 | public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin'; | ||
59 | |||
60 | public static $PAGE_THUMBS_UPDATE = 'thumbs_update'; | ||
61 | |||
62 | public static $GET_TOKEN = 'token'; | ||
63 | } | ||
diff --git a/application/legacy/LegacyUpdater.php b/application/legacy/LegacyUpdater.php new file mode 100644 index 00000000..0ab3a55b --- /dev/null +++ b/application/legacy/LegacyUpdater.php | |||
@@ -0,0 +1,618 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Legacy; | ||
4 | |||
5 | use Exception; | ||
6 | use RainTPL; | ||
7 | use ReflectionClass; | ||
8 | use ReflectionException; | ||
9 | use ReflectionMethod; | ||
10 | use Shaarli\ApplicationUtils; | ||
11 | use Shaarli\Bookmark\Bookmark; | ||
12 | use Shaarli\Bookmark\BookmarkArray; | ||
13 | use Shaarli\Bookmark\BookmarkFilter; | ||
14 | use Shaarli\Bookmark\BookmarkIO; | ||
15 | use Shaarli\Bookmark\LinkDB; | ||
16 | use Shaarli\Config\ConfigJson; | ||
17 | use Shaarli\Config\ConfigManager; | ||
18 | use Shaarli\Config\ConfigPhp; | ||
19 | use Shaarli\Exceptions\IOException; | ||
20 | use Shaarli\Thumbnailer; | ||
21 | use Shaarli\Updater\Exception\UpdaterException; | ||
22 | |||
23 | /** | ||
24 | * Class updater. | ||
25 | * Used to update stuff when a new Shaarli's version is reached. | ||
26 | * Update methods are ran only once, and the stored in a JSON file. | ||
27 | * | ||
28 | * @deprecated | ||
29 | */ | ||
30 | class LegacyUpdater | ||
31 | { | ||
32 | /** | ||
33 | * @var array Updates which are already done. | ||
34 | */ | ||
35 | protected $doneUpdates; | ||
36 | |||
37 | /** | ||
38 | * @var LegacyLinkDB instance. | ||
39 | */ | ||
40 | protected $linkDB; | ||
41 | |||
42 | /** | ||
43 | * @var ConfigManager $conf Configuration Manager instance. | ||
44 | */ | ||
45 | protected $conf; | ||
46 | |||
47 | /** | ||
48 | * @var bool True if the user is logged in, false otherwise. | ||
49 | */ | ||
50 | protected $isLoggedIn; | ||
51 | |||
52 | /** | ||
53 | * @var array $_SESSION | ||
54 | */ | ||
55 | protected $session; | ||
56 | |||
57 | /** | ||
58 | * @var ReflectionMethod[] List of current class methods. | ||
59 | */ | ||
60 | protected $methods; | ||
61 | |||
62 | /** | ||
63 | * Object constructor. | ||
64 | * | ||
65 | * @param array $doneUpdates Updates which are already done. | ||
66 | * @param LegacyLinkDB $linkDB LinkDB instance. | ||
67 | * @param ConfigManager $conf Configuration Manager instance. | ||
68 | * @param boolean $isLoggedIn True if the user is logged in. | ||
69 | * @param array $session $_SESSION (by reference) | ||
70 | * | ||
71 | * @throws ReflectionException | ||
72 | */ | ||
73 | public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn, &$session = []) | ||
74 | { | ||
75 | $this->doneUpdates = $doneUpdates; | ||
76 | $this->linkDB = $linkDB; | ||
77 | $this->conf = $conf; | ||
78 | $this->isLoggedIn = $isLoggedIn; | ||
79 | $this->session = &$session; | ||
80 | |||
81 | // Retrieve all update methods. | ||
82 | $class = new ReflectionClass($this); | ||
83 | $this->methods = $class->getMethods(); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * Run all new updates. | ||
88 | * Update methods have to start with 'updateMethod' and return true (on success). | ||
89 | * | ||
90 | * @return array An array containing ran updates. | ||
91 | * | ||
92 | * @throws UpdaterException If something went wrong. | ||
93 | */ | ||
94 | public function update() | ||
95 | { | ||
96 | $updatesRan = array(); | ||
97 | |||
98 | // If the user isn't logged in, exit without updating. | ||
99 | if ($this->isLoggedIn !== true) { | ||
100 | return $updatesRan; | ||
101 | } | ||
102 | |||
103 | if ($this->methods === null) { | ||
104 | throw new UpdaterException(t('Couldn\'t retrieve updater class methods.')); | ||
105 | } | ||
106 | |||
107 | foreach ($this->methods as $method) { | ||
108 | // Not an update method or already done, pass. | ||
109 | if (!startsWith($method->getName(), 'updateMethod') | ||
110 | || in_array($method->getName(), $this->doneUpdates) | ||
111 | ) { | ||
112 | continue; | ||
113 | } | ||
114 | |||
115 | try { | ||
116 | $method->setAccessible(true); | ||
117 | $res = $method->invoke($this); | ||
118 | // Update method must return true to be considered processed. | ||
119 | if ($res === true) { | ||
120 | $updatesRan[] = $method->getName(); | ||
121 | } | ||
122 | } catch (Exception $e) { | ||
123 | throw new UpdaterException($method, $e); | ||
124 | } | ||
125 | } | ||
126 | |||
127 | $this->doneUpdates = array_merge($this->doneUpdates, $updatesRan); | ||
128 | |||
129 | return $updatesRan; | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * @return array Updates methods already processed. | ||
134 | */ | ||
135 | public function getDoneUpdates() | ||
136 | { | ||
137 | return $this->doneUpdates; | ||
138 | } | ||
139 | |||
140 | /** | ||
141 | * Move deprecated options.php to config.php. | ||
142 | * | ||
143 | * Milestone 0.9 (old versioning) - shaarli/Shaarli#41: | ||
144 | * options.php is not supported anymore. | ||
145 | */ | ||
146 | public function updateMethodMergeDeprecatedConfigFile() | ||
147 | { | ||
148 | if (is_file($this->conf->get('resource.data_dir') . '/options.php')) { | ||
149 | include $this->conf->get('resource.data_dir') . '/options.php'; | ||
150 | |||
151 | // Load GLOBALS into config | ||
152 | $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS); | ||
153 | $allowedKeys[] = 'config'; | ||
154 | foreach ($GLOBALS as $key => $value) { | ||
155 | if (in_array($key, $allowedKeys)) { | ||
156 | $this->conf->set($key, $value); | ||
157 | } | ||
158 | } | ||
159 | $this->conf->write($this->isLoggedIn); | ||
160 | unlink($this->conf->get('resource.data_dir') . '/options.php'); | ||
161 | } | ||
162 | |||
163 | return true; | ||
164 | } | ||
165 | |||
166 | /** | ||
167 | * Move old configuration in PHP to the new config system in JSON format. | ||
168 | * | ||
169 | * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'. | ||
170 | * It will also convert legacy setting keys to the new ones. | ||
171 | */ | ||
172 | public function updateMethodConfigToJson() | ||
173 | { | ||
174 | // JSON config already exists, nothing to do. | ||
175 | if ($this->conf->getConfigIO() instanceof ConfigJson) { | ||
176 | return true; | ||
177 | } | ||
178 | |||
179 | $configPhp = new ConfigPhp(); | ||
180 | $configJson = new ConfigJson(); | ||
181 | $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php'); | ||
182 | rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php'); | ||
183 | $this->conf->setConfigIO($configJson); | ||
184 | $this->conf->reload(); | ||
185 | |||
186 | $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING); | ||
187 | foreach (ConfigPhp::$ROOT_KEYS as $key) { | ||
188 | $this->conf->set($legacyMap[$key], $oldConfig[$key]); | ||
189 | } | ||
190 | |||
191 | // Set sub config keys (config and plugins) | ||
192 | $subConfig = array('config', 'plugins'); | ||
193 | foreach ($subConfig as $sub) { | ||
194 | foreach ($oldConfig[$sub] as $key => $value) { | ||
195 | if (isset($legacyMap[$sub . '.' . $key])) { | ||
196 | $configKey = $legacyMap[$sub . '.' . $key]; | ||
197 | } else { | ||
198 | $configKey = $sub . '.' . $key; | ||
199 | } | ||
200 | $this->conf->set($configKey, $value); | ||
201 | } | ||
202 | } | ||
203 | |||
204 | try { | ||
205 | $this->conf->write($this->isLoggedIn); | ||
206 | return true; | ||
207 | } catch (IOException $e) { | ||
208 | error_log($e->getMessage()); | ||
209 | return false; | ||
210 | } | ||
211 | } | ||
212 | |||
213 | /** | ||
214 | * Escape settings which have been manually escaped in every request in previous versions: | ||
215 | * - general.title | ||
216 | * - general.header_link | ||
217 | * - redirector.url | ||
218 | * | ||
219 | * @return bool true if the update is successful, false otherwise. | ||
220 | */ | ||
221 | public function updateMethodEscapeUnescapedConfig() | ||
222 | { | ||
223 | try { | ||
224 | $this->conf->set('general.title', escape($this->conf->get('general.title'))); | ||
225 | $this->conf->set('general.header_link', escape($this->conf->get('general.header_link'))); | ||
226 | $this->conf->write($this->isLoggedIn); | ||
227 | } catch (Exception $e) { | ||
228 | error_log($e->getMessage()); | ||
229 | return false; | ||
230 | } | ||
231 | return true; | ||
232 | } | ||
233 | |||
234 | /** | ||
235 | * Update the database to use the new ID system, which replaces linkdate primary keys. | ||
236 | * Also, creation and update dates are now DateTime objects (done by LinkDB). | ||
237 | * | ||
238 | * Since this update is very sensitve (changing the whole database), the datastore will be | ||
239 | * automatically backed up into the file datastore.<datetime>.php. | ||
240 | * | ||
241 | * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash), | ||
242 | * which will be saved by this method. | ||
243 | * | ||
244 | * @return bool true if the update is successful, false otherwise. | ||
245 | */ | ||
246 | public function updateMethodDatastoreIds() | ||
247 | { | ||
248 | $first = 'update'; | ||
249 | foreach ($this->linkDB as $key => $link) { | ||
250 | $first = $key; | ||
251 | break; | ||
252 | } | ||
253 | |||
254 | // up to date database | ||
255 | if (is_int($first)) { | ||
256 | return true; | ||
257 | } | ||
258 | |||
259 | $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '.php'; | ||
260 | copy($this->conf->get('resource.datastore'), $save); | ||
261 | |||
262 | $links = array(); | ||
263 | foreach ($this->linkDB as $offset => $value) { | ||
264 | $links[] = $value; | ||
265 | unset($this->linkDB[$offset]); | ||
266 | } | ||
267 | $links = array_reverse($links); | ||
268 | $cpt = 0; | ||
269 | foreach ($links as $l) { | ||
270 | unset($l['linkdate']); | ||
271 | $l['id'] = $cpt; | ||
272 | $this->linkDB[$cpt++] = $l; | ||
273 | } | ||
274 | |||
275 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
276 | $this->linkDB->reorder(); | ||
277 | |||
278 | return true; | ||
279 | } | ||
280 | |||
281 | /** | ||
282 | * Rename tags starting with a '-' to work with tag exclusion search. | ||
283 | */ | ||
284 | public function updateMethodRenameDashTags() | ||
285 | { | ||
286 | $linklist = $this->linkDB->filterSearch(); | ||
287 | foreach ($linklist as $key => $link) { | ||
288 | $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); | ||
289 | $link['tags'] = implode(' ', array_unique(BookmarkFilter::tagsStrToArray($link['tags'], true))); | ||
290 | $this->linkDB[$key] = $link; | ||
291 | } | ||
292 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
293 | return true; | ||
294 | } | ||
295 | |||
296 | /** | ||
297 | * Initialize API settings: | ||
298 | * - api.enabled: true | ||
299 | * - api.secret: generated secret | ||
300 | */ | ||
301 | public function updateMethodApiSettings() | ||
302 | { | ||
303 | if ($this->conf->exists('api.secret')) { | ||
304 | return true; | ||
305 | } | ||
306 | |||
307 | $this->conf->set('api.enabled', true); | ||
308 | $this->conf->set( | ||
309 | 'api.secret', | ||
310 | generate_api_secret( | ||
311 | $this->conf->get('credentials.login'), | ||
312 | $this->conf->get('credentials.salt') | ||
313 | ) | ||
314 | ); | ||
315 | $this->conf->write($this->isLoggedIn); | ||
316 | return true; | ||
317 | } | ||
318 | |||
319 | /** | ||
320 | * New setting: theme name. If the default theme is used, nothing to do. | ||
321 | * | ||
322 | * If the user uses a custom theme, raintpl_tpl dir is updated to the parent directory, | ||
323 | * and the current theme is set as default in the theme setting. | ||
324 | * | ||
325 | * @return bool true if the update is successful, false otherwise. | ||
326 | */ | ||
327 | public function updateMethodDefaultTheme() | ||
328 | { | ||
329 | // raintpl_tpl isn't the root template directory anymore. | ||
330 | // We run the update only if this folder still contains the template files. | ||
331 | $tplDir = $this->conf->get('resource.raintpl_tpl'); | ||
332 | $tplFile = $tplDir . '/linklist.html'; | ||
333 | if (!file_exists($tplFile)) { | ||
334 | return true; | ||
335 | } | ||
336 | |||
337 | $parent = dirname($tplDir); | ||
338 | $this->conf->set('resource.raintpl_tpl', $parent); | ||
339 | $this->conf->set('resource.theme', trim(str_replace($parent, '', $tplDir), '/')); | ||
340 | $this->conf->write($this->isLoggedIn); | ||
341 | |||
342 | // Dependency injection gore | ||
343 | RainTPL::$tpl_dir = $tplDir; | ||
344 | |||
345 | return true; | ||
346 | } | ||
347 | |||
348 | /** | ||
349 | * Move the file to inc/user.css to data/user.css. | ||
350 | * | ||
351 | * Note: Due to hardcoded paths, it's not unit testable. But one line of code should be fine. | ||
352 | * | ||
353 | * @return bool true if the update is successful, false otherwise. | ||
354 | */ | ||
355 | public function updateMethodMoveUserCss() | ||
356 | { | ||
357 | if (!is_file('inc/user.css')) { | ||
358 | return true; | ||
359 | } | ||
360 | |||
361 | return rename('inc/user.css', 'data/user.css'); | ||
362 | } | ||
363 | |||
364 | /** | ||
365 | * * `markdown_escape` is a new setting, set to true as default. | ||
366 | * | ||
367 | * If the markdown plugin was already enabled, escaping is disabled to avoid | ||
368 | * breaking existing entries. | ||
369 | */ | ||
370 | public function updateMethodEscapeMarkdown() | ||
371 | { | ||
372 | if ($this->conf->exists('security.markdown_escape')) { | ||
373 | return true; | ||
374 | } | ||
375 | |||
376 | if (in_array('markdown', $this->conf->get('general.enabled_plugins'))) { | ||
377 | $this->conf->set('security.markdown_escape', false); | ||
378 | } else { | ||
379 | $this->conf->set('security.markdown_escape', true); | ||
380 | } | ||
381 | $this->conf->write($this->isLoggedIn); | ||
382 | |||
383 | return true; | ||
384 | } | ||
385 | |||
386 | /** | ||
387 | * Add 'http://' to Piwik URL the setting is set. | ||
388 | * | ||
389 | * @return bool true if the update is successful, false otherwise. | ||
390 | */ | ||
391 | public function updateMethodPiwikUrl() | ||
392 | { | ||
393 | if (!$this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) { | ||
394 | return true; | ||
395 | } | ||
396 | |||
397 | $this->conf->set('plugins.PIWIK_URL', 'http://' . $this->conf->get('plugins.PIWIK_URL')); | ||
398 | $this->conf->write($this->isLoggedIn); | ||
399 | |||
400 | return true; | ||
401 | } | ||
402 | |||
403 | /** | ||
404 | * Use ATOM feed as default. | ||
405 | */ | ||
406 | public function updateMethodAtomDefault() | ||
407 | { | ||
408 | if (!$this->conf->exists('feed.show_atom') || $this->conf->get('feed.show_atom') === true) { | ||
409 | return true; | ||
410 | } | ||
411 | |||
412 | $this->conf->set('feed.show_atom', true); | ||
413 | $this->conf->write($this->isLoggedIn); | ||
414 | |||
415 | return true; | ||
416 | } | ||
417 | |||
418 | /** | ||
419 | * Update updates.check_updates_branch setting. | ||
420 | * | ||
421 | * If the current major version digit matches the latest branch | ||
422 | * major version digit, we set the branch to `latest`, | ||
423 | * otherwise we'll check updates on the `stable` branch. | ||
424 | * | ||
425 | * No update required for the dev version. | ||
426 | * | ||
427 | * Note: due to hardcoded URL and lack of dependency injection, this is not unit testable. | ||
428 | * | ||
429 | * FIXME! This needs to be removed when we switch to first digit major version | ||
430 | * instead of the second one since the versionning process will change. | ||
431 | */ | ||
432 | public function updateMethodCheckUpdateRemoteBranch() | ||
433 | { | ||
434 | if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') { | ||
435 | return true; | ||
436 | } | ||
437 | |||
438 | // Get latest branch major version digit | ||
439 | $latestVersion = ApplicationUtils::getLatestGitVersionCode( | ||
440 | 'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php', | ||
441 | 5 | ||
442 | ); | ||
443 | if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) { | ||
444 | return false; | ||
445 | } | ||
446 | $latestMajor = $matches[1]; | ||
447 | |||
448 | // Get current major version digit | ||
449 | preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches); | ||
450 | $currentMajor = $matches[1]; | ||
451 | |||
452 | if ($currentMajor === $latestMajor) { | ||
453 | $branch = 'latest'; | ||
454 | } else { | ||
455 | $branch = 'stable'; | ||
456 | } | ||
457 | $this->conf->set('updates.check_updates_branch', $branch); | ||
458 | $this->conf->write($this->isLoggedIn); | ||
459 | return true; | ||
460 | } | ||
461 | |||
462 | /** | ||
463 | * Reset history store file due to date format change. | ||
464 | */ | ||
465 | public function updateMethodResetHistoryFile() | ||
466 | { | ||
467 | if (is_file($this->conf->get('resource.history'))) { | ||
468 | unlink($this->conf->get('resource.history')); | ||
469 | } | ||
470 | return true; | ||
471 | } | ||
472 | |||
473 | /** | ||
474 | * Save the datastore -> the link order is now applied when bookmarks are saved. | ||
475 | */ | ||
476 | public function updateMethodReorderDatastore() | ||
477 | { | ||
478 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
479 | return true; | ||
480 | } | ||
481 | |||
482 | /** | ||
483 | * Change privateonly session key to visibility. | ||
484 | */ | ||
485 | public function updateMethodVisibilitySession() | ||
486 | { | ||
487 | if (isset($_SESSION['privateonly'])) { | ||
488 | unset($_SESSION['privateonly']); | ||
489 | $_SESSION['visibility'] = 'private'; | ||
490 | } | ||
491 | return true; | ||
492 | } | ||
493 | |||
494 | /** | ||
495 | * Add download size and timeout to the configuration file | ||
496 | * | ||
497 | * @return bool true if the update is successful, false otherwise. | ||
498 | */ | ||
499 | public function updateMethodDownloadSizeAndTimeoutConf() | ||
500 | { | ||
501 | if ($this->conf->exists('general.download_max_size') | ||
502 | && $this->conf->exists('general.download_timeout') | ||
503 | ) { | ||
504 | return true; | ||
505 | } | ||
506 | |||
507 | if (!$this->conf->exists('general.download_max_size')) { | ||
508 | $this->conf->set('general.download_max_size', 1024 * 1024 * 4); | ||
509 | } | ||
510 | |||
511 | if (!$this->conf->exists('general.download_timeout')) { | ||
512 | $this->conf->set('general.download_timeout', 30); | ||
513 | } | ||
514 | |||
515 | $this->conf->write($this->isLoggedIn); | ||
516 | return true; | ||
517 | } | ||
518 | |||
519 | /** | ||
520 | * * Move thumbnails management to WebThumbnailer, coming with new settings. | ||
521 | */ | ||
522 | public function updateMethodWebThumbnailer() | ||
523 | { | ||
524 | if ($this->conf->exists('thumbnails.mode')) { | ||
525 | return true; | ||
526 | } | ||
527 | |||
528 | $thumbnailsEnabled = extension_loaded('gd') && $this->conf->get('thumbnail.enable_thumbnails', true); | ||
529 | $this->conf->set('thumbnails.mode', $thumbnailsEnabled ? Thumbnailer::MODE_ALL : Thumbnailer::MODE_NONE); | ||
530 | $this->conf->set('thumbnails.width', 125); | ||
531 | $this->conf->set('thumbnails.height', 90); | ||
532 | $this->conf->remove('thumbnail'); | ||
533 | $this->conf->write(true); | ||
534 | |||
535 | if ($thumbnailsEnabled) { | ||
536 | $this->session['warnings'][] = t( | ||
537 | t('You have enabled or changed thumbnails mode.') . | ||
538 | '<a href="./admin/thumbnails">' . t('Please synchronize them.') . '</a>' | ||
539 | ); | ||
540 | } | ||
541 | |||
542 | return true; | ||
543 | } | ||
544 | |||
545 | /** | ||
546 | * Set sticky = false on all bookmarks | ||
547 | * | ||
548 | * @return bool true if the update is successful, false otherwise. | ||
549 | */ | ||
550 | public function updateMethodSetSticky() | ||
551 | { | ||
552 | foreach ($this->linkDB as $key => $link) { | ||
553 | if (isset($link['sticky'])) { | ||
554 | return true; | ||
555 | } | ||
556 | $link['sticky'] = false; | ||
557 | $this->linkDB[$key] = $link; | ||
558 | } | ||
559 | |||
560 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
561 | |||
562 | return true; | ||
563 | } | ||
564 | |||
565 | /** | ||
566 | * Remove redirector settings. | ||
567 | */ | ||
568 | public function updateMethodRemoveRedirector() | ||
569 | { | ||
570 | $this->conf->remove('redirector'); | ||
571 | $this->conf->write(true); | ||
572 | return true; | ||
573 | } | ||
574 | |||
575 | /** | ||
576 | * Migrate the legacy arrays to Bookmark objects. | ||
577 | * Also make a backup of the datastore. | ||
578 | */ | ||
579 | public function updateMethodMigrateDatabase() | ||
580 | { | ||
581 | $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '_1.php'; | ||
582 | if (! copy($this->conf->get('resource.datastore'), $save)) { | ||
583 | die('Could not backup the datastore.'); | ||
584 | } | ||
585 | |||
586 | $linksArray = new BookmarkArray(); | ||
587 | foreach ($this->linkDB as $key => $link) { | ||
588 | $linksArray[$key] = (new Bookmark())->fromArray($link); | ||
589 | } | ||
590 | $linksIo = new BookmarkIO($this->conf); | ||
591 | $linksIo->write($linksArray); | ||
592 | |||
593 | return true; | ||
594 | } | ||
595 | |||
596 | /** | ||
597 | * Write the `formatter` setting in config file. | ||
598 | * Use markdown if the markdown plugin is enabled, the default one otherwise. | ||
599 | * Also remove markdown plugin setting as it is now integrated to the core. | ||
600 | */ | ||
601 | public function updateMethodFormatterSetting() | ||
602 | { | ||
603 | if (!$this->conf->exists('formatter') || $this->conf->get('formatter') === 'default') { | ||
604 | $enabledPlugins = $this->conf->get('general.enabled_plugins'); | ||
605 | if (($pos = array_search('markdown', $enabledPlugins)) !== false) { | ||
606 | $formatter = 'markdown'; | ||
607 | unset($enabledPlugins[$pos]); | ||
608 | $this->conf->set('general.enabled_plugins', array_values($enabledPlugins)); | ||
609 | } else { | ||
610 | $formatter = 'default'; | ||
611 | } | ||
612 | $this->conf->set('formatter', $formatter); | ||
613 | $this->conf->write(true); | ||
614 | } | ||
615 | |||
616 | return true; | ||
617 | } | ||
618 | } | ||
diff --git a/application/legacy/UnknowLegacyRouteException.php b/application/legacy/UnknowLegacyRouteException.php new file mode 100644 index 00000000..ae1518ad --- /dev/null +++ b/application/legacy/UnknowLegacyRouteException.php | |||
@@ -0,0 +1,9 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Legacy; | ||
6 | |||
7 | class UnknowLegacyRouteException extends \Exception | ||
8 | { | ||
9 | } | ||
diff --git a/application/netscape/NetscapeBookmarkUtils.php b/application/netscape/NetscapeBookmarkUtils.php index 28665941..b83f16f8 100644 --- a/application/netscape/NetscapeBookmarkUtils.php +++ b/application/netscape/NetscapeBookmarkUtils.php | |||
@@ -6,56 +6,69 @@ use DateTime; | |||
6 | use DateTimeZone; | 6 | use DateTimeZone; |
7 | use Exception; | 7 | use Exception; |
8 | use Katzgrau\KLogger\Logger; | 8 | use Katzgrau\KLogger\Logger; |
9 | use Psr\Http\Message\UploadedFileInterface; | ||
9 | use Psr\Log\LogLevel; | 10 | use Psr\Log\LogLevel; |
10 | use Shaarli\Bookmark\LinkDB; | 11 | use Shaarli\Bookmark\Bookmark; |
12 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
11 | use Shaarli\Config\ConfigManager; | 13 | use Shaarli\Config\ConfigManager; |
14 | use Shaarli\Formatter\BookmarkFormatter; | ||
12 | use Shaarli\History; | 15 | use Shaarli\History; |
13 | use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser; | 16 | use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser; |
14 | 17 | ||
15 | /** | 18 | /** |
16 | * Utilities to import and export bookmarks using the Netscape format | 19 | * Utilities to import and export bookmarks using the Netscape format |
17 | * TODO: Not static, use a container. | ||
18 | */ | 20 | */ |
19 | class NetscapeBookmarkUtils | 21 | class NetscapeBookmarkUtils |
20 | { | 22 | { |
23 | /** @var BookmarkServiceInterface */ | ||
24 | protected $bookmarkService; | ||
25 | |||
26 | /** @var ConfigManager */ | ||
27 | protected $conf; | ||
28 | |||
29 | /** @var History */ | ||
30 | protected $history; | ||
31 | |||
32 | public function __construct(BookmarkServiceInterface $bookmarkService, ConfigManager $conf, History $history) | ||
33 | { | ||
34 | $this->bookmarkService = $bookmarkService; | ||
35 | $this->conf = $conf; | ||
36 | $this->history = $history; | ||
37 | } | ||
21 | 38 | ||
22 | /** | 39 | /** |
23 | * Filters links and adds Netscape-formatted fields | 40 | * Filters bookmarks and adds Netscape-formatted fields |
24 | * | 41 | * |
25 | * Added fields: | 42 | * Added fields: |
26 | * - timestamp link addition date, using the Unix epoch format | 43 | * - timestamp link addition date, using the Unix epoch format |
27 | * - taglist comma-separated tag list | 44 | * - taglist comma-separated tag list |
28 | * | 45 | * |
29 | * @param LinkDB $linkDb Link datastore | 46 | * @param BookmarkFormatter $formatter instance |
30 | * @param string $selection Which links to export: (all|private|public) | 47 | * @param string $selection Which bookmarks to export: (all|private|public) |
31 | * @param bool $prependNoteUrl Prepend note permalinks with the server's URL | 48 | * @param bool $prependNoteUrl Prepend note permalinks with the server's URL |
32 | * @param string $indexUrl Absolute URL of the Shaarli index page | 49 | * @param string $indexUrl Absolute URL of the Shaarli index page |
33 | * | 50 | * |
34 | * @throws Exception Invalid export selection | 51 | * @return array The bookmarks to be exported, with additional fields |
35 | * | 52 | * |
36 | * @return array The links to be exported, with additional fields | 53 | * @throws Exception Invalid export selection |
37 | */ | 54 | */ |
38 | public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl) | 55 | public function filterAndFormat( |
39 | { | 56 | $formatter, |
57 | $selection, | ||
58 | $prependNoteUrl, | ||
59 | $indexUrl | ||
60 | ) { | ||
40 | // see tpl/export.html for possible values | 61 | // see tpl/export.html for possible values |
41 | if (!in_array($selection, array('all', 'public', 'private'))) { | 62 | if (!in_array($selection, array('all', 'public', 'private'))) { |
42 | throw new Exception(t('Invalid export selection:') . ' "' . $selection . '"'); | 63 | throw new Exception(t('Invalid export selection:') . ' "' . $selection . '"'); |
43 | } | 64 | } |
44 | 65 | ||
45 | $bookmarkLinks = array(); | 66 | $bookmarkLinks = array(); |
46 | foreach ($linkDb as $link) { | 67 | foreach ($this->bookmarkService->search([], $selection) as $bookmark) { |
47 | if ($link['private'] != 0 && $selection == 'public') { | 68 | $link = $formatter->format($bookmark); |
48 | continue; | 69 | $link['taglist'] = implode(',', $bookmark->getTags()); |
49 | } | 70 | if ($bookmark->isNote() && $prependNoteUrl) { |
50 | if ($link['private'] == 0 && $selection == 'private') { | 71 | $link['url'] = rtrim($indexUrl, '/') . '/' . ltrim($link['url'], '/'); |
51 | continue; | ||
52 | } | ||
53 | $date = $link['created']; | ||
54 | $link['timestamp'] = $date->getTimestamp(); | ||
55 | $link['taglist'] = str_replace(' ', ',', $link['tags']); | ||
56 | |||
57 | if (is_note($link['url']) && $prependNoteUrl) { | ||
58 | $link['url'] = $indexUrl . $link['url']; | ||
59 | } | 72 | } |
60 | 73 | ||
61 | $bookmarkLinks[] = $link; | 74 | $bookmarkLinks[] = $link; |
@@ -65,66 +78,28 @@ class NetscapeBookmarkUtils | |||
65 | } | 78 | } |
66 | 79 | ||
67 | /** | 80 | /** |
68 | * Generates an import status summary | ||
69 | * | ||
70 | * @param string $filename name of the file to import | ||
71 | * @param int $filesize size of the file to import | ||
72 | * @param int $importCount how many links were imported | ||
73 | * @param int $overwriteCount how many links were overwritten | ||
74 | * @param int $skipCount how many links were skipped | ||
75 | * @param int $duration how many seconds did the import take | ||
76 | * | ||
77 | * @return string Summary of the bookmark import status | ||
78 | */ | ||
79 | private static function importStatus( | ||
80 | $filename, | ||
81 | $filesize, | ||
82 | $importCount = 0, | ||
83 | $overwriteCount = 0, | ||
84 | $skipCount = 0, | ||
85 | $duration = 0 | ||
86 | ) { | ||
87 | $status = sprintf(t('File %s (%d bytes) '), $filename, $filesize); | ||
88 | if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { | ||
89 | $status .= t('has an unknown file format. Nothing was imported.'); | ||
90 | } else { | ||
91 | $status .= vsprintf( | ||
92 | t( | ||
93 | 'was successfully processed in %d seconds: ' | ||
94 | . '%d links imported, %d links overwritten, %d links skipped.' | ||
95 | ), | ||
96 | [$duration, $importCount, $overwriteCount, $skipCount] | ||
97 | ); | ||
98 | } | ||
99 | return $status; | ||
100 | } | ||
101 | |||
102 | /** | ||
103 | * Imports Web bookmarks from an uploaded Netscape bookmark dump | 81 | * Imports Web bookmarks from an uploaded Netscape bookmark dump |
104 | * | 82 | * |
105 | * @param array $post Server $_POST parameters | 83 | * @param array $post Server $_POST parameters |
106 | * @param array $files Server $_FILES parameters | 84 | * @param UploadedFileInterface $file File in PSR-7 object format |
107 | * @param LinkDB $linkDb Loaded LinkDB instance | ||
108 | * @param ConfigManager $conf instance | ||
109 | * @param History $history History instance | ||
110 | * | 85 | * |
111 | * @return string Summary of the bookmark import status | 86 | * @return string Summary of the bookmark import status |
112 | */ | 87 | */ |
113 | public static function import($post, $files, $linkDb, $conf, $history) | 88 | public function import($post, UploadedFileInterface $file) |
114 | { | 89 | { |
115 | $start = time(); | 90 | $start = time(); |
116 | $filename = $files['filetoupload']['name']; | 91 | $filename = $file->getClientFilename(); |
117 | $filesize = $files['filetoupload']['size']; | 92 | $filesize = $file->getSize(); |
118 | $data = file_get_contents($files['filetoupload']['tmp_name']); | 93 | $data = (string) $file->getStream(); |
119 | 94 | ||
120 | if (preg_match('/<!DOCTYPE NETSCAPE-Bookmark-file-1>/i', $data) === 0) { | 95 | if (preg_match('/<!DOCTYPE NETSCAPE-Bookmark-file-1>/i', $data) === 0) { |
121 | return self::importStatus($filename, $filesize); | 96 | return $this->importStatus($filename, $filesize); |
122 | } | 97 | } |
123 | 98 | ||
124 | // Overwrite existing links? | 99 | // Overwrite existing bookmarks? |
125 | $overwrite = !empty($post['overwrite']); | 100 | $overwrite = !empty($post['overwrite']); |
126 | 101 | ||
127 | // Add tags to all imported links? | 102 | // Add tags to all imported bookmarks? |
128 | if (empty($post['default_tags'])) { | 103 | if (empty($post['default_tags'])) { |
129 | $defaultTags = array(); | 104 | $defaultTags = array(); |
130 | } else { | 105 | } else { |
@@ -134,18 +109,18 @@ class NetscapeBookmarkUtils | |||
134 | ); | 109 | ); |
135 | } | 110 | } |
136 | 111 | ||
137 | // links are imported as public by default | 112 | // bookmarks are imported as public by default |
138 | $defaultPrivacy = 0; | 113 | $defaultPrivacy = 0; |
139 | 114 | ||
140 | $parser = new NetscapeBookmarkParser( | 115 | $parser = new NetscapeBookmarkParser( |
141 | true, // nested tag support | 116 | true, // nested tag support |
142 | $defaultTags, // additional user-specified tags | 117 | $defaultTags, // additional user-specified tags |
143 | strval(1 - $defaultPrivacy), // defaultPub = 1 - defaultPrivacy | 118 | strval(1 - $defaultPrivacy), // defaultPub = 1 - defaultPrivacy |
144 | $conf->get('resource.data_dir') // log path, will be overridden | 119 | $this->conf->get('resource.data_dir') // log path, will be overridden |
145 | ); | 120 | ); |
146 | $logger = new Logger( | 121 | $logger = new Logger( |
147 | $conf->get('resource.data_dir'), | 122 | $this->conf->get('resource.data_dir'), |
148 | !$conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG, | 123 | !$this->conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG, |
149 | [ | 124 | [ |
150 | 'prefix' => 'import.', | 125 | 'prefix' => 'import.', |
151 | 'extension' => 'log', | 126 | 'extension' => 'log', |
@@ -164,22 +139,18 @@ class NetscapeBookmarkUtils | |||
164 | // use value from the imported file | 139 | // use value from the imported file |
165 | $private = $bkm['pub'] == '1' ? 0 : 1; | 140 | $private = $bkm['pub'] == '1' ? 0 : 1; |
166 | } elseif ($post['privacy'] == 'private') { | 141 | } elseif ($post['privacy'] == 'private') { |
167 | // all imported links are private | 142 | // all imported bookmarks are private |
168 | $private = 1; | 143 | $private = 1; |
169 | } elseif ($post['privacy'] == 'public') { | 144 | } elseif ($post['privacy'] == 'public') { |
170 | // all imported links are public | 145 | // all imported bookmarks are public |
171 | $private = 0; | 146 | $private = 0; |
172 | } | 147 | } |
173 | 148 | ||
174 | $newLink = array( | 149 | $link = $this->bookmarkService->findByUrl($bkm['uri']); |
175 | 'title' => $bkm['title'], | 150 | $existingLink = $link !== null; |
176 | 'url' => $bkm['uri'], | 151 | if (! $existingLink) { |
177 | 'description' => $bkm['note'], | 152 | $link = new Bookmark(); |
178 | 'private' => $private, | 153 | } |
179 | 'tags' => $bkm['tags'] | ||
180 | ); | ||
181 | |||
182 | $existingLink = $linkDb->getLinkFromUrl($bkm['uri']); | ||
183 | 154 | ||
184 | if ($existingLink !== false) { | 155 | if ($existingLink !== false) { |
185 | if ($overwrite === false) { | 156 | if ($overwrite === false) { |
@@ -188,32 +159,30 @@ class NetscapeBookmarkUtils | |||
188 | continue; | 159 | continue; |
189 | } | 160 | } |
190 | 161 | ||
191 | // Overwrite an existing link, keep its date | 162 | $link->setUpdated(new DateTime()); |
192 | $newLink['id'] = $existingLink['id']; | ||
193 | $newLink['created'] = $existingLink['created']; | ||
194 | $newLink['updated'] = new DateTime(); | ||
195 | $newLink['shorturl'] = $existingLink['shorturl']; | ||
196 | $linkDb[$existingLink['id']] = $newLink; | ||
197 | $importCount++; | ||
198 | $overwriteCount++; | 163 | $overwriteCount++; |
199 | continue; | 164 | } else { |
165 | $newLinkDate = new DateTime('@' . strval($bkm['time'])); | ||
166 | $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get())); | ||
167 | $link->setCreated($newLinkDate); | ||
200 | } | 168 | } |
201 | 169 | ||
202 | // Add a new link - @ used for UNIX timestamps | 170 | $link->setTitle($bkm['title']); |
203 | $newLinkDate = new DateTime('@' . strval($bkm['time'])); | 171 | $link->setUrl($bkm['uri'], $this->conf->get('security.allowed_protocols')); |
204 | $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get())); | 172 | $link->setDescription($bkm['note']); |
205 | $newLink['created'] = $newLinkDate; | 173 | $link->setPrivate($private); |
206 | $newLink['id'] = $linkDb->getNextId(); | 174 | $link->setTagsString($bkm['tags']); |
207 | $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); | 175 | |
208 | $linkDb[$newLink['id']] = $newLink; | 176 | $this->bookmarkService->addOrSet($link, false); |
209 | $importCount++; | 177 | $importCount++; |
210 | } | 178 | } |
211 | 179 | ||
212 | $linkDb->save($conf->get('resource.page_cache')); | 180 | $this->bookmarkService->save(); |
213 | $history->importLinks(); | 181 | $this->history->importLinks(); |
214 | 182 | ||
215 | $duration = time() - $start; | 183 | $duration = time() - $start; |
216 | return self::importStatus( | 184 | |
185 | return $this->importStatus( | ||
217 | $filename, | 186 | $filename, |
218 | $filesize, | 187 | $filesize, |
219 | $importCount, | 188 | $importCount, |
@@ -222,4 +191,39 @@ class NetscapeBookmarkUtils | |||
222 | $duration | 191 | $duration |
223 | ); | 192 | ); |
224 | } | 193 | } |
194 | |||
195 | /** | ||
196 | * Generates an import status summary | ||
197 | * | ||
198 | * @param string $filename name of the file to import | ||
199 | * @param int $filesize size of the file to import | ||
200 | * @param int $importCount how many bookmarks were imported | ||
201 | * @param int $overwriteCount how many bookmarks were overwritten | ||
202 | * @param int $skipCount how many bookmarks were skipped | ||
203 | * @param int $duration how many seconds did the import take | ||
204 | * | ||
205 | * @return string Summary of the bookmark import status | ||
206 | */ | ||
207 | protected function importStatus( | ||
208 | $filename, | ||
209 | $filesize, | ||
210 | $importCount = 0, | ||
211 | $overwriteCount = 0, | ||
212 | $skipCount = 0, | ||
213 | $duration = 0 | ||
214 | ) { | ||
215 | $status = sprintf(t('File %s (%d bytes) '), $filename, $filesize); | ||
216 | if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { | ||
217 | $status .= t('has an unknown file format. Nothing was imported.'); | ||
218 | } else { | ||
219 | $status .= vsprintf( | ||
220 | t( | ||
221 | 'was successfully processed in %d seconds: ' | ||
222 | . '%d bookmarks imported, %d bookmarks overwritten, %d bookmarks skipped.' | ||
223 | ), | ||
224 | [$duration, $importCount, $overwriteCount, $skipCount] | ||
225 | ); | ||
226 | } | ||
227 | return $status; | ||
228 | } | ||
225 | } | 229 | } |
diff --git a/application/plugin/PluginManager.php b/application/plugin/PluginManager.php index f7b24a8e..1b2197c9 100644 --- a/application/plugin/PluginManager.php +++ b/application/plugin/PluginManager.php | |||
@@ -16,7 +16,7 @@ class PluginManager | |||
16 | * | 16 | * |
17 | * @var array $authorizedPlugins | 17 | * @var array $authorizedPlugins |
18 | */ | 18 | */ |
19 | private $authorizedPlugins; | 19 | private $authorizedPlugins = []; |
20 | 20 | ||
21 | /** | 21 | /** |
22 | * List of loaded plugins. | 22 | * List of loaded plugins. |
@@ -100,21 +100,35 @@ class PluginManager | |||
100 | */ | 100 | */ |
101 | public function executeHooks($hook, &$data, $params = array()) | 101 | public function executeHooks($hook, &$data, $params = array()) |
102 | { | 102 | { |
103 | if (!empty($params['target'])) { | 103 | $metadataParameters = [ |
104 | $data['_PAGE_'] = $params['target']; | 104 | 'target' => '_PAGE_', |
105 | } | 105 | 'loggedin' => '_LOGGEDIN_', |
106 | 106 | 'basePath' => '_BASE_PATH_', | |
107 | if (isset($params['loggedin'])) { | 107 | 'bookmarkService' => '_BOOKMARK_SERVICE_', |
108 | $data['_LOGGEDIN_'] = $params['loggedin']; | 108 | ]; |
109 | |||
110 | foreach ($metadataParameters as $parameter => $metaKey) { | ||
111 | if (array_key_exists($parameter, $params)) { | ||
112 | $data[$metaKey] = $params[$parameter]; | ||
113 | } | ||
109 | } | 114 | } |
110 | 115 | ||
111 | foreach ($this->loadedPlugins as $plugin) { | 116 | foreach ($this->loadedPlugins as $plugin) { |
112 | $hookFunction = $this->buildHookName($hook, $plugin); | 117 | $hookFunction = $this->buildHookName($hook, $plugin); |
113 | 118 | ||
114 | if (function_exists($hookFunction)) { | 119 | if (function_exists($hookFunction)) { |
115 | $data = call_user_func($hookFunction, $data, $this->conf); | 120 | try { |
121 | $data = call_user_func($hookFunction, $data, $this->conf); | ||
122 | } catch (\Throwable $e) { | ||
123 | $error = $plugin . t(' [plugin incompatibility]: ') . $e->getMessage(); | ||
124 | $this->errors = array_unique(array_merge($this->errors, [$error])); | ||
125 | } | ||
116 | } | 126 | } |
117 | } | 127 | } |
128 | |||
129 | foreach ($metadataParameters as $metaKey) { | ||
130 | unset($data[$metaKey]); | ||
131 | } | ||
118 | } | 132 | } |
119 | 133 | ||
120 | /** | 134 | /** |
diff --git a/application/render/PageBuilder.php b/application/render/PageBuilder.php index 3f86fc26..41b357dd 100644 --- a/application/render/PageBuilder.php +++ b/application/render/PageBuilder.php | |||
@@ -3,10 +3,12 @@ | |||
3 | namespace Shaarli\Render; | 3 | namespace Shaarli\Render; |
4 | 4 | ||
5 | use Exception; | 5 | use Exception; |
6 | use exceptions\MissingBasePathException; | ||
6 | use RainTPL; | 7 | use RainTPL; |
7 | use Shaarli\ApplicationUtils; | 8 | use Shaarli\ApplicationUtils; |
8 | use Shaarli\Bookmark\LinkDB; | 9 | use Shaarli\Bookmark\BookmarkServiceInterface; |
9 | use Shaarli\Config\ConfigManager; | 10 | use Shaarli\Config\ConfigManager; |
11 | use Shaarli\Security\SessionManager; | ||
10 | use Shaarli\Thumbnailer; | 12 | use Shaarli\Thumbnailer; |
11 | 13 | ||
12 | /** | 14 | /** |
@@ -34,9 +36,9 @@ class PageBuilder | |||
34 | protected $session; | 36 | protected $session; |
35 | 37 | ||
36 | /** | 38 | /** |
37 | * @var LinkDB $linkDB instance. | 39 | * @var BookmarkServiceInterface $bookmarkService instance. |
38 | */ | 40 | */ |
39 | protected $linkDB; | 41 | protected $bookmarkService; |
40 | 42 | ||
41 | /** | 43 | /** |
42 | * @var null|string XSRF token | 44 | * @var null|string XSRF token |
@@ -52,23 +54,32 @@ class PageBuilder | |||
52 | * PageBuilder constructor. | 54 | * PageBuilder constructor. |
53 | * $tpl is initialized at false for lazy loading. | 55 | * $tpl is initialized at false for lazy loading. |
54 | * | 56 | * |
55 | * @param ConfigManager $conf Configuration Manager instance (reference). | 57 | * @param ConfigManager $conf Configuration Manager instance (reference). |
56 | * @param array $session $_SESSION array | 58 | * @param array $session $_SESSION array |
57 | * @param LinkDB $linkDB instance. | 59 | * @param BookmarkServiceInterface $linkDB instance. |
58 | * @param string $token Session token | 60 | * @param string $token Session token |
59 | * @param bool $isLoggedIn | 61 | * @param bool $isLoggedIn |
60 | */ | 62 | */ |
61 | public function __construct(&$conf, $session, $linkDB = null, $token = null, $isLoggedIn = false) | 63 | public function __construct(&$conf, $session, $linkDB = null, $token = null, $isLoggedIn = false) |
62 | { | 64 | { |
63 | $this->tpl = false; | 65 | $this->tpl = false; |
64 | $this->conf = $conf; | 66 | $this->conf = $conf; |
65 | $this->session = $session; | 67 | $this->session = $session; |
66 | $this->linkDB = $linkDB; | 68 | $this->bookmarkService = $linkDB; |
67 | $this->token = $token; | 69 | $this->token = $token; |
68 | $this->isLoggedIn = $isLoggedIn; | 70 | $this->isLoggedIn = $isLoggedIn; |
69 | } | 71 | } |
70 | 72 | ||
71 | /** | 73 | /** |
74 | * Reset current state of template rendering. | ||
75 | * Mostly useful for error handling. We remove everything, and display the error template. | ||
76 | */ | ||
77 | public function reset(): void | ||
78 | { | ||
79 | $this->tpl = false; | ||
80 | } | ||
81 | |||
82 | /** | ||
72 | * Initialize all default tpl tags. | 83 | * Initialize all default tpl tags. |
73 | */ | 84 | */ |
74 | private function initialize() | 85 | private function initialize() |
@@ -125,8 +136,8 @@ class PageBuilder | |||
125 | 136 | ||
126 | $this->tpl->assign('language', $this->conf->get('translation.language')); | 137 | $this->tpl->assign('language', $this->conf->get('translation.language')); |
127 | 138 | ||
128 | if ($this->linkDB !== null) { | 139 | if ($this->bookmarkService !== null) { |
129 | $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); | 140 | $this->tpl->assign('tags', escape($this->bookmarkService->bookmarksCountPerTag())); |
130 | } | 141 | } |
131 | 142 | ||
132 | $this->tpl->assign( | 143 | $this->tpl->assign( |
@@ -136,16 +147,43 @@ class PageBuilder | |||
136 | $this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width')); | 147 | $this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width')); |
137 | $this->tpl->assign('thumbnails_height', $this->conf->get('thumbnails.height')); | 148 | $this->tpl->assign('thumbnails_height', $this->conf->get('thumbnails.height')); |
138 | 149 | ||
139 | if (!empty($_SESSION['warnings'])) { | 150 | $this->tpl->assign('formatter', $this->conf->get('formatter', 'default')); |
140 | $this->tpl->assign('global_warnings', $_SESSION['warnings']); | 151 | |
141 | unset($_SESSION['warnings']); | 152 | $this->tpl->assign('links_per_page', $this->session['LINKS_PER_PAGE']); |
142 | } | ||
143 | 153 | ||
144 | // To be removed with a proper theme configuration. | 154 | // To be removed with a proper theme configuration. |
145 | $this->tpl->assign('conf', $this->conf); | 155 | $this->tpl->assign('conf', $this->conf); |
146 | } | 156 | } |
147 | 157 | ||
148 | /** | 158 | /** |
159 | * Affect variable after controller processing. | ||
160 | * Used for alert messages. | ||
161 | */ | ||
162 | protected function finalize(string $basePath): void | ||
163 | { | ||
164 | // TODO: use the SessionManager | ||
165 | $messageKeys = [ | ||
166 | SessionManager::KEY_SUCCESS_MESSAGES, | ||
167 | SessionManager::KEY_WARNING_MESSAGES, | ||
168 | SessionManager::KEY_ERROR_MESSAGES | ||
169 | ]; | ||
170 | foreach ($messageKeys as $messageKey) { | ||
171 | if (!empty($_SESSION[$messageKey])) { | ||
172 | $this->tpl->assign('global_' . $messageKey, $_SESSION[$messageKey]); | ||
173 | unset($_SESSION[$messageKey]); | ||
174 | } | ||
175 | } | ||
176 | |||
177 | $this->assign('base_path', $basePath); | ||
178 | $this->assign( | ||
179 | 'asset_path', | ||
180 | $basePath . '/' . | ||
181 | rtrim($this->conf->get('resource.raintpl_tpl', 'tpl'), '/') . '/' . | ||
182 | $this->conf->get('resource.theme', 'default') | ||
183 | ); | ||
184 | } | ||
185 | |||
186 | /** | ||
149 | * The following assign() method is basically the same as RainTPL (except lazy loading) | 187 | * The following assign() method is basically the same as RainTPL (except lazy loading) |
150 | * | 188 | * |
151 | * @param string $placeholder Template placeholder. | 189 | * @param string $placeholder Template placeholder. |
@@ -183,33 +221,21 @@ class PageBuilder | |||
183 | } | 221 | } |
184 | 222 | ||
185 | /** | 223 | /** |
186 | * Render a specific page (using a template file). | 224 | * Render a specific page as string (using a template file). |
187 | * e.g. $pb->renderPage('picwall'); | 225 | * e.g. $pb->render('picwall'); |
188 | * | 226 | * |
189 | * @param string $page Template filename (without extension). | 227 | * @param string $page Template filename (without extension). |
228 | * | ||
229 | * @return string Processed template content | ||
190 | */ | 230 | */ |
191 | public function renderPage($page) | 231 | public function render(string $page, string $basePath): string |
192 | { | 232 | { |
193 | if ($this->tpl === false) { | 233 | if ($this->tpl === false) { |
194 | $this->initialize(); | 234 | $this->initialize(); |
195 | } | 235 | } |
196 | 236 | ||
197 | $this->tpl->draw($page); | 237 | $this->finalize($basePath); |
198 | } | ||
199 | 238 | ||
200 | /** | 239 | return $this->tpl->draw($page, true); |
201 | * Render a 404 page (uses the template : tpl/404.tpl) | ||
202 | * usage: $PAGE->render404('The link was deleted') | ||
203 | * | ||
204 | * @param string $message A message to display what is not found | ||
205 | */ | ||
206 | public function render404($message = '') | ||
207 | { | ||
208 | if (empty($message)) { | ||
209 | $message = t('The page you are trying to reach does not exist or has been deleted.'); | ||
210 | } | ||
211 | header($_SERVER['SERVER_PROTOCOL'] . ' ' . t('404 Not Found')); | ||
212 | $this->tpl->assign('error_message', $message); | ||
213 | $this->renderPage('404'); | ||
214 | } | 240 | } |
215 | } | 241 | } |
diff --git a/application/render/PageCacheManager.php b/application/render/PageCacheManager.php new file mode 100644 index 00000000..97805c35 --- /dev/null +++ b/application/render/PageCacheManager.php | |||
@@ -0,0 +1,60 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Render; | ||
4 | |||
5 | use Shaarli\Feed\CachedPage; | ||
6 | |||
7 | /** | ||
8 | * Cache utilities | ||
9 | */ | ||
10 | class PageCacheManager | ||
11 | { | ||
12 | /** @var string Cache directory */ | ||
13 | protected $pageCacheDir; | ||
14 | |||
15 | /** @var bool */ | ||
16 | protected $isLoggedIn; | ||
17 | |||
18 | public function __construct(string $pageCacheDir, bool $isLoggedIn) | ||
19 | { | ||
20 | $this->pageCacheDir = $pageCacheDir; | ||
21 | $this->isLoggedIn = $isLoggedIn; | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Purges all cached pages | ||
26 | * | ||
27 | * @return string|null an error string if the directory is missing | ||
28 | */ | ||
29 | public function purgeCachedPages(): ?string | ||
30 | { | ||
31 | if (!is_dir($this->pageCacheDir)) { | ||
32 | $error = sprintf(t('Cannot purge %s: no directory'), $this->pageCacheDir); | ||
33 | error_log($error); | ||
34 | |||
35 | return $error; | ||
36 | } | ||
37 | |||
38 | array_map('unlink', glob($this->pageCacheDir . '/*.cache')); | ||
39 | |||
40 | return null; | ||
41 | } | ||
42 | |||
43 | /** | ||
44 | * Invalidates caches when the database is changed or the user logs out. | ||
45 | */ | ||
46 | public function invalidateCaches(): void | ||
47 | { | ||
48 | // Purge page cache shared by sessions. | ||
49 | $this->purgeCachedPages(); | ||
50 | } | ||
51 | |||
52 | public function getCachePage(string $pageUrl): CachedPage | ||
53 | { | ||
54 | return new CachedPage( | ||
55 | $this->pageCacheDir, | ||
56 | $pageUrl, | ||
57 | false === $this->isLoggedIn | ||
58 | ); | ||
59 | } | ||
60 | } | ||
diff --git a/application/render/TemplatePage.php b/application/render/TemplatePage.php new file mode 100644 index 00000000..8af8228a --- /dev/null +++ b/application/render/TemplatePage.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Render; | ||
6 | |||
7 | interface TemplatePage | ||
8 | { | ||
9 | public const ERROR_404 = '404'; | ||
10 | public const ADDLINK = 'addlink'; | ||
11 | public const CHANGE_PASSWORD = 'changepassword'; | ||
12 | public const CHANGE_TAG = 'changetag'; | ||
13 | public const CONFIGURE = 'configure'; | ||
14 | public const DAILY = 'daily'; | ||
15 | public const DAILY_RSS = 'dailyrss'; | ||
16 | public const EDIT_LINK = 'editlink'; | ||
17 | public const ERROR = 'error'; | ||
18 | public const EXPORT = 'export'; | ||
19 | public const NETSCAPE_EXPORT_BOOKMARKS = 'export.bookmarks'; | ||
20 | public const FEED_ATOM = 'feed.atom'; | ||
21 | public const FEED_RSS = 'feed.rss'; | ||
22 | public const IMPORT = 'import'; | ||
23 | public const INSTALL = 'install'; | ||
24 | public const LINKLIST = 'linklist'; | ||
25 | public const LOGIN = 'loginform'; | ||
26 | public const OPEN_SEARCH = 'opensearch'; | ||
27 | public const PICTURE_WALL = 'picwall'; | ||
28 | public const PLUGINS_ADMIN = 'pluginsadmin'; | ||
29 | public const TAG_CLOUD = 'tag.cloud'; | ||
30 | public const TAG_LIST = 'tag.list'; | ||
31 | public const THUMBNAILS = 'thumbnails'; | ||
32 | public const TOOLS = 'tools'; | ||
33 | } | ||
diff --git a/application/security/CookieManager.php b/application/security/CookieManager.php new file mode 100644 index 00000000..cde4746e --- /dev/null +++ b/application/security/CookieManager.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Security; | ||
6 | |||
7 | class CookieManager | ||
8 | { | ||
9 | /** @var string Name of the cookie set after logging in **/ | ||
10 | public const STAY_SIGNED_IN = 'shaarli_staySignedIn'; | ||
11 | |||
12 | /** @var mixed $_COOKIE set by reference */ | ||
13 | protected $cookies; | ||
14 | |||
15 | public function __construct(array &$cookies) | ||
16 | { | ||
17 | $this->cookies = $cookies; | ||
18 | } | ||
19 | |||
20 | public function setCookieParameter(string $key, string $value, int $expires, string $path): self | ||
21 | { | ||
22 | $this->cookies[$key] = $value; | ||
23 | |||
24 | setcookie($key, $value, $expires, $path); | ||
25 | |||
26 | return $this; | ||
27 | } | ||
28 | |||
29 | public function getCookieParameter(string $key, string $default = null): ?string | ||
30 | { | ||
31 | return $this->cookies[$key] ?? $default; | ||
32 | } | ||
33 | } | ||
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php index 0b0ce0b1..d74c3118 100644 --- a/application/security/LoginManager.php +++ b/application/security/LoginManager.php | |||
@@ -1,6 +1,7 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli\Security; | 2 | namespace Shaarli\Security; |
3 | 3 | ||
4 | use Exception; | ||
4 | use Shaarli\Config\ConfigManager; | 5 | use Shaarli\Config\ConfigManager; |
5 | 6 | ||
6 | /** | 7 | /** |
@@ -8,9 +9,6 @@ use Shaarli\Config\ConfigManager; | |||
8 | */ | 9 | */ |
9 | class LoginManager | 10 | class LoginManager |
10 | { | 11 | { |
11 | /** @var string Name of the cookie set after logging in **/ | ||
12 | public static $STAY_SIGNED_IN_COOKIE = 'shaarli_staySignedIn'; | ||
13 | |||
14 | /** @var array A reference to the $_GLOBALS array */ | 12 | /** @var array A reference to the $_GLOBALS array */ |
15 | protected $globals = []; | 13 | protected $globals = []; |
16 | 14 | ||
@@ -31,17 +29,21 @@ class LoginManager | |||
31 | 29 | ||
32 | /** @var string User sign-in token depending on remote IP and credentials */ | 30 | /** @var string User sign-in token depending on remote IP and credentials */ |
33 | protected $staySignedInToken = ''; | 31 | protected $staySignedInToken = ''; |
32 | /** @var CookieManager */ | ||
33 | protected $cookieManager; | ||
34 | 34 | ||
35 | /** | 35 | /** |
36 | * Constructor | 36 | * Constructor |
37 | * | 37 | * |
38 | * @param ConfigManager $configManager Configuration Manager instance | 38 | * @param ConfigManager $configManager Configuration Manager instance |
39 | * @param SessionManager $sessionManager SessionManager instance | 39 | * @param SessionManager $sessionManager SessionManager instance |
40 | * @param CookieManager $cookieManager CookieManager instance | ||
40 | */ | 41 | */ |
41 | public function __construct($configManager, $sessionManager) | 42 | public function __construct($configManager, $sessionManager, $cookieManager) |
42 | { | 43 | { |
43 | $this->configManager = $configManager; | 44 | $this->configManager = $configManager; |
44 | $this->sessionManager = $sessionManager; | 45 | $this->sessionManager = $sessionManager; |
46 | $this->cookieManager = $cookieManager; | ||
45 | $this->banManager = new BanManager( | 47 | $this->banManager = new BanManager( |
46 | $this->configManager->get('security.trusted_proxies', []), | 48 | $this->configManager->get('security.trusted_proxies', []), |
47 | $this->configManager->get('security.ban_after'), | 49 | $this->configManager->get('security.ban_after'), |
@@ -85,10 +87,9 @@ class LoginManager | |||
85 | /** | 87 | /** |
86 | * Check user session state and validity (expiration) | 88 | * Check user session state and validity (expiration) |
87 | * | 89 | * |
88 | * @param array $cookie The $_COOKIE array | ||
89 | * @param string $clientIpId Client IP address identifier | 90 | * @param string $clientIpId Client IP address identifier |
90 | */ | 91 | */ |
91 | public function checkLoginState($cookie, $clientIpId) | 92 | public function checkLoginState($clientIpId) |
92 | { | 93 | { |
93 | if (! $this->configManager->exists('credentials.login')) { | 94 | if (! $this->configManager->exists('credentials.login')) { |
94 | // Shaarli is not configured yet | 95 | // Shaarli is not configured yet |
@@ -96,9 +97,7 @@ class LoginManager | |||
96 | return; | 97 | return; |
97 | } | 98 | } |
98 | 99 | ||
99 | if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE]) | 100 | if ($this->staySignedInToken === $this->cookieManager->getCookieParameter(CookieManager::STAY_SIGNED_IN)) { |
100 | && $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken | ||
101 | ) { | ||
102 | // The user client has a valid stay-signed-in cookie | 101 | // The user client has a valid stay-signed-in cookie |
103 | // Session information is updated with the current client information | 102 | // Session information is updated with the current client information |
104 | $this->sessionManager->storeLoginInfo($clientIpId); | 103 | $this->sessionManager->storeLoginInfo($clientIpId); |
@@ -139,26 +138,86 @@ class LoginManager | |||
139 | */ | 138 | */ |
140 | public function checkCredentials($remoteIp, $clientIpId, $login, $password) | 139 | public function checkCredentials($remoteIp, $clientIpId, $login, $password) |
141 | { | 140 | { |
142 | $hash = sha1($password . $login . $this->configManager->get('credentials.salt')); | 141 | // Check login matches config |
142 | if ($login !== $this->configManager->get('credentials.login')) { | ||
143 | return false; | ||
144 | } | ||
143 | 145 | ||
144 | if ($login != $this->configManager->get('credentials.login') | 146 | // Check credentials |
145 | || $hash != $this->configManager->get('credentials.hash') | 147 | try { |
146 | ) { | 148 | $useLdapLogin = !empty($this->configManager->get('ldap.host')); |
149 | if ((false === $useLdapLogin && $this->checkCredentialsFromLocalConfig($login, $password)) | ||
150 | || (true === $useLdapLogin && $this->checkCredentialsFromLdap($login, $password)) | ||
151 | ) { | ||
152 | $this->sessionManager->storeLoginInfo($clientIpId); | ||
153 | logm( | ||
154 | $this->configManager->get('resource.log'), | ||
155 | $remoteIp, | ||
156 | 'Login successful' | ||
157 | ); | ||
158 | return true; | ||
159 | } | ||
160 | } | ||
161 | catch(Exception $exception) { | ||
147 | logm( | 162 | logm( |
148 | $this->configManager->get('resource.log'), | 163 | $this->configManager->get('resource.log'), |
149 | $remoteIp, | 164 | $remoteIp, |
150 | 'Login failed for user ' . $login | 165 | 'Exception while checking credentials: ' . $exception |
151 | ); | 166 | ); |
152 | return false; | ||
153 | } | 167 | } |
154 | 168 | ||
155 | $this->sessionManager->storeLoginInfo($clientIpId); | ||
156 | logm( | 169 | logm( |
157 | $this->configManager->get('resource.log'), | 170 | $this->configManager->get('resource.log'), |
158 | $remoteIp, | 171 | $remoteIp, |
159 | 'Login successful' | 172 | 'Login failed for user ' . $login |
173 | ); | ||
174 | return false; | ||
175 | } | ||
176 | |||
177 | |||
178 | /** | ||
179 | * Check user credentials from local config | ||
180 | * | ||
181 | * @param string $login Username | ||
182 | * @param string $password Password | ||
183 | * | ||
184 | * @return bool true if the provided credentials are valid, false otherwise | ||
185 | */ | ||
186 | public function checkCredentialsFromLocalConfig($login, $password) { | ||
187 | $hash = sha1($password . $login . $this->configManager->get('credentials.salt')); | ||
188 | |||
189 | return $login == $this->configManager->get('credentials.login') | ||
190 | && $hash == $this->configManager->get('credentials.hash'); | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * Check user credentials are valid through LDAP bind | ||
195 | * | ||
196 | * @param string $remoteIp Remote client IP address | ||
197 | * @param string $clientIpId Client IP address identifier | ||
198 | * @param string $login Username | ||
199 | * @param string $password Password | ||
200 | * | ||
201 | * @return bool true if the provided credentials are valid, false otherwise | ||
202 | */ | ||
203 | public function checkCredentialsFromLdap($login, $password, $connect = null, $bind = null) | ||
204 | { | ||
205 | $connect = $connect ?? function($host) { | ||
206 | $resource = ldap_connect($host); | ||
207 | |||
208 | ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3); | ||
209 | |||
210 | return $resource; | ||
211 | }; | ||
212 | $bind = $bind ?? function($handle, $dn, $password) { | ||
213 | return ldap_bind($handle, $dn, $password); | ||
214 | }; | ||
215 | |||
216 | return $bind( | ||
217 | $connect($this->configManager->get('ldap.host')), | ||
218 | sprintf($this->configManager->get('ldap.dn'), $login), | ||
219 | $password | ||
160 | ); | 220 | ); |
161 | return true; | ||
162 | } | 221 | } |
163 | 222 | ||
164 | /** | 223 | /** |
diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php index b8b8ab8d..36df8c1c 100644 --- a/application/security/SessionManager.php +++ b/application/security/SessionManager.php | |||
@@ -8,6 +8,14 @@ use Shaarli\Config\ConfigManager; | |||
8 | */ | 8 | */ |
9 | class SessionManager | 9 | class SessionManager |
10 | { | 10 | { |
11 | public const KEY_LINKS_PER_PAGE = 'LINKS_PER_PAGE'; | ||
12 | public const KEY_VISIBILITY = 'visibility'; | ||
13 | public const KEY_UNTAGGED_ONLY = 'untaggedonly'; | ||
14 | |||
15 | public const KEY_SUCCESS_MESSAGES = 'successes'; | ||
16 | public const KEY_WARNING_MESSAGES = 'warnings'; | ||
17 | public const KEY_ERROR_MESSAGES = 'errors'; | ||
18 | |||
11 | /** @var int Session expiration timeout, in seconds */ | 19 | /** @var int Session expiration timeout, in seconds */ |
12 | public static $SHORT_TIMEOUT = 3600; // 1 hour | 20 | public static $SHORT_TIMEOUT = 3600; // 1 hour |
13 | 21 | ||
@@ -23,16 +31,35 @@ class SessionManager | |||
23 | /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */ | 31 | /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */ |
24 | protected $staySignedIn = false; | 32 | protected $staySignedIn = false; |
25 | 33 | ||
34 | /** @var string */ | ||
35 | protected $savePath; | ||
36 | |||
26 | /** | 37 | /** |
27 | * Constructor | 38 | * Constructor |
28 | * | 39 | * |
29 | * @param array $session The $_SESSION array (reference) | 40 | * @param array $session The $_SESSION array (reference) |
30 | * @param ConfigManager $conf ConfigManager instance | 41 | * @param ConfigManager $conf ConfigManager instance |
42 | * @param string $savePath Session save path returned by builtin function session_save_path() | ||
31 | */ | 43 | */ |
32 | public function __construct(& $session, $conf) | 44 | public function __construct(&$session, $conf, string $savePath) |
33 | { | 45 | { |
34 | $this->session = &$session; | 46 | $this->session = &$session; |
35 | $this->conf = $conf; | 47 | $this->conf = $conf; |
48 | $this->savePath = $savePath; | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Initialize XSRF token and links per page session variables. | ||
53 | */ | ||
54 | public function initialize(): void | ||
55 | { | ||
56 | if (!isset($this->session['tokens'])) { | ||
57 | $this->session['tokens'] = []; | ||
58 | } | ||
59 | |||
60 | if (!isset($this->session['LINKS_PER_PAGE'])) { | ||
61 | $this->session['LINKS_PER_PAGE'] = $this->conf->get('general.links_per_page', 20); | ||
62 | } | ||
36 | } | 63 | } |
37 | 64 | ||
38 | /** | 65 | /** |
@@ -156,7 +183,6 @@ class SessionManager | |||
156 | unset($this->session['expires_on']); | 183 | unset($this->session['expires_on']); |
157 | unset($this->session['username']); | 184 | unset($this->session['username']); |
158 | unset($this->session['visibility']); | 185 | unset($this->session['visibility']); |
159 | unset($this->session['untaggedonly']); | ||
160 | } | 186 | } |
161 | } | 187 | } |
162 | 188 | ||
@@ -196,4 +222,84 @@ class SessionManager | |||
196 | } | 222 | } |
197 | return true; | 223 | return true; |
198 | } | 224 | } |
225 | |||
226 | /** @return array Local reference to the global $_SESSION array */ | ||
227 | public function getSession(): array | ||
228 | { | ||
229 | return $this->session; | ||
230 | } | ||
231 | |||
232 | /** | ||
233 | * @param mixed $default value which will be returned if the $key is undefined | ||
234 | * | ||
235 | * @return mixed Content stored in session | ||
236 | */ | ||
237 | public function getSessionParameter(string $key, $default = null) | ||
238 | { | ||
239 | return $this->session[$key] ?? $default; | ||
240 | } | ||
241 | |||
242 | /** | ||
243 | * Store a variable in user session. | ||
244 | * | ||
245 | * @param string $key Session key | ||
246 | * @param mixed $value Session value to store | ||
247 | * | ||
248 | * @return $this | ||
249 | */ | ||
250 | public function setSessionParameter(string $key, $value): self | ||
251 | { | ||
252 | $this->session[$key] = $value; | ||
253 | |||
254 | return $this; | ||
255 | } | ||
256 | |||
257 | /** | ||
258 | * Store a variable in user session. | ||
259 | * | ||
260 | * @param string $key Session key | ||
261 | * | ||
262 | * @return $this | ||
263 | */ | ||
264 | public function deleteSessionParameter(string $key): self | ||
265 | { | ||
266 | unset($this->session[$key]); | ||
267 | |||
268 | return $this; | ||
269 | } | ||
270 | |||
271 | public function getSavePath(): string | ||
272 | { | ||
273 | return $this->savePath; | ||
274 | } | ||
275 | |||
276 | /* | ||
277 | * Next public functions wrapping native PHP session API. | ||
278 | */ | ||
279 | |||
280 | public function destroy(): bool | ||
281 | { | ||
282 | $this->session = []; | ||
283 | |||
284 | return session_destroy(); | ||
285 | } | ||
286 | |||
287 | public function start(): bool | ||
288 | { | ||
289 | if (session_status() === PHP_SESSION_ACTIVE) { | ||
290 | $this->destroy(); | ||
291 | } | ||
292 | |||
293 | return session_start(); | ||
294 | } | ||
295 | |||
296 | public function cookieParameters(int $lifeTime, string $path, string $domain): bool | ||
297 | { | ||
298 | return session_set_cookie_params($lifeTime, $path, $domain); | ||
299 | } | ||
300 | |||
301 | public function regenerateId(bool $deleteOldSession = false): bool | ||
302 | { | ||
303 | return session_regenerate_id($deleteOldSession); | ||
304 | } | ||
199 | } | 305 | } |
diff --git a/application/updater/Updater.php b/application/updater/Updater.php index beb9ea9b..88a7bc7b 100644 --- a/application/updater/Updater.php +++ b/application/updater/Updater.php | |||
@@ -2,25 +2,14 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Updater; | 3 | namespace Shaarli\Updater; |
4 | 4 | ||
5 | use Exception; | 5 | use Shaarli\Bookmark\BookmarkServiceInterface; |
6 | use RainTPL; | ||
7 | use ReflectionClass; | ||
8 | use ReflectionException; | ||
9 | use ReflectionMethod; | ||
10 | use Shaarli\ApplicationUtils; | ||
11 | use Shaarli\Bookmark\LinkDB; | ||
12 | use Shaarli\Bookmark\LinkFilter; | ||
13 | use Shaarli\Config\ConfigJson; | ||
14 | use Shaarli\Config\ConfigManager; | 6 | use Shaarli\Config\ConfigManager; |
15 | use Shaarli\Config\ConfigPhp; | ||
16 | use Shaarli\Exceptions\IOException; | ||
17 | use Shaarli\Thumbnailer; | ||
18 | use Shaarli\Updater\Exception\UpdaterException; | 7 | use Shaarli\Updater\Exception\UpdaterException; |
19 | 8 | ||
20 | /** | 9 | /** |
21 | * Class updater. | 10 | * Class Updater. |
22 | * Used to update stuff when a new Shaarli's version is reached. | 11 | * Used to update stuff when a new Shaarli's version is reached. |
23 | * Update methods are ran only once, and the stored in a JSON file. | 12 | * Update methods are ran only once, and the stored in a TXT file. |
24 | */ | 13 | */ |
25 | class Updater | 14 | class Updater |
26 | { | 15 | { |
@@ -30,9 +19,9 @@ class Updater | |||
30 | protected $doneUpdates; | 19 | protected $doneUpdates; |
31 | 20 | ||
32 | /** | 21 | /** |
33 | * @var LinkDB instance. | 22 | * @var BookmarkServiceInterface instance. |
34 | */ | 23 | */ |
35 | protected $linkDB; | 24 | protected $bookmarkService; |
36 | 25 | ||
37 | /** | 26 | /** |
38 | * @var ConfigManager $conf Configuration Manager instance. | 27 | * @var ConfigManager $conf Configuration Manager instance. |
@@ -45,36 +34,32 @@ class Updater | |||
45 | protected $isLoggedIn; | 34 | protected $isLoggedIn; |
46 | 35 | ||
47 | /** | 36 | /** |
48 | * @var array $_SESSION | 37 | * @var \ReflectionMethod[] List of current class methods. |
49 | */ | 38 | */ |
50 | protected $session; | 39 | protected $methods; |
51 | 40 | ||
52 | /** | 41 | /** |
53 | * @var ReflectionMethod[] List of current class methods. | 42 | * @var string $basePath Shaarli root directory (from HTTP Request) |
54 | */ | 43 | */ |
55 | protected $methods; | 44 | protected $basePath = null; |
56 | 45 | ||
57 | /** | 46 | /** |
58 | * Object constructor. | 47 | * Object constructor. |
59 | * | 48 | * |
60 | * @param array $doneUpdates Updates which are already done. | 49 | * @param array $doneUpdates Updates which are already done. |
61 | * @param LinkDB $linkDB LinkDB instance. | 50 | * @param BookmarkServiceInterface $linkDB LinksService instance. |
62 | * @param ConfigManager $conf Configuration Manager instance. | 51 | * @param ConfigManager $conf Configuration Manager instance. |
63 | * @param boolean $isLoggedIn True if the user is logged in. | 52 | * @param boolean $isLoggedIn True if the user is logged in. |
64 | * @param array $session $_SESSION (by reference) | ||
65 | * | ||
66 | * @throws ReflectionException | ||
67 | */ | 53 | */ |
68 | public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn, &$session = []) | 54 | public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) |
69 | { | 55 | { |
70 | $this->doneUpdates = $doneUpdates; | 56 | $this->doneUpdates = $doneUpdates; |
71 | $this->linkDB = $linkDB; | 57 | $this->bookmarkService = $linkDB; |
72 | $this->conf = $conf; | 58 | $this->conf = $conf; |
73 | $this->isLoggedIn = $isLoggedIn; | 59 | $this->isLoggedIn = $isLoggedIn; |
74 | $this->session = &$session; | ||
75 | 60 | ||
76 | // Retrieve all update methods. | 61 | // Retrieve all update methods. |
77 | $class = new ReflectionClass($this); | 62 | $class = new \ReflectionClass($this); |
78 | $this->methods = $class->getMethods(); | 63 | $this->methods = $class->getMethods(); |
79 | } | 64 | } |
80 | 65 | ||
@@ -82,13 +67,15 @@ class Updater | |||
82 | * Run all new updates. | 67 | * Run all new updates. |
83 | * Update methods have to start with 'updateMethod' and return true (on success). | 68 | * Update methods have to start with 'updateMethod' and return true (on success). |
84 | * | 69 | * |
70 | * @param string $basePath Shaarli root directory (from HTTP Request) | ||
71 | * | ||
85 | * @return array An array containing ran updates. | 72 | * @return array An array containing ran updates. |
86 | * | 73 | * |
87 | * @throws UpdaterException If something went wrong. | 74 | * @throws UpdaterException If something went wrong. |
88 | */ | 75 | */ |
89 | public function update() | 76 | public function update(string $basePath = null) |
90 | { | 77 | { |
91 | $updatesRan = array(); | 78 | $updatesRan = []; |
92 | 79 | ||
93 | // If the user isn't logged in, exit without updating. | 80 | // If the user isn't logged in, exit without updating. |
94 | if ($this->isLoggedIn !== true) { | 81 | if ($this->isLoggedIn !== true) { |
@@ -96,12 +83,12 @@ class Updater | |||
96 | } | 83 | } |
97 | 84 | ||
98 | if ($this->methods === null) { | 85 | if ($this->methods === null) { |
99 | throw new UpdaterException(t('Couldn\'t retrieve updater class methods.')); | 86 | throw new UpdaterException('Couldn\'t retrieve LegacyUpdater class methods.'); |
100 | } | 87 | } |
101 | 88 | ||
102 | foreach ($this->methods as $method) { | 89 | foreach ($this->methods as $method) { |
103 | // Not an update method or already done, pass. | 90 | // Not an update method or already done, pass. |
104 | if (!startsWith($method->getName(), 'updateMethod') | 91 | if (! startsWith($method->getName(), 'updateMethod') |
105 | || in_array($method->getName(), $this->doneUpdates) | 92 | || in_array($method->getName(), $this->doneUpdates) |
106 | ) { | 93 | ) { |
107 | continue; | 94 | continue; |
@@ -114,7 +101,7 @@ class Updater | |||
114 | if ($res === true) { | 101 | if ($res === true) { |
115 | $updatesRan[] = $method->getName(); | 102 | $updatesRan[] = $method->getName(); |
116 | } | 103 | } |
117 | } catch (Exception $e) { | 104 | } catch (\Exception $e) { |
118 | throw new UpdaterException($method, $e); | 105 | throw new UpdaterException($method, $e); |
119 | } | 106 | } |
120 | } | 107 | } |
@@ -132,431 +119,61 @@ class Updater | |||
132 | return $this->doneUpdates; | 119 | return $this->doneUpdates; |
133 | } | 120 | } |
134 | 121 | ||
135 | /** | 122 | public function readUpdates(string $updatesFilepath): array |
136 | * Move deprecated options.php to config.php. | ||
137 | * | ||
138 | * Milestone 0.9 (old versioning) - shaarli/Shaarli#41: | ||
139 | * options.php is not supported anymore. | ||
140 | */ | ||
141 | public function updateMethodMergeDeprecatedConfigFile() | ||
142 | { | ||
143 | if (is_file($this->conf->get('resource.data_dir') . '/options.php')) { | ||
144 | include $this->conf->get('resource.data_dir') . '/options.php'; | ||
145 | |||
146 | // Load GLOBALS into config | ||
147 | $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS); | ||
148 | $allowedKeys[] = 'config'; | ||
149 | foreach ($GLOBALS as $key => $value) { | ||
150 | if (in_array($key, $allowedKeys)) { | ||
151 | $this->conf->set($key, $value); | ||
152 | } | ||
153 | } | ||
154 | $this->conf->write($this->isLoggedIn); | ||
155 | unlink($this->conf->get('resource.data_dir') . '/options.php'); | ||
156 | } | ||
157 | |||
158 | return true; | ||
159 | } | ||
160 | |||
161 | /** | ||
162 | * Move old configuration in PHP to the new config system in JSON format. | ||
163 | * | ||
164 | * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'. | ||
165 | * It will also convert legacy setting keys to the new ones. | ||
166 | */ | ||
167 | public function updateMethodConfigToJson() | ||
168 | { | ||
169 | // JSON config already exists, nothing to do. | ||
170 | if ($this->conf->getConfigIO() instanceof ConfigJson) { | ||
171 | return true; | ||
172 | } | ||
173 | |||
174 | $configPhp = new ConfigPhp(); | ||
175 | $configJson = new ConfigJson(); | ||
176 | $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php'); | ||
177 | rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php'); | ||
178 | $this->conf->setConfigIO($configJson); | ||
179 | $this->conf->reload(); | ||
180 | |||
181 | $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING); | ||
182 | foreach (ConfigPhp::$ROOT_KEYS as $key) { | ||
183 | $this->conf->set($legacyMap[$key], $oldConfig[$key]); | ||
184 | } | ||
185 | |||
186 | // Set sub config keys (config and plugins) | ||
187 | $subConfig = array('config', 'plugins'); | ||
188 | foreach ($subConfig as $sub) { | ||
189 | foreach ($oldConfig[$sub] as $key => $value) { | ||
190 | if (isset($legacyMap[$sub . '.' . $key])) { | ||
191 | $configKey = $legacyMap[$sub . '.' . $key]; | ||
192 | } else { | ||
193 | $configKey = $sub . '.' . $key; | ||
194 | } | ||
195 | $this->conf->set($configKey, $value); | ||
196 | } | ||
197 | } | ||
198 | |||
199 | try { | ||
200 | $this->conf->write($this->isLoggedIn); | ||
201 | return true; | ||
202 | } catch (IOException $e) { | ||
203 | error_log($e->getMessage()); | ||
204 | return false; | ||
205 | } | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * Escape settings which have been manually escaped in every request in previous versions: | ||
210 | * - general.title | ||
211 | * - general.header_link | ||
212 | * - redirector.url | ||
213 | * | ||
214 | * @return bool true if the update is successful, false otherwise. | ||
215 | */ | ||
216 | public function updateMethodEscapeUnescapedConfig() | ||
217 | { | ||
218 | try { | ||
219 | $this->conf->set('general.title', escape($this->conf->get('general.title'))); | ||
220 | $this->conf->set('general.header_link', escape($this->conf->get('general.header_link'))); | ||
221 | $this->conf->write($this->isLoggedIn); | ||
222 | } catch (Exception $e) { | ||
223 | error_log($e->getMessage()); | ||
224 | return false; | ||
225 | } | ||
226 | return true; | ||
227 | } | ||
228 | |||
229 | /** | ||
230 | * Update the database to use the new ID system, which replaces linkdate primary keys. | ||
231 | * Also, creation and update dates are now DateTime objects (done by LinkDB). | ||
232 | * | ||
233 | * Since this update is very sensitve (changing the whole database), the datastore will be | ||
234 | * automatically backed up into the file datastore.<datetime>.php. | ||
235 | * | ||
236 | * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash), | ||
237 | * which will be saved by this method. | ||
238 | * | ||
239 | * @return bool true if the update is successful, false otherwise. | ||
240 | */ | ||
241 | public function updateMethodDatastoreIds() | ||
242 | { | ||
243 | // up to date database | ||
244 | if (isset($this->linkDB[0])) { | ||
245 | return true; | ||
246 | } | ||
247 | |||
248 | $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '.php'; | ||
249 | copy($this->conf->get('resource.datastore'), $save); | ||
250 | |||
251 | $links = array(); | ||
252 | foreach ($this->linkDB as $offset => $value) { | ||
253 | $links[] = $value; | ||
254 | unset($this->linkDB[$offset]); | ||
255 | } | ||
256 | $links = array_reverse($links); | ||
257 | $cpt = 0; | ||
258 | foreach ($links as $l) { | ||
259 | unset($l['linkdate']); | ||
260 | $l['id'] = $cpt; | ||
261 | $this->linkDB[$cpt++] = $l; | ||
262 | } | ||
263 | |||
264 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
265 | $this->linkDB->reorder(); | ||
266 | |||
267 | return true; | ||
268 | } | ||
269 | |||
270 | /** | ||
271 | * Rename tags starting with a '-' to work with tag exclusion search. | ||
272 | */ | ||
273 | public function updateMethodRenameDashTags() | ||
274 | { | 123 | { |
275 | $linklist = $this->linkDB->filterSearch(); | 124 | return UpdaterUtils::read_updates_file($updatesFilepath); |
276 | foreach ($linklist as $key => $link) { | ||
277 | $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); | ||
278 | $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); | ||
279 | $this->linkDB[$key] = $link; | ||
280 | } | ||
281 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
282 | return true; | ||
283 | } | 125 | } |
284 | 126 | ||
285 | /** | 127 | public function writeUpdates(string $updatesFilepath, array $updates): void |
286 | * Initialize API settings: | ||
287 | * - api.enabled: true | ||
288 | * - api.secret: generated secret | ||
289 | */ | ||
290 | public function updateMethodApiSettings() | ||
291 | { | 128 | { |
292 | if ($this->conf->exists('api.secret')) { | 129 | UpdaterUtils::write_updates_file($updatesFilepath, $updates); |
293 | return true; | ||
294 | } | ||
295 | |||
296 | $this->conf->set('api.enabled', true); | ||
297 | $this->conf->set( | ||
298 | 'api.secret', | ||
299 | generate_api_secret( | ||
300 | $this->conf->get('credentials.login'), | ||
301 | $this->conf->get('credentials.salt') | ||
302 | ) | ||
303 | ); | ||
304 | $this->conf->write($this->isLoggedIn); | ||
305 | return true; | ||
306 | } | 130 | } |
307 | 131 | ||
308 | /** | 132 | /** |
309 | * New setting: theme name. If the default theme is used, nothing to do. | 133 | * With the Slim routing system, default header link should be `/subfolder/` instead of `?`. |
310 | * | 134 | * Otherwise you can not go back to the home page. |
311 | * If the user uses a custom theme, raintpl_tpl dir is updated to the parent directory, | 135 | * Example: `/subfolder/picture-wall` -> `/subfolder/picture-wall?` instead of `/subfolder/`. |
312 | * and the current theme is set as default in the theme setting. | ||
313 | * | ||
314 | * @return bool true if the update is successful, false otherwise. | ||
315 | */ | 136 | */ |
316 | public function updateMethodDefaultTheme() | 137 | public function updateMethodRelativeHomeLink(): bool |
317 | { | 138 | { |
318 | // raintpl_tpl isn't the root template directory anymore. | 139 | if ('?' === trim($this->conf->get('general.header_link'))) { |
319 | // We run the update only if this folder still contains the template files. | 140 | $this->conf->set('general.header_link', $this->basePath . '/', true, true); |
320 | $tplDir = $this->conf->get('resource.raintpl_tpl'); | ||
321 | $tplFile = $tplDir . '/linklist.html'; | ||
322 | if (!file_exists($tplFile)) { | ||
323 | return true; | ||
324 | } | 141 | } |
325 | 142 | ||
326 | $parent = dirname($tplDir); | ||
327 | $this->conf->set('resource.raintpl_tpl', $parent); | ||
328 | $this->conf->set('resource.theme', trim(str_replace($parent, '', $tplDir), '/')); | ||
329 | $this->conf->write($this->isLoggedIn); | ||
330 | |||
331 | // Dependency injection gore | ||
332 | RainTPL::$tpl_dir = $tplDir; | ||
333 | |||
334 | return true; | 143 | return true; |
335 | } | 144 | } |
336 | 145 | ||
337 | /** | 146 | /** |
338 | * Move the file to inc/user.css to data/user.css. | 147 | * With the Slim routing system, note bookmarks URL formatted `?abcdef` |
339 | * | 148 | * should be replaced with `/shaare/abcdef` |
340 | * Note: Due to hardcoded paths, it's not unit testable. But one line of code should be fine. | ||
341 | * | ||
342 | * @return bool true if the update is successful, false otherwise. | ||
343 | */ | 149 | */ |
344 | public function updateMethodMoveUserCss() | 150 | public function updateMethodMigrateExistingNotesUrl(): bool |
345 | { | 151 | { |
346 | if (!is_file('inc/user.css')) { | 152 | $updated = false; |
347 | return true; | ||
348 | } | ||
349 | |||
350 | return rename('inc/user.css', 'data/user.css'); | ||
351 | } | ||
352 | 153 | ||
353 | /** | 154 | foreach ($this->bookmarkService->search() as $bookmark) { |
354 | * * `markdown_escape` is a new setting, set to true as default. | 155 | if ($bookmark->isNote() |
355 | * | 156 | && startsWith($bookmark->getUrl(), '?') |
356 | * If the markdown plugin was already enabled, escaping is disabled to avoid | 157 | && 1 === preg_match('/^\?([a-zA-Z0-9-_@]{6})($|&|#)/', $bookmark->getUrl(), $match) |
357 | * breaking existing entries. | 158 | ) { |
358 | */ | 159 | $updated = true; |
359 | public function updateMethodEscapeMarkdown() | 160 | $bookmark = $bookmark->setUrl('/shaare/' . $match[1]); |
360 | { | ||
361 | if ($this->conf->exists('security.markdown_escape')) { | ||
362 | return true; | ||
363 | } | ||
364 | |||
365 | if (in_array('markdown', $this->conf->get('general.enabled_plugins'))) { | ||
366 | $this->conf->set('security.markdown_escape', false); | ||
367 | } else { | ||
368 | $this->conf->set('security.markdown_escape', true); | ||
369 | } | ||
370 | $this->conf->write($this->isLoggedIn); | ||
371 | |||
372 | return true; | ||
373 | } | ||
374 | |||
375 | /** | ||
376 | * Add 'http://' to Piwik URL the setting is set. | ||
377 | * | ||
378 | * @return bool true if the update is successful, false otherwise. | ||
379 | */ | ||
380 | public function updateMethodPiwikUrl() | ||
381 | { | ||
382 | if (!$this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) { | ||
383 | return true; | ||
384 | } | ||
385 | |||
386 | $this->conf->set('plugins.PIWIK_URL', 'http://' . $this->conf->get('plugins.PIWIK_URL')); | ||
387 | $this->conf->write($this->isLoggedIn); | ||
388 | |||
389 | return true; | ||
390 | } | ||
391 | |||
392 | /** | ||
393 | * Use ATOM feed as default. | ||
394 | */ | ||
395 | public function updateMethodAtomDefault() | ||
396 | { | ||
397 | if (!$this->conf->exists('feed.show_atom') || $this->conf->get('feed.show_atom') === true) { | ||
398 | return true; | ||
399 | } | ||
400 | |||
401 | $this->conf->set('feed.show_atom', true); | ||
402 | $this->conf->write($this->isLoggedIn); | ||
403 | |||
404 | return true; | ||
405 | } | ||
406 | |||
407 | /** | ||
408 | * Update updates.check_updates_branch setting. | ||
409 | * | ||
410 | * If the current major version digit matches the latest branch | ||
411 | * major version digit, we set the branch to `latest`, | ||
412 | * otherwise we'll check updates on the `stable` branch. | ||
413 | * | ||
414 | * No update required for the dev version. | ||
415 | * | ||
416 | * Note: due to hardcoded URL and lack of dependency injection, this is not unit testable. | ||
417 | * | ||
418 | * FIXME! This needs to be removed when we switch to first digit major version | ||
419 | * instead of the second one since the versionning process will change. | ||
420 | */ | ||
421 | public function updateMethodCheckUpdateRemoteBranch() | ||
422 | { | ||
423 | if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') { | ||
424 | return true; | ||
425 | } | ||
426 | |||
427 | // Get latest branch major version digit | ||
428 | $latestVersion = ApplicationUtils::getLatestGitVersionCode( | ||
429 | 'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php', | ||
430 | 5 | ||
431 | ); | ||
432 | if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) { | ||
433 | return false; | ||
434 | } | ||
435 | $latestMajor = $matches[1]; | ||
436 | |||
437 | // Get current major version digit | ||
438 | preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches); | ||
439 | $currentMajor = $matches[1]; | ||
440 | |||
441 | if ($currentMajor === $latestMajor) { | ||
442 | $branch = 'latest'; | ||
443 | } else { | ||
444 | $branch = 'stable'; | ||
445 | } | ||
446 | $this->conf->set('updates.check_updates_branch', $branch); | ||
447 | $this->conf->write($this->isLoggedIn); | ||
448 | return true; | ||
449 | } | ||
450 | |||
451 | /** | ||
452 | * Reset history store file due to date format change. | ||
453 | */ | ||
454 | public function updateMethodResetHistoryFile() | ||
455 | { | ||
456 | if (is_file($this->conf->get('resource.history'))) { | ||
457 | unlink($this->conf->get('resource.history')); | ||
458 | } | ||
459 | return true; | ||
460 | } | ||
461 | |||
462 | /** | ||
463 | * Save the datastore -> the link order is now applied when links are saved. | ||
464 | */ | ||
465 | public function updateMethodReorderDatastore() | ||
466 | { | ||
467 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
468 | return true; | ||
469 | } | ||
470 | |||
471 | /** | ||
472 | * Change privateonly session key to visibility. | ||
473 | */ | ||
474 | public function updateMethodVisibilitySession() | ||
475 | { | ||
476 | if (isset($_SESSION['privateonly'])) { | ||
477 | unset($_SESSION['privateonly']); | ||
478 | $_SESSION['visibility'] = 'private'; | ||
479 | } | ||
480 | return true; | ||
481 | } | ||
482 | |||
483 | /** | ||
484 | * Add download size and timeout to the configuration file | ||
485 | * | ||
486 | * @return bool true if the update is successful, false otherwise. | ||
487 | */ | ||
488 | public function updateMethodDownloadSizeAndTimeoutConf() | ||
489 | { | ||
490 | if ($this->conf->exists('general.download_max_size') | ||
491 | && $this->conf->exists('general.download_timeout') | ||
492 | ) { | ||
493 | return true; | ||
494 | } | ||
495 | |||
496 | if (!$this->conf->exists('general.download_max_size')) { | ||
497 | $this->conf->set('general.download_max_size', 1024 * 1024 * 4); | ||
498 | } | ||
499 | |||
500 | if (!$this->conf->exists('general.download_timeout')) { | ||
501 | $this->conf->set('general.download_timeout', 30); | ||
502 | } | ||
503 | |||
504 | $this->conf->write($this->isLoggedIn); | ||
505 | return true; | ||
506 | } | ||
507 | 161 | ||
508 | /** | 162 | $this->bookmarkService->set($bookmark, false); |
509 | * * Move thumbnails management to WebThumbnailer, coming with new settings. | 163 | } |
510 | */ | ||
511 | public function updateMethodWebThumbnailer() | ||
512 | { | ||
513 | if ($this->conf->exists('thumbnails.mode')) { | ||
514 | return true; | ||
515 | } | 164 | } |
516 | 165 | ||
517 | $thumbnailsEnabled = extension_loaded('gd') && $this->conf->get('thumbnail.enable_thumbnails', true); | 166 | if ($updated) { |
518 | $this->conf->set('thumbnails.mode', $thumbnailsEnabled ? Thumbnailer::MODE_ALL : Thumbnailer::MODE_NONE); | 167 | $this->bookmarkService->save(); |
519 | $this->conf->set('thumbnails.width', 125); | ||
520 | $this->conf->set('thumbnails.height', 90); | ||
521 | $this->conf->remove('thumbnail'); | ||
522 | $this->conf->write(true); | ||
523 | |||
524 | if ($thumbnailsEnabled) { | ||
525 | $this->session['warnings'][] = t( | ||
526 | 'You have enabled or changed thumbnails mode. <a href="?do=thumbs_update">Please synchronize them</a>.' | ||
527 | ); | ||
528 | } | 168 | } |
529 | 169 | ||
530 | return true; | 170 | return true; |
531 | } | 171 | } |
532 | 172 | ||
533 | /** | 173 | public function setBasePath(string $basePath): self |
534 | * Set sticky = false on all links | ||
535 | * | ||
536 | * @return bool true if the update is successful, false otherwise. | ||
537 | */ | ||
538 | public function updateMethodSetSticky() | ||
539 | { | 174 | { |
540 | foreach ($this->linkDB as $key => $link) { | 175 | $this->basePath = $basePath; |
541 | if (isset($link['sticky'])) { | ||
542 | return true; | ||
543 | } | ||
544 | $link['sticky'] = false; | ||
545 | $this->linkDB[$key] = $link; | ||
546 | } | ||
547 | |||
548 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
549 | |||
550 | return true; | ||
551 | } | ||
552 | 176 | ||
553 | /** | 177 | return $this; |
554 | * Remove redirector settings. | ||
555 | */ | ||
556 | public function updateMethodRemoveRedirector() | ||
557 | { | ||
558 | $this->conf->remove('redirector'); | ||
559 | $this->conf->write(true); | ||
560 | return true; | ||
561 | } | 178 | } |
562 | } | 179 | } |
diff --git a/application/updater/UpdaterUtils.php b/application/updater/UpdaterUtils.php index 34d4f422..828a49fc 100644 --- a/application/updater/UpdaterUtils.php +++ b/application/updater/UpdaterUtils.php | |||
@@ -1,39 +1,44 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | /** | 3 | namespace Shaarli\Updater; |
4 | * Read the updates file, and return already done updates. | 4 | |
5 | * | 5 | class UpdaterUtils |
6 | * @param string $updatesFilepath Updates file path. | ||
7 | * | ||
8 | * @return array Already done update methods. | ||
9 | */ | ||
10 | function read_updates_file($updatesFilepath) | ||
11 | { | 6 | { |
12 | if (! empty($updatesFilepath) && is_file($updatesFilepath)) { | 7 | /** |
13 | $content = file_get_contents($updatesFilepath); | 8 | * Read the updates file, and return already done updates. |
14 | if (! empty($content)) { | 9 | * |
15 | return explode(';', $content); | 10 | * @param string $updatesFilepath Updates file path. |
11 | * | ||
12 | * @return array Already done update methods. | ||
13 | */ | ||
14 | public static function read_updates_file($updatesFilepath) | ||
15 | { | ||
16 | if (! empty($updatesFilepath) && is_file($updatesFilepath)) { | ||
17 | $content = file_get_contents($updatesFilepath); | ||
18 | if (! empty($content)) { | ||
19 | return explode(';', $content); | ||
20 | } | ||
16 | } | 21 | } |
22 | return array(); | ||
17 | } | 23 | } |
18 | return array(); | ||
19 | } | ||
20 | 24 | ||
21 | /** | 25 | /** |
22 | * Write updates file. | 26 | * Write updates file. |
23 | * | 27 | * |
24 | * @param string $updatesFilepath Updates file path. | 28 | * @param string $updatesFilepath Updates file path. |
25 | * @param array $updates Updates array to write. | 29 | * @param array $updates Updates array to write. |
26 | * | 30 | * |
27 | * @throws Exception Couldn't write version number. | 31 | * @throws \Exception Couldn't write version number. |
28 | */ | 32 | */ |
29 | function write_updates_file($updatesFilepath, $updates) | 33 | public static function write_updates_file($updatesFilepath, $updates) |
30 | { | 34 | { |
31 | if (empty($updatesFilepath)) { | 35 | if (empty($updatesFilepath)) { |
32 | throw new Exception(t('Updates file path is not set, can\'t write updates.')); | 36 | throw new \Exception('Updates file path is not set, can\'t write updates.'); |
33 | } | 37 | } |
34 | 38 | ||
35 | $res = file_put_contents($updatesFilepath, implode(';', $updates)); | 39 | $res = file_put_contents($updatesFilepath, implode(';', $updates)); |
36 | if ($res === false) { | 40 | if ($res === false) { |
37 | throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.')); | 41 | throw new \Exception('Unable to write updates in '. $updatesFilepath . '.'); |
42 | } | ||
38 | } | 43 | } |
39 | } | 44 | } |
diff --git a/plugins/markdown/markdown.css b/assets/common/css/markdown.css index ce19cd2a..f651e67e 100644 --- a/plugins/markdown/markdown.css +++ b/assets/common/css/markdown.css | |||
@@ -140,7 +140,7 @@ | |||
140 | -webkit-hyphens: none; | 140 | -webkit-hyphens: none; |
141 | -moz-hyphens: none; | 141 | -moz-hyphens: none; |
142 | -ms-hyphens: none; | 142 | -ms-hyphens: none; |
143 | hyphens: none; | 143 | hyphens: none; |
144 | } | 144 | } |
145 | 145 | ||
146 | .markdown :not(pre) code { | 146 | .markdown :not(pre) code { |
@@ -155,7 +155,7 @@ | |||
155 | } | 155 | } |
156 | 156 | ||
157 | /* | 157 | /* |
158 | Remove header links style | 158 | Remove header bookmarks style |
159 | */ | 159 | */ |
160 | #pageheader .md_help a { | 160 | #pageheader .md_help a { |
161 | color: lightgray; | 161 | color: lightgray; |
diff --git a/assets/common/js/thumbnails-update.js b/assets/common/js/thumbnails-update.js index b66ca3ae..3cd4c2a7 100644 --- a/assets/common/js/thumbnails-update.js +++ b/assets/common/js/thumbnails-update.js | |||
@@ -10,13 +10,14 @@ | |||
10 | * It contains a recursive call to retrieve the thumb of the next link when it succeed. | 10 | * It contains a recursive call to retrieve the thumb of the next link when it succeed. |
11 | * It also update the progress bar and other visual feedback elements. | 11 | * It also update the progress bar and other visual feedback elements. |
12 | * | 12 | * |
13 | * @param {string} basePath Shaarli subfolder for XHR requests | ||
13 | * @param {array} ids List of LinkID to update | 14 | * @param {array} ids List of LinkID to update |
14 | * @param {int} i Current index in ids | 15 | * @param {int} i Current index in ids |
15 | * @param {object} elements List of DOM element to avoid retrieving them at each iteration | 16 | * @param {object} elements List of DOM element to avoid retrieving them at each iteration |
16 | */ | 17 | */ |
17 | function updateThumb(ids, i, elements) { | 18 | function updateThumb(basePath, ids, i, elements) { |
18 | const xhr = new XMLHttpRequest(); | 19 | const xhr = new XMLHttpRequest(); |
19 | xhr.open('POST', '?do=ajax_thumb_update'); | 20 | xhr.open('PATCH', `${basePath}/admin/shaare/${ids[i]}/update-thumbnail`); |
20 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | 21 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
21 | xhr.responseType = 'json'; | 22 | xhr.responseType = 'json'; |
22 | xhr.onload = () => { | 23 | xhr.onload = () => { |
@@ -29,17 +30,18 @@ function updateThumb(ids, i, elements) { | |||
29 | elements.current.innerHTML = i; | 30 | elements.current.innerHTML = i; |
30 | elements.title.innerHTML = response.title; | 31 | elements.title.innerHTML = response.title; |
31 | if (response.thumbnail !== false) { | 32 | if (response.thumbnail !== false) { |
32 | elements.thumbnail.innerHTML = `<img src="${response.thumbnail}">`; | 33 | elements.thumbnail.innerHTML = `<img src="${basePath}/${response.thumbnail}">`; |
33 | } | 34 | } |
34 | if (i < ids.length) { | 35 | if (i < ids.length) { |
35 | updateThumb(ids, i, elements); | 36 | updateThumb(basePath, ids, i, elements); |
36 | } | 37 | } |
37 | } | 38 | } |
38 | }; | 39 | }; |
39 | xhr.send(`id=${ids[i]}`); | 40 | xhr.send(); |
40 | } | 41 | } |
41 | 42 | ||
42 | (() => { | 43 | (() => { |
44 | const basePath = document.querySelector('input[name="js_base_path"]').value; | ||
43 | const ids = document.getElementsByName('ids')[0].value.split(','); | 45 | const ids = document.getElementsByName('ids')[0].value.split(','); |
44 | const elements = { | 46 | const elements = { |
45 | progressBar: document.querySelector('.progressbar > div'), | 47 | progressBar: document.querySelector('.progressbar > div'), |
@@ -47,5 +49,5 @@ function updateThumb(ids, i, elements) { | |||
47 | thumbnail: document.querySelector('.thumbnail-placeholder'), | 49 | thumbnail: document.querySelector('.thumbnail-placeholder'), |
48 | title: document.querySelector('.thumbnail-link-title'), | 50 | title: document.querySelector('.thumbnail-link-title'), |
49 | }; | 51 | }; |
50 | updateThumb(ids, 0, elements); | 52 | updateThumb(basePath, ids, 0, elements); |
51 | })(); | 53 | })(); |
diff --git a/assets/default/js/base.js b/assets/default/js/base.js index d5c29c69..be986ae0 100644 --- a/assets/default/js/base.js +++ b/assets/default/js/base.js | |||
@@ -10,7 +10,7 @@ import Awesomplete from 'awesomplete'; | |||
10 | * @returns Found element or null. | 10 | * @returns Found element or null. |
11 | */ | 11 | */ |
12 | function findParent(element, tagName, attributes) { | 12 | function findParent(element, tagName, attributes) { |
13 | const parentMatch = key => attributes[key] !== '' && element.getAttribute(key).indexOf(attributes[key]) !== -1; | 13 | const parentMatch = (key) => attributes[key] !== '' && element.getAttribute(key).indexOf(attributes[key]) !== -1; |
14 | while (element) { | 14 | while (element) { |
15 | if (element.tagName.toLowerCase() === tagName) { | 15 | if (element.tagName.toLowerCase() === tagName) { |
16 | if (Object.keys(attributes).find(parentMatch)) { | 16 | if (Object.keys(attributes).find(parentMatch)) { |
@@ -25,12 +25,18 @@ function findParent(element, tagName, attributes) { | |||
25 | /** | 25 | /** |
26 | * Ajax request to refresh the CSRF token. | 26 | * Ajax request to refresh the CSRF token. |
27 | */ | 27 | */ |
28 | function refreshToken() { | 28 | function refreshToken(basePath, callback) { |
29 | const xhr = new XMLHttpRequest(); | 29 | const xhr = new XMLHttpRequest(); |
30 | xhr.open('GET', '?do=token'); | 30 | xhr.open('GET', `${basePath}/admin/token`); |
31 | xhr.onload = () => { | 31 | xhr.onload = () => { |
32 | const token = document.getElementById('token'); | 32 | const elements = document.querySelectorAll('input[name="token"]'); |
33 | token.setAttribute('value', xhr.responseText); | 33 | [...elements].forEach((element) => { |
34 | element.setAttribute('value', xhr.responseText); | ||
35 | }); | ||
36 | |||
37 | if (callback) { | ||
38 | callback(xhr.response); | ||
39 | } | ||
34 | }; | 40 | }; |
35 | xhr.send(); | 41 | xhr.send(); |
36 | } | 42 | } |
@@ -95,7 +101,7 @@ function updateAwesompleteList(selector, tags, instances) { | |||
95 | * @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript | 101 | * @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript |
96 | */ | 102 | */ |
97 | function htmlEntities(str) { | 103 | function htmlEntities(str) { |
98 | return str.replace(/[\u00A0-\u9999<>&]/gim, i => `&#${i.charCodeAt(0)};`); | 104 | return str.replace(/[\u00A0-\u9999<>&]/gim, (i) => `&#${i.charCodeAt(0)};`); |
99 | } | 105 | } |
100 | 106 | ||
101 | /** | 107 | /** |
@@ -188,8 +194,8 @@ function removeClass(element, classname) { | |||
188 | function init(description) { | 194 | function init(description) { |
189 | function resize() { | 195 | function resize() { |
190 | /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */ | 196 | /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */ |
191 | const scrollTop = window.pageYOffset || | 197 | const scrollTop = window.pageYOffset |
192 | (document.documentElement || document.body.parentNode || document.body).scrollTop; | 198 | || (document.documentElement || document.body.parentNode || document.body).scrollTop; |
193 | 199 | ||
194 | description.style.height = 'auto'; | 200 | description.style.height = 'auto'; |
195 | description.style.height = `${description.scrollHeight + 10}px`; | 201 | description.style.height = `${description.scrollHeight + 10}px`; |
@@ -215,6 +221,8 @@ function init(description) { | |||
215 | } | 221 | } |
216 | 222 | ||
217 | (() => { | 223 | (() => { |
224 | const basePath = document.querySelector('input[name="js_base_path"]').value; | ||
225 | |||
218 | /** | 226 | /** |
219 | * Handle responsive menu. | 227 | * Handle responsive menu. |
220 | * Source: http://purecss.io/layouts/tucked-menu-vertical/ | 228 | * Source: http://purecss.io/layouts/tucked-menu-vertical/ |
@@ -461,7 +469,7 @@ function init(description) { | |||
461 | }); | 469 | }); |
462 | 470 | ||
463 | if (window.confirm(message)) { | 471 | if (window.confirm(message)) { |
464 | window.location = `?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`; | 472 | window.location = `${basePath}/admin/shaare/delete?id=${ids.join('+')}&token=${token.value}`; |
465 | } | 473 | } |
466 | }); | 474 | }); |
467 | } | 475 | } |
@@ -482,8 +490,10 @@ function init(description) { | |||
482 | }); | 490 | }); |
483 | }); | 491 | }); |
484 | 492 | ||
485 | const ids = links.map(item => item.id); | 493 | const ids = links.map((item) => item.id); |
486 | window.location = `?change_visibility&token=${token.value}&newVisibility=${visibility}&ids=${ids.join('+')}`; | 494 | window.location = ( |
495 | `${basePath}/admin/shaare/visibility?token=${token.value}&newVisibility=${visibility}&id=${ids.join('+')}` | ||
496 | ); | ||
487 | }); | 497 | }); |
488 | }); | 498 | }); |
489 | } | 499 | } |
@@ -545,8 +555,9 @@ function init(description) { | |||
545 | } | 555 | } |
546 | const refreshedToken = document.getElementById('token').value; | 556 | const refreshedToken = document.getElementById('token').value; |
547 | const fromtag = block.getAttribute('data-tag'); | 557 | const fromtag = block.getAttribute('data-tag'); |
558 | const fromtagUrl = block.getAttribute('data-tag-url'); | ||
548 | const xhr = new XMLHttpRequest(); | 559 | const xhr = new XMLHttpRequest(); |
549 | xhr.open('POST', '?do=changetag'); | 560 | xhr.open('POST', `${basePath}/admin/tags`); |
550 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | 561 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
551 | xhr.onload = () => { | 562 | xhr.onload = () => { |
552 | if (xhr.status !== 200) { | 563 | if (xhr.status !== 200) { |
@@ -554,20 +565,28 @@ function init(description) { | |||
554 | location.reload(); | 565 | location.reload(); |
555 | } else { | 566 | } else { |
556 | block.setAttribute('data-tag', totag); | 567 | block.setAttribute('data-tag', totag); |
568 | block.setAttribute('data-tag-url', encodeURIComponent(totag)); | ||
557 | input.setAttribute('name', totag); | 569 | input.setAttribute('name', totag); |
558 | input.setAttribute('value', totag); | 570 | input.setAttribute('value', totag); |
559 | findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; | 571 | findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; |
560 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); | 572 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); |
561 | block.querySelector('a.tag-link').setAttribute('href', `?searchtags=${encodeURIComponent(totag)}`); | 573 | block |
562 | block.querySelector('a.rename-tag').setAttribute('href', `?do=changetag&fromtag=${encodeURIComponent(totag)}`); | 574 | .querySelector('a.tag-link') |
575 | .setAttribute('href', `${basePath}/?searchtags=${encodeURIComponent(totag)}`); | ||
576 | block | ||
577 | .querySelector('a.count') | ||
578 | .setAttribute('href', `${basePath}/add-tag/${encodeURIComponent(totag)}`); | ||
579 | block | ||
580 | .querySelector('a.rename-tag') | ||
581 | .setAttribute('href', `${basePath}/admin/tags?fromtag=${encodeURIComponent(totag)}`); | ||
563 | 582 | ||
564 | // Refresh awesomplete values | 583 | // Refresh awesomplete values |
565 | existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag)); | 584 | existingTags = existingTags.map((tag) => (tag === fromtag ? totag : tag)); |
566 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); | 585 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); |
567 | } | 586 | } |
568 | }; | 587 | }; |
569 | xhr.send(`renametag=1&fromtag=${encodeURIComponent(fromtag)}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`); | 588 | xhr.send(`renametag=1&fromtag=${fromtagUrl}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`); |
570 | refreshToken(); | 589 | refreshToken(basePath); |
571 | }); | 590 | }); |
572 | }); | 591 | }); |
573 | 592 | ||
@@ -589,19 +608,20 @@ function init(description) { | |||
589 | event.preventDefault(); | 608 | event.preventDefault(); |
590 | const block = findParent(event.target, 'div', { class: 'tag-list-item' }); | 609 | const block = findParent(event.target, 'div', { class: 'tag-list-item' }); |
591 | const tag = block.getAttribute('data-tag'); | 610 | const tag = block.getAttribute('data-tag'); |
611 | const tagUrl = block.getAttribute('data-tag-url'); | ||
592 | const refreshedToken = document.getElementById('token').value; | 612 | const refreshedToken = document.getElementById('token').value; |
593 | 613 | ||
594 | if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) { | 614 | if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) { |
595 | const xhr = new XMLHttpRequest(); | 615 | const xhr = new XMLHttpRequest(); |
596 | xhr.open('POST', '?do=changetag'); | 616 | xhr.open('POST', `${basePath}/admin/tags`); |
597 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | 617 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
598 | xhr.onload = () => { | 618 | xhr.onload = () => { |
599 | block.remove(); | 619 | block.remove(); |
600 | }; | 620 | }; |
601 | xhr.send(encodeURI(`deletetag=1&fromtag=${tag}&token=${refreshedToken}`)); | 621 | xhr.send(`deletetag=1&fromtag=${tagUrl}&token=${refreshedToken}`); |
602 | refreshToken(); | 622 | refreshToken(basePath); |
603 | 623 | ||
604 | existingTags = existingTags.filter(tagItem => tagItem !== tag); | 624 | existingTags = existingTags.filter((tagItem) => tagItem !== tag); |
605 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); | 625 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); |
606 | } | 626 | } |
607 | }); | 627 | }); |
@@ -611,4 +631,15 @@ function init(description) { | |||
611 | [...autocompleteFields].forEach((autocompleteField) => { | 631 | [...autocompleteFields].forEach((autocompleteField) => { |
612 | awesomepletes.push(createAwesompleteInstance(autocompleteField)); | 632 | awesomepletes.push(createAwesompleteInstance(autocompleteField)); |
613 | }); | 633 | }); |
634 | |||
635 | const exportForm = document.querySelector('#exportform'); | ||
636 | if (exportForm != null) { | ||
637 | exportForm.addEventListener('submit', (event) => { | ||
638 | event.preventDefault(); | ||
639 | |||
640 | refreshToken(basePath, () => { | ||
641 | event.target.submit(); | ||
642 | }); | ||
643 | }); | ||
644 | } | ||
614 | })(); | 645 | })(); |
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss index 61e382b6..a528adb0 100644 --- a/assets/default/scss/shaarli.scss +++ b/assets/default/scss/shaarli.scss | |||
@@ -69,20 +69,22 @@ pre { | |||
69 | font-family: 'Roboto'; | 69 | font-family: 'Roboto'; |
70 | font-weight: 400; | 70 | font-weight: 400; |
71 | font-style: normal; | 71 | font-style: normal; |
72 | src: local('Roboto'), | 72 | src: |
73 | local('Roboto-Regular'), | 73 | local('Roboto'), |
74 | url('../fonts/Roboto-Regular.woff2') format('woff2'), | 74 | local('Roboto-Regular'), |
75 | url('../fonts/Roboto-Regular.woff') format('woff'); | 75 | url('../fonts/Roboto-Regular.woff2') format('woff2'), |
76 | url('../fonts/Roboto-Regular.woff') format('woff'); | ||
76 | } | 77 | } |
77 | 78 | ||
78 | @font-face { | 79 | @font-face { |
79 | font-family: 'Roboto'; | 80 | font-family: 'Roboto'; |
80 | font-weight: 700; | 81 | font-weight: 700; |
81 | font-style: normal; | 82 | font-style: normal; |
82 | src: local('Roboto'), | 83 | src: |
83 | local('Roboto-Bold'), | 84 | local('Roboto'), |
84 | url('../fonts/Roboto-Bold.woff2') format('woff2'), | 85 | local('Roboto-Bold'), |
85 | url('../fonts/Roboto-Bold.woff') format('woff'); | 86 | url('../fonts/Roboto-Bold.woff2') format('woff2'), |
87 | url('../fonts/Roboto-Bold.woff') format('woff'); | ||
86 | } | 88 | } |
87 | 89 | ||
88 | body, | 90 | body, |
@@ -375,7 +377,7 @@ body, | |||
375 | } | 377 | } |
376 | 378 | ||
377 | @media screen and (max-width: 64em) { | 379 | @media screen and (max-width: 64em) { |
378 | .header-search , | 380 | .header-search, |
379 | .header-search * { | 381 | .header-search * { |
380 | visibility: hidden; | 382 | visibility: hidden; |
381 | } | 383 | } |
@@ -490,6 +492,10 @@ body, | |||
490 | } | 492 | } |
491 | } | 493 | } |
492 | 494 | ||
495 | .header-alert-message { | ||
496 | text-align: center; | ||
497 | } | ||
498 | |||
493 | // CONTENT - GENERAL | 499 | // CONTENT - GENERAL |
494 | .container { | 500 | .container { |
495 | position: relative; | 501 | position: relative; |
@@ -550,7 +556,6 @@ body, | |||
550 | color: $dark-grey; | 556 | color: $dark-grey; |
551 | font-size: .9em; | 557 | font-size: .9em; |
552 | 558 | ||
553 | |||
554 | a { | 559 | a { |
555 | display: inline-block; | 560 | display: inline-block; |
556 | margin: 3px 0; | 561 | margin: 3px 0; |
@@ -612,6 +617,11 @@ body, | |||
612 | padding: 5px; | 617 | padding: 5px; |
613 | text-decoration: none; | 618 | text-decoration: none; |
614 | color: $dark-grey; | 619 | color: $dark-grey; |
620 | |||
621 | &.selected { | ||
622 | background: var(--main-color); | ||
623 | color: $white; | ||
624 | } | ||
615 | } | 625 | } |
616 | 626 | ||
617 | input { | 627 | input { |
@@ -1236,8 +1246,19 @@ form { | |||
1236 | color: $dark-grey; | 1246 | color: $dark-grey; |
1237 | } | 1247 | } |
1238 | 1248 | ||
1239 | .page404-container { | 1249 | .page-error-container { |
1240 | color: $dark-grey; | 1250 | color: $dark-grey; |
1251 | |||
1252 | h2 { | ||
1253 | margin: 70px 0 25px; | ||
1254 | } | ||
1255 | |||
1256 | pre { | ||
1257 | margin: 0 20%; | ||
1258 | padding: 20px 0; | ||
1259 | text-align: left; | ||
1260 | line-height: .7em; | ||
1261 | } | ||
1241 | } | 1262 | } |
1242 | 1263 | ||
1243 | // EDIT LINK | 1264 | // EDIT LINK |
@@ -1436,6 +1457,8 @@ form { | |||
1436 | -webkit-transition: opacity 500ms ease-in-out; | 1457 | -webkit-transition: opacity 500ms ease-in-out; |
1437 | -moz-transition: opacity 500ms ease-in-out; | 1458 | -moz-transition: opacity 500ms ease-in-out; |
1438 | -o-transition: opacity 500ms ease-in-out; | 1459 | -o-transition: opacity 500ms ease-in-out; |
1460 | min-width: 1px; | ||
1461 | min-height: 1px; | ||
1439 | 1462 | ||
1440 | &.b-loaded { | 1463 | &.b-loaded { |
1441 | opacity: 1; | 1464 | opacity: 1; |
@@ -1535,11 +1558,11 @@ form { | |||
1535 | text-align: center; | 1558 | text-align: center; |
1536 | 1559 | ||
1537 | a { | 1560 | a { |
1561 | background: $almost-white; | ||
1538 | display: inline-block; | 1562 | display: inline-block; |
1539 | margin: 0 15px; | 1563 | padding: 5px; |
1540 | text-decoration: none; | 1564 | text-decoration: none; |
1541 | color: $white; | 1565 | color: $dark-grey; |
1542 | font-weight: bold; | ||
1543 | } | 1566 | } |
1544 | } | 1567 | } |
1545 | 1568 | ||
@@ -1587,13 +1610,14 @@ form { | |||
1587 | 1610 | ||
1588 | > div { | 1611 | > div { |
1589 | border-radius: 10px; | 1612 | border-radius: 10px; |
1590 | background: repeating-linear-gradient( | 1613 | background: |
1591 | -45deg, | 1614 | repeating-linear-gradient( |
1592 | $almost-white, | 1615 | -45deg, |
1593 | $almost-white 6px, | 1616 | $almost-white, |
1594 | var(--background-color) 6px, | 1617 | $almost-white 6px, |
1595 | var(--background-color) 12px | 1618 | var(--background-color) 6px, |
1596 | ); | 1619 | var(--background-color) 12px |
1620 | ); | ||
1597 | width: 0%; | 1621 | width: 0%; |
1598 | height: 10px; | 1622 | height: 10px; |
1599 | } | 1623 | } |
diff --git a/assets/vintage/css/shaarli.css b/assets/vintage/css/shaarli.css index 87c440c8..1688dce0 100644 --- a/assets/vintage/css/shaarli.css +++ b/assets/vintage/css/shaarli.css | |||
@@ -746,8 +746,6 @@ a.bigbutton, #pageheader a.bigbutton { | |||
746 | text-align: left; | 746 | text-align: left; |
747 | background-color: transparent; | 747 | background-color: transparent; |
748 | background-color: rgba(0, 0, 0, 0.4); | 748 | background-color: rgba(0, 0, 0, 0.4); |
749 | /* FF3+, Saf3+, Opera 10.10+, Chrome, IE9 */ | ||
750 | filter: progid: DXImageTransform.Microsoft.gradient(startColorstr=#66000000, endColorstr=#66000000); | ||
751 | /* IE6–IE9 */ | 749 | /* IE6–IE9 */ |
752 | text-shadow: 2px 2px 1px #000000; | 750 | text-shadow: 2px 2px 1px #000000; |
753 | } | 751 | } |
diff --git a/composer.json b/composer.json index 109d5d7b..cd9fcf5b 100644 --- a/composer.json +++ b/composer.json | |||
@@ -21,15 +21,14 @@ | |||
21 | "shaarli/netscape-bookmark-parser": "^2.1", | 21 | "shaarli/netscape-bookmark-parser": "^2.1", |
22 | "erusev/parsedown": "^1.6", | 22 | "erusev/parsedown": "^1.6", |
23 | "slim/slim": "^3.0", | 23 | "slim/slim": "^3.0", |
24 | "arthurhoaro/web-thumbnailer": "^1.1", | 24 | "arthurhoaro/web-thumbnailer": "^2.0", |
25 | "pubsubhubbub/publisher": "dev-master", | 25 | "pubsubhubbub/publisher": "dev-master", |
26 | "gettext/gettext": "^4.4" | 26 | "gettext/gettext": "^4.4" |
27 | }, | 27 | }, |
28 | "require-dev": { | 28 | "require-dev": { |
29 | "roave/security-advisories": "dev-master", | 29 | "roave/security-advisories": "dev-master", |
30 | "phpunit/phpcov": "*", | 30 | "squizlabs/php_codesniffer": "3.*", |
31 | "phpunit/phpunit": "^5.0", | 31 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" |
32 | "squizlabs/php_codesniffer": "2.*" | ||
33 | }, | 32 | }, |
34 | "suggest": { | 33 | "suggest": { |
35 | "ext-curl": "Allows fetching web pages and thumbnails in a more robust way", | 34 | "ext-curl": "Allows fetching web pages and thumbnails in a more robust way", |
@@ -48,9 +47,16 @@ | |||
48 | "Shaarli\\Bookmark\\Exception\\": "application/bookmark/exception", | 47 | "Shaarli\\Bookmark\\Exception\\": "application/bookmark/exception", |
49 | "Shaarli\\Config\\": "application/config/", | 48 | "Shaarli\\Config\\": "application/config/", |
50 | "Shaarli\\Config\\Exception\\": "application/config/exception", | 49 | "Shaarli\\Config\\Exception\\": "application/config/exception", |
50 | "Shaarli\\Container\\": "application/container", | ||
51 | "Shaarli\\Exceptions\\": "application/exceptions", | 51 | "Shaarli\\Exceptions\\": "application/exceptions", |
52 | "Shaarli\\Feed\\": "application/feed", | 52 | "Shaarli\\Feed\\": "application/feed", |
53 | "Shaarli\\Formatter\\": "application/formatter", | ||
54 | "Shaarli\\Front\\": "application/front", | ||
55 | "Shaarli\\Front\\Controller\\Admin\\": "application/front/controller/admin", | ||
56 | "Shaarli\\Front\\Controller\\Visitor\\": "application/front/controller/visitor", | ||
57 | "Shaarli\\Front\\Exception\\": "application/front/exceptions", | ||
53 | "Shaarli\\Http\\": "application/http", | 58 | "Shaarli\\Http\\": "application/http", |
59 | "Shaarli\\Legacy\\": "application/legacy", | ||
54 | "Shaarli\\Netscape\\": "application/netscape", | 60 | "Shaarli\\Netscape\\": "application/netscape", |
55 | "Shaarli\\Plugin\\": "application/plugin", | 61 | "Shaarli\\Plugin\\": "application/plugin", |
56 | "Shaarli\\Plugin\\Exception\\": "application/plugin/exception", | 62 | "Shaarli\\Plugin\\Exception\\": "application/plugin/exception", |
diff --git a/composer.lock b/composer.lock index e6c938db..2c8b0ea7 100644 --- a/composer.lock +++ b/composer.lock | |||
@@ -4,33 +4,32 @@ | |||
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", |
5 | "This file is @generated automatically" | 5 | "This file is @generated automatically" |
6 | ], | 6 | ], |
7 | "content-hash": "085b03ce38cd1106ad0e3af1ef3014c2", | 7 | "content-hash": "98520a05a7185503ee13d05ffaa535f6", |
8 | "packages": [ | 8 | "packages": [ |
9 | { | 9 | { |
10 | "name": "arthurhoaro/web-thumbnailer", | 10 | "name": "arthurhoaro/web-thumbnailer", |
11 | "version": "v1.3.1", | 11 | "version": "v2.0.3", |
12 | "source": { | 12 | "source": { |
13 | "type": "git", | 13 | "type": "git", |
14 | "url": "https://github.com/ArthurHoaro/web-thumbnailer.git", | 14 | "url": "https://github.com/ArthurHoaro/web-thumbnailer.git", |
15 | "reference": "7142bd94ec93719a756a7012ebb8e1c5813c6860" | 15 | "reference": "39bfd4f3136d9e6096496b9720e877326cfe4775" |
16 | }, | 16 | }, |
17 | "dist": { | 17 | "dist": { |
18 | "type": "zip", | 18 | "type": "zip", |
19 | "url": "https://api.github.com/repos/ArthurHoaro/web-thumbnailer/zipball/7142bd94ec93719a756a7012ebb8e1c5813c6860", | 19 | "url": "https://api.github.com/repos/ArthurHoaro/web-thumbnailer/zipball/39bfd4f3136d9e6096496b9720e877326cfe4775", |
20 | "reference": "7142bd94ec93719a756a7012ebb8e1c5813c6860", | 20 | "reference": "39bfd4f3136d9e6096496b9720e877326cfe4775", |
21 | "shasum": "" | 21 | "shasum": "" |
22 | }, | 22 | }, |
23 | "require": { | 23 | "require": { |
24 | "php": ">=5.6", | 24 | "php": ">=7.1", |
25 | "phpunit/php-text-template": "^1.2" | 25 | "phpunit/php-text-template": "^1.2 || ^2.0" |
26 | }, | ||
27 | "conflict": { | ||
28 | "phpunit/php-timer": ">=2" | ||
29 | }, | 26 | }, |
30 | "require-dev": { | 27 | "require-dev": { |
28 | "gskema/phpcs-type-sniff": "^0.13.1", | ||
31 | "php-coveralls/php-coveralls": "^2.0", | 29 | "php-coveralls/php-coveralls": "^2.0", |
32 | "phpunit/phpunit": "5.2.*", | 30 | "phpstan/phpstan": "^0.12.9", |
33 | "squizlabs/php_codesniffer": "^3.2" | 31 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", |
32 | "squizlabs/php_codesniffer": "^3.0" | ||
34 | }, | 33 | }, |
35 | "type": "library", | 34 | "type": "library", |
36 | "autoload": { | 35 | "autoload": { |
@@ -52,51 +51,24 @@ | |||
52 | } | 51 | } |
53 | ], | 52 | ], |
54 | "description": "PHP library which will retrieve a thumbnail for any given URL", | 53 | "description": "PHP library which will retrieve a thumbnail for any given URL", |
55 | "time": "2018-08-11T12:21:52+00:00" | 54 | "support": { |
56 | }, | 55 | "issues": "https://github.com/ArthurHoaro/web-thumbnailer/issues", |
57 | { | 56 | "source": "https://github.com/ArthurHoaro/web-thumbnailer/tree/v2.0.3" |
58 | "name": "container-interop/container-interop", | ||
59 | "version": "1.2.0", | ||
60 | "source": { | ||
61 | "type": "git", | ||
62 | "url": "https://github.com/container-interop/container-interop.git", | ||
63 | "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" | ||
64 | }, | ||
65 | "dist": { | ||
66 | "type": "zip", | ||
67 | "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", | ||
68 | "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", | ||
69 | "shasum": "" | ||
70 | }, | 57 | }, |
71 | "require": { | 58 | "time": "2020-09-29T15:51:03+00:00" |
72 | "psr/container": "^1.0" | ||
73 | }, | ||
74 | "type": "library", | ||
75 | "autoload": { | ||
76 | "psr-4": { | ||
77 | "Interop\\Container\\": "src/Interop/Container/" | ||
78 | } | ||
79 | }, | ||
80 | "notification-url": "https://packagist.org/downloads/", | ||
81 | "license": [ | ||
82 | "MIT" | ||
83 | ], | ||
84 | "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", | ||
85 | "homepage": "https://github.com/container-interop/container-interop", | ||
86 | "time": "2017-02-14T19:40:03+00:00" | ||
87 | }, | 59 | }, |
88 | { | 60 | { |
89 | "name": "erusev/parsedown", | 61 | "name": "erusev/parsedown", |
90 | "version": "1.7.3", | 62 | "version": "1.7.4", |
91 | "source": { | 63 | "source": { |
92 | "type": "git", | 64 | "type": "git", |
93 | "url": "https://github.com/erusev/parsedown.git", | 65 | "url": "https://github.com/erusev/parsedown.git", |
94 | "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7" | 66 | "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" |
95 | }, | 67 | }, |
96 | "dist": { | 68 | "dist": { |
97 | "type": "zip", | 69 | "type": "zip", |
98 | "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7", | 70 | "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", |
99 | "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7", | 71 | "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", |
100 | "shasum": "" | 72 | "shasum": "" |
101 | }, | 73 | }, |
102 | "require": { | 74 | "require": { |
@@ -129,20 +101,24 @@ | |||
129 | "markdown", | 101 | "markdown", |
130 | "parser" | 102 | "parser" |
131 | ], | 103 | ], |
132 | "time": "2019-03-17T18:48:37+00:00" | 104 | "support": { |
105 | "issues": "https://github.com/erusev/parsedown/issues", | ||
106 | "source": "https://github.com/erusev/parsedown/tree/1.7.x" | ||
107 | }, | ||
108 | "time": "2019-12-30T22:54:17+00:00" | ||
133 | }, | 109 | }, |
134 | { | 110 | { |
135 | "name": "gettext/gettext", | 111 | "name": "gettext/gettext", |
136 | "version": "v4.6.3", | 112 | "version": "v4.8.2", |
137 | "source": { | 113 | "source": { |
138 | "type": "git", | 114 | "type": "git", |
139 | "url": "https://github.com/oscarotero/Gettext.git", | 115 | "url": "https://github.com/php-gettext/Gettext.git", |
140 | "reference": "70c6ff2fecd275e6ef9cdd542f55939a3d1904d6" | 116 | "reference": "e474f872f2c8636cf53fd283ec4ce1218f3d236a" |
141 | }, | 117 | }, |
142 | "dist": { | 118 | "dist": { |
143 | "type": "zip", | 119 | "type": "zip", |
144 | "url": "https://api.github.com/repos/oscarotero/Gettext/zipball/70c6ff2fecd275e6ef9cdd542f55939a3d1904d6", | 120 | "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/e474f872f2c8636cf53fd283ec4ce1218f3d236a", |
145 | "reference": "70c6ff2fecd275e6ef9cdd542f55939a3d1904d6", | 121 | "reference": "e474f872f2c8636cf53fd283ec4ce1218f3d236a", |
146 | "shasum": "" | 122 | "shasum": "" |
147 | }, | 123 | }, |
148 | "require": { | 124 | "require": { |
@@ -191,31 +167,36 @@ | |||
191 | "po", | 167 | "po", |
192 | "translation" | 168 | "translation" |
193 | ], | 169 | ], |
194 | "time": "2019-07-15T12:56:31+00:00" | 170 | "support": { |
171 | "email": "oom@oscarotero.com", | ||
172 | "issues": "https://github.com/oscarotero/Gettext/issues", | ||
173 | "source": "https://github.com/php-gettext/Gettext/tree/v4.8.2" | ||
174 | }, | ||
175 | "time": "2019-12-02T10:21:14+00:00" | ||
195 | }, | 176 | }, |
196 | { | 177 | { |
197 | "name": "gettext/languages", | 178 | "name": "gettext/languages", |
198 | "version": "2.5.0", | 179 | "version": "2.6.0", |
199 | "source": { | 180 | "source": { |
200 | "type": "git", | 181 | "type": "git", |
201 | "url": "https://github.com/mlocati/cldr-to-gettext-plural-rules.git", | 182 | "url": "https://github.com/php-gettext/Languages.git", |
202 | "reference": "78db2d17933f0765a102f368a6663f057162ddbd" | 183 | "reference": "38ea0482f649e0802e475f0ed19fa993bcb7a618" |
203 | }, | 184 | }, |
204 | "dist": { | 185 | "dist": { |
205 | "type": "zip", | 186 | "type": "zip", |
206 | "url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/78db2d17933f0765a102f368a6663f057162ddbd", | 187 | "url": "https://api.github.com/repos/php-gettext/Languages/zipball/38ea0482f649e0802e475f0ed19fa993bcb7a618", |
207 | "reference": "78db2d17933f0765a102f368a6663f057162ddbd", | 188 | "reference": "38ea0482f649e0802e475f0ed19fa993bcb7a618", |
208 | "shasum": "" | 189 | "shasum": "" |
209 | }, | 190 | }, |
210 | "require": { | 191 | "require": { |
211 | "php": ">=5.3" | 192 | "php": ">=5.3" |
212 | }, | 193 | }, |
213 | "require-dev": { | 194 | "require-dev": { |
214 | "phpunit/phpunit": "^4" | 195 | "friendsofphp/php-cs-fixer": "^2.16.0", |
196 | "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4" | ||
215 | }, | 197 | }, |
216 | "bin": [ | 198 | "bin": [ |
217 | "bin/export-plural-rules", | 199 | "bin/export-plural-rules" |
218 | "bin/export-plural-rules.php" | ||
219 | ], | 200 | ], |
220 | "type": "library", | 201 | "type": "library", |
221 | "autoload": { | 202 | "autoload": { |
@@ -235,7 +216,7 @@ | |||
235 | } | 216 | } |
236 | ], | 217 | ], |
237 | "description": "gettext languages with plural rules", | 218 | "description": "gettext languages with plural rules", |
238 | "homepage": "https://github.com/mlocati/cldr-to-gettext-plural-rules", | 219 | "homepage": "https://github.com/php-gettext/Languages", |
239 | "keywords": [ | 220 | "keywords": [ |
240 | "cldr", | 221 | "cldr", |
241 | "i18n", | 222 | "i18n", |
@@ -252,7 +233,11 @@ | |||
252 | "translations", | 233 | "translations", |
253 | "unicode" | 234 | "unicode" |
254 | ], | 235 | ], |
255 | "time": "2018-11-13T22:06:07+00:00" | 236 | "support": { |
237 | "issues": "https://github.com/php-gettext/Languages/issues", | ||
238 | "source": "https://github.com/php-gettext/Languages/tree/2.6.0" | ||
239 | }, | ||
240 | "time": "2019-11-13T10:30:21+00:00" | ||
256 | }, | 241 | }, |
257 | { | 242 | { |
258 | "name": "katzgrau/klogger", | 243 | "name": "katzgrau/klogger", |
@@ -302,6 +287,10 @@ | |||
302 | "keywords": [ | 287 | "keywords": [ |
303 | "logging" | 288 | "logging" |
304 | ], | 289 | ], |
290 | "support": { | ||
291 | "issues": "https://github.com/katzgrau/KLogger/issues", | ||
292 | "source": "https://github.com/katzgrau/KLogger/tree/master" | ||
293 | }, | ||
305 | "time": "2016-11-07T19:29:14+00:00" | 294 | "time": "2016-11-07T19:29:14+00:00" |
306 | }, | 295 | }, |
307 | { | 296 | { |
@@ -348,6 +337,10 @@ | |||
348 | "router", | 337 | "router", |
349 | "routing" | 338 | "routing" |
350 | ], | 339 | ], |
340 | "support": { | ||
341 | "issues": "https://github.com/nikic/FastRoute/issues", | ||
342 | "source": "https://github.com/nikic/FastRoute/tree/master" | ||
343 | }, | ||
351 | "time": "2018-02-13T20:26:39+00:00" | 344 | "time": "2018-02-13T20:26:39+00:00" |
352 | }, | 345 | }, |
353 | { | 346 | { |
@@ -389,6 +382,10 @@ | |||
389 | "keywords": [ | 382 | "keywords": [ |
390 | "template" | 383 | "template" |
391 | ], | 384 | ], |
385 | "support": { | ||
386 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", | ||
387 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" | ||
388 | }, | ||
392 | "time": "2015-06-21T13:50:34+00:00" | 389 | "time": "2015-06-21T13:50:34+00:00" |
393 | }, | 390 | }, |
394 | { | 391 | { |
@@ -439,6 +436,10 @@ | |||
439 | "container", | 436 | "container", |
440 | "dependency injection" | 437 | "dependency injection" |
441 | ], | 438 | ], |
439 | "support": { | ||
440 | "issues": "https://github.com/silexphp/Pimple/issues", | ||
441 | "source": "https://github.com/silexphp/Pimple/tree/master" | ||
442 | }, | ||
442 | "time": "2018-01-21T07:42:36+00:00" | 443 | "time": "2018-01-21T07:42:36+00:00" |
443 | }, | 444 | }, |
444 | { | 445 | { |
@@ -488,6 +489,10 @@ | |||
488 | "container-interop", | 489 | "container-interop", |
489 | "psr" | 490 | "psr" |
490 | ], | 491 | ], |
492 | "support": { | ||
493 | "issues": "https://github.com/php-fig/container/issues", | ||
494 | "source": "https://github.com/php-fig/container/tree/master" | ||
495 | }, | ||
491 | "time": "2017-02-14T16:28:37+00:00" | 496 | "time": "2017-02-14T16:28:37+00:00" |
492 | }, | 497 | }, |
493 | { | 498 | { |
@@ -538,20 +543,23 @@ | |||
538 | "request", | 543 | "request", |
539 | "response" | 544 | "response" |
540 | ], | 545 | ], |
546 | "support": { | ||
547 | "source": "https://github.com/php-fig/http-message/tree/master" | ||
548 | }, | ||
541 | "time": "2016-08-06T14:39:51+00:00" | 549 | "time": "2016-08-06T14:39:51+00:00" |
542 | }, | 550 | }, |
543 | { | 551 | { |
544 | "name": "psr/log", | 552 | "name": "psr/log", |
545 | "version": "1.1.0", | 553 | "version": "1.1.3", |
546 | "source": { | 554 | "source": { |
547 | "type": "git", | 555 | "type": "git", |
548 | "url": "https://github.com/php-fig/log.git", | 556 | "url": "https://github.com/php-fig/log.git", |
549 | "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" | 557 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" |
550 | }, | 558 | }, |
551 | "dist": { | 559 | "dist": { |
552 | "type": "zip", | 560 | "type": "zip", |
553 | "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", | 561 | "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", |
554 | "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", | 562 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", |
555 | "shasum": "" | 563 | "shasum": "" |
556 | }, | 564 | }, |
557 | "require": { | 565 | "require": { |
@@ -560,7 +568,7 @@ | |||
560 | "type": "library", | 568 | "type": "library", |
561 | "extra": { | 569 | "extra": { |
562 | "branch-alias": { | 570 | "branch-alias": { |
563 | "dev-master": "1.0.x-dev" | 571 | "dev-master": "1.1.x-dev" |
564 | } | 572 | } |
565 | }, | 573 | }, |
566 | "autoload": { | 574 | "autoload": { |
@@ -585,7 +593,10 @@ | |||
585 | "psr", | 593 | "psr", |
586 | "psr-3" | 594 | "psr-3" |
587 | ], | 595 | ], |
588 | "time": "2018-11-20T15:27:04+00:00" | 596 | "support": { |
597 | "source": "https://github.com/php-fig/log/tree/1.1.3" | ||
598 | }, | ||
599 | "time": "2020-03-23T09:12:05+00:00" | ||
589 | }, | 600 | }, |
590 | { | 601 | { |
591 | "name": "pubsubhubbub/publisher", | 602 | "name": "pubsubhubbub/publisher", |
@@ -605,6 +616,7 @@ | |||
605 | "ext-curl": "*", | 616 | "ext-curl": "*", |
606 | "php": "~5.4 || ~7.0" | 617 | "php": "~5.4 || ~7.0" |
607 | }, | 618 | }, |
619 | "default-branch": true, | ||
608 | "type": "library", | 620 | "type": "library", |
609 | "autoload": { | 621 | "autoload": { |
610 | "psr-4": { | 622 | "psr-4": { |
@@ -630,20 +642,24 @@ | |||
630 | "pubsubhubbub", | 642 | "pubsubhubbub", |
631 | "websub" | 643 | "websub" |
632 | ], | 644 | ], |
645 | "support": { | ||
646 | "issues": "https://github.com/pubsubhubbub/php-publisher/issues", | ||
647 | "source": "https://github.com/pubsubhubbub/php-publisher/tree/master" | ||
648 | }, | ||
633 | "time": "2018-10-09T05:20:28+00:00" | 649 | "time": "2018-10-09T05:20:28+00:00" |
634 | }, | 650 | }, |
635 | { | 651 | { |
636 | "name": "shaarli/netscape-bookmark-parser", | 652 | "name": "shaarli/netscape-bookmark-parser", |
637 | "version": "v2.1.0", | 653 | "version": "v2.2.0", |
638 | "source": { | 654 | "source": { |
639 | "type": "git", | 655 | "type": "git", |
640 | "url": "https://github.com/shaarli/netscape-bookmark-parser.git", | 656 | "url": "https://github.com/shaarli/netscape-bookmark-parser.git", |
641 | "reference": "819008ee42c4dd7e45d988176a4a22d6ed689577" | 657 | "reference": "432a010af2bb1832d6fbc4763e6b0100b980a1df" |
642 | }, | 658 | }, |
643 | "dist": { | 659 | "dist": { |
644 | "type": "zip", | 660 | "type": "zip", |
645 | "url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/819008ee42c4dd7e45d988176a4a22d6ed689577", | 661 | "url": "https://api.github.com/repos/shaarli/netscape-bookmark-parser/zipball/432a010af2bb1832d6fbc4763e6b0100b980a1df", |
646 | "reference": "819008ee42c4dd7e45d988176a4a22d6ed689577", | 662 | "reference": "432a010af2bb1832d6fbc4763e6b0100b980a1df", |
647 | "shasum": "" | 663 | "shasum": "" |
648 | }, | 664 | }, |
649 | "require": { | 665 | "require": { |
@@ -683,26 +699,32 @@ | |||
683 | "bookmark", | 699 | "bookmark", |
684 | "link", | 700 | "link", |
685 | "netscape", | 701 | "netscape", |
686 | "parser" | 702 | "parse" |
687 | ], | 703 | ], |
688 | "time": "2018-10-06T14:43:38+00:00" | 704 | "support": { |
705 | "issues": "https://github.com/shaarli/netscape-bookmark-parser/issues", | ||
706 | "source": "https://github.com/shaarli/netscape-bookmark-parser/tree/v2.2.0" | ||
707 | }, | ||
708 | "time": "2020-06-06T15:53:53+00:00" | ||
689 | }, | 709 | }, |
690 | { | 710 | { |
691 | "name": "slim/slim", | 711 | "name": "slim/slim", |
692 | "version": "3.12.1", | 712 | "version": "3.12.3", |
693 | "source": { | 713 | "source": { |
694 | "type": "git", | 714 | "type": "git", |
695 | "url": "https://github.com/slimphp/Slim.git", | 715 | "url": "https://github.com/slimphp/Slim.git", |
696 | "reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b" | 716 | "reference": "1c9318a84ffb890900901136d620b4f03a59da38" |
697 | }, | 717 | }, |
698 | "dist": { | 718 | "dist": { |
699 | "type": "zip", | 719 | "type": "zip", |
700 | "url": "https://api.github.com/repos/slimphp/Slim/zipball/eaee12ef8d0750db62b8c548016d82fb33addb6b", | 720 | "url": "https://api.github.com/repos/slimphp/Slim/zipball/1c9318a84ffb890900901136d620b4f03a59da38", |
701 | "reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b", | 721 | "reference": "1c9318a84ffb890900901136d620b4f03a59da38", |
702 | "shasum": "" | 722 | "shasum": "" |
703 | }, | 723 | }, |
704 | "require": { | 724 | "require": { |
705 | "container-interop/container-interop": "^1.2", | 725 | "ext-json": "*", |
726 | "ext-libxml": "*", | ||
727 | "ext-simplexml": "*", | ||
706 | "nikic/fast-route": "^1.0", | 728 | "nikic/fast-route": "^1.0", |
707 | "php": ">=5.5.0", | 729 | "php": ">=5.5.0", |
708 | "pimple/pimple": "^3.0", | 730 | "pimple/pimple": "^3.0", |
@@ -728,24 +750,24 @@ | |||
728 | ], | 750 | ], |
729 | "authors": [ | 751 | "authors": [ |
730 | { | 752 | { |
731 | "name": "Rob Allen", | ||
732 | "email": "rob@akrabat.com", | ||
733 | "homepage": "http://akrabat.com" | ||
734 | }, | ||
735 | { | ||
736 | "name": "Josh Lockhart", | 753 | "name": "Josh Lockhart", |
737 | "email": "hello@joshlockhart.com", | 754 | "email": "hello@joshlockhart.com", |
738 | "homepage": "https://joshlockhart.com" | 755 | "homepage": "https://joshlockhart.com" |
739 | }, | 756 | }, |
740 | { | 757 | { |
741 | "name": "Gabriel Manricks", | ||
742 | "email": "gmanricks@me.com", | ||
743 | "homepage": "http://gabrielmanricks.com" | ||
744 | }, | ||
745 | { | ||
746 | "name": "Andrew Smith", | 758 | "name": "Andrew Smith", |
747 | "email": "a.smith@silentworks.co.uk", | 759 | "email": "a.smith@silentworks.co.uk", |
748 | "homepage": "http://silentworks.co.uk" | 760 | "homepage": "http://silentworks.co.uk" |
761 | }, | ||
762 | { | ||
763 | "name": "Rob Allen", | ||
764 | "email": "rob@akrabat.com", | ||
765 | "homepage": "http://akrabat.com" | ||
766 | }, | ||
767 | { | ||
768 | "name": "Gabriel Manricks", | ||
769 | "email": "gmanricks@me.com", | ||
770 | "homepage": "http://gabrielmanricks.com" | ||
749 | } | 771 | } |
750 | ], | 772 | ], |
751 | "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", | 773 | "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", |
@@ -756,26 +778,30 @@ | |||
756 | "micro", | 778 | "micro", |
757 | "router" | 779 | "router" |
758 | ], | 780 | ], |
759 | "time": "2019-04-16T16:47:29+00:00" | 781 | "support": { |
782 | "issues": "https://github.com/slimphp/Slim/issues", | ||
783 | "source": "https://github.com/slimphp/Slim/tree/3.x" | ||
784 | }, | ||
785 | "time": "2019-11-28T17:40:33+00:00" | ||
760 | } | 786 | } |
761 | ], | 787 | ], |
762 | "packages-dev": [ | 788 | "packages-dev": [ |
763 | { | 789 | { |
764 | "name": "doctrine/instantiator", | 790 | "name": "doctrine/instantiator", |
765 | "version": "1.2.0", | 791 | "version": "1.3.1", |
766 | "source": { | 792 | "source": { |
767 | "type": "git", | 793 | "type": "git", |
768 | "url": "https://github.com/doctrine/instantiator.git", | 794 | "url": "https://github.com/doctrine/instantiator.git", |
769 | "reference": "a2c590166b2133a4633738648b6b064edae0814a" | 795 | "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" |
770 | }, | 796 | }, |
771 | "dist": { | 797 | "dist": { |
772 | "type": "zip", | 798 | "type": "zip", |
773 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", | 799 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", |
774 | "reference": "a2c590166b2133a4633738648b6b064edae0814a", | 800 | "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", |
775 | "shasum": "" | 801 | "shasum": "" |
776 | }, | 802 | }, |
777 | "require": { | 803 | "require": { |
778 | "php": "^7.1" | 804 | "php": "^7.1 || ^8.0" |
779 | }, | 805 | }, |
780 | "require-dev": { | 806 | "require-dev": { |
781 | "doctrine/coding-standard": "^6.0", | 807 | "doctrine/coding-standard": "^6.0", |
@@ -814,24 +840,42 @@ | |||
814 | "constructor", | 840 | "constructor", |
815 | "instantiate" | 841 | "instantiate" |
816 | ], | 842 | ], |
817 | "time": "2019-03-17T17:37:11+00:00" | 843 | "support": { |
844 | "issues": "https://github.com/doctrine/instantiator/issues", | ||
845 | "source": "https://github.com/doctrine/instantiator/tree/1.3.x" | ||
846 | }, | ||
847 | "funding": [ | ||
848 | { | ||
849 | "url": "https://www.doctrine-project.org/sponsorship.html", | ||
850 | "type": "custom" | ||
851 | }, | ||
852 | { | ||
853 | "url": "https://www.patreon.com/phpdoctrine", | ||
854 | "type": "patreon" | ||
855 | }, | ||
856 | { | ||
857 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", | ||
858 | "type": "tidelift" | ||
859 | } | ||
860 | ], | ||
861 | "time": "2020-05-29T17:27:14+00:00" | ||
818 | }, | 862 | }, |
819 | { | 863 | { |
820 | "name": "myclabs/deep-copy", | 864 | "name": "myclabs/deep-copy", |
821 | "version": "1.9.1", | 865 | "version": "1.10.1", |
822 | "source": { | 866 | "source": { |
823 | "type": "git", | 867 | "type": "git", |
824 | "url": "https://github.com/myclabs/DeepCopy.git", | 868 | "url": "https://github.com/myclabs/DeepCopy.git", |
825 | "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72" | 869 | "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" |
826 | }, | 870 | }, |
827 | "dist": { | 871 | "dist": { |
828 | "type": "zip", | 872 | "type": "zip", |
829 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", | 873 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", |
830 | "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", | 874 | "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", |
831 | "shasum": "" | 875 | "shasum": "" |
832 | }, | 876 | }, |
833 | "require": { | 877 | "require": { |
834 | "php": "^7.1" | 878 | "php": "^7.1 || ^8.0" |
835 | }, | 879 | }, |
836 | "replace": { | 880 | "replace": { |
837 | "myclabs/deep-copy": "self.version" | 881 | "myclabs/deep-copy": "self.version" |
@@ -862,39 +906,154 @@ | |||
862 | "object", | 906 | "object", |
863 | "object graph" | 907 | "object graph" |
864 | ], | 908 | ], |
865 | "time": "2019-04-07T13:18:21+00:00" | 909 | "support": { |
910 | "issues": "https://github.com/myclabs/DeepCopy/issues", | ||
911 | "source": "https://github.com/myclabs/DeepCopy/tree/1.x" | ||
912 | }, | ||
913 | "funding": [ | ||
914 | { | ||
915 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", | ||
916 | "type": "tidelift" | ||
917 | } | ||
918 | ], | ||
919 | "time": "2020-06-29T13:22:24+00:00" | ||
920 | }, | ||
921 | { | ||
922 | "name": "phar-io/manifest", | ||
923 | "version": "1.0.3", | ||
924 | "source": { | ||
925 | "type": "git", | ||
926 | "url": "https://github.com/phar-io/manifest.git", | ||
927 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" | ||
928 | }, | ||
929 | "dist": { | ||
930 | "type": "zip", | ||
931 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", | ||
932 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", | ||
933 | "shasum": "" | ||
934 | }, | ||
935 | "require": { | ||
936 | "ext-dom": "*", | ||
937 | "ext-phar": "*", | ||
938 | "phar-io/version": "^2.0", | ||
939 | "php": "^5.6 || ^7.0" | ||
940 | }, | ||
941 | "type": "library", | ||
942 | "extra": { | ||
943 | "branch-alias": { | ||
944 | "dev-master": "1.0.x-dev" | ||
945 | } | ||
946 | }, | ||
947 | "autoload": { | ||
948 | "classmap": [ | ||
949 | "src/" | ||
950 | ] | ||
951 | }, | ||
952 | "notification-url": "https://packagist.org/downloads/", | ||
953 | "license": [ | ||
954 | "BSD-3-Clause" | ||
955 | ], | ||
956 | "authors": [ | ||
957 | { | ||
958 | "name": "Arne Blankerts", | ||
959 | "email": "arne@blankerts.de", | ||
960 | "role": "Developer" | ||
961 | }, | ||
962 | { | ||
963 | "name": "Sebastian Heuer", | ||
964 | "email": "sebastian@phpeople.de", | ||
965 | "role": "Developer" | ||
966 | }, | ||
967 | { | ||
968 | "name": "Sebastian Bergmann", | ||
969 | "email": "sebastian@phpunit.de", | ||
970 | "role": "Developer" | ||
971 | } | ||
972 | ], | ||
973 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", | ||
974 | "support": { | ||
975 | "issues": "https://github.com/phar-io/manifest/issues", | ||
976 | "source": "https://github.com/phar-io/manifest/tree/master" | ||
977 | }, | ||
978 | "time": "2018-07-08T19:23:20+00:00" | ||
979 | }, | ||
980 | { | ||
981 | "name": "phar-io/version", | ||
982 | "version": "2.0.1", | ||
983 | "source": { | ||
984 | "type": "git", | ||
985 | "url": "https://github.com/phar-io/version.git", | ||
986 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" | ||
987 | }, | ||
988 | "dist": { | ||
989 | "type": "zip", | ||
990 | "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", | ||
991 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", | ||
992 | "shasum": "" | ||
993 | }, | ||
994 | "require": { | ||
995 | "php": "^5.6 || ^7.0" | ||
996 | }, | ||
997 | "type": "library", | ||
998 | "autoload": { | ||
999 | "classmap": [ | ||
1000 | "src/" | ||
1001 | ] | ||
1002 | }, | ||
1003 | "notification-url": "https://packagist.org/downloads/", | ||
1004 | "license": [ | ||
1005 | "BSD-3-Clause" | ||
1006 | ], | ||
1007 | "authors": [ | ||
1008 | { | ||
1009 | "name": "Arne Blankerts", | ||
1010 | "email": "arne@blankerts.de", | ||
1011 | "role": "Developer" | ||
1012 | }, | ||
1013 | { | ||
1014 | "name": "Sebastian Heuer", | ||
1015 | "email": "sebastian@phpeople.de", | ||
1016 | "role": "Developer" | ||
1017 | }, | ||
1018 | { | ||
1019 | "name": "Sebastian Bergmann", | ||
1020 | "email": "sebastian@phpunit.de", | ||
1021 | "role": "Developer" | ||
1022 | } | ||
1023 | ], | ||
1024 | "description": "Library for handling version information and constraints", | ||
1025 | "support": { | ||
1026 | "issues": "https://github.com/phar-io/version/issues", | ||
1027 | "source": "https://github.com/phar-io/version/tree/master" | ||
1028 | }, | ||
1029 | "time": "2018-07-08T19:19:57+00:00" | ||
866 | }, | 1030 | }, |
867 | { | 1031 | { |
868 | "name": "phpdocumentor/reflection-common", | 1032 | "name": "phpdocumentor/reflection-common", |
869 | "version": "1.0.1", | 1033 | "version": "2.1.0", |
870 | "source": { | 1034 | "source": { |
871 | "type": "git", | 1035 | "type": "git", |
872 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", | 1036 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", |
873 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" | 1037 | "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" |
874 | }, | 1038 | }, |
875 | "dist": { | 1039 | "dist": { |
876 | "type": "zip", | 1040 | "type": "zip", |
877 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", | 1041 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", |
878 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", | 1042 | "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", |
879 | "shasum": "" | 1043 | "shasum": "" |
880 | }, | 1044 | }, |
881 | "require": { | 1045 | "require": { |
882 | "php": ">=5.5" | 1046 | "php": ">=7.1" |
883 | }, | ||
884 | "require-dev": { | ||
885 | "phpunit/phpunit": "^4.6" | ||
886 | }, | 1047 | }, |
887 | "type": "library", | 1048 | "type": "library", |
888 | "extra": { | 1049 | "extra": { |
889 | "branch-alias": { | 1050 | "branch-alias": { |
890 | "dev-master": "1.0.x-dev" | 1051 | "dev-master": "2.x-dev" |
891 | } | 1052 | } |
892 | }, | 1053 | }, |
893 | "autoload": { | 1054 | "autoload": { |
894 | "psr-4": { | 1055 | "psr-4": { |
895 | "phpDocumentor\\Reflection\\": [ | 1056 | "phpDocumentor\\Reflection\\": "src/" |
896 | "src" | ||
897 | ] | ||
898 | } | 1057 | } |
899 | }, | 1058 | }, |
900 | "notification-url": "https://packagist.org/downloads/", | 1059 | "notification-url": "https://packagist.org/downloads/", |
@@ -916,31 +1075,36 @@ | |||
916 | "reflection", | 1075 | "reflection", |
917 | "static analysis" | 1076 | "static analysis" |
918 | ], | 1077 | ], |
919 | "time": "2017-09-11T18:02:19+00:00" | 1078 | "support": { |
1079 | "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", | ||
1080 | "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master" | ||
1081 | }, | ||
1082 | "time": "2020-04-27T09:25:28+00:00" | ||
920 | }, | 1083 | }, |
921 | { | 1084 | { |
922 | "name": "phpdocumentor/reflection-docblock", | 1085 | "name": "phpdocumentor/reflection-docblock", |
923 | "version": "4.3.1", | 1086 | "version": "4.3.4", |
924 | "source": { | 1087 | "source": { |
925 | "type": "git", | 1088 | "type": "git", |
926 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", | 1089 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", |
927 | "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" | 1090 | "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" |
928 | }, | 1091 | }, |
929 | "dist": { | 1092 | "dist": { |
930 | "type": "zip", | 1093 | "type": "zip", |
931 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", | 1094 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", |
932 | "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", | 1095 | "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", |
933 | "shasum": "" | 1096 | "shasum": "" |
934 | }, | 1097 | }, |
935 | "require": { | 1098 | "require": { |
936 | "php": "^7.0", | 1099 | "php": "^7.0", |
937 | "phpdocumentor/reflection-common": "^1.0.0", | 1100 | "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", |
938 | "phpdocumentor/type-resolver": "^0.4.0", | 1101 | "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", |
939 | "webmozart/assert": "^1.0" | 1102 | "webmozart/assert": "^1.0" |
940 | }, | 1103 | }, |
941 | "require-dev": { | 1104 | "require-dev": { |
942 | "doctrine/instantiator": "~1.0.5", | 1105 | "doctrine/instantiator": "^1.0.5", |
943 | "mockery/mockery": "^1.0", | 1106 | "mockery/mockery": "^1.0", |
1107 | "phpdocumentor/type-resolver": "0.4.*", | ||
944 | "phpunit/phpunit": "^6.4" | 1108 | "phpunit/phpunit": "^6.4" |
945 | }, | 1109 | }, |
946 | "type": "library", | 1110 | "type": "library", |
@@ -967,41 +1131,44 @@ | |||
967 | } | 1131 | } |
968 | ], | 1132 | ], |
969 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", | 1133 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", |
970 | "time": "2019-04-30T17:48:53+00:00" | 1134 | "support": { |
1135 | "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", | ||
1136 | "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/4.x" | ||
1137 | }, | ||
1138 | "time": "2019-12-28T18:55:12+00:00" | ||
971 | }, | 1139 | }, |
972 | { | 1140 | { |
973 | "name": "phpdocumentor/type-resolver", | 1141 | "name": "phpdocumentor/type-resolver", |
974 | "version": "0.4.0", | 1142 | "version": "1.0.1", |
975 | "source": { | 1143 | "source": { |
976 | "type": "git", | 1144 | "type": "git", |
977 | "url": "https://github.com/phpDocumentor/TypeResolver.git", | 1145 | "url": "https://github.com/phpDocumentor/TypeResolver.git", |
978 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" | 1146 | "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" |
979 | }, | 1147 | }, |
980 | "dist": { | 1148 | "dist": { |
981 | "type": "zip", | 1149 | "type": "zip", |
982 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", | 1150 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", |
983 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", | 1151 | "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", |
984 | "shasum": "" | 1152 | "shasum": "" |
985 | }, | 1153 | }, |
986 | "require": { | 1154 | "require": { |
987 | "php": "^5.5 || ^7.0", | 1155 | "php": "^7.1", |
988 | "phpdocumentor/reflection-common": "^1.0" | 1156 | "phpdocumentor/reflection-common": "^2.0" |
989 | }, | 1157 | }, |
990 | "require-dev": { | 1158 | "require-dev": { |
991 | "mockery/mockery": "^0.9.4", | 1159 | "ext-tokenizer": "^7.1", |
992 | "phpunit/phpunit": "^5.2||^4.8.24" | 1160 | "mockery/mockery": "~1", |
1161 | "phpunit/phpunit": "^7.0" | ||
993 | }, | 1162 | }, |
994 | "type": "library", | 1163 | "type": "library", |
995 | "extra": { | 1164 | "extra": { |
996 | "branch-alias": { | 1165 | "branch-alias": { |
997 | "dev-master": "1.0.x-dev" | 1166 | "dev-master": "1.x-dev" |
998 | } | 1167 | } |
999 | }, | 1168 | }, |
1000 | "autoload": { | 1169 | "autoload": { |
1001 | "psr-4": { | 1170 | "psr-4": { |
1002 | "phpDocumentor\\Reflection\\": [ | 1171 | "phpDocumentor\\Reflection\\": "src" |
1003 | "src/" | ||
1004 | ] | ||
1005 | } | 1172 | } |
1006 | }, | 1173 | }, |
1007 | "notification-url": "https://packagist.org/downloads/", | 1174 | "notification-url": "https://packagist.org/downloads/", |
@@ -1014,37 +1181,42 @@ | |||
1014 | "email": "me@mikevanriel.com" | 1181 | "email": "me@mikevanriel.com" |
1015 | } | 1182 | } |
1016 | ], | 1183 | ], |
1017 | "time": "2017-07-14T14:27:02+00:00" | 1184 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", |
1185 | "support": { | ||
1186 | "issues": "https://github.com/phpDocumentor/TypeResolver/issues", | ||
1187 | "source": "https://github.com/phpDocumentor/TypeResolver/tree/0.7.2" | ||
1188 | }, | ||
1189 | "time": "2019-08-22T18:11:29+00:00" | ||
1018 | }, | 1190 | }, |
1019 | { | 1191 | { |
1020 | "name": "phpspec/prophecy", | 1192 | "name": "phpspec/prophecy", |
1021 | "version": "1.8.1", | 1193 | "version": "v1.10.3", |
1022 | "source": { | 1194 | "source": { |
1023 | "type": "git", | 1195 | "type": "git", |
1024 | "url": "https://github.com/phpspec/prophecy.git", | 1196 | "url": "https://github.com/phpspec/prophecy.git", |
1025 | "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" | 1197 | "reference": "451c3cd1418cf640de218914901e51b064abb093" |
1026 | }, | 1198 | }, |
1027 | "dist": { | 1199 | "dist": { |
1028 | "type": "zip", | 1200 | "type": "zip", |
1029 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", | 1201 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", |
1030 | "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", | 1202 | "reference": "451c3cd1418cf640de218914901e51b064abb093", |
1031 | "shasum": "" | 1203 | "shasum": "" |
1032 | }, | 1204 | }, |
1033 | "require": { | 1205 | "require": { |
1034 | "doctrine/instantiator": "^1.0.2", | 1206 | "doctrine/instantiator": "^1.0.2", |
1035 | "php": "^5.3|^7.0", | 1207 | "php": "^5.3|^7.0", |
1036 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", | 1208 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", |
1037 | "sebastian/comparator": "^1.1|^2.0|^3.0", | 1209 | "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", |
1038 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" | 1210 | "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" |
1039 | }, | 1211 | }, |
1040 | "require-dev": { | 1212 | "require-dev": { |
1041 | "phpspec/phpspec": "^2.5|^3.2", | 1213 | "phpspec/phpspec": "^2.5 || ^3.2", |
1042 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" | 1214 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" |
1043 | }, | 1215 | }, |
1044 | "type": "library", | 1216 | "type": "library", |
1045 | "extra": { | 1217 | "extra": { |
1046 | "branch-alias": { | 1218 | "branch-alias": { |
1047 | "dev-master": "1.8.x-dev" | 1219 | "dev-master": "1.10.x-dev" |
1048 | } | 1220 | } |
1049 | }, | 1221 | }, |
1050 | "autoload": { | 1222 | "autoload": { |
@@ -1077,44 +1249,48 @@ | |||
1077 | "spy", | 1249 | "spy", |
1078 | "stub" | 1250 | "stub" |
1079 | ], | 1251 | ], |
1080 | "time": "2019-06-13T12:50:23+00:00" | 1252 | "support": { |
1253 | "issues": "https://github.com/phpspec/prophecy/issues", | ||
1254 | "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" | ||
1255 | }, | ||
1256 | "time": "2020-03-05T15:02:03+00:00" | ||
1081 | }, | 1257 | }, |
1082 | { | 1258 | { |
1083 | "name": "phpunit/php-code-coverage", | 1259 | "name": "phpunit/php-code-coverage", |
1084 | "version": "4.0.8", | 1260 | "version": "6.1.4", |
1085 | "source": { | 1261 | "source": { |
1086 | "type": "git", | 1262 | "type": "git", |
1087 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", | 1263 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", |
1088 | "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" | 1264 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" |
1089 | }, | 1265 | }, |
1090 | "dist": { | 1266 | "dist": { |
1091 | "type": "zip", | 1267 | "type": "zip", |
1092 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", | 1268 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", |
1093 | "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", | 1269 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", |
1094 | "shasum": "" | 1270 | "shasum": "" |
1095 | }, | 1271 | }, |
1096 | "require": { | 1272 | "require": { |
1097 | "ext-dom": "*", | 1273 | "ext-dom": "*", |
1098 | "ext-xmlwriter": "*", | 1274 | "ext-xmlwriter": "*", |
1099 | "php": "^5.6 || ^7.0", | 1275 | "php": "^7.1", |
1100 | "phpunit/php-file-iterator": "^1.3", | 1276 | "phpunit/php-file-iterator": "^2.0", |
1101 | "phpunit/php-text-template": "^1.2", | 1277 | "phpunit/php-text-template": "^1.2.1", |
1102 | "phpunit/php-token-stream": "^1.4.2 || ^2.0", | 1278 | "phpunit/php-token-stream": "^3.0", |
1103 | "sebastian/code-unit-reverse-lookup": "^1.0", | 1279 | "sebastian/code-unit-reverse-lookup": "^1.0.1", |
1104 | "sebastian/environment": "^1.3.2 || ^2.0", | 1280 | "sebastian/environment": "^3.1 || ^4.0", |
1105 | "sebastian/version": "^1.0 || ^2.0" | 1281 | "sebastian/version": "^2.0.1", |
1282 | "theseer/tokenizer": "^1.1" | ||
1106 | }, | 1283 | }, |
1107 | "require-dev": { | 1284 | "require-dev": { |
1108 | "ext-xdebug": "^2.1.4", | 1285 | "phpunit/phpunit": "^7.0" |
1109 | "phpunit/phpunit": "^5.7" | ||
1110 | }, | 1286 | }, |
1111 | "suggest": { | 1287 | "suggest": { |
1112 | "ext-xdebug": "^2.5.1" | 1288 | "ext-xdebug": "^2.6.0" |
1113 | }, | 1289 | }, |
1114 | "type": "library", | 1290 | "type": "library", |
1115 | "extra": { | 1291 | "extra": { |
1116 | "branch-alias": { | 1292 | "branch-alias": { |
1117 | "dev-master": "4.0.x-dev" | 1293 | "dev-master": "6.1-dev" |
1118 | } | 1294 | } |
1119 | }, | 1295 | }, |
1120 | "autoload": { | 1296 | "autoload": { |
@@ -1129,7 +1305,7 @@ | |||
1129 | "authors": [ | 1305 | "authors": [ |
1130 | { | 1306 | { |
1131 | "name": "Sebastian Bergmann", | 1307 | "name": "Sebastian Bergmann", |
1132 | "email": "sb@sebastian-bergmann.de", | 1308 | "email": "sebastian@phpunit.de", |
1133 | "role": "lead" | 1309 | "role": "lead" |
1134 | } | 1310 | } |
1135 | ], | 1311 | ], |
@@ -1140,29 +1316,36 @@ | |||
1140 | "testing", | 1316 | "testing", |
1141 | "xunit" | 1317 | "xunit" |
1142 | ], | 1318 | ], |
1143 | "time": "2017-04-02T07:44:40+00:00" | 1319 | "support": { |
1320 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", | ||
1321 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/master" | ||
1322 | }, | ||
1323 | "time": "2018-10-31T16:06:48+00:00" | ||
1144 | }, | 1324 | }, |
1145 | { | 1325 | { |
1146 | "name": "phpunit/php-file-iterator", | 1326 | "name": "phpunit/php-file-iterator", |
1147 | "version": "1.4.5", | 1327 | "version": "2.0.2", |
1148 | "source": { | 1328 | "source": { |
1149 | "type": "git", | 1329 | "type": "git", |
1150 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", | 1330 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", |
1151 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" | 1331 | "reference": "050bedf145a257b1ff02746c31894800e5122946" |
1152 | }, | 1332 | }, |
1153 | "dist": { | 1333 | "dist": { |
1154 | "type": "zip", | 1334 | "type": "zip", |
1155 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", | 1335 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", |
1156 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", | 1336 | "reference": "050bedf145a257b1ff02746c31894800e5122946", |
1157 | "shasum": "" | 1337 | "shasum": "" |
1158 | }, | 1338 | }, |
1159 | "require": { | 1339 | "require": { |
1160 | "php": ">=5.3.3" | 1340 | "php": "^7.1" |
1341 | }, | ||
1342 | "require-dev": { | ||
1343 | "phpunit/phpunit": "^7.1" | ||
1161 | }, | 1344 | }, |
1162 | "type": "library", | 1345 | "type": "library", |
1163 | "extra": { | 1346 | "extra": { |
1164 | "branch-alias": { | 1347 | "branch-alias": { |
1165 | "dev-master": "1.4.x-dev" | 1348 | "dev-master": "2.0.x-dev" |
1166 | } | 1349 | } |
1167 | }, | 1350 | }, |
1168 | "autoload": { | 1351 | "autoload": { |
@@ -1177,7 +1360,7 @@ | |||
1177 | "authors": [ | 1360 | "authors": [ |
1178 | { | 1361 | { |
1179 | "name": "Sebastian Bergmann", | 1362 | "name": "Sebastian Bergmann", |
1180 | "email": "sb@sebastian-bergmann.de", | 1363 | "email": "sebastian@phpunit.de", |
1181 | "role": "lead" | 1364 | "role": "lead" |
1182 | } | 1365 | } |
1183 | ], | 1366 | ], |
@@ -1187,32 +1370,36 @@ | |||
1187 | "filesystem", | 1370 | "filesystem", |
1188 | "iterator" | 1371 | "iterator" |
1189 | ], | 1372 | ], |
1190 | "time": "2017-11-27T13:52:08+00:00" | 1373 | "support": { |
1374 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", | ||
1375 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.2" | ||
1376 | }, | ||
1377 | "time": "2018-09-13T20:33:42+00:00" | ||
1191 | }, | 1378 | }, |
1192 | { | 1379 | { |
1193 | "name": "phpunit/php-timer", | 1380 | "name": "phpunit/php-timer", |
1194 | "version": "1.0.9", | 1381 | "version": "2.1.2", |
1195 | "source": { | 1382 | "source": { |
1196 | "type": "git", | 1383 | "type": "git", |
1197 | "url": "https://github.com/sebastianbergmann/php-timer.git", | 1384 | "url": "https://github.com/sebastianbergmann/php-timer.git", |
1198 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" | 1385 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" |
1199 | }, | 1386 | }, |
1200 | "dist": { | 1387 | "dist": { |
1201 | "type": "zip", | 1388 | "type": "zip", |
1202 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", | 1389 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", |
1203 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", | 1390 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", |
1204 | "shasum": "" | 1391 | "shasum": "" |
1205 | }, | 1392 | }, |
1206 | "require": { | 1393 | "require": { |
1207 | "php": "^5.3.3 || ^7.0" | 1394 | "php": "^7.1" |
1208 | }, | 1395 | }, |
1209 | "require-dev": { | 1396 | "require-dev": { |
1210 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" | 1397 | "phpunit/phpunit": "^7.0" |
1211 | }, | 1398 | }, |
1212 | "type": "library", | 1399 | "type": "library", |
1213 | "extra": { | 1400 | "extra": { |
1214 | "branch-alias": { | 1401 | "branch-alias": { |
1215 | "dev-master": "1.0-dev" | 1402 | "dev-master": "2.1-dev" |
1216 | } | 1403 | } |
1217 | }, | 1404 | }, |
1218 | "autoload": { | 1405 | "autoload": { |
@@ -1227,7 +1414,7 @@ | |||
1227 | "authors": [ | 1414 | "authors": [ |
1228 | { | 1415 | { |
1229 | "name": "Sebastian Bergmann", | 1416 | "name": "Sebastian Bergmann", |
1230 | "email": "sb@sebastian-bergmann.de", | 1417 | "email": "sebastian@phpunit.de", |
1231 | "role": "lead" | 1418 | "role": "lead" |
1232 | } | 1419 | } |
1233 | ], | 1420 | ], |
@@ -1236,33 +1423,37 @@ | |||
1236 | "keywords": [ | 1423 | "keywords": [ |
1237 | "timer" | 1424 | "timer" |
1238 | ], | 1425 | ], |
1239 | "time": "2017-02-26T11:10:40+00:00" | 1426 | "support": { |
1427 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", | ||
1428 | "source": "https://github.com/sebastianbergmann/php-timer/tree/master" | ||
1429 | }, | ||
1430 | "time": "2019-06-07T04:22:29+00:00" | ||
1240 | }, | 1431 | }, |
1241 | { | 1432 | { |
1242 | "name": "phpunit/php-token-stream", | 1433 | "name": "phpunit/php-token-stream", |
1243 | "version": "2.0.2", | 1434 | "version": "3.1.1", |
1244 | "source": { | 1435 | "source": { |
1245 | "type": "git", | 1436 | "type": "git", |
1246 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", | 1437 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", |
1247 | "reference": "791198a2c6254db10131eecfe8c06670700904db" | 1438 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" |
1248 | }, | 1439 | }, |
1249 | "dist": { | 1440 | "dist": { |
1250 | "type": "zip", | 1441 | "type": "zip", |
1251 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", | 1442 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", |
1252 | "reference": "791198a2c6254db10131eecfe8c06670700904db", | 1443 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", |
1253 | "shasum": "" | 1444 | "shasum": "" |
1254 | }, | 1445 | }, |
1255 | "require": { | 1446 | "require": { |
1256 | "ext-tokenizer": "*", | 1447 | "ext-tokenizer": "*", |
1257 | "php": "^7.0" | 1448 | "php": "^7.1" |
1258 | }, | 1449 | }, |
1259 | "require-dev": { | 1450 | "require-dev": { |
1260 | "phpunit/phpunit": "^6.2.4" | 1451 | "phpunit/phpunit": "^7.0" |
1261 | }, | 1452 | }, |
1262 | "type": "library", | 1453 | "type": "library", |
1263 | "extra": { | 1454 | "extra": { |
1264 | "branch-alias": { | 1455 | "branch-alias": { |
1265 | "dev-master": "2.0-dev" | 1456 | "dev-master": "3.1-dev" |
1266 | } | 1457 | } |
1267 | }, | 1458 | }, |
1268 | "autoload": { | 1459 | "autoload": { |
@@ -1285,107 +1476,62 @@ | |||
1285 | "keywords": [ | 1476 | "keywords": [ |
1286 | "tokenizer" | 1477 | "tokenizer" |
1287 | ], | 1478 | ], |
1288 | "time": "2017-11-27T05:48:46+00:00" | 1479 | "support": { |
1289 | }, | 1480 | "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", |
1290 | { | 1481 | "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.1" |
1291 | "name": "phpunit/phpcov", | ||
1292 | "version": "3.1.0", | ||
1293 | "source": { | ||
1294 | "type": "git", | ||
1295 | "url": "https://github.com/sebastianbergmann/phpcov.git", | ||
1296 | "reference": "2005bd90c2c8aae6d93ec82d9cda9d55dca96c3d" | ||
1297 | }, | ||
1298 | "dist": { | ||
1299 | "type": "zip", | ||
1300 | "url": "https://api.github.com/repos/sebastianbergmann/phpcov/zipball/2005bd90c2c8aae6d93ec82d9cda9d55dca96c3d", | ||
1301 | "reference": "2005bd90c2c8aae6d93ec82d9cda9d55dca96c3d", | ||
1302 | "shasum": "" | ||
1303 | }, | ||
1304 | "require": { | ||
1305 | "php": "^5.6 || ^7.0", | ||
1306 | "phpunit/php-code-coverage": "^4.0", | ||
1307 | "phpunit/phpunit": "^5.0", | ||
1308 | "sebastian/diff": "^1.1", | ||
1309 | "sebastian/finder-facade": "^1.1", | ||
1310 | "sebastian/version": "^1.0|^2.0", | ||
1311 | "symfony/console": "^2|^3" | ||
1312 | }, | ||
1313 | "bin": [ | ||
1314 | "phpcov" | ||
1315 | ], | ||
1316 | "type": "library", | ||
1317 | "extra": { | ||
1318 | "branch-alias": { | ||
1319 | "dev-master": "3.1.x-dev" | ||
1320 | } | ||
1321 | }, | 1482 | }, |
1322 | "autoload": { | 1483 | "abandoned": true, |
1323 | "classmap": [ | 1484 | "time": "2019-09-17T06:23:10+00:00" |
1324 | "src/" | ||
1325 | ] | ||
1326 | }, | ||
1327 | "notification-url": "https://packagist.org/downloads/", | ||
1328 | "license": [ | ||
1329 | "BSD-3-Clause" | ||
1330 | ], | ||
1331 | "authors": [ | ||
1332 | { | ||
1333 | "name": "Sebastian Bergmann", | ||
1334 | "email": "sebastian@phpunit.de", | ||
1335 | "role": "lead" | ||
1336 | } | ||
1337 | ], | ||
1338 | "description": "CLI frontend for PHP_CodeCoverage", | ||
1339 | "homepage": "https://github.com/sebastianbergmann/phpcov", | ||
1340 | "time": "2016-06-03T07:01:55+00:00" | ||
1341 | }, | 1485 | }, |
1342 | { | 1486 | { |
1343 | "name": "phpunit/phpunit", | 1487 | "name": "phpunit/phpunit", |
1344 | "version": "5.7.27", | 1488 | "version": "7.5.20", |
1345 | "source": { | 1489 | "source": { |
1346 | "type": "git", | 1490 | "type": "git", |
1347 | "url": "https://github.com/sebastianbergmann/phpunit.git", | 1491 | "url": "https://github.com/sebastianbergmann/phpunit.git", |
1348 | "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" | 1492 | "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" |
1349 | }, | 1493 | }, |
1350 | "dist": { | 1494 | "dist": { |
1351 | "type": "zip", | 1495 | "type": "zip", |
1352 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", | 1496 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", |
1353 | "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", | 1497 | "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", |
1354 | "shasum": "" | 1498 | "shasum": "" |
1355 | }, | 1499 | }, |
1356 | "require": { | 1500 | "require": { |
1501 | "doctrine/instantiator": "^1.1", | ||
1357 | "ext-dom": "*", | 1502 | "ext-dom": "*", |
1358 | "ext-json": "*", | 1503 | "ext-json": "*", |
1359 | "ext-libxml": "*", | 1504 | "ext-libxml": "*", |
1360 | "ext-mbstring": "*", | 1505 | "ext-mbstring": "*", |
1361 | "ext-xml": "*", | 1506 | "ext-xml": "*", |
1362 | "myclabs/deep-copy": "~1.3", | 1507 | "myclabs/deep-copy": "^1.7", |
1363 | "php": "^5.6 || ^7.0", | 1508 | "phar-io/manifest": "^1.0.2", |
1364 | "phpspec/prophecy": "^1.6.2", | 1509 | "phar-io/version": "^2.0", |
1365 | "phpunit/php-code-coverage": "^4.0.4", | 1510 | "php": "^7.1", |
1366 | "phpunit/php-file-iterator": "~1.4", | 1511 | "phpspec/prophecy": "^1.7", |
1367 | "phpunit/php-text-template": "~1.2", | 1512 | "phpunit/php-code-coverage": "^6.0.7", |
1368 | "phpunit/php-timer": "^1.0.6", | 1513 | "phpunit/php-file-iterator": "^2.0.1", |
1369 | "phpunit/phpunit-mock-objects": "^3.2", | 1514 | "phpunit/php-text-template": "^1.2.1", |
1370 | "sebastian/comparator": "^1.2.4", | 1515 | "phpunit/php-timer": "^2.1", |
1371 | "sebastian/diff": "^1.4.3", | 1516 | "sebastian/comparator": "^3.0", |
1372 | "sebastian/environment": "^1.3.4 || ^2.0", | 1517 | "sebastian/diff": "^3.0", |
1373 | "sebastian/exporter": "~2.0", | 1518 | "sebastian/environment": "^4.0", |
1374 | "sebastian/global-state": "^1.1", | 1519 | "sebastian/exporter": "^3.1", |
1375 | "sebastian/object-enumerator": "~2.0", | 1520 | "sebastian/global-state": "^2.0", |
1376 | "sebastian/resource-operations": "~1.0", | 1521 | "sebastian/object-enumerator": "^3.0.3", |
1377 | "sebastian/version": "^1.0.6|^2.0.1", | 1522 | "sebastian/resource-operations": "^2.0", |
1378 | "symfony/yaml": "~2.1|~3.0|~4.0" | 1523 | "sebastian/version": "^2.0.1" |
1379 | }, | 1524 | }, |
1380 | "conflict": { | 1525 | "conflict": { |
1381 | "phpdocumentor/reflection-docblock": "3.0.2" | 1526 | "phpunit/phpunit-mock-objects": "*" |
1382 | }, | 1527 | }, |
1383 | "require-dev": { | 1528 | "require-dev": { |
1384 | "ext-pdo": "*" | 1529 | "ext-pdo": "*" |
1385 | }, | 1530 | }, |
1386 | "suggest": { | 1531 | "suggest": { |
1532 | "ext-soap": "*", | ||
1387 | "ext-xdebug": "*", | 1533 | "ext-xdebug": "*", |
1388 | "phpunit/php-invoker": "~1.1" | 1534 | "phpunit/php-invoker": "^2.0" |
1389 | }, | 1535 | }, |
1390 | "bin": [ | 1536 | "bin": [ |
1391 | "phpunit" | 1537 | "phpunit" |
@@ -1393,7 +1539,7 @@ | |||
1393 | "type": "library", | 1539 | "type": "library", |
1394 | "extra": { | 1540 | "extra": { |
1395 | "branch-alias": { | 1541 | "branch-alias": { |
1396 | "dev-master": "5.7.x-dev" | 1542 | "dev-master": "7.5-dev" |
1397 | } | 1543 | } |
1398 | }, | 1544 | }, |
1399 | "autoload": { | 1545 | "autoload": { |
@@ -1419,67 +1565,11 @@ | |||
1419 | "testing", | 1565 | "testing", |
1420 | "xunit" | 1566 | "xunit" |
1421 | ], | 1567 | ], |
1422 | "time": "2018-02-01T05:50:59+00:00" | 1568 | "support": { |
1423 | }, | 1569 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", |
1424 | { | 1570 | "source": "https://github.com/sebastianbergmann/phpunit/tree/7.5.20" |
1425 | "name": "phpunit/phpunit-mock-objects", | ||
1426 | "version": "3.4.4", | ||
1427 | "source": { | ||
1428 | "type": "git", | ||
1429 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", | ||
1430 | "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" | ||
1431 | }, | ||
1432 | "dist": { | ||
1433 | "type": "zip", | ||
1434 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", | ||
1435 | "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", | ||
1436 | "shasum": "" | ||
1437 | }, | ||
1438 | "require": { | ||
1439 | "doctrine/instantiator": "^1.0.2", | ||
1440 | "php": "^5.6 || ^7.0", | ||
1441 | "phpunit/php-text-template": "^1.2", | ||
1442 | "sebastian/exporter": "^1.2 || ^2.0" | ||
1443 | }, | ||
1444 | "conflict": { | ||
1445 | "phpunit/phpunit": "<5.4.0" | ||
1446 | }, | ||
1447 | "require-dev": { | ||
1448 | "phpunit/phpunit": "^5.4" | ||
1449 | }, | 1571 | }, |
1450 | "suggest": { | 1572 | "time": "2020-01-08T08:45:45+00:00" |
1451 | "ext-soap": "*" | ||
1452 | }, | ||
1453 | "type": "library", | ||
1454 | "extra": { | ||
1455 | "branch-alias": { | ||
1456 | "dev-master": "3.2.x-dev" | ||
1457 | } | ||
1458 | }, | ||
1459 | "autoload": { | ||
1460 | "classmap": [ | ||
1461 | "src/" | ||
1462 | ] | ||
1463 | }, | ||
1464 | "notification-url": "https://packagist.org/downloads/", | ||
1465 | "license": [ | ||
1466 | "BSD-3-Clause" | ||
1467 | ], | ||
1468 | "authors": [ | ||
1469 | { | ||
1470 | "name": "Sebastian Bergmann", | ||
1471 | "email": "sb@sebastian-bergmann.de", | ||
1472 | "role": "lead" | ||
1473 | } | ||
1474 | ], | ||
1475 | "description": "Mock Object library for PHPUnit", | ||
1476 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", | ||
1477 | "keywords": [ | ||
1478 | "mock", | ||
1479 | "xunit" | ||
1480 | ], | ||
1481 | "abandoned": true, | ||
1482 | "time": "2017-06-30T09:13:00+00:00" | ||
1483 | }, | 1573 | }, |
1484 | { | 1574 | { |
1485 | "name": "roave/security-advisories", | 1575 | "name": "roave/security-advisories", |
@@ -1487,12 +1577,12 @@ | |||
1487 | "source": { | 1577 | "source": { |
1488 | "type": "git", | 1578 | "type": "git", |
1489 | "url": "https://github.com/Roave/SecurityAdvisories.git", | 1579 | "url": "https://github.com/Roave/SecurityAdvisories.git", |
1490 | "reference": "ea693fa060702164985511acc3ceb5389c9ac761" | 1580 | "reference": "0749ceaf15c136d085b722a5bb88141398a54142" |
1491 | }, | 1581 | }, |
1492 | "dist": { | 1582 | "dist": { |
1493 | "type": "zip", | 1583 | "type": "zip", |
1494 | "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ea693fa060702164985511acc3ceb5389c9ac761", | 1584 | "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0749ceaf15c136d085b722a5bb88141398a54142", |
1495 | "reference": "ea693fa060702164985511acc3ceb5389c9ac761", | 1585 | "reference": "0749ceaf15c136d085b722a5bb88141398a54142", |
1496 | "shasum": "" | 1586 | "shasum": "" |
1497 | }, | 1587 | }, |
1498 | "conflict": { | 1588 | "conflict": { |
@@ -1501,22 +1591,32 @@ | |||
1501 | "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", | 1591 | "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", |
1502 | "amphp/artax": "<1.0.6|>=2,<2.0.6", | 1592 | "amphp/artax": "<1.0.6|>=2,<2.0.6", |
1503 | "amphp/http": "<1.0.1", | 1593 | "amphp/http": "<1.0.1", |
1594 | "amphp/http-client": ">=4,<4.4", | ||
1504 | "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6", | 1595 | "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6", |
1505 | "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", | 1596 | "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", |
1506 | "aws/aws-sdk-php": ">=3,<3.2.1", | 1597 | "aws/aws-sdk-php": ">=3,<3.2.1", |
1598 | "bagisto/bagisto": "<0.1.5", | ||
1599 | "barrelstrength/sprout-base-email": "<1.2.7", | ||
1600 | "barrelstrength/sprout-forms": "<3.9", | ||
1601 | "baserproject/basercms": ">=4,<=4.3.6", | ||
1602 | "bolt/bolt": "<3.7.1", | ||
1507 | "brightlocal/phpwhois": "<=4.2.5", | 1603 | "brightlocal/phpwhois": "<=4.2.5", |
1604 | "buddypress/buddypress": "<5.1.2", | ||
1508 | "bugsnag/bugsnag-laravel": ">=2,<2.0.2", | 1605 | "bugsnag/bugsnag-laravel": ">=2,<2.0.2", |
1509 | "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.5.18|>=3.6,<3.6.15|>=3.7,<3.7.7", | 1606 | "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.5.18|>=3.6,<3.6.15|>=3.7,<3.7.7", |
1510 | "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", | 1607 | "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", |
1511 | "cartalyst/sentry": "<=2.1.6", | 1608 | "cartalyst/sentry": "<=2.1.6", |
1609 | "centreon/centreon": "<18.10.8|>=19,<19.4.5", | ||
1610 | "cesnet/simplesamlphp-module-proxystatistics": "<3.1", | ||
1512 | "codeigniter/framework": "<=3.0.6", | 1611 | "codeigniter/framework": "<=3.0.6", |
1513 | "composer/composer": "<=1-alpha.11", | 1612 | "composer/composer": "<=1-alpha.11", |
1514 | "contao-components/mediaelement": ">=2.14.2,<2.21.1", | 1613 | "contao-components/mediaelement": ">=2.14.2,<2.21.1", |
1515 | "contao/core": ">=2,<3.5.39", | 1614 | "contao/core": ">=2,<3.5.39", |
1516 | "contao/core-bundle": ">=4,<4.4.39|>=4.5,<4.7.5", | 1615 | "contao/core-bundle": ">=4,<4.4.52|>=4.5,<4.9.6|= 4.10.0", |
1517 | "contao/listing-bundle": ">=4,<4.4.8", | 1616 | "contao/listing-bundle": ">=4,<4.4.8", |
1518 | "contao/newsletter-bundle": ">=4,<4.1", | 1617 | "datadog/dd-trace": ">=0.30,<0.30.2", |
1519 | "david-garcia/phpwhois": "<=4.3.1", | 1618 | "david-garcia/phpwhois": "<=4.3.1", |
1619 | "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1", | ||
1520 | "doctrine/annotations": ">=1,<1.2.7", | 1620 | "doctrine/annotations": ">=1,<1.2.7", |
1521 | "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", | 1621 | "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", |
1522 | "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", | 1622 | "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", |
@@ -1526,45 +1626,75 @@ | |||
1526 | "doctrine/mongodb-odm": ">=1,<1.0.2", | 1626 | "doctrine/mongodb-odm": ">=1,<1.0.2", |
1527 | "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", | 1627 | "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", |
1528 | "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", | 1628 | "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", |
1629 | "dolibarr/dolibarr": "<11.0.4", | ||
1529 | "dompdf/dompdf": ">=0.6,<0.6.2", | 1630 | "dompdf/dompdf": ">=0.6,<0.6.2", |
1530 | "drupal/core": ">=7,<7.67|>=8,<8.6.16|>=8.7,<8.7.1|>8.7.3,<8.7.5", | 1631 | "drupal/core": ">=7,<7.73|>=8,<8.8.10|>=8.9,<8.9.6|>=9,<9.0.6", |
1531 | "drupal/drupal": ">=7,<7.67|>=8,<8.6.16|>=8.7,<8.7.1|>8.7.3,<8.7.5", | 1632 | "drupal/drupal": ">=7,<7.73|>=8,<8.8.10|>=8.9,<8.9.6|>=9,<9.0.6", |
1633 | "endroid/qr-code-bundle": "<3.4.2", | ||
1634 | "enshrined/svg-sanitize": "<0.13.1", | ||
1532 | "erusev/parsedown": "<1.7.2", | 1635 | "erusev/parsedown": "<1.7.2", |
1533 | "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.4", | 1636 | "ezsystems/demobundle": ">=5.4,<5.4.6.1", |
1534 | "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.13.1|>=6,<6.7.9.1|>=6.8,<6.13.5.1|>=7,<7.2.4.1|>=7.3,<7.3.2.1", | 1637 | "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1", |
1535 | "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.12.3|>=2011,<2017.12.4.3|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3", | 1638 | "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1", |
1639 | "ezsystems/ezplatform": ">=1.7,<1.7.9.1|>=1.13,<1.13.5.1|>=2.5,<2.5.4", | ||
1640 | "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6", | ||
1641 | "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", | ||
1642 | "ezsystems/ezplatform-kernel": ">=1,<1.0.2.1", | ||
1643 | "ezsystems/ezplatform-user": ">=1,<1.0.1", | ||
1644 | "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.14.2|>=6,<6.7.9.1|>=6.8,<6.13.6.3|>=7,<7.2.4.1|>=7.3,<7.3.2.1|>=7.5,<7.5.7.1", | ||
1645 | "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.14.1|>=2011,<2017.12.7.2|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3|>=2019.3,<2019.3.4.2", | ||
1646 | "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", | ||
1536 | "ezsystems/repository-forms": ">=2.3,<2.3.2.1", | 1647 | "ezsystems/repository-forms": ">=2.3,<2.3.2.1", |
1537 | "ezyang/htmlpurifier": "<4.1.1", | 1648 | "ezyang/htmlpurifier": "<4.1.1", |
1538 | "firebase/php-jwt": "<2", | 1649 | "firebase/php-jwt": "<2", |
1539 | "fooman/tcpdf": "<6.2.22", | 1650 | "fooman/tcpdf": "<6.2.22", |
1540 | "fossar/tcpdf-parser": "<6.2.22", | 1651 | "fossar/tcpdf-parser": "<6.2.22", |
1652 | "friendsofsymfony/oauth2-php": "<1.3", | ||
1541 | "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", | 1653 | "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", |
1542 | "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", | 1654 | "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", |
1655 | "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", | ||
1543 | "fuel/core": "<1.8.1", | 1656 | "fuel/core": "<1.8.1", |
1657 | "getgrav/grav": "<1.7-beta.8", | ||
1658 | "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", | ||
1544 | "gree/jose": "<=2.2", | 1659 | "gree/jose": "<=2.2", |
1545 | "gregwar/rst": "<1.0.3", | 1660 | "gregwar/rst": "<1.0.3", |
1546 | "guzzlehttp/guzzle": ">=4-rc.2,<4.2.4|>=5,<5.3.1|>=6,<6.2.1", | 1661 | "guzzlehttp/guzzle": ">=4-rc.2,<4.2.4|>=5,<5.3.1|>=6,<6.2.1", |
1547 | "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", | 1662 | "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", |
1548 | "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", | 1663 | "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4", |
1549 | "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", | 1664 | "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29|>=5.5,<=5.5.44|>=6,<6.18.34|>=7,<7.23.2", |
1550 | "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", | 1665 | "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", |
1666 | "illuminate/view": ">=7,<7.1.2", | ||
1551 | "ivankristianto/phpwhois": "<=4.3", | 1667 | "ivankristianto/phpwhois": "<=4.3", |
1552 | "james-heinrich/getid3": "<1.9.9", | 1668 | "james-heinrich/getid3": "<1.9.9", |
1553 | "joomla/session": "<1.3.1", | 1669 | "joomla/session": "<1.3.1", |
1554 | "jsmitty12/phpwhois": "<5.1", | 1670 | "jsmitty12/phpwhois": "<5.1", |
1555 | "kazist/phpwhois": "<=4.2.6", | 1671 | "kazist/phpwhois": "<=4.2.6", |
1672 | "kitodo/presentation": "<3.1.2", | ||
1556 | "kreait/firebase-php": ">=3.2,<3.8.1", | 1673 | "kreait/firebase-php": ">=3.2,<3.8.1", |
1557 | "la-haute-societe/tcpdf": "<6.2.22", | 1674 | "la-haute-societe/tcpdf": "<6.2.22", |
1558 | "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", | 1675 | "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.34|>=7,<7.23.2", |
1559 | "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", | 1676 | "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", |
1560 | "league/commonmark": "<0.18.3", | 1677 | "league/commonmark": "<0.18.3", |
1561 | "magento/magento1ce": "<1.9.4.1", | 1678 | "librenms/librenms": "<1.53", |
1562 | "magento/magento1ee": ">=1.9,<1.14.4.1", | 1679 | "livewire/livewire": ">2.2.4,<2.2.6", |
1563 | "magento/product-community-edition": ">=2,<2.2.8|>=2.3,<2.3.1", | 1680 | "magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.3", |
1681 | "magento/magento1ce": "<1.9.4.3", | ||
1682 | "magento/magento1ee": ">=1,<1.14.4.3", | ||
1683 | "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", | ||
1684 | "marcwillmann/turn": "<0.3.3", | ||
1685 | "mittwald/typo3_forum": "<1.2.1", | ||
1564 | "monolog/monolog": ">=1.8,<1.12", | 1686 | "monolog/monolog": ">=1.8,<1.12", |
1565 | "namshi/jose": "<2.2", | 1687 | "namshi/jose": "<2.2", |
1688 | "nystudio107/craft-seomatic": "<3.3", | ||
1689 | "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", | ||
1690 | "october/backend": ">=1.0.319,<1.0.467", | ||
1691 | "october/cms": ">=1.0.319,<1.0.466", | ||
1692 | "october/october": ">=1.0.319,<1.0.466", | ||
1693 | "october/rain": ">=1.0.319,<1.0.468", | ||
1566 | "onelogin/php-saml": "<2.10.4", | 1694 | "onelogin/php-saml": "<2.10.4", |
1695 | "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", | ||
1567 | "openid/php-openid": "<2.3", | 1696 | "openid/php-openid": "<2.3", |
1697 | "openmage/magento-lts": "<19.4.6|>=20,<20.0.2", | ||
1568 | "oro/crm": ">=1.7,<1.7.4", | 1698 | "oro/crm": ">=1.7,<1.7.4", |
1569 | "oro/platform": ">=1.7,<1.7.4", | 1699 | "oro/platform": ">=1.7,<1.7.4", |
1570 | "padraic/humbug_get_contents": "<1.1.2", | 1700 | "padraic/humbug_get_contents": "<1.1.2", |
@@ -1572,66 +1702,97 @@ | |||
1572 | "paragonie/random_compat": "<2", | 1702 | "paragonie/random_compat": "<2", |
1573 | "paypal/merchant-sdk-php": "<3.12", | 1703 | "paypal/merchant-sdk-php": "<3.12", |
1574 | "pear/archive_tar": "<1.4.4", | 1704 | "pear/archive_tar": "<1.4.4", |
1575 | "phpmailer/phpmailer": ">=5,<5.2.27|>=6,<6.0.6", | 1705 | "personnummer/personnummer": "<3.0.2", |
1576 | "phpoffice/phpexcel": "<=1.8.1", | 1706 | "phpfastcache/phpfastcache": ">=5,<5.0.13", |
1577 | "phpoffice/phpspreadsheet": "<=1.5", | 1707 | "phpmailer/phpmailer": "<6.1.6", |
1708 | "phpmussel/phpmussel": ">=1,<1.6", | ||
1709 | "phpmyadmin/phpmyadmin": "<4.9.2", | ||
1710 | "phpoffice/phpexcel": "<1.8.2", | ||
1711 | "phpoffice/phpspreadsheet": "<1.8", | ||
1578 | "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", | 1712 | "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", |
1579 | "phpwhois/phpwhois": "<=4.2.5", | 1713 | "phpwhois/phpwhois": "<=4.2.5", |
1580 | "phpxmlrpc/extras": "<0.6.1", | 1714 | "phpxmlrpc/extras": "<0.6.1", |
1715 | "pimcore/pimcore": "<6.3", | ||
1716 | "prestashop/autoupgrade": ">=4,<4.10.1", | ||
1717 | "prestashop/contactform": ">1.0.1,<4.3", | ||
1718 | "prestashop/gamification": "<2.3.2", | ||
1719 | "prestashop/ps_facetedsearch": "<3.4.1", | ||
1720 | "privatebin/privatebin": "<1.2.2|>=1.3,<1.3.2", | ||
1581 | "propel/propel": ">=2-alpha.1,<=2-alpha.7", | 1721 | "propel/propel": ">=2-alpha.1,<=2-alpha.7", |
1582 | "propel/propel1": ">=1,<=1.7.1", | 1722 | "propel/propel1": ">=1,<=1.7.1", |
1583 | "pusher/pusher-php-server": "<2.2.1", | 1723 | "pusher/pusher-php-server": "<2.2.1", |
1584 | "robrichards/xmlseclibs": ">=1,<3.0.2", | 1724 | "rainlab/debugbar-plugin": "<3.1", |
1725 | "robrichards/xmlseclibs": "<3.0.4", | ||
1726 | "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", | ||
1585 | "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", | 1727 | "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", |
1728 | "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", | ||
1586 | "sensiolabs/connect": "<4.2.3", | 1729 | "sensiolabs/connect": "<4.2.3", |
1587 | "serluck/phpwhois": "<=4.2.6", | 1730 | "serluck/phpwhois": "<=4.2.6", |
1731 | "shopware/core": "<=6.3.1", | ||
1732 | "shopware/platform": "<=6.3.1", | ||
1588 | "shopware/shopware": "<5.3.7", | 1733 | "shopware/shopware": "<5.3.7", |
1589 | "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11", | 1734 | "silverstripe/admin": ">=1.0.3,<1.0.4|>=1.1,<1.1.1", |
1735 | "silverstripe/assets": ">=1,<1.4.7|>=1.5,<1.5.2", | ||
1736 | "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4", | ||
1737 | "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", | ||
1590 | "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", | 1738 | "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", |
1591 | "silverstripe/framework": ">=3,<3.6.7|>=3.7,<3.7.3|>=4,<4.4", | 1739 | "silverstripe/framework": "<4.4.7|>=4.5,<4.5.4", |
1592 | "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.1.2", | 1740 | "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.1.2|>=3.2,<3.2.4", |
1593 | "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", | 1741 | "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", |
1594 | "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", | 1742 | "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", |
1743 | "silverstripe/subsites": ">=2,<2.1.1", | ||
1744 | "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", | ||
1595 | "silverstripe/userforms": "<3", | 1745 | "silverstripe/userforms": "<3", |
1596 | "simple-updates/phpwhois": "<=1", | 1746 | "simple-updates/phpwhois": "<=1", |
1597 | "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", | 1747 | "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", |
1598 | "simplesamlphp/simplesamlphp": "<1.17.3", | 1748 | "simplesamlphp/simplesamlphp": "<1.18.6", |
1599 | "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", | 1749 | "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", |
1750 | "simplito/elliptic-php": "<1.0.6", | ||
1600 | "slim/slim": "<2.6", | 1751 | "slim/slim": "<2.6", |
1601 | "smarty/smarty": "<3.1.33", | 1752 | "smarty/smarty": "<3.1.33", |
1602 | "socalnick/scn-social-auth": "<1.15.2", | 1753 | "socalnick/scn-social-auth": "<1.15.2", |
1603 | "spoonity/tcpdf": "<6.2.22", | 1754 | "spoonity/tcpdf": "<6.2.22", |
1604 | "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", | 1755 | "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", |
1756 | "ssddanbrown/bookstack": "<0.29.2", | ||
1605 | "stormpath/sdk": ">=0,<9.9.99", | 1757 | "stormpath/sdk": ">=0,<9.9.99", |
1758 | "studio-42/elfinder": "<2.1.49", | ||
1759 | "sulu/sulu": "<1.6.34|>=2,<2.0.10|>=2.1,<2.1.1", | ||
1606 | "swiftmailer/swiftmailer": ">=4,<5.4.5", | 1760 | "swiftmailer/swiftmailer": ">=4,<5.4.5", |
1607 | "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", | 1761 | "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", |
1608 | "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", | 1762 | "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", |
1609 | "sylius/grid-bundle": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", | 1763 | "sylius/grid-bundle": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", |
1610 | "sylius/sylius": ">=1,<1.1.18|>=1.2,<1.2.17|>=1.3,<1.3.12|>=1.4,<1.4.4", | 1764 | "sylius/resource-bundle": "<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", |
1611 | "symfony/cache": ">=3.1,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1765 | "sylius/sylius": "<1.3.16|>=1.4,<1.4.12|>=1.5,<1.5.9|>=1.6,<1.6.5", |
1766 | "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", | ||
1767 | "symbiote/silverstripe-versionedfiles": "<=2.0.3", | ||
1768 | "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", | ||
1612 | "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1769 | "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", |
1770 | "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", | ||
1613 | "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", | 1771 | "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", |
1614 | "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1772 | "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", |
1615 | "symfony/http-foundation": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1773 | "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", |
1616 | "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8", | 1774 | "symfony/http-kernel": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.4.13|>=5,<5.1.5", |
1617 | "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", | 1775 | "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", |
1776 | "symfony/mime": ">=4.3,<4.3.8", | ||
1618 | "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1777 | "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", |
1619 | "symfony/polyfill": ">=1,<1.10", | 1778 | "symfony/polyfill": ">=1,<1.10", |
1620 | "symfony/polyfill-php55": ">=1,<1.10", | 1779 | "symfony/polyfill-php55": ">=1,<1.10", |
1621 | "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1780 | "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", |
1622 | "symfony/routing": ">=2,<2.0.19", | 1781 | "symfony/routing": ">=2,<2.0.19", |
1623 | "symfony/security": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1782 | "symfony/security": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=4.4,<4.4.7|>=5,<5.0.7", |
1624 | "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", | 1783 | "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", |
1625 | "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7", | 1784 | "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7", |
1626 | "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", | 1785 | "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", |
1627 | "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", | 1786 | "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", |
1628 | "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1787 | "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", |
1629 | "symfony/serializer": ">=2,<2.0.11", | 1788 | "symfony/serializer": ">=2,<2.0.11", |
1630 | "symfony/symfony": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", | 1789 | "symfony/symfony": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.4.13|>=5,<5.1.5", |
1631 | "symfony/translation": ">=2,<2.0.17", | 1790 | "symfony/translation": ">=2,<2.0.17", |
1632 | "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", | 1791 | "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", |
1792 | "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", | ||
1633 | "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", | 1793 | "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", |
1634 | "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", | 1794 | "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", |
1795 | "t3g/svg-sanitizer": "<1.0.3", | ||
1635 | "tecnickcom/tcpdf": "<6.2.22", | 1796 | "tecnickcom/tcpdf": "<6.2.22", |
1636 | "thelia/backoffice-default-template": ">=2.1,<2.1.2", | 1797 | "thelia/backoffice-default-template": ">=2.1,<2.1.2", |
1637 | "thelia/thelia": ">=2.1-beta.1,<2.1.3", | 1798 | "thelia/thelia": ">=2.1-beta.1,<2.1.3", |
@@ -1639,22 +1800,26 @@ | |||
1639 | "titon/framework": ">=0,<9.9.99", | 1800 | "titon/framework": ">=0,<9.9.99", |
1640 | "truckersmp/phpwhois": "<=4.3.1", | 1801 | "truckersmp/phpwhois": "<=4.3.1", |
1641 | "twig/twig": "<1.38|>=2,<2.7", | 1802 | "twig/twig": "<1.38|>=2,<2.7", |
1642 | "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.27|>=9,<9.5.8", | 1803 | "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.20|>=10,<10.4.6", |
1643 | "typo3/cms-core": ">=8,<8.7.27|>=9,<9.5.8", | 1804 | "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.20|>=10,<10.4.6", |
1644 | "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", | 1805 | "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", |
1645 | "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", | 1806 | "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", |
1646 | "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", | 1807 | "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", |
1647 | "ua-parser/uap-php": "<3.8", | 1808 | "ua-parser/uap-php": "<3.8", |
1809 | "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", | ||
1810 | "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", | ||
1648 | "wallabag/tcpdf": "<6.2.22", | 1811 | "wallabag/tcpdf": "<6.2.22", |
1649 | "willdurand/js-translation-bundle": "<2.1.1", | 1812 | "willdurand/js-translation-bundle": "<2.1.1", |
1813 | "yii2mod/yii2-cms": "<1.9.2", | ||
1650 | "yiisoft/yii": ">=1.1.14,<1.1.15", | 1814 | "yiisoft/yii": ">=1.1.14,<1.1.15", |
1651 | "yiisoft/yii2": "<2.0.15", | 1815 | "yiisoft/yii2": "<2.0.38", |
1652 | "yiisoft/yii2-bootstrap": "<2.0.4", | 1816 | "yiisoft/yii2-bootstrap": "<2.0.4", |
1653 | "yiisoft/yii2-dev": "<2.0.15", | 1817 | "yiisoft/yii2-dev": "<2.0.15", |
1654 | "yiisoft/yii2-elasticsearch": "<2.0.5", | 1818 | "yiisoft/yii2-elasticsearch": "<2.0.5", |
1655 | "yiisoft/yii2-gii": "<2.0.4", | 1819 | "yiisoft/yii2-gii": "<2.0.4", |
1656 | "yiisoft/yii2-jui": "<2.0.4", | 1820 | "yiisoft/yii2-jui": "<2.0.4", |
1657 | "yiisoft/yii2-redis": "<2.0.8", | 1821 | "yiisoft/yii2-redis": "<2.0.8", |
1822 | "yourls/yourls": "<1.7.4", | ||
1658 | "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", | 1823 | "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", |
1659 | "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", | 1824 | "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", |
1660 | "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", | 1825 | "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", |
@@ -1691,10 +1856,29 @@ | |||
1691 | "name": "Marco Pivetta", | 1856 | "name": "Marco Pivetta", |
1692 | "email": "ocramius@gmail.com", | 1857 | "email": "ocramius@gmail.com", |
1693 | "role": "maintainer" | 1858 | "role": "maintainer" |
1859 | }, | ||
1860 | { | ||
1861 | "name": "Ilya Tribusean", | ||
1862 | "email": "slash3b@gmail.com", | ||
1863 | "role": "maintainer" | ||
1694 | } | 1864 | } |
1695 | ], | 1865 | ], |
1696 | "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", | 1866 | "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", |
1697 | "time": "2019-07-18T15:17:58+00:00" | 1867 | "support": { |
1868 | "issues": "https://github.com/Roave/SecurityAdvisories/issues", | ||
1869 | "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" | ||
1870 | }, | ||
1871 | "funding": [ | ||
1872 | { | ||
1873 | "url": "https://github.com/Ocramius", | ||
1874 | "type": "github" | ||
1875 | }, | ||
1876 | { | ||
1877 | "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", | ||
1878 | "type": "tidelift" | ||
1879 | } | ||
1880 | ], | ||
1881 | "time": "2020-09-24T17:02:11+00:00" | ||
1698 | }, | 1882 | }, |
1699 | { | 1883 | { |
1700 | "name": "sebastian/code-unit-reverse-lookup", | 1884 | "name": "sebastian/code-unit-reverse-lookup", |
@@ -1739,34 +1923,38 @@ | |||
1739 | ], | 1923 | ], |
1740 | "description": "Looks up which function or method a line of code belongs to", | 1924 | "description": "Looks up which function or method a line of code belongs to", |
1741 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", | 1925 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", |
1926 | "support": { | ||
1927 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", | ||
1928 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/master" | ||
1929 | }, | ||
1742 | "time": "2017-03-04T06:30:41+00:00" | 1930 | "time": "2017-03-04T06:30:41+00:00" |
1743 | }, | 1931 | }, |
1744 | { | 1932 | { |
1745 | "name": "sebastian/comparator", | 1933 | "name": "sebastian/comparator", |
1746 | "version": "1.2.4", | 1934 | "version": "3.0.2", |
1747 | "source": { | 1935 | "source": { |
1748 | "type": "git", | 1936 | "type": "git", |
1749 | "url": "https://github.com/sebastianbergmann/comparator.git", | 1937 | "url": "https://github.com/sebastianbergmann/comparator.git", |
1750 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" | 1938 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" |
1751 | }, | 1939 | }, |
1752 | "dist": { | 1940 | "dist": { |
1753 | "type": "zip", | 1941 | "type": "zip", |
1754 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", | 1942 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", |
1755 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", | 1943 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", |
1756 | "shasum": "" | 1944 | "shasum": "" |
1757 | }, | 1945 | }, |
1758 | "require": { | 1946 | "require": { |
1759 | "php": ">=5.3.3", | 1947 | "php": "^7.1", |
1760 | "sebastian/diff": "~1.2", | 1948 | "sebastian/diff": "^3.0", |
1761 | "sebastian/exporter": "~1.2 || ~2.0" | 1949 | "sebastian/exporter": "^3.1" |
1762 | }, | 1950 | }, |
1763 | "require-dev": { | 1951 | "require-dev": { |
1764 | "phpunit/phpunit": "~4.4" | 1952 | "phpunit/phpunit": "^7.1" |
1765 | }, | 1953 | }, |
1766 | "type": "library", | 1954 | "type": "library", |
1767 | "extra": { | 1955 | "extra": { |
1768 | "branch-alias": { | 1956 | "branch-alias": { |
1769 | "dev-master": "1.2.x-dev" | 1957 | "dev-master": "3.0-dev" |
1770 | } | 1958 | } |
1771 | }, | 1959 | }, |
1772 | "autoload": { | 1960 | "autoload": { |
@@ -1797,38 +1985,43 @@ | |||
1797 | } | 1985 | } |
1798 | ], | 1986 | ], |
1799 | "description": "Provides the functionality to compare PHP values for equality", | 1987 | "description": "Provides the functionality to compare PHP values for equality", |
1800 | "homepage": "http://www.github.com/sebastianbergmann/comparator", | 1988 | "homepage": "https://github.com/sebastianbergmann/comparator", |
1801 | "keywords": [ | 1989 | "keywords": [ |
1802 | "comparator", | 1990 | "comparator", |
1803 | "compare", | 1991 | "compare", |
1804 | "equality" | 1992 | "equality" |
1805 | ], | 1993 | ], |
1806 | "time": "2017-01-29T09:50:25+00:00" | 1994 | "support": { |
1995 | "issues": "https://github.com/sebastianbergmann/comparator/issues", | ||
1996 | "source": "https://github.com/sebastianbergmann/comparator/tree/master" | ||
1997 | }, | ||
1998 | "time": "2018-07-12T15:12:46+00:00" | ||
1807 | }, | 1999 | }, |
1808 | { | 2000 | { |
1809 | "name": "sebastian/diff", | 2001 | "name": "sebastian/diff", |
1810 | "version": "1.4.3", | 2002 | "version": "3.0.2", |
1811 | "source": { | 2003 | "source": { |
1812 | "type": "git", | 2004 | "type": "git", |
1813 | "url": "https://github.com/sebastianbergmann/diff.git", | 2005 | "url": "https://github.com/sebastianbergmann/diff.git", |
1814 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" | 2006 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" |
1815 | }, | 2007 | }, |
1816 | "dist": { | 2008 | "dist": { |
1817 | "type": "zip", | 2009 | "type": "zip", |
1818 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", | 2010 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", |
1819 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", | 2011 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", |
1820 | "shasum": "" | 2012 | "shasum": "" |
1821 | }, | 2013 | }, |
1822 | "require": { | 2014 | "require": { |
1823 | "php": "^5.3.3 || ^7.0" | 2015 | "php": "^7.1" |
1824 | }, | 2016 | }, |
1825 | "require-dev": { | 2017 | "require-dev": { |
1826 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" | 2018 | "phpunit/phpunit": "^7.5 || ^8.0", |
2019 | "symfony/process": "^2 || ^3.3 || ^4" | ||
1827 | }, | 2020 | }, |
1828 | "type": "library", | 2021 | "type": "library", |
1829 | "extra": { | 2022 | "extra": { |
1830 | "branch-alias": { | 2023 | "branch-alias": { |
1831 | "dev-master": "1.4-dev" | 2024 | "dev-master": "3.0-dev" |
1832 | } | 2025 | } |
1833 | }, | 2026 | }, |
1834 | "autoload": { | 2027 | "autoload": { |
@@ -1853,34 +2046,44 @@ | |||
1853 | "description": "Diff implementation", | 2046 | "description": "Diff implementation", |
1854 | "homepage": "https://github.com/sebastianbergmann/diff", | 2047 | "homepage": "https://github.com/sebastianbergmann/diff", |
1855 | "keywords": [ | 2048 | "keywords": [ |
1856 | "diff" | 2049 | "diff", |
2050 | "udiff", | ||
2051 | "unidiff", | ||
2052 | "unified diff" | ||
1857 | ], | 2053 | ], |
1858 | "time": "2017-05-22T07:24:03+00:00" | 2054 | "support": { |
2055 | "issues": "https://github.com/sebastianbergmann/diff/issues", | ||
2056 | "source": "https://github.com/sebastianbergmann/diff/tree/master" | ||
2057 | }, | ||
2058 | "time": "2019-02-04T06:01:07+00:00" | ||
1859 | }, | 2059 | }, |
1860 | { | 2060 | { |
1861 | "name": "sebastian/environment", | 2061 | "name": "sebastian/environment", |
1862 | "version": "2.0.0", | 2062 | "version": "4.2.3", |
1863 | "source": { | 2063 | "source": { |
1864 | "type": "git", | 2064 | "type": "git", |
1865 | "url": "https://github.com/sebastianbergmann/environment.git", | 2065 | "url": "https://github.com/sebastianbergmann/environment.git", |
1866 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" | 2066 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" |
1867 | }, | 2067 | }, |
1868 | "dist": { | 2068 | "dist": { |
1869 | "type": "zip", | 2069 | "type": "zip", |
1870 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", | 2070 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", |
1871 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", | 2071 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", |
1872 | "shasum": "" | 2072 | "shasum": "" |
1873 | }, | 2073 | }, |
1874 | "require": { | 2074 | "require": { |
1875 | "php": "^5.6 || ^7.0" | 2075 | "php": "^7.1" |
1876 | }, | 2076 | }, |
1877 | "require-dev": { | 2077 | "require-dev": { |
1878 | "phpunit/phpunit": "^5.0" | 2078 | "phpunit/phpunit": "^7.5" |
2079 | }, | ||
2080 | "suggest": { | ||
2081 | "ext-posix": "*" | ||
1879 | }, | 2082 | }, |
1880 | "type": "library", | 2083 | "type": "library", |
1881 | "extra": { | 2084 | "extra": { |
1882 | "branch-alias": { | 2085 | "branch-alias": { |
1883 | "dev-master": "2.0.x-dev" | 2086 | "dev-master": "4.2-dev" |
1884 | } | 2087 | } |
1885 | }, | 2088 | }, |
1886 | "autoload": { | 2089 | "autoload": { |
@@ -1905,34 +2108,38 @@ | |||
1905 | "environment", | 2108 | "environment", |
1906 | "hhvm" | 2109 | "hhvm" |
1907 | ], | 2110 | ], |
1908 | "time": "2016-11-26T07:53:53+00:00" | 2111 | "support": { |
2112 | "issues": "https://github.com/sebastianbergmann/environment/issues", | ||
2113 | "source": "https://github.com/sebastianbergmann/environment/tree/4.2.3" | ||
2114 | }, | ||
2115 | "time": "2019-11-20T08:46:58+00:00" | ||
1909 | }, | 2116 | }, |
1910 | { | 2117 | { |
1911 | "name": "sebastian/exporter", | 2118 | "name": "sebastian/exporter", |
1912 | "version": "2.0.0", | 2119 | "version": "3.1.2", |
1913 | "source": { | 2120 | "source": { |
1914 | "type": "git", | 2121 | "type": "git", |
1915 | "url": "https://github.com/sebastianbergmann/exporter.git", | 2122 | "url": "https://github.com/sebastianbergmann/exporter.git", |
1916 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" | 2123 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" |
1917 | }, | 2124 | }, |
1918 | "dist": { | 2125 | "dist": { |
1919 | "type": "zip", | 2126 | "type": "zip", |
1920 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", | 2127 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", |
1921 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", | 2128 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", |
1922 | "shasum": "" | 2129 | "shasum": "" |
1923 | }, | 2130 | }, |
1924 | "require": { | 2131 | "require": { |
1925 | "php": ">=5.3.3", | 2132 | "php": "^7.0", |
1926 | "sebastian/recursion-context": "~2.0" | 2133 | "sebastian/recursion-context": "^3.0" |
1927 | }, | 2134 | }, |
1928 | "require-dev": { | 2135 | "require-dev": { |
1929 | "ext-mbstring": "*", | 2136 | "ext-mbstring": "*", |
1930 | "phpunit/phpunit": "~4.4" | 2137 | "phpunit/phpunit": "^6.0" |
1931 | }, | 2138 | }, |
1932 | "type": "library", | 2139 | "type": "library", |
1933 | "extra": { | 2140 | "extra": { |
1934 | "branch-alias": { | 2141 | "branch-alias": { |
1935 | "dev-master": "2.0.x-dev" | 2142 | "dev-master": "3.1.x-dev" |
1936 | } | 2143 | } |
1937 | }, | 2144 | }, |
1938 | "autoload": { | 2145 | "autoload": { |
@@ -1946,6 +2153,10 @@ | |||
1946 | ], | 2153 | ], |
1947 | "authors": [ | 2154 | "authors": [ |
1948 | { | 2155 | { |
2156 | "name": "Sebastian Bergmann", | ||
2157 | "email": "sebastian@phpunit.de" | ||
2158 | }, | ||
2159 | { | ||
1949 | "name": "Jeff Welch", | 2160 | "name": "Jeff Welch", |
1950 | "email": "whatthejeff@gmail.com" | 2161 | "email": "whatthejeff@gmail.com" |
1951 | }, | 2162 | }, |
@@ -1954,16 +2165,12 @@ | |||
1954 | "email": "github@wallbash.com" | 2165 | "email": "github@wallbash.com" |
1955 | }, | 2166 | }, |
1956 | { | 2167 | { |
1957 | "name": "Bernhard Schussek", | ||
1958 | "email": "bschussek@2bepublished.at" | ||
1959 | }, | ||
1960 | { | ||
1961 | "name": "Sebastian Bergmann", | ||
1962 | "email": "sebastian@phpunit.de" | ||
1963 | }, | ||
1964 | { | ||
1965 | "name": "Adam Harvey", | 2168 | "name": "Adam Harvey", |
1966 | "email": "aharvey@php.net" | 2169 | "email": "aharvey@php.net" |
2170 | }, | ||
2171 | { | ||
2172 | "name": "Bernhard Schussek", | ||
2173 | "email": "bschussek@gmail.com" | ||
1967 | } | 2174 | } |
1968 | ], | 2175 | ], |
1969 | "description": "Provides the functionality to export PHP variables for visualization", | 2176 | "description": "Provides the functionality to export PHP variables for visualization", |
@@ -1972,27 +2179,41 @@ | |||
1972 | "export", | 2179 | "export", |
1973 | "exporter" | 2180 | "exporter" |
1974 | ], | 2181 | ], |
1975 | "time": "2016-11-19T08:54:04+00:00" | 2182 | "support": { |
2183 | "issues": "https://github.com/sebastianbergmann/exporter/issues", | ||
2184 | "source": "https://github.com/sebastianbergmann/exporter/tree/master" | ||
2185 | }, | ||
2186 | "time": "2019-09-14T09:02:43+00:00" | ||
1976 | }, | 2187 | }, |
1977 | { | 2188 | { |
1978 | "name": "sebastian/finder-facade", | 2189 | "name": "sebastian/global-state", |
1979 | "version": "1.2.2", | 2190 | "version": "2.0.0", |
1980 | "source": { | 2191 | "source": { |
1981 | "type": "git", | 2192 | "type": "git", |
1982 | "url": "https://github.com/sebastianbergmann/finder-facade.git", | 2193 | "url": "https://github.com/sebastianbergmann/global-state.git", |
1983 | "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f" | 2194 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" |
1984 | }, | 2195 | }, |
1985 | "dist": { | 2196 | "dist": { |
1986 | "type": "zip", | 2197 | "type": "zip", |
1987 | "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", | 2198 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", |
1988 | "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", | 2199 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", |
1989 | "shasum": "" | 2200 | "shasum": "" |
1990 | }, | 2201 | }, |
1991 | "require": { | 2202 | "require": { |
1992 | "symfony/finder": "~2.3|~3.0|~4.0", | 2203 | "php": "^7.0" |
1993 | "theseer/fdomdocument": "~1.3" | 2204 | }, |
2205 | "require-dev": { | ||
2206 | "phpunit/phpunit": "^6.0" | ||
2207 | }, | ||
2208 | "suggest": { | ||
2209 | "ext-uopz": "*" | ||
1994 | }, | 2210 | }, |
1995 | "type": "library", | 2211 | "type": "library", |
2212 | "extra": { | ||
2213 | "branch-alias": { | ||
2214 | "dev-master": "2.0-dev" | ||
2215 | } | ||
2216 | }, | ||
1996 | "autoload": { | 2217 | "autoload": { |
1997 | "classmap": [ | 2218 | "classmap": [ |
1998 | "src/" | 2219 | "src/" |
@@ -2005,41 +2226,46 @@ | |||
2005 | "authors": [ | 2226 | "authors": [ |
2006 | { | 2227 | { |
2007 | "name": "Sebastian Bergmann", | 2228 | "name": "Sebastian Bergmann", |
2008 | "email": "sebastian@phpunit.de", | 2229 | "email": "sebastian@phpunit.de" |
2009 | "role": "lead" | ||
2010 | } | 2230 | } |
2011 | ], | 2231 | ], |
2012 | "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", | 2232 | "description": "Snapshotting of global state", |
2013 | "homepage": "https://github.com/sebastianbergmann/finder-facade", | 2233 | "homepage": "http://www.github.com/sebastianbergmann/global-state", |
2014 | "time": "2017-11-18T17:31:49+00:00" | 2234 | "keywords": [ |
2235 | "global state" | ||
2236 | ], | ||
2237 | "support": { | ||
2238 | "issues": "https://github.com/sebastianbergmann/global-state/issues", | ||
2239 | "source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0" | ||
2240 | }, | ||
2241 | "time": "2017-04-27T15:39:26+00:00" | ||
2015 | }, | 2242 | }, |
2016 | { | 2243 | { |
2017 | "name": "sebastian/global-state", | 2244 | "name": "sebastian/object-enumerator", |
2018 | "version": "1.1.1", | 2245 | "version": "3.0.3", |
2019 | "source": { | 2246 | "source": { |
2020 | "type": "git", | 2247 | "type": "git", |
2021 | "url": "https://github.com/sebastianbergmann/global-state.git", | 2248 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", |
2022 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" | 2249 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" |
2023 | }, | 2250 | }, |
2024 | "dist": { | 2251 | "dist": { |
2025 | "type": "zip", | 2252 | "type": "zip", |
2026 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", | 2253 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", |
2027 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", | 2254 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", |
2028 | "shasum": "" | 2255 | "shasum": "" |
2029 | }, | 2256 | }, |
2030 | "require": { | 2257 | "require": { |
2031 | "php": ">=5.3.3" | 2258 | "php": "^7.0", |
2259 | "sebastian/object-reflector": "^1.1.1", | ||
2260 | "sebastian/recursion-context": "^3.0" | ||
2032 | }, | 2261 | }, |
2033 | "require-dev": { | 2262 | "require-dev": { |
2034 | "phpunit/phpunit": "~4.2" | 2263 | "phpunit/phpunit": "^6.0" |
2035 | }, | ||
2036 | "suggest": { | ||
2037 | "ext-uopz": "*" | ||
2038 | }, | 2264 | }, |
2039 | "type": "library", | 2265 | "type": "library", |
2040 | "extra": { | 2266 | "extra": { |
2041 | "branch-alias": { | 2267 | "branch-alias": { |
2042 | "dev-master": "1.0-dev" | 2268 | "dev-master": "3.0.x-dev" |
2043 | } | 2269 | } |
2044 | }, | 2270 | }, |
2045 | "autoload": { | 2271 | "autoload": { |
@@ -2057,38 +2283,38 @@ | |||
2057 | "email": "sebastian@phpunit.de" | 2283 | "email": "sebastian@phpunit.de" |
2058 | } | 2284 | } |
2059 | ], | 2285 | ], |
2060 | "description": "Snapshotting of global state", | 2286 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", |
2061 | "homepage": "http://www.github.com/sebastianbergmann/global-state", | 2287 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", |
2062 | "keywords": [ | 2288 | "support": { |
2063 | "global state" | 2289 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", |
2064 | ], | 2290 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/master" |
2065 | "time": "2015-10-12T03:26:01+00:00" | 2291 | }, |
2292 | "time": "2017-08-03T12:35:26+00:00" | ||
2066 | }, | 2293 | }, |
2067 | { | 2294 | { |
2068 | "name": "sebastian/object-enumerator", | 2295 | "name": "sebastian/object-reflector", |
2069 | "version": "2.0.1", | 2296 | "version": "1.1.1", |
2070 | "source": { | 2297 | "source": { |
2071 | "type": "git", | 2298 | "type": "git", |
2072 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", | 2299 | "url": "https://github.com/sebastianbergmann/object-reflector.git", |
2073 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" | 2300 | "reference": "773f97c67f28de00d397be301821b06708fca0be" |
2074 | }, | 2301 | }, |
2075 | "dist": { | 2302 | "dist": { |
2076 | "type": "zip", | 2303 | "type": "zip", |
2077 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", | 2304 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", |
2078 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", | 2305 | "reference": "773f97c67f28de00d397be301821b06708fca0be", |
2079 | "shasum": "" | 2306 | "shasum": "" |
2080 | }, | 2307 | }, |
2081 | "require": { | 2308 | "require": { |
2082 | "php": ">=5.6", | 2309 | "php": "^7.0" |
2083 | "sebastian/recursion-context": "~2.0" | ||
2084 | }, | 2310 | }, |
2085 | "require-dev": { | 2311 | "require-dev": { |
2086 | "phpunit/phpunit": "~5" | 2312 | "phpunit/phpunit": "^6.0" |
2087 | }, | 2313 | }, |
2088 | "type": "library", | 2314 | "type": "library", |
2089 | "extra": { | 2315 | "extra": { |
2090 | "branch-alias": { | 2316 | "branch-alias": { |
2091 | "dev-master": "2.0.x-dev" | 2317 | "dev-master": "1.1-dev" |
2092 | } | 2318 | } |
2093 | }, | 2319 | }, |
2094 | "autoload": { | 2320 | "autoload": { |
@@ -2106,34 +2332,38 @@ | |||
2106 | "email": "sebastian@phpunit.de" | 2332 | "email": "sebastian@phpunit.de" |
2107 | } | 2333 | } |
2108 | ], | 2334 | ], |
2109 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", | 2335 | "description": "Allows reflection of object attributes, including inherited and non-public ones", |
2110 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", | 2336 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", |
2111 | "time": "2017-02-18T15:18:39+00:00" | 2337 | "support": { |
2338 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", | ||
2339 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/master" | ||
2340 | }, | ||
2341 | "time": "2017-03-29T09:07:27+00:00" | ||
2112 | }, | 2342 | }, |
2113 | { | 2343 | { |
2114 | "name": "sebastian/recursion-context", | 2344 | "name": "sebastian/recursion-context", |
2115 | "version": "2.0.0", | 2345 | "version": "3.0.0", |
2116 | "source": { | 2346 | "source": { |
2117 | "type": "git", | 2347 | "type": "git", |
2118 | "url": "https://github.com/sebastianbergmann/recursion-context.git", | 2348 | "url": "https://github.com/sebastianbergmann/recursion-context.git", |
2119 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" | 2349 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" |
2120 | }, | 2350 | }, |
2121 | "dist": { | 2351 | "dist": { |
2122 | "type": "zip", | 2352 | "type": "zip", |
2123 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", | 2353 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", |
2124 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", | 2354 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", |
2125 | "shasum": "" | 2355 | "shasum": "" |
2126 | }, | 2356 | }, |
2127 | "require": { | 2357 | "require": { |
2128 | "php": ">=5.3.3" | 2358 | "php": "^7.0" |
2129 | }, | 2359 | }, |
2130 | "require-dev": { | 2360 | "require-dev": { |
2131 | "phpunit/phpunit": "~4.4" | 2361 | "phpunit/phpunit": "^6.0" |
2132 | }, | 2362 | }, |
2133 | "type": "library", | 2363 | "type": "library", |
2134 | "extra": { | 2364 | "extra": { |
2135 | "branch-alias": { | 2365 | "branch-alias": { |
2136 | "dev-master": "2.0.x-dev" | 2366 | "dev-master": "3.0.x-dev" |
2137 | } | 2367 | } |
2138 | }, | 2368 | }, |
2139 | "autoload": { | 2369 | "autoload": { |
@@ -2161,29 +2391,33 @@ | |||
2161 | ], | 2391 | ], |
2162 | "description": "Provides functionality to recursively process PHP variables", | 2392 | "description": "Provides functionality to recursively process PHP variables", |
2163 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", | 2393 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", |
2164 | "time": "2016-11-19T07:33:16+00:00" | 2394 | "support": { |
2395 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", | ||
2396 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" | ||
2397 | }, | ||
2398 | "time": "2017-03-03T06:23:57+00:00" | ||
2165 | }, | 2399 | }, |
2166 | { | 2400 | { |
2167 | "name": "sebastian/resource-operations", | 2401 | "name": "sebastian/resource-operations", |
2168 | "version": "1.0.0", | 2402 | "version": "2.0.1", |
2169 | "source": { | 2403 | "source": { |
2170 | "type": "git", | 2404 | "type": "git", |
2171 | "url": "https://github.com/sebastianbergmann/resource-operations.git", | 2405 | "url": "https://github.com/sebastianbergmann/resource-operations.git", |
2172 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" | 2406 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" |
2173 | }, | 2407 | }, |
2174 | "dist": { | 2408 | "dist": { |
2175 | "type": "zip", | 2409 | "type": "zip", |
2176 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", | 2410 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", |
2177 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", | 2411 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", |
2178 | "shasum": "" | 2412 | "shasum": "" |
2179 | }, | 2413 | }, |
2180 | "require": { | 2414 | "require": { |
2181 | "php": ">=5.6.0" | 2415 | "php": "^7.1" |
2182 | }, | 2416 | }, |
2183 | "type": "library", | 2417 | "type": "library", |
2184 | "extra": { | 2418 | "extra": { |
2185 | "branch-alias": { | 2419 | "branch-alias": { |
2186 | "dev-master": "1.0.x-dev" | 2420 | "dev-master": "2.0-dev" |
2187 | } | 2421 | } |
2188 | }, | 2422 | }, |
2189 | "autoload": { | 2423 | "autoload": { |
@@ -2203,7 +2437,11 @@ | |||
2203 | ], | 2437 | ], |
2204 | "description": "Provides a list of PHP built-in functions that operate on resources", | 2438 | "description": "Provides a list of PHP built-in functions that operate on resources", |
2205 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", | 2439 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", |
2206 | "time": "2015-07-28T20:34:47+00:00" | 2440 | "support": { |
2441 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", | ||
2442 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/master" | ||
2443 | }, | ||
2444 | "time": "2018-10-04T04:07:39+00:00" | ||
2207 | }, | 2445 | }, |
2208 | { | 2446 | { |
2209 | "name": "sebastian/version", | 2447 | "name": "sebastian/version", |
@@ -2246,68 +2484,45 @@ | |||
2246 | ], | 2484 | ], |
2247 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", | 2485 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", |
2248 | "homepage": "https://github.com/sebastianbergmann/version", | 2486 | "homepage": "https://github.com/sebastianbergmann/version", |
2487 | "support": { | ||
2488 | "issues": "https://github.com/sebastianbergmann/version/issues", | ||
2489 | "source": "https://github.com/sebastianbergmann/version/tree/master" | ||
2490 | }, | ||
2249 | "time": "2016-10-03T07:35:21+00:00" | 2491 | "time": "2016-10-03T07:35:21+00:00" |
2250 | }, | 2492 | }, |
2251 | { | 2493 | { |
2252 | "name": "squizlabs/php_codesniffer", | 2494 | "name": "squizlabs/php_codesniffer", |
2253 | "version": "2.9.2", | 2495 | "version": "3.5.6", |
2254 | "source": { | 2496 | "source": { |
2255 | "type": "git", | 2497 | "type": "git", |
2256 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", | 2498 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", |
2257 | "reference": "2acf168de78487db620ab4bc524135a13cfe6745" | 2499 | "reference": "e97627871a7eab2f70e59166072a6b767d5834e0" |
2258 | }, | 2500 | }, |
2259 | "dist": { | 2501 | "dist": { |
2260 | "type": "zip", | 2502 | "type": "zip", |
2261 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", | 2503 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/e97627871a7eab2f70e59166072a6b767d5834e0", |
2262 | "reference": "2acf168de78487db620ab4bc524135a13cfe6745", | 2504 | "reference": "e97627871a7eab2f70e59166072a6b767d5834e0", |
2263 | "shasum": "" | 2505 | "shasum": "" |
2264 | }, | 2506 | }, |
2265 | "require": { | 2507 | "require": { |
2266 | "ext-simplexml": "*", | 2508 | "ext-simplexml": "*", |
2267 | "ext-tokenizer": "*", | 2509 | "ext-tokenizer": "*", |
2268 | "ext-xmlwriter": "*", | 2510 | "ext-xmlwriter": "*", |
2269 | "php": ">=5.1.2" | 2511 | "php": ">=5.4.0" |
2270 | }, | 2512 | }, |
2271 | "require-dev": { | 2513 | "require-dev": { |
2272 | "phpunit/phpunit": "~4.0" | 2514 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" |
2273 | }, | 2515 | }, |
2274 | "bin": [ | 2516 | "bin": [ |
2275 | "scripts/phpcs", | 2517 | "bin/phpcs", |
2276 | "scripts/phpcbf" | 2518 | "bin/phpcbf" |
2277 | ], | 2519 | ], |
2278 | "type": "library", | 2520 | "type": "library", |
2279 | "extra": { | 2521 | "extra": { |
2280 | "branch-alias": { | 2522 | "branch-alias": { |
2281 | "dev-master": "2.x-dev" | 2523 | "dev-master": "3.x-dev" |
2282 | } | 2524 | } |
2283 | }, | 2525 | }, |
2284 | "autoload": { | ||
2285 | "classmap": [ | ||
2286 | "CodeSniffer.php", | ||
2287 | "CodeSniffer/CLI.php", | ||
2288 | "CodeSniffer/Exception.php", | ||
2289 | "CodeSniffer/File.php", | ||
2290 | "CodeSniffer/Fixer.php", | ||
2291 | "CodeSniffer/Report.php", | ||
2292 | "CodeSniffer/Reporting.php", | ||
2293 | "CodeSniffer/Sniff.php", | ||
2294 | "CodeSniffer/Tokens.php", | ||
2295 | "CodeSniffer/Reports/", | ||
2296 | "CodeSniffer/Tokenizers/", | ||
2297 | "CodeSniffer/DocGenerators/", | ||
2298 | "CodeSniffer/Standards/AbstractPatternSniff.php", | ||
2299 | "CodeSniffer/Standards/AbstractScopeSniff.php", | ||
2300 | "CodeSniffer/Standards/AbstractVariableSniff.php", | ||
2301 | "CodeSniffer/Standards/IncorrectPatternException.php", | ||
2302 | "CodeSniffer/Standards/Generic/Sniffs/", | ||
2303 | "CodeSniffer/Standards/MySource/Sniffs/", | ||
2304 | "CodeSniffer/Standards/PEAR/Sniffs/", | ||
2305 | "CodeSniffer/Standards/PSR1/Sniffs/", | ||
2306 | "CodeSniffer/Standards/PSR2/Sniffs/", | ||
2307 | "CodeSniffer/Standards/Squiz/Sniffs/", | ||
2308 | "CodeSniffer/Standards/Zend/Sniffs/" | ||
2309 | ] | ||
2310 | }, | ||
2311 | "notification-url": "https://packagist.org/downloads/", | 2526 | "notification-url": "https://packagist.org/downloads/", |
2312 | "license": [ | 2527 | "license": [ |
2313 | "BSD-3-Clause" | 2528 | "BSD-3-Clause" |
@@ -2319,202 +2534,30 @@ | |||
2319 | } | 2534 | } |
2320 | ], | 2535 | ], |
2321 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", | 2536 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", |
2322 | "homepage": "http://www.squizlabs.com/php-codesniffer", | 2537 | "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", |
2323 | "keywords": [ | 2538 | "keywords": [ |
2324 | "phpcs", | 2539 | "phpcs", |
2325 | "standards" | 2540 | "standards" |
2326 | ], | 2541 | ], |
2327 | "time": "2018-11-07T22:31:41+00:00" | 2542 | "support": { |
2328 | }, | 2543 | "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", |
2329 | { | 2544 | "source": "https://github.com/squizlabs/PHP_CodeSniffer", |
2330 | "name": "symfony/console", | 2545 | "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" |
2331 | "version": "v3.4.30", | ||
2332 | "source": { | ||
2333 | "type": "git", | ||
2334 | "url": "https://github.com/symfony/console.git", | ||
2335 | "reference": "12940f20a816c978860fa4925b3f1bbb27e9ac46" | ||
2336 | }, | ||
2337 | "dist": { | ||
2338 | "type": "zip", | ||
2339 | "url": "https://api.github.com/repos/symfony/console/zipball/12940f20a816c978860fa4925b3f1bbb27e9ac46", | ||
2340 | "reference": "12940f20a816c978860fa4925b3f1bbb27e9ac46", | ||
2341 | "shasum": "" | ||
2342 | }, | ||
2343 | "require": { | ||
2344 | "php": "^5.5.9|>=7.0.8", | ||
2345 | "symfony/debug": "~2.8|~3.0|~4.0", | ||
2346 | "symfony/polyfill-mbstring": "~1.0" | ||
2347 | }, | ||
2348 | "conflict": { | ||
2349 | "symfony/dependency-injection": "<3.4", | ||
2350 | "symfony/process": "<3.3" | ||
2351 | }, | ||
2352 | "provide": { | ||
2353 | "psr/log-implementation": "1.0" | ||
2354 | }, | ||
2355 | "require-dev": { | ||
2356 | "psr/log": "~1.0", | ||
2357 | "symfony/config": "~3.3|~4.0", | ||
2358 | "symfony/dependency-injection": "~3.4|~4.0", | ||
2359 | "symfony/event-dispatcher": "~2.8|~3.0|~4.0", | ||
2360 | "symfony/lock": "~3.4|~4.0", | ||
2361 | "symfony/process": "~3.3|~4.0" | ||
2362 | }, | ||
2363 | "suggest": { | ||
2364 | "psr/log": "For using the console logger", | ||
2365 | "symfony/event-dispatcher": "", | ||
2366 | "symfony/lock": "", | ||
2367 | "symfony/process": "" | ||
2368 | }, | ||
2369 | "type": "library", | ||
2370 | "extra": { | ||
2371 | "branch-alias": { | ||
2372 | "dev-master": "3.4-dev" | ||
2373 | } | ||
2374 | }, | ||
2375 | "autoload": { | ||
2376 | "psr-4": { | ||
2377 | "Symfony\\Component\\Console\\": "" | ||
2378 | }, | ||
2379 | "exclude-from-classmap": [ | ||
2380 | "/Tests/" | ||
2381 | ] | ||
2382 | }, | ||
2383 | "notification-url": "https://packagist.org/downloads/", | ||
2384 | "license": [ | ||
2385 | "MIT" | ||
2386 | ], | ||
2387 | "authors": [ | ||
2388 | { | ||
2389 | "name": "Fabien Potencier", | ||
2390 | "email": "fabien@symfony.com" | ||
2391 | }, | ||
2392 | { | ||
2393 | "name": "Symfony Community", | ||
2394 | "homepage": "https://symfony.com/contributors" | ||
2395 | } | ||
2396 | ], | ||
2397 | "description": "Symfony Console Component", | ||
2398 | "homepage": "https://symfony.com", | ||
2399 | "time": "2019-07-24T14:46:41+00:00" | ||
2400 | }, | ||
2401 | { | ||
2402 | "name": "symfony/debug", | ||
2403 | "version": "v4.3.3", | ||
2404 | "source": { | ||
2405 | "type": "git", | ||
2406 | "url": "https://github.com/symfony/debug.git", | ||
2407 | "reference": "527887c3858a2462b0137662c74837288b998ee3" | ||
2408 | }, | ||
2409 | "dist": { | ||
2410 | "type": "zip", | ||
2411 | "url": "https://api.github.com/repos/symfony/debug/zipball/527887c3858a2462b0137662c74837288b998ee3", | ||
2412 | "reference": "527887c3858a2462b0137662c74837288b998ee3", | ||
2413 | "shasum": "" | ||
2414 | }, | ||
2415 | "require": { | ||
2416 | "php": "^7.1.3", | ||
2417 | "psr/log": "~1.0" | ||
2418 | }, | ||
2419 | "conflict": { | ||
2420 | "symfony/http-kernel": "<3.4" | ||
2421 | }, | ||
2422 | "require-dev": { | ||
2423 | "symfony/http-kernel": "~3.4|~4.0" | ||
2424 | }, | ||
2425 | "type": "library", | ||
2426 | "extra": { | ||
2427 | "branch-alias": { | ||
2428 | "dev-master": "4.3-dev" | ||
2429 | } | ||
2430 | }, | ||
2431 | "autoload": { | ||
2432 | "psr-4": { | ||
2433 | "Symfony\\Component\\Debug\\": "" | ||
2434 | }, | ||
2435 | "exclude-from-classmap": [ | ||
2436 | "/Tests/" | ||
2437 | ] | ||
2438 | }, | ||
2439 | "notification-url": "https://packagist.org/downloads/", | ||
2440 | "license": [ | ||
2441 | "MIT" | ||
2442 | ], | ||
2443 | "authors": [ | ||
2444 | { | ||
2445 | "name": "Fabien Potencier", | ||
2446 | "email": "fabien@symfony.com" | ||
2447 | }, | ||
2448 | { | ||
2449 | "name": "Symfony Community", | ||
2450 | "homepage": "https://symfony.com/contributors" | ||
2451 | } | ||
2452 | ], | ||
2453 | "description": "Symfony Debug Component", | ||
2454 | "homepage": "https://symfony.com", | ||
2455 | "time": "2019-07-23T11:21:36+00:00" | ||
2456 | }, | ||
2457 | { | ||
2458 | "name": "symfony/finder", | ||
2459 | "version": "v4.3.3", | ||
2460 | "source": { | ||
2461 | "type": "git", | ||
2462 | "url": "https://github.com/symfony/finder.git", | ||
2463 | "reference": "9638d41e3729459860bb96f6247ccb61faaa45f2" | ||
2464 | }, | 2546 | }, |
2465 | "dist": { | 2547 | "time": "2020-08-10T04:50:15+00:00" |
2466 | "type": "zip", | ||
2467 | "url": "https://api.github.com/repos/symfony/finder/zipball/9638d41e3729459860bb96f6247ccb61faaa45f2", | ||
2468 | "reference": "9638d41e3729459860bb96f6247ccb61faaa45f2", | ||
2469 | "shasum": "" | ||
2470 | }, | ||
2471 | "require": { | ||
2472 | "php": "^7.1.3" | ||
2473 | }, | ||
2474 | "type": "library", | ||
2475 | "extra": { | ||
2476 | "branch-alias": { | ||
2477 | "dev-master": "4.3-dev" | ||
2478 | } | ||
2479 | }, | ||
2480 | "autoload": { | ||
2481 | "psr-4": { | ||
2482 | "Symfony\\Component\\Finder\\": "" | ||
2483 | }, | ||
2484 | "exclude-from-classmap": [ | ||
2485 | "/Tests/" | ||
2486 | ] | ||
2487 | }, | ||
2488 | "notification-url": "https://packagist.org/downloads/", | ||
2489 | "license": [ | ||
2490 | "MIT" | ||
2491 | ], | ||
2492 | "authors": [ | ||
2493 | { | ||
2494 | "name": "Fabien Potencier", | ||
2495 | "email": "fabien@symfony.com" | ||
2496 | }, | ||
2497 | { | ||
2498 | "name": "Symfony Community", | ||
2499 | "homepage": "https://symfony.com/contributors" | ||
2500 | } | ||
2501 | ], | ||
2502 | "description": "Symfony Finder Component", | ||
2503 | "homepage": "https://symfony.com", | ||
2504 | "time": "2019-06-28T13:16:30+00:00" | ||
2505 | }, | 2548 | }, |
2506 | { | 2549 | { |
2507 | "name": "symfony/polyfill-ctype", | 2550 | "name": "symfony/polyfill-ctype", |
2508 | "version": "v1.11.0", | 2551 | "version": "v1.18.1", |
2509 | "source": { | 2552 | "source": { |
2510 | "type": "git", | 2553 | "type": "git", |
2511 | "url": "https://github.com/symfony/polyfill-ctype.git", | 2554 | "url": "https://github.com/symfony/polyfill-ctype.git", |
2512 | "reference": "82ebae02209c21113908c229e9883c419720738a" | 2555 | "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" |
2513 | }, | 2556 | }, |
2514 | "dist": { | 2557 | "dist": { |
2515 | "type": "zip", | 2558 | "type": "zip", |
2516 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", | 2559 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", |
2517 | "reference": "82ebae02209c21113908c229e9883c419720738a", | 2560 | "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", |
2518 | "shasum": "" | 2561 | "shasum": "" |
2519 | }, | 2562 | }, |
2520 | "require": { | 2563 | "require": { |
@@ -2526,7 +2569,11 @@ | |||
2526 | "type": "library", | 2569 | "type": "library", |
2527 | "extra": { | 2570 | "extra": { |
2528 | "branch-alias": { | 2571 | "branch-alias": { |
2529 | "dev-master": "1.11-dev" | 2572 | "dev-master": "1.18-dev" |
2573 | }, | ||
2574 | "thanks": { | ||
2575 | "name": "symfony/polyfill", | ||
2576 | "url": "https://github.com/symfony/polyfill" | ||
2530 | } | 2577 | } |
2531 | }, | 2578 | }, |
2532 | "autoload": { | 2579 | "autoload": { |
@@ -2543,160 +2590,60 @@ | |||
2543 | ], | 2590 | ], |
2544 | "authors": [ | 2591 | "authors": [ |
2545 | { | 2592 | { |
2546 | "name": "Symfony Community", | ||
2547 | "homepage": "https://symfony.com/contributors" | ||
2548 | }, | ||
2549 | { | ||
2550 | "name": "Gert de Pagter", | 2593 | "name": "Gert de Pagter", |
2551 | "email": "BackEndTea@gmail.com" | 2594 | "email": "BackEndTea@gmail.com" |
2552 | } | ||
2553 | ], | ||
2554 | "description": "Symfony polyfill for ctype functions", | ||
2555 | "homepage": "https://symfony.com", | ||
2556 | "keywords": [ | ||
2557 | "compatibility", | ||
2558 | "ctype", | ||
2559 | "polyfill", | ||
2560 | "portable" | ||
2561 | ], | ||
2562 | "time": "2019-02-06T07:57:58+00:00" | ||
2563 | }, | ||
2564 | { | ||
2565 | "name": "symfony/polyfill-mbstring", | ||
2566 | "version": "v1.11.0", | ||
2567 | "source": { | ||
2568 | "type": "git", | ||
2569 | "url": "https://github.com/symfony/polyfill-mbstring.git", | ||
2570 | "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" | ||
2571 | }, | ||
2572 | "dist": { | ||
2573 | "type": "zip", | ||
2574 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", | ||
2575 | "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", | ||
2576 | "shasum": "" | ||
2577 | }, | ||
2578 | "require": { | ||
2579 | "php": ">=5.3.3" | ||
2580 | }, | ||
2581 | "suggest": { | ||
2582 | "ext-mbstring": "For best performance" | ||
2583 | }, | ||
2584 | "type": "library", | ||
2585 | "extra": { | ||
2586 | "branch-alias": { | ||
2587 | "dev-master": "1.11-dev" | ||
2588 | } | ||
2589 | }, | ||
2590 | "autoload": { | ||
2591 | "psr-4": { | ||
2592 | "Symfony\\Polyfill\\Mbstring\\": "" | ||
2593 | }, | ||
2594 | "files": [ | ||
2595 | "bootstrap.php" | ||
2596 | ] | ||
2597 | }, | ||
2598 | "notification-url": "https://packagist.org/downloads/", | ||
2599 | "license": [ | ||
2600 | "MIT" | ||
2601 | ], | ||
2602 | "authors": [ | ||
2603 | { | ||
2604 | "name": "Nicolas Grekas", | ||
2605 | "email": "p@tchwork.com" | ||
2606 | }, | 2595 | }, |
2607 | { | 2596 | { |
2608 | "name": "Symfony Community", | 2597 | "name": "Symfony Community", |
2609 | "homepage": "https://symfony.com/contributors" | 2598 | "homepage": "https://symfony.com/contributors" |
2610 | } | 2599 | } |
2611 | ], | 2600 | ], |
2612 | "description": "Symfony polyfill for the Mbstring extension", | 2601 | "description": "Symfony polyfill for ctype functions", |
2613 | "homepage": "https://symfony.com", | 2602 | "homepage": "https://symfony.com", |
2614 | "keywords": [ | 2603 | "keywords": [ |
2615 | "compatibility", | 2604 | "compatibility", |
2616 | "mbstring", | 2605 | "ctype", |
2617 | "polyfill", | 2606 | "polyfill", |
2618 | "portable", | 2607 | "portable" |
2619 | "shim" | ||
2620 | ], | 2608 | ], |
2621 | "time": "2019-02-06T07:57:58+00:00" | 2609 | "support": { |
2622 | }, | 2610 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.18.0" |
2623 | { | ||
2624 | "name": "symfony/yaml", | ||
2625 | "version": "v4.3.3", | ||
2626 | "source": { | ||
2627 | "type": "git", | ||
2628 | "url": "https://github.com/symfony/yaml.git", | ||
2629 | "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6" | ||
2630 | }, | ||
2631 | "dist": { | ||
2632 | "type": "zip", | ||
2633 | "url": "https://api.github.com/repos/symfony/yaml/zipball/34d29c2acd1ad65688f58452fd48a46bd996d5a6", | ||
2634 | "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6", | ||
2635 | "shasum": "" | ||
2636 | }, | ||
2637 | "require": { | ||
2638 | "php": "^7.1.3", | ||
2639 | "symfony/polyfill-ctype": "~1.8" | ||
2640 | }, | ||
2641 | "conflict": { | ||
2642 | "symfony/console": "<3.4" | ||
2643 | }, | ||
2644 | "require-dev": { | ||
2645 | "symfony/console": "~3.4|~4.0" | ||
2646 | }, | 2611 | }, |
2647 | "suggest": { | 2612 | "funding": [ |
2648 | "symfony/console": "For validating YAML files using the lint command" | 2613 | { |
2649 | }, | 2614 | "url": "https://symfony.com/sponsor", |
2650 | "type": "library", | 2615 | "type": "custom" |
2651 | "extra": { | ||
2652 | "branch-alias": { | ||
2653 | "dev-master": "4.3-dev" | ||
2654 | } | ||
2655 | }, | ||
2656 | "autoload": { | ||
2657 | "psr-4": { | ||
2658 | "Symfony\\Component\\Yaml\\": "" | ||
2659 | }, | 2616 | }, |
2660 | "exclude-from-classmap": [ | ||
2661 | "/Tests/" | ||
2662 | ] | ||
2663 | }, | ||
2664 | "notification-url": "https://packagist.org/downloads/", | ||
2665 | "license": [ | ||
2666 | "MIT" | ||
2667 | ], | ||
2668 | "authors": [ | ||
2669 | { | 2617 | { |
2670 | "name": "Fabien Potencier", | 2618 | "url": "https://github.com/fabpot", |
2671 | "email": "fabien@symfony.com" | 2619 | "type": "github" |
2672 | }, | 2620 | }, |
2673 | { | 2621 | { |
2674 | "name": "Symfony Community", | 2622 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", |
2675 | "homepage": "https://symfony.com/contributors" | 2623 | "type": "tidelift" |
2676 | } | 2624 | } |
2677 | ], | 2625 | ], |
2678 | "description": "Symfony Yaml Component", | 2626 | "time": "2020-07-14T12:35:20+00:00" |
2679 | "homepage": "https://symfony.com", | ||
2680 | "time": "2019-07-24T14:47:54+00:00" | ||
2681 | }, | 2627 | }, |
2682 | { | 2628 | { |
2683 | "name": "theseer/fdomdocument", | 2629 | "name": "theseer/tokenizer", |
2684 | "version": "1.6.6", | 2630 | "version": "1.1.3", |
2685 | "source": { | 2631 | "source": { |
2686 | "type": "git", | 2632 | "type": "git", |
2687 | "url": "https://github.com/theseer/fDOMDocument.git", | 2633 | "url": "https://github.com/theseer/tokenizer.git", |
2688 | "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca" | 2634 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" |
2689 | }, | 2635 | }, |
2690 | "dist": { | 2636 | "dist": { |
2691 | "type": "zip", | 2637 | "type": "zip", |
2692 | "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/6e8203e40a32a9c770bcb62fe37e68b948da6dca", | 2638 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", |
2693 | "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca", | 2639 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", |
2694 | "shasum": "" | 2640 | "shasum": "" |
2695 | }, | 2641 | }, |
2696 | "require": { | 2642 | "require": { |
2697 | "ext-dom": "*", | 2643 | "ext-dom": "*", |
2698 | "lib-libxml": "*", | 2644 | "ext-tokenizer": "*", |
2699 | "php": ">=5.3.3" | 2645 | "ext-xmlwriter": "*", |
2646 | "php": "^7.0" | ||
2700 | }, | 2647 | }, |
2701 | "type": "library", | 2648 | "type": "library", |
2702 | "autoload": { | 2649 | "autoload": { |
@@ -2712,41 +2659,42 @@ | |||
2712 | { | 2659 | { |
2713 | "name": "Arne Blankerts", | 2660 | "name": "Arne Blankerts", |
2714 | "email": "arne@blankerts.de", | 2661 | "email": "arne@blankerts.de", |
2715 | "role": "lead" | 2662 | "role": "Developer" |
2716 | } | 2663 | } |
2717 | ], | 2664 | ], |
2718 | "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", | 2665 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", |
2719 | "homepage": "https://github.com/theseer/fDOMDocument", | 2666 | "support": { |
2720 | "time": "2017-06-30T11:53:12+00:00" | 2667 | "issues": "https://github.com/theseer/tokenizer/issues", |
2668 | "source": "https://github.com/theseer/tokenizer/tree/master" | ||
2669 | }, | ||
2670 | "time": "2019-06-13T22:48:21+00:00" | ||
2721 | }, | 2671 | }, |
2722 | { | 2672 | { |
2723 | "name": "webmozart/assert", | 2673 | "name": "webmozart/assert", |
2724 | "version": "1.4.0", | 2674 | "version": "1.9.1", |
2725 | "source": { | 2675 | "source": { |
2726 | "type": "git", | 2676 | "type": "git", |
2727 | "url": "https://github.com/webmozart/assert.git", | 2677 | "url": "https://github.com/webmozart/assert.git", |
2728 | "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" | 2678 | "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" |
2729 | }, | 2679 | }, |
2730 | "dist": { | 2680 | "dist": { |
2731 | "type": "zip", | 2681 | "type": "zip", |
2732 | "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", | 2682 | "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", |
2733 | "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", | 2683 | "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", |
2734 | "shasum": "" | 2684 | "shasum": "" |
2735 | }, | 2685 | }, |
2736 | "require": { | 2686 | "require": { |
2737 | "php": "^5.3.3 || ^7.0", | 2687 | "php": "^5.3.3 || ^7.0 || ^8.0", |
2738 | "symfony/polyfill-ctype": "^1.8" | 2688 | "symfony/polyfill-ctype": "^1.8" |
2739 | }, | 2689 | }, |
2690 | "conflict": { | ||
2691 | "phpstan/phpstan": "<0.12.20", | ||
2692 | "vimeo/psalm": "<3.9.1" | ||
2693 | }, | ||
2740 | "require-dev": { | 2694 | "require-dev": { |
2741 | "phpunit/phpunit": "^4.6", | 2695 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" |
2742 | "sebastian/version": "^1.0.1" | ||
2743 | }, | 2696 | }, |
2744 | "type": "library", | 2697 | "type": "library", |
2745 | "extra": { | ||
2746 | "branch-alias": { | ||
2747 | "dev-master": "1.3-dev" | ||
2748 | } | ||
2749 | }, | ||
2750 | "autoload": { | 2698 | "autoload": { |
2751 | "psr-4": { | 2699 | "psr-4": { |
2752 | "Webmozart\\Assert\\": "src/" | 2700 | "Webmozart\\Assert\\": "src/" |
@@ -2768,7 +2716,11 @@ | |||
2768 | "check", | 2716 | "check", |
2769 | "validate" | 2717 | "validate" |
2770 | ], | 2718 | ], |
2771 | "time": "2018-12-25T11:19:39+00:00" | 2719 | "support": { |
2720 | "issues": "https://github.com/webmozart/assert/issues", | ||
2721 | "source": "https://github.com/webmozart/assert/tree/master" | ||
2722 | }, | ||
2723 | "time": "2020-07-08T17:02:28+00:00" | ||
2772 | } | 2724 | } |
2773 | ], | 2725 | ], |
2774 | "aliases": [], | 2726 | "aliases": [], |
@@ -2787,5 +2739,6 @@ | |||
2787 | "platform-dev": [], | 2739 | "platform-dev": [], |
2788 | "platform-overrides": { | 2740 | "platform-overrides": { |
2789 | "php": "7.1.29" | 2741 | "php": "7.1.29" |
2790 | } | 2742 | }, |
2743 | "plugin-api-version": "2.0.0" | ||
2791 | } | 2744 | } |
diff --git a/doc/md/3rd-party-libraries.md b/doc/md/3rd-party-libraries.md deleted file mode 100644 index 7e7dd334..00000000 --- a/doc/md/3rd-party-libraries.md +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | ## CSS | ||
2 | |||
3 | - Yahoo UI [CSS Reset](http://yuilibrary.com/yui/docs/cssreset/) - standardize cross-browser rendering | ||
4 | |||
5 | ## Javascript | ||
6 | |||
7 | - [Awesomeplete](https://leaverou.github.io/awesomplete/) ([GitHub](https://github.com/LeaVerou/awesomplete)) - autocompletion in input forms | ||
8 | - [bLazy](http://dinbror.dk/blazy/) ([GitHub](https://github.com/dinbror/blazy)) - lazy loading for thumbnails | ||
9 | - [qr.js](http://neocotic.com/qr.js/) ([GitHub](https://github.com/neocotic/qr.js)) - QR code generation | ||
10 | |||
11 | ## PHP | ||
12 | |||
13 | - [RainTPL](https://github.com/rainphp/raintpl) - HTML templating for PHP | ||
14 | |||
15 | ### Composer | ||
16 | |||
17 | Library | Usage | ||
18 | ---|--- | ||
19 | [`shaarli/netscape-bookmark-parser`](https://packagist.org/packages/shaarli/netscape-bookmark-parser) | Import bookmarks from Netscape files | ||
20 | [`erusev/parsedown`](https://packagist.org/packages/erusev/parsedown) | Parse MarkDown syntax for the MarkDown plugin | ||
21 | [`slim/slim`](https://packagist.org/packages/slim/slim) | Handle routes and middleware for the REST API | ||
diff --git a/doc/md/Backup-and-restore.md b/doc/md/Backup-and-restore.md new file mode 100644 index 00000000..e7e2775c --- /dev/null +++ b/doc/md/Backup-and-restore.md | |||
@@ -0,0 +1,11 @@ | |||
1 | ## Backup and restore | ||
2 | |||
3 | All data and [configuration](Shaarli-configuration.md) is kept in the `data` directory. Backup this directory: | ||
4 | |||
5 | ```bash | ||
6 | rsync -avzP my.server.com:/var/www/shaarli.mydomain.org/data ~/backups/shaarli-data-$(date +%Y-%m-%d_%H%M) | ||
7 | ``` | ||
8 | |||
9 | It is strongly recommended to do periodic, automatic backups to a seperate machine. You can automate the command above using a cron job or full-featured backup solutions such as [rsnapshot](https://rsnapshot.org/) | ||
10 | |||
11 | To restore a backup, simply put back the `data/` directory in place, owerwriting any existing files. \ No newline at end of file | ||
diff --git a/doc/md/Browsing-and-searching.md b/doc/md/Browsing-and-searching.md deleted file mode 100644 index 16c69855..00000000 --- a/doc/md/Browsing-and-searching.md +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | ## Plain text search | ||
2 | |||
3 | Use the `Search text` field to search in _any_ of the fields of all links (Title, URL, Description...) | ||
4 | |||
5 | **Exclude text/tags:** Use the `-` operator before a word or tag (example `-uninteresting`) to prevent entries containing (or tagged) `uninteresting` from showing up in the search results. | ||
6 | |||
7 | **Exact text search:** Use double-quotes (example `"exact search"`) to search for the exact expression. | ||
8 | |||
9 | Both exclude patterns and exact searches can be combined with normal searches (example `"exact search" term otherterm -notthis "very exact" stuff -notagain`) | ||
10 | |||
11 | ## Tags search | ||
12 | |||
13 | Use the `Filter by tags` field to restrict displayed links to entries tagged with one or multiple tags (use space to separate tags). | ||
14 | |||
15 | **Hidden tags:** Tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in. | ||
16 | |||
17 | ### Tag cloud | ||
18 | |||
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. | ||
25 | |||
26 | ## Filtering RSS feeds/Picture wall | ||
27 | |||
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-and-related-software.md index c66d1c70..53a7555e 100644 --- a/doc/md/Community-&-Related-software.md +++ b/doc/md/Community-and-related-software.md | |||
@@ -1,64 +1,87 @@ | |||
1 | # Community & related software | ||
2 | |||
1 | _Unofficial but related work on Shaarli. If you maintain one of these, | 3 | _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._ | 4 | please get in touch with us to help us find a way to adapt your work to our fork._ |
3 | 5 | ||
4 | ## Related software | ||
5 | 6 | ||
7 | ## Related software | ||
6 | 8 | ||
7 | ### REST API clients | 9 | ### REST API clients |
8 | See [REST API](REST-API) for a list of official and community clients. | 10 | See [REST API](REST-API) for a list of official and community clients. |
9 | 11 | ||
10 | 12 | ||
11 | ### Third party plugins | 13 | ### Third party plugins |
12 | - [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown. | 14 | |
13 | - [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter. | 15 | - [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a Shaare to avoid any loss in case of crash or unexpected shutdown. |
14 | - [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli. | 16 | - [code-coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter. |
15 | - [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli. | 17 | - [custom-css](https://github.com/immanuelfodor/shaarli-custom-css) by [@immanuelfodor](https://github.com/immanuelfodor) - Customize the look and feel of the UI with custom CSS rules |
16 | - [twemoji](https://github.com/NerosTie/twemoji) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli (Twemoji version) | 18 | - [disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli. |
19 | - [emojione](https://github.com/immanuelfodor/emojione) by [@immanuelfodor](https://github.com/immanuelfodor) - Resurrected fork of the original emojione project | ||
20 | - [favicons](https://github.com/trailjeep/shaarli-favicons) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to add favicon/filetype icons to Shaares. | ||
17 | - [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support | 21 | - [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support |
18 | - [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli. | 22 | - [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. | 23 | - [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 Shaare. |
20 | - [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related links based on the number of identical tags. | 24 | - [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related Shaares based on the number of identical tags. |
21 | - [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks. | 25 | - [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 Shaare. |
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. | 26 | - [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. | 27 | - [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your Shaares from Shaarli |
28 | - [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks. | ||
25 | - [urlextern](https://github.com/trailjeep/shaarli-urlextern) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to open external links in a new tab/window. | 29 | - [urlextern](https://github.com/trailjeep/shaarli-urlextern) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to open external links in a new tab/window. |
26 | - [favicons](https://github.com/trailjeep/shaarli-favicons) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to add favicon/filetype icons to links. | 30 | - [webhooks](https://gitlab.com/flow.gunso/shaarli-webhooks) by [@flow.gunso](https://gitlab.com/flow.gunso) - Shaarli plugin that enables user-defined callback URL, i.e. webhooks, for specific Shaarli events (link saving, deletion...) |
31 | |||
27 | 32 | ||
28 | ### Third-party themes | 33 | ### Third-party themes |
34 | |||
29 | See [Theming](Theming) for a list of community-contributed themes, and an installation guide. | 35 | See [Theming](Theming) for a list of community-contributed themes, and an installation guide. |
30 | 36 | ||
31 | 37 | ||
32 | ### Integration with other platforms | 38 | ### Integration with other platforms |
39 | |||
33 | - [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 | 40 | - [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 |
34 | - [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar | 41 | - [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli Shaares on the sidebar |
35 | - [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle | 42 | - [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle |
36 | - [Shaarli app for Cloudron](https://git.cloudron.io/cloudron/shaarli-app) - Effortlessly run Shaarli with the help of [Cloudron](https://cloudron.io/) [](https://cloudron.io/button.html?app=com.github.shaarli) | 43 | - [Shaarli app for Cloudron](https://git.cloudron.io/cloudron/shaarli-app) - Effortlessly run Shaarli with the help of [Cloudron](https://cloudron.io/) [](https://cloudron.io/button.html?app=com.github.shaarli) |
37 | - [Shaarli_ynh](https://github.com/YunoHost-Apps/shaarli_ynh) - Shaarli is available as a [Yunohost](https://yunohost.org) app [](https://install-app.yunohost.org/?app=shaarli) | 44 | - [Shaarli_ynh](https://github.com/YunoHost-Apps/shaarli_ynh) - Shaarli is available as a [Yunohost](https://yunohost.org) app [](https://install-app.yunohost.org/?app=shaarli) |
45 | - [pelican](https://blog.getpelican.com) static blog generator plugin to auto-post articles on a Shaarli instance: [shaarli_poster](https://github.com/getpelican/pelican-plugins/tree/master/shaarli_poster) | ||
46 | |||
38 | 47 | ||
39 | ### Mobile Apps | 48 | ### Mobile Apps |
49 | |||
40 | - [ShaarliOS](https://github.com/mro/ShaarliOS) - Apple iOS share extension. | 50 | - [ShaarliOS](https://github.com/mro/ShaarliOS) - Apple iOS share extension. |
41 | - [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider | 51 | - [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider |
42 | - [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add links directly into your Shaarli | 52 | - [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add Shaares directly into your Shaarli |
43 | - [Stakali for Android](https://stakali.toneiv.eu) - Stakali is a personal bookmark manager which synchronizes with Shaarli | 53 | - [Stakali for Android](https://stakali.toneiv.eu) - Stakali is a personal bookmark manager which synchronizes with Shaarli |
44 | 54 | ||
55 | |||
56 | ### Desktop Apps | ||
57 | |||
58 | - [Ulauncher Extension](https://github.com/sebw/ulauncher-shaarli) - Ulauncher is an an application launcher for Linux, this extension allows research in your Shaarli | ||
59 | |||
60 | |||
45 | ### Browser addons | 61 | ### Browser addons |
62 | |||
46 | - [Shaarli Firefox Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli. | 63 | - [Shaarli Firefox Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli. |
47 | - [Shaarli Chrome Extension](https://github.com/octplane/Shiny-Shaarli) - toolbar button to share your current tab with Shaarli. | 64 | - [Shaarli Chrome Extension](https://github.com/octplane/Shiny-Shaarli) - toolbar button to share your current tab with Shaarli. |
48 | 65 | ||
66 | |||
49 | ### Server apps | 67 | ### Server apps |
68 | |||
50 | - [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content | 69 | - [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content |
51 | - [shaarli-river](https://github.com/mknexen/shaarli-river) - An aggregator for shaarlis with many features | 70 | - [shaarli-river](https://github.com/mknexen/shaarli-river) - An aggregator for shaarlis with many features |
52 | - [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among French shaarliers: [shaarli.fr](http://shaarli.fr/)) | 71 | - [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features |
53 | - [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis | 72 | - [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis |
54 | - [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli | 73 | - [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli |
55 | - [Self dead link](https://framagit.org/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://framagit.org/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming). | 74 | - [Self dead link](https://framagit.org/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://framagit.org/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming). |
56 | - [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. | 75 | - [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. |
57 | 76 | ||
77 | |||
58 | ## Alternatives to Shaarli | 78 | ## Alternatives to Shaarli |
79 | |||
59 | See [awesome-selfhosted: bookmarks & link sharing](https://github.com/Kickball/awesome-selfhosted/#bookmarks--link-sharing). | 80 | See [awesome-selfhosted: bookmarks & link sharing](https://github.com/Kickball/awesome-selfhosted/#bookmarks--link-sharing). |
60 | 81 | ||
82 | |||
61 | ## Community | 83 | ## Community |
84 | |||
62 | - [Liens en vrac de sebsauvage](http://sebsauvage.net/links/) - the original Shaarli | 85 | - [Liens en vrac de sebsauvage](http://sebsauvage.net/links/) - the original Shaarli |
63 | - [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html) | 86 | - [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html) |
64 | - [A list of working Shaarli aggregators](https://raw.githubusercontent.com/Oros42/find_shaarlis/master/annuaires.json) | 87 | - [A list of working Shaarli aggregators](https://raw.githubusercontent.com/Oros42/find_shaarlis/master/annuaires.json) |
@@ -69,8 +92,17 @@ See [awesome-selfhosted: bookmarks & link sharing](https://github.com/Kickball/a | |||
69 | - [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) | 92 | - [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) |
70 | - [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni) | 93 | - [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni) |
71 | 94 | ||
95 | |||
72 | ### Articles and social media discussions | 96 | ### Articles and social media discussions |
73 | - 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176 | 97 | - 2020-04-05 - Hacker News - [Self-hosted instance of Shaarli - it is simple, fast and reliable](https://news.ycombinator.com/item?id=22780219) |
98 | - 2016-10-10 - Framasoft - [MyFrama : vos favoris partout, avec vous, rien qu’à vous !](https://framablog.org/2016/10/10/myframa-vos-favoris-et-framasofteries-partout-avec-vous-rien-qua-vous/) | ||
99 | - 2016-09-22 - Hacker News - [Shaarli – Personal, minimalist, database-free, bookmarking service (github.com)](https://news.ycombinator.com/item?id=12552176) | ||
74 | - 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/) | 100 | - 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/) |
75 | - 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366 | 101 | - 2015-06-22 - Hacker News - [Shaarli: Self-hosted del.icio.us alternative (sebsauvage.net)](https://news.ycombinator.com/item?id=9755366) |
76 | - 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/) | 102 | - 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/) |
103 | - 2014-10-15 - OpenSource.com - [Five open source alternatives to popular web apps](https://opensource.com/life/14/10/five-open-source-alternatives-popular-web-apps) | ||
104 | |||
105 | It also appears in the following recommendation lists: | ||
106 | - [AlternativeTo](https://alternativeto.net/software/shaarli/) | ||
107 | - [FramaLibre](https://framalibre.org/content/shaarli) | ||
108 | - [Project Awesome: Selfhosted Bookmarks and Link Sharing](https://project-awesome.org/Kickball/awesome-selfhosted) | ||
diff --git a/doc/md/Continuous-integration-tools.md b/doc/md/Continuous-integration-tools.md deleted file mode 100644 index 4ca6bdc7..00000000 --- a/doc/md/Continuous-integration-tools.md +++ /dev/null | |||
@@ -1,29 +0,0 @@ | |||
1 | ## Local development | ||
2 | A [`Makefile`](https://github.com/shaarli/Shaarli/blob/master/Makefile) is available to perform project-related operations: | ||
3 | |||
4 | - Documentation - generate a local HTML copy of the GitHub wiki | ||
5 | - [Static analysis](Static-analysis) - check that the code is compliant to PHP conventions | ||
6 | - [Unit tests](Unit-tests) - ensure there are no regressions introduced by new commits | ||
7 | |||
8 | ## Automatic builds | ||
9 | [Travis CI](http://docs.travis-ci.com/) is a Continuous Integration build server, that runs a build: | ||
10 | |||
11 | - each time a commit is merged to the mainline (`master` branch) | ||
12 | - each time a Pull Request is submitted or updated | ||
13 | |||
14 | A build is composed of several jobs: one for each supported PHP version (see [Server requirements](Server requirements)). | ||
15 | |||
16 | Each build job: | ||
17 | |||
18 | - updates Composer | ||
19 | - installs 3rd-party test dependencies with Composer | ||
20 | - runs [Unit tests](Unit-tests) | ||
21 | - runs ESLint check | ||
22 | |||
23 | After all jobs have finished, Travis returns the results to GitHub: | ||
24 | |||
25 | - a status icon represents the result for the `master` branch: [](https://travis-ci.org/shaarli/Shaarli) | ||
26 | - Pull Requests are updated with the Travis result | ||
27 | - Green: all tests have passed | ||
28 | - Red: some tests failed | ||
29 | - Orange: tests are pending | ||
diff --git a/doc/md/Development-guidelines.md b/doc/md/Development-guidelines.md deleted file mode 100644 index 46b7c6f8..00000000 --- a/doc/md/Development-guidelines.md +++ /dev/null | |||
@@ -1,13 +0,0 @@ | |||
1 | ## Development guidelines | ||
2 | |||
3 | Please have a look at the following pages: | ||
4 | |||
5 | - [Contributing to Shaarli](https://github.com/shaarli/Shaarli/tree/master/CONTRIBUTING.md) | ||
6 | - [Static analysis](Static-analysis) - patches should try to stick to the | ||
7 | [PHP Standard Recommendations](http://www.php-fig.org/psr/) (PSR), especially: | ||
8 | - [PSR-1](http://www.php-fig.org/psr/psr-1/) - Basic Coding Standard | ||
9 | - [PSR-2](http://www.php-fig.org/psr/psr-2/) - Coding Style Guide | ||
10 | - [Unit tests](Unit-tests) | ||
11 | - Javascript linting - Shaarli uses [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript). | ||
12 | Run `make eslint` to check JS style. | ||
13 | - [GnuPG signature](GnuPG-signature) for tags/releases | ||
diff --git a/doc/md/Directory-structure.md b/doc/md/Directory-structure.md deleted file mode 100644 index c0b49393..00000000 --- a/doc/md/Directory-structure.md +++ /dev/null | |||
@@ -1,54 +0,0 @@ | |||
1 | ## Directory structure | ||
2 | |||
3 | Here is the directory structure of Shaarli and the purpose of the different files: | ||
4 | |||
5 | ```bash | ||
6 | index.php # Main program | ||
7 | application/ # Shaarli classes | ||
8 | ├── LinkDB.php | ||
9 | |||
10 | ... | ||
11 | |||
12 | └── Utils.php | ||
13 | tests/ # Shaarli unitary & functional tests | ||
14 | ├── LinkDBTest.php | ||
15 | |||
16 | ... | ||
17 | |||
18 | ├── utils # utilities to ease testing | ||
19 | │ └── ReferenceLinkDB.php | ||
20 | └── UtilsTest.php | ||
21 | assets/ | ||
22 | ├── common/ # Assets shared by multiple themes | ||
23 | ├── ... | ||
24 | ├── default/ # Assets for the default template, before compilation | ||
25 | ├── fonts/ # Font files | ||
26 | ├── img/ # Images used by the default theme | ||
27 | ├── js/ # JavaScript files in ES6 syntax | ||
28 | ├── scss/ # SASS files | ||
29 | └── vintage/ # Assets for the vintage template, before compilation | ||
30 | └── ... | ||
31 | COPYING # Shaarli license | ||
32 | inc/ # static assets and 3rd party libraries | ||
33 | └── rain.tpl.class.php # RainTPL templating library | ||
34 | images/ # Images and icons used in Shaarli | ||
35 | data/ # data storage: bookmark database, configuration, logs, banlist... | ||
36 | ├── config.json.php # Shaarli configuration (login, password, timezone, title...) | ||
37 | ├── datastore.php # Your link database (compressed). | ||
38 | ├── ipban.php # IP address ban system data | ||
39 | ├── lastupdatecheck.txt # Update check timestamp file | ||
40 | └── log.txt # login/IPban log. | ||
41 | tpl/ # RainTPL templates for Shaarli. They are used to build the pages. | ||
42 | ├── default/ # Default Shaarli theme | ||
43 | ├── fonts/ # Font files | ||
44 | ├── img/ # Images | ||
45 | ├── js/ # JavaScript files compiled by Babel and compatible with all browsers | ||
46 | ├── css/ # CSS files compiled with SASS | ||
47 | └── vintage/ # Legacy Shaarli theme | ||
48 | └── ... | ||
49 | cache/ # thumbnails cache | ||
50 | # This directory is automatically created. You can erase it anytime you want. | ||
51 | tmp/ # Temporary directory for compiled RainTPL templates. | ||
52 | # This directory is automatically created. You can erase it anytime you want. | ||
53 | vendor/ # Third-party dependencies. This directory is created by Composer | ||
54 | ``` | ||
diff --git a/doc/md/Docker.md b/doc/md/Docker.md new file mode 100644 index 00000000..c152fe92 --- /dev/null +++ b/doc/md/Docker.md | |||
@@ -0,0 +1,227 @@ | |||
1 | # Docker | ||
2 | |||
3 | [Docker](https://docs.docker.com/get-started/overview/) is an open platform for developing, shipping, and running applications | ||
4 | |||
5 | ## Install Docker | ||
6 | |||
7 | Install [Docker](https://docs.docker.com/engine/install/), by following the instructions relevant to your OS / distribution, and start the service. For example on [Debian](https://docs.docker.com/engine/install/debian/): | ||
8 | |||
9 | ```bash | ||
10 | # update your package lists | ||
11 | sudo apt update | ||
12 | # remove old versions | ||
13 | sudo apt-get remove docker docker-engine docker.io containerd runc | ||
14 | # install requirements | ||
15 | sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common | ||
16 | # add docker's GPG signing key | ||
17 | curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - | ||
18 | # add the repository | ||
19 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | ||
20 | # install docker engine | ||
21 | sudo apt-get update | ||
22 | sudo apt-get install docker-ce docker-ce-cli containerd.io | ||
23 | # Start and enable Docker service | ||
24 | sudo systemctl enable docker && sudo systemctl start docker | ||
25 | # verify that Docker is properly configured | ||
26 | sudo docker run hello-world | ||
27 | ``` | ||
28 | |||
29 | In order to run Docker commands as a non-root user, you must add the `docker` group to this user: | ||
30 | |||
31 | ```bash | ||
32 | # Add docker group as secondary group | ||
33 | sudo usermod -aG docker your-user | ||
34 | # Reboot or logout | ||
35 | # Then verify that Docker is properly configured, as "your-user" | ||
36 | docker run hello-world | ||
37 | ``` | ||
38 | |||
39 | ## Get and run a Shaarli image | ||
40 | |||
41 | Shaarli images are available on [DockerHub](https://hub.docker.com/r/shaarli/shaarli/) `shaarli/shaarli`: | ||
42 | |||
43 | - `latest`: latest branch (last release) | ||
44 | - `stable`: stable branch (last release in previous major version) | ||
45 | - `master`: master branch (development branch) | ||
46 | |||
47 | These images are built automatically on DockerHub and rely on: | ||
48 | |||
49 | - [Alpine Linux](https://www.alpinelinux.org/) | ||
50 | - [PHP7-FPM](http://php-fpm.org/) | ||
51 | - [Nginx](http://nginx.org/) | ||
52 | |||
53 | Additional Dockerfiles 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/). | ||
54 | |||
55 | Here is an example of how to run Shaarli latest image using Docker: | ||
56 | |||
57 | ```bash | ||
58 | # download the 'latest' image from dockerhub | ||
59 | docker pull shaarli/shaarli | ||
60 | |||
61 | # create persistent data volumes/directories on the host | ||
62 | docker volume create shaarli-data | ||
63 | docker volume create shaarli-cache | ||
64 | |||
65 | # create a new container using the Shaarli image | ||
66 | # --detach: run the container in background | ||
67 | # --name: name of the created container/instance | ||
68 | # --publish: map the host's :8000 port to the container's :80 port | ||
69 | # --rm: automatically remove the container when it exits | ||
70 | # --volume: mount persistent volumes in the container ($volume_name:$volume_mountpoint) | ||
71 | docker run --detach \ | ||
72 | --name myshaarli \ | ||
73 | --publish 8000:80 \ | ||
74 | --rm \ | ||
75 | --volume shaarli-data:/var/www/shaarli/data \ | ||
76 | --volume shaarli-cache:/var/www/shaarli/cache \ | ||
77 | shaarli/shaarli:latest | ||
78 | |||
79 | # verify that the container is running | ||
80 | docker ps | grep myshaarli | ||
81 | |||
82 | # to completely remove the container | ||
83 | docker stop myshaarli # stop the running container | ||
84 | docker ps | grep myshaarli # verify the container is no longer running | ||
85 | docker ps -a | grep myshaarli # verify the container is stopped | ||
86 | docker rm myshaarli # destroy the container | ||
87 | docker ps -a | grep myshaarli # verify th container has been destroyed | ||
88 | |||
89 | ``` | ||
90 | |||
91 | After running `docker run` command, your Shaarli instance should be available on the host machine at [localhost:8000](http://localhost:8000). In order to access your instance through a reverse proxy, we recommend using our [Docker Compose](#docker-compose) build. | ||
92 | |||
93 | ## Docker Compose | ||
94 | |||
95 | A [Compose file](https://docs.docker.com/compose/compose-file/) is a common format for defining and running multi-container Docker applications. | ||
96 | |||
97 | A `docker-compose.yml` file can be used to run a persistent/autostarted shaarli service using [Docker Compose](https://docs.docker.com/compose/) or in a [Docker stack](https://docs.docker.com/engine/reference/commandline/stack_deploy/). | ||
98 | |||
99 | Shaarli provides configuration file for Docker Compose, that will setup a Shaarli instance, a [Træfik](https://containo.us/traefik/) instance (reverse proxy) with [Let's Encrypt](https://letsencrypt.org/) certificates, a Docker network, and volumes for Shaarli data and Træfik TLS configuration and certificates. | ||
100 | |||
101 | Download docker-compose from the [release page](https://docs.docker.com/compose/install/): | ||
102 | |||
103 | ```bash | ||
104 | $ sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose | ||
105 | $ sudo chmod +x /usr/local/bin/docker-compose | ||
106 | ``` | ||
107 | |||
108 | To run Shaarli container and its reverse proxy, you can execute the following commands: | ||
109 | |||
110 | ```bash | ||
111 | # create a new directory to store the configuration: | ||
112 | $ mkdir shaarli && cd shaarli | ||
113 | # Download the latest version of Shaarli's docker-compose.yml | ||
114 | $ curl -L https://raw.githubusercontent.com/shaarli/Shaarli/latest/docker-compose.yml -o docker-compose.yml | ||
115 | # Create the .env file and fill in your VPS and domain information | ||
116 | # (replace <MY_SHAARLI_DOMAIN> and <MY_CONTACT_EMAIL> with your actual information) | ||
117 | $ echo 'SHAARLI_VIRTUAL_HOST=shaarli.mydomain.org' > .env | ||
118 | $ echo 'SHAARLI_LETSENCRYPT_EMAIL=admin@mydomain.org' >> .env | ||
119 | # Pull the Docker images | ||
120 | $ docker-compose pull | ||
121 | # Run! | ||
122 | $ docker-compose up -d | ||
123 | ``` | ||
124 | |||
125 | After a few seconds, you should be able to access your Shaarli instance at [https://shaarli.mydomain.org](https://shaarli.mydomain.org) (replace your own domain name). | ||
126 | |||
127 | ## Running dockerized Shaarli as a systemd service | ||
128 | |||
129 | It is possible to start a dockerized Shaarli instance as a systemd service (systemd is the service management tool on several distributions). After installing Docker, use the following steps to run your shaarli container Shaarli to run on system start. | ||
130 | |||
131 | As root, create `/etc/systemd/system/docker.shaarli.service`: | ||
132 | |||
133 | ```ini | ||
134 | [Unit] | ||
135 | Description=Shaarli Bookmark Manager Container | ||
136 | After=docker.service | ||
137 | Requires=docker.service | ||
138 | |||
139 | |||
140 | [Service] | ||
141 | Restart=always | ||
142 | |||
143 | # Put any environment you want in an included file, like $host- or $domainname in this example | ||
144 | EnvironmentFile=/etc/sysconfig/box-environment | ||
145 | |||
146 | # It's just an example.. | ||
147 | ExecStart=/usr/bin/docker run \ | ||
148 | -p 28010:80 \ | ||
149 | --name ${hostname}-shaarli \ | ||
150 | --hostname shaarli.${domainname} \ | ||
151 | -v /srv/docker-volumes-local/shaarli-data:/var/www/shaarli/data:rw \ | ||
152 | -v /etc/localtime:/etc/localtime:ro \ | ||
153 | shaarli/shaarli:latest | ||
154 | |||
155 | ExecStop=/usr/bin/docker rm -f ${hostname}-shaarli | ||
156 | |||
157 | [Install] | ||
158 | WantedBy=multi-user.target | ||
159 | ``` | ||
160 | |||
161 | ```bash | ||
162 | # reload systemd services definitions | ||
163 | systemctl daemon-reload | ||
164 | # start the servie and enable it a boot time | ||
165 | systemctl enable docker.shaarli.service --now | ||
166 | # verify that the service is running | ||
167 | systemctl status docker.* | ||
168 | # inspect system log if needed | ||
169 | journalctl -f | ||
170 | ``` | ||
171 | |||
172 | |||
173 | |||
174 | ## Docker cheatsheet | ||
175 | |||
176 | ```bash | ||
177 | # pull/update an image | ||
178 | $ docker pull shaarli/shaarli:release | ||
179 | # run a container from an image | ||
180 | $ docker run shaarli/shaarli:latest | ||
181 | # list available images | ||
182 | $ docker images ls | ||
183 | # list running containers | ||
184 | $ docker ps | ||
185 | # list running AND stopped containers | ||
186 | $ docker ps -a | ||
187 | # run a command in a running container | ||
188 | $ docker exec -ti <container-name-or-first-letters-of-id> bash | ||
189 | # follow logs of a running container | ||
190 | $ docker logs -f <container-name-or-first-letters-of-id> | ||
191 | # delete unused images to free up disk space | ||
192 | $ docker system prune --images | ||
193 | # delete unused volumes to free up disk space (CAUTION all data in unused volumes will be lost) | ||
194 | $ docker system prunt --volumes | ||
195 | # delete unused containers | ||
196 | $ docker system prune | ||
197 | ``` | ||
198 | |||
199 | |||
200 | ## References | ||
201 | |||
202 | - [Docker: using volumes](https://docs.docker.com/storage/volumes/) | ||
203 | - [Dockerfile best practices](https://docs.docker.com/articles/dockerfile_best-practices/) | ||
204 | - [Dockerfile reference](https://docs.docker.com/reference/builder/) | ||
205 | - [DockerHub: GitHub automated build](https://docs.docker.com/docker-hub/github/) | ||
206 | - [DockerHub: Repositories](https://docs.docker.com/userguide/dockerrepos/) | ||
207 | - [DockerHub: Teams and organizations](https://docs.docker.com/docker-hub/orgs/) | ||
208 | - [Get Docker CE for Debian](https://docs.docker.com/install/linux/docker-ce/debian/) | ||
209 | - [Install Docker Compose](https://docs.docker.com/compose/install/) | ||
210 | - [Interactive Docker training portal](https://www.katacoda.com/courses/docker/) on [Katakoda](https://www.katacoda.com/) | ||
211 | - [Service management: Nginx in the foreground](http://nginx.org/en/docs/ngx_core_module.html#daemon) | ||
212 | - [Service management: Using supervisord](https://docs.docker.com/articles/using_supervisord/) | ||
213 | - [Volumes](https://docs.docker.com/storage/volumes/) | ||
214 | - [Volumes](https://docs.docker.com/userguide/dockervolumes/) | ||
215 | - [Where are Docker images stored?](http://blog.thoward37.me/articles/where-are-docker-images-stored/) | ||
216 | - [docker create](https://docs.docker.com/engine/reference/commandline/create/) | ||
217 | - [Docker Documentation](https://docs.docker.com/) | ||
218 | - [docker exec](https://docs.docker.com/engine/reference/commandline/exec/) | ||
219 | - [docker images](https://docs.docker.com/engine/reference/commandline/images/) | ||
220 | - [docker logs](https://docs.docker.com/engine/reference/commandline/logs/) | ||
221 | - [docker logs](https://docs.docker.com/engine/reference/commandline/logs/) | ||
222 | - [Docker Overview](https://docs.docker.com/engine/docker-overview/) | ||
223 | - [docker ps](https://docs.docker.com/engine/reference/commandline/ps/) | ||
224 | - [docker pull](https://docs.docker.com/engine/reference/commandline/pull/) | ||
225 | - [docker run](https://docs.docker.com/engine/reference/commandline/run/) | ||
226 | - [docker-compose logs](https://docs.docker.com/compose/reference/logs/) | ||
227 | - Træfik: [Getting Started](https://docs.traefik.io/), [Docker backend](https://docs.traefik.io/configuration/backends/docker/), [Let's Encrypt](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/), [Docker image](https://hub.docker.com/_/traefik/) \ No newline at end of file | ||
diff --git a/doc/md/Download-and-Installation.md b/doc/md/Download-and-Installation.md deleted file mode 100644 index ec68762e..00000000 --- a/doc/md/Download-and-Installation.md +++ /dev/null | |||
@@ -1,124 +0,0 @@ | |||
1 | To install Shaarli, simply place the files in a directory under your webserver's | ||
2 | Document Root (or directly at the document root). | ||
3 | |||
4 | Also, please make sure your server is properly [configured](Server-configuration.md). | ||
5 | |||
6 | Multiple releases branches are available: | ||
7 | |||
8 | - latest (last release) | ||
9 | - stable (previous major release) | ||
10 | - master (development) | ||
11 | |||
12 | Using one of the following methods: | ||
13 | |||
14 | - by downloading full release archives including all dependencies | ||
15 | - by downloading Github archives | ||
16 | - by cloning the Git repository | ||
17 | - using Docker: [see the documentation](docker/shaarli-images.md) | ||
18 | |||
19 | -------------------------------------------------------------------------------- | ||
20 | |||
21 | ## Latest release (recommended) | ||
22 | |||
23 | ### Download as an archive | ||
24 | |||
25 | 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. | ||
26 | |||
27 | The current latest released version is `v0.10.4` | ||
28 | |||
29 | ```bash | ||
30 | $ wget https://github.com/shaarli/Shaarli/releases/download/v0.10.4/shaarli-v0.10.4-full.zip | ||
31 | $ unzip shaarli-v0.10.4-full.zip | ||
32 | $ mv Shaarli /path/to/shaarli/ | ||
33 | ``` | ||
34 | |||
35 | ### Using git | ||
36 | |||
37 | Cloning using `git` or downloading Github branches as zip files requires additional steps: | ||
38 | |||
39 | * Install [Composer](Unit-tests.md#install_composer) to manage third-party [PHP dependencies](3rd-party-libraries.md#composer). | ||
40 | * Install [yarn](https://yarnpkg.com/lang/en/docs/install/) to build the frontend dependencies. | ||
41 | * Install [python3-virtualenv](https://pypi.python.org/pypi/virtualenv) to build the local HTML documentation. | ||
42 | |||
43 | ``` | ||
44 | $ mkdir -p /path/to/shaarli && cd /path/to/shaarli/ | ||
45 | $ git clone -b latest https://github.com/shaarli/Shaarli.git . | ||
46 | $ composer install --no-dev --prefer-dist | ||
47 | $ make build_frontend | ||
48 | $ make translate | ||
49 | $ make htmldoc | ||
50 | ``` | ||
51 | |||
52 | -------------------------------------------------------------------------------- | ||
53 | |||
54 | ## Stable version | ||
55 | |||
56 | The stable version has been experienced by Shaarli users, and will receive security updates. | ||
57 | |||
58 | |||
59 | ### Download as an archive | ||
60 | |||
61 | As a .zip archive: | ||
62 | |||
63 | ```bash | ||
64 | $ wget https://github.com/shaarli/Shaarli/archive/stable.zip | ||
65 | $ unzip stable.zip | ||
66 | $ mv Shaarli-stable /path/to/shaarli/ | ||
67 | ``` | ||
68 | |||
69 | As a .tar.gz archive : | ||
70 | |||
71 | ```bash | ||
72 | $ wget https://github.com/shaarli/Shaarli/archive/stable.tar.gz | ||
73 | $ tar xvf stable.tar.gz | ||
74 | $ mv Shaarli-stable /path/to/shaarli/ | ||
75 | ``` | ||
76 | |||
77 | ### Using git | ||
78 | |||
79 | Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies. | ||
80 | |||
81 | ```bash | ||
82 | $ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/ | ||
83 | # install/update third-party dependencies | ||
84 | $ cd /path/to/shaarli/ | ||
85 | $ composer install --no-dev --prefer-dist | ||
86 | ``` | ||
87 | |||
88 | |||
89 | -------------------------------------------------------------------------------- | ||
90 | |||
91 | ## Development version (mainline) | ||
92 | |||
93 | _Use at your own risk!_ | ||
94 | |||
95 | Install [Composer](Unit-tests.md#install_composer) to manage Shaarli PHP dependencies, | ||
96 | and [yarn](https://yarnpkg.com/lang/en/docs/install/) | ||
97 | for front-end dependencies. | ||
98 | |||
99 | To get the latest changes from the `master` branch: | ||
100 | |||
101 | ```bash | ||
102 | # clone the repository | ||
103 | $ git clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/ | ||
104 | # install/update third-party dependencies | ||
105 | $ cd /path/to/shaarli | ||
106 | $ composer install --no-dev --prefer-dist | ||
107 | $ make build_frontend | ||
108 | $ make translate | ||
109 | $ make htmldoc | ||
110 | ``` | ||
111 | |||
112 | ------------------------------------------------------------------------------- | ||
113 | |||
114 | ## Finish Installation | ||
115 | |||
116 | Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser. | ||
117 | |||
118 |  | ||
119 | |||
120 | Setup your Shaarli installation, and it's ready to use! | ||
121 | |||
122 | ## Updating Shaarli | ||
123 | |||
124 | See [Upgrade and Migration](Upgrade-and-migration) | ||
diff --git a/doc/md/FAQ.md b/doc/md/FAQ.md deleted file mode 100644 index a2ec7d57..00000000 --- a/doc/md/FAQ.md +++ /dev/null | |||
@@ -1,46 +0,0 @@ | |||
1 | ### Why did you create Shaarli ? | ||
2 | |||
3 | I was a StumbleUpon user. Then I got fed up with they big toolbar. I switched to delicious, which was lighter, faster and more beautiful. Until Yahoo bought it. Then the export API broke all the time, delicious became slow and was ditched by Yahoo. I switched to Diigo, which is not bad, but does too much. And Diigo is sslllooooowww and their Firefox extension a bit buggy. And… oh… **their Firefox addon sends to Diigo every single URL you visit** (Don't believe me ? Use [Tamper Data](https://addons.mozilla.org/en-US/firefox/addon/tamper-data/) and open any page). | ||
4 | |||
5 | Enough is enough. Saving simple links should not be a complicated heavy thing. I ditched them all and wrote my own: Shaarli. It's simple, but it does the job and does it well. And my data is not hosted on a foreign server, but on my server. | ||
6 | |||
7 | ### Why use Shaarli and not Delicious/Diigo ? | ||
8 | |||
9 | With Shaarli: | ||
10 | |||
11 | - The data is yours: It's hosted on your server. | ||
12 | - Never fear of having your data locked-in. | ||
13 | - Never fear to have your data sold to third party. | ||
14 | - Your private links are not hosted on a third party server. | ||
15 | - You are not tracked by browser addons (like Diigo does) | ||
16 | - You can change the look and feel of the pages if you want. | ||
17 | - You can change the behaviour of the program. | ||
18 | - It's magnitude faster than most bookmarking services. | ||
19 | |||
20 | ### What does Shaarli mean? | ||
21 | |||
22 | Shaarli stands for _shaaring_ your _links_. | ||
23 | |||
24 | ### My Shaarli is broken! | ||
25 | First of all, ensure that both the [web server](Server-configuration) and | ||
26 | [Shaarli](Shaarli-configuration) are correctly configured, and that your | ||
27 | installation is [supported](Server-configuration). | ||
28 | |||
29 | If everything looks right but the issue(s) remain(s), please: | ||
30 | |||
31 | - take a look at the [troubleshooting](Troubleshooting) section | ||
32 | - come [chat with us](https://gitter.im/shaarli/Shaarli) on Gitter, we'll be happy to help ;-) | ||
33 | - browse active [issues](https://github.com/shaarli/Shaarli/issues) and [Pull Requests](https://github.com/shaarli/Shaarli/pulls) | ||
34 | - if you find one that is related to the issue, feel free to comment and provide additional details (host/Shaarli setup) | ||
35 | - else, [open a new issue](https://github.com/shaarli/Shaarli/issues/new), and provide information about the problem: | ||
36 | - _what happens?_ - display glitches, invalid data, security flaws... | ||
37 | - _what is your configuration?_ - OS, server version, activated extensions, web browser... | ||
38 | - _is it reproducible?_ | ||
39 | |||
40 | ### Why not use a real database? Files are slow! | ||
41 | |||
42 | Does browsing [this page](http://sebsauvage.net/links/) feel slow? Try browsing older pages, too. | ||
43 | |||
44 | It's not slow at all, is it? And don't forget the database contains more than 16000 links, and it's on a shared host, with 32000 visitors/day for my website alone. And it's still damn fast. Why? | ||
45 | |||
46 | The data file is only 3.7 Mb. It's read 99% of the time, and is probably already in the operation system disk cache. So generating a page involves no I/O at all most of the time. | ||
diff --git a/doc/md/Installation.md b/doc/md/Installation.md new file mode 100644 index 00000000..11b5da85 --- /dev/null +++ b/doc/md/Installation.md | |||
@@ -0,0 +1,78 @@ | |||
1 | # Installation | ||
2 | |||
3 | Once your server is [configured](Server-configuration.md), install Shaarli: | ||
4 | |||
5 | ## From release ZIP | ||
6 | |||
7 | To install Shaarli, simply place the files from the latest [release .zip archive](https://github.com/shaarli/Shaarli/releases) under your webserver's document root (directly at the document root, or in a subdirectory). Download the **shaarli-vX.X.X-full** archive to include dependencies. | ||
8 | |||
9 | ```bash | ||
10 | wget https://github.com/shaarli/Shaarli/releases/download/v0.11.1/shaarli-v0.11.1-full.zip | ||
11 | unzip shaarli-v0.11.1-full.zip | ||
12 | sudo rsync -avP Shaarli/ /var/www/shaarli.mydomain.org/ | ||
13 | ``` | ||
14 | |||
15 | ## From sources | ||
16 | |||
17 | These components are required to build Shaarli: | ||
18 | |||
19 | - [Composer](dev/Development.md#install-composer) to manage third-party [PHP dependencies](dev/Development#third-party-libraries). | ||
20 | - [yarn](https://yarnpkg.com/lang/en/docs/install/) to build frontend dependencies. | ||
21 | - [python3-virtualenv](https://pypi.python.org/pypi/virtualenv) to build local HTML documentation. | ||
22 | |||
23 | Clone the repository, either pointing to: | ||
24 | |||
25 | - any [tagged release](https://github.com/shaarli/Shaarli/releases) | ||
26 | - `latest`: the latest tagged release | ||
27 | - `master`: development branch | ||
28 | |||
29 | ```bash | ||
30 | # clone the branch/tag of your choice | ||
31 | $ git clone -b latest https://github.com/shaarli/Shaarli.git /home/me/Shaarli | ||
32 | # OR download/extract the tar.gz/zip: wget https://github.com/shaarli/Shaarli/archive/latest.tar.gz... | ||
33 | |||
34 | # enter the directory | ||
35 | $ cd /home/me/Shaarli | ||
36 | # install 3rd-party PHP dependencies | ||
37 | $ composer install --no-dev --prefer-dist | ||
38 | # build frontend static assets | ||
39 | $ make build_frontend | ||
40 | # build translations | ||
41 | $ make translate | ||
42 | # build HTML documentation | ||
43 | $ make htmldoc | ||
44 | # copy the resulting shaarli directory under your webserver's document root | ||
45 | $ rsync -avP /home/me/Shaarli/ /var/www/shaarli.mydomain.org/ | ||
46 | ``` | ||
47 | |||
48 | ## Set file permissions | ||
49 | |||
50 | Regardless of the installation method, appropriate [file permissions](dev/Development.md#directory-structure) must be set: | ||
51 | |||
52 | ```bash | ||
53 | sudo chown -R root:www-data /var/www/shaarli.mydomain.org | ||
54 | sudo chmod -R g+rX /var/www/shaarli.mydomain.org | ||
55 | sudo chmod -R g+rwX /var/www/shaarli.mydomain.org/{cache/,data/,pagecache/,tmp/} | ||
56 | ``` | ||
57 | |||
58 | ## Using Docker | ||
59 | |||
60 | [See the documentation](Docker.md) | ||
61 | |||
62 | |||
63 | ## Finish Installation | ||
64 | |||
65 | Once Shaarli is downloaded and files have been placed at the correct location, open this location your web browser. | ||
66 | |||
67 | Enter basic settings for your Shaarli installation, and it's ready to use! | ||
68 | |||
69 |  | ||
70 | |||
71 | Congratulations! Your Shaarli is now available at `https://shaarli.mydomain.org`. | ||
72 | |||
73 | You can further [configure Shaarli](Shaarli-configuration.md), setup [Plugins](Plugins.md) or [additional software](Community-and-related-software.md). | ||
74 | |||
75 | |||
76 | ## Upgrading Shaarli | ||
77 | |||
78 | See [Upgrade and Migration](Upgrade-and-migration) | ||
diff --git a/doc/md/Link-structure.md b/doc/md/Link-structure.md deleted file mode 100644 index 0a2d0f88..00000000 --- a/doc/md/Link-structure.md +++ /dev/null | |||
@@ -1,18 +0,0 @@ | |||
1 | ## Link structure | ||
2 | |||
3 | Every link available through the `LinkDB` object is represented as an array | ||
4 | containing the following fields: | ||
5 | |||
6 | * `id` (integer): Unique identifier. | ||
7 | * `title` (string): Title of the link. | ||
8 | * `url` (string): URL of the link. Used for displayable links (without redirector, url encoding, etc.). | ||
9 | Can be absolute or relative for Notes. | ||
10 | * `real_url` (string): Real destination URL, can be redirected, encoded, etc. | ||
11 | * `shorturl` (string): Permalink small hash. | ||
12 | * `description` (string): Link text description. | ||
13 | * `private` (boolean): whether the link is private or not. | ||
14 | * `tags` (string): all link tags separated by a single space | ||
15 | * `thumbnail` (string|boolean): relative path of the thumbnail cache file, or false if there isn't any. | ||
16 | * `created` (DateTime): link creation date time. | ||
17 | * `updated` (DateTime): last modification date time. | ||
18 | \ No newline at end of file | ||
diff --git a/doc/md/Plugins.md b/doc/md/Plugins.md index 3e261815..a9f5f1a8 100644 --- a/doc/md/Plugins.md +++ b/doc/md/Plugins.md | |||
@@ -1,14 +1,13 @@ | |||
1 | ## Plugin installation | 1 | # Plugins |
2 | 2 | ||
3 | There is a bunch of plugins shipped with Shaarli, where there is nothing to do to install them. | 3 | ## Installation |
4 | 4 | ||
5 | If you want to install a third party plugin: | 5 | For plugins shipped with Shaarli, no installation is required. |
6 | 6 | ||
7 | - Download it. | 7 | If you want to install a third party plugin, download it to the `plugins` directory in Shaarli's installation folder: |
8 | - Put it in the `plugins` directory in Shaarli's installation folder. | ||
9 | - Make sure you put it correctly: | ||
10 | 8 | ||
11 | ``` | 9 | ```bash |
10 | # example directory structure | ||
12 | | index.php | 11 | | index.php |
13 | | plugins/ | 12 | | plugins/ |
14 | |---| custom_plugin/ | 13 | |---| custom_plugin/ |
@@ -17,63 +16,47 @@ If you want to install a third party plugin: | |||
17 | 16 | ||
18 | ``` | 17 | ``` |
19 | 18 | ||
20 | * Make sure your webserver can read and write the files in your plugin folder. | 19 | Make sure your webserver can read and write the files in your plugin folder. |
21 | 20 | ||
22 | ## Plugin configuration | ||
23 | 21 | ||
24 | In Shaarli's administration page (`Tools` link), go to `Plugin administration`. | 22 | ## Configuration |
25 | 23 | ||
26 | Here you can enable and disable all plugins available, and configure them. | 24 | From Shaarli's administration page (`Tools` link), go to `Plugin administration`. Here you can enable and disable all plugins available, and configure them. |
27 | 25 | ||
28 |  | 26 |  |
29 | 27 | ||
30 | ## Plugin order | 28 | |
29 | ## Order | ||
31 | 30 | ||
32 | In the plugin administration page, you can move enabled plugins to the top or bottom of the list. The first plugins in the list will be processed first. | 31 | In the plugin administration page, you can move enabled plugins to the top or bottom of the list. The first plugins in the list will be processed first. |
33 | 32 | ||
34 | This is important in case plugins are depending on each other. Read plugins README details for more information. | 33 | This is important in case plugins depend on each other. Read plugins READMEs for more information. |
35 | 34 | ||
36 | **Use case**: The (non existent) plugin `shaares_footer` adds a footer to every shaare in Markdown syntax. It needs to be processed *before* (higher in the list) the Markdown plugin. Otherwise its syntax won't be translated in HTML. | 35 | **Use case**: The (non existent) plugin `shaares_footer` adds a footer to every shaare in Markdown syntax. It needs to be processed *before* (higher in the list) the Markdown plugin. Otherwise its syntax won't be translated in HTML. |
37 | 36 | ||
38 | ## File mode | ||
39 | 37 | ||
40 | Enabled plugin are stored in your `config.json.php` parameters file, under the `array`: | 38 | ## Configuration file |
41 | 39 | ||
42 | ```php | 40 | Enabled plugins are stored in your [Configuration file](Shaarli-configuration). |
43 | $GLOBALS['config']['ENABLED_PLUGINS'] | ||
44 | ``` | ||
45 | 41 | ||
46 | You can edit them manually here. | 42 | ## Usage |
47 | Example: | ||
48 | 43 | ||
49 | ```php | 44 | ### Official plugins |
50 | $GLOBALS['config']['ENABLED_PLUGINS'] = array( | ||
51 | 'qrcode', | ||
52 | 'archiveorg', | ||
53 | 'wallabag', | ||
54 | 'markdown', | ||
55 | ); | ||
56 | ``` | ||
57 | |||
58 | ### Plugin usage | ||
59 | |||
60 | #### Official plugins | ||
61 | 45 | ||
62 | Usage of each plugin is documented in it's README file: | 46 | Usage of each plugin is documented in it's README file: |
63 | 47 | ||
64 | * `addlink-toolbar`: Adds the addlink input on the linklist page | 48 | * `addlink-toolbar`: Adds the addlink input on the Shaares list page |
65 | * `archiveorg`: For each link, add an Archive.org icon | 49 | * `archiveorg`: For each Shaare, add a link to the archived page on Archive.org |
66 | * `default_colors`: Override default theme colors. | 50 | * `default_colors`: Override default theme colors. |
67 | * `isso`: Let visitor comment your shaares on permalinks with Isso. | 51 | * `isso`: Let visitor comment your shaares on permalinks with Isso. |
68 | * [`markdown`](https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md): Render shaare description with Markdown syntax. | 52 | * [`markdown`](https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md): Render shaare description with Markdown syntax. |
69 | * `piwik`: A plugin that adds Piwik tracking code to Shaarli pages. | 53 | * `piwik`: A plugin that adds Piwik tracking code to Shaarli pages. |
70 | * [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos. | 54 | * [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos. |
71 | * `pubsubhubbub`: Enable PubSubHubbub feed publishing | 55 | * `pubsubhubbub`: Enable PubSubHubbub feed publishing |
72 | * `qrcode`: For each link, add a QRCode icon. | 56 | * `qrcode`: For each Shaare, add a QRCode icon. |
73 | * [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each link, add a Wallabag icon to save it in your instance. | 57 | * [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each Shaare, add a Wallabag icon to save it in your instance. |
74 | |||
75 | 58 | ||
76 | 59 | ||
77 | #### Third party plugins | 60 | ### Third party plugins |
78 | 61 | ||
79 | See [Community & related software](https://shaarli.readthedocs.io/en/master/Community-&-Related-software/) | 62 | See [Community & related software](https://shaarli.readthedocs.io/en/master/Community-and-Related-software/) |
diff --git a/doc/md/REST-API.md b/doc/md/REST-API.md index 11bd1cd2..01071d8e 100644 --- a/doc/md/REST-API.md +++ b/doc/md/REST-API.md | |||
@@ -1,101 +1,24 @@ | |||
1 | ## Usage and Prerequisites | 1 | # REST API |
2 | 2 | ||
3 | See the [REST API documentation](http://shaarli.github.io/api-documentation/) | 3 | ## Server requirements |
4 | for a list of available endpoints and parameters. | ||
5 | 4 | ||
6 | Please ensure that your server meets the | 5 | See the **[REST API documentation](http://shaarli.github.io/api-documentation/)** for a list of available endpoints and parameters. |
7 | [requirements](Server-configuration#prerequisites) and is properly | 6 | |
8 | [configured](Server-configuration): | 7 | Please ensure that your server meets the requirements and is properly [configured](Server-configuration): |
9 | 8 | ||
10 | - URL rewriting is enabled (see specific Apache and Nginx sections) | 9 | - URL rewriting is enabled (see specific Apache and Nginx sections) |
11 | - the server's timezone is properly defined | 10 | - the server's timezone is properly defined |
12 | - the server's clock is synchronized with | 11 | - the server's clock is synchronized with [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol) |
13 | [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol) | ||
14 | |||
15 | The host where the API client is invoked should also be synchronized with NTP, | ||
16 | see [token expiration](#payload). | ||
17 | |||
18 | ## Authentication | ||
19 | |||
20 | All requests to Shaarli's API must include a JWT token to verify their authenticity. | ||
21 | |||
22 | This token has to be included as an HTTP header called `Authentication: Bearer <jwt token>`. | ||
23 | |||
24 | JWT resources : | ||
25 | |||
26 | - [jwt.io](https://jwt.io) (including a list of client per language). | ||
27 | - RFC : https://tools.ietf.org/html/rfc7519 | ||
28 | - https://float-middle.com/json-web-tokens-jwt-vs-sessions/ | ||
29 | - HackerNews thread: https://news.ycombinator.com/item?id=11929267 | ||
30 | |||
31 | |||
32 | ### Shaarli JWT Token | ||
33 | |||
34 | JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64: | ||
35 | |||
36 | ``` | ||
37 | [header].[payload].[signature] | ||
38 | ``` | ||
39 | |||
40 | #### Header | ||
41 | |||
42 | Shaarli only allow one hash algorithm, so the header will always be the same: | ||
43 | |||
44 | ```json | ||
45 | { | ||
46 | "typ": "JWT", | ||
47 | "alg": "HS512" | ||
48 | } | ||
49 | ``` | ||
50 | |||
51 | Encoded in base64, it gives: | ||
52 | |||
53 | ``` | ||
54 | ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ== | ||
55 | ``` | ||
56 | |||
57 | #### Payload | ||
58 | |||
59 | **Token expiration** | ||
60 | |||
61 | To avoid infinite token validity, JWT tokens must include their creation date | ||
62 | in UNIX timestamp format (timezone independent - UTC) under the key `iat` (issued at). | ||
63 | This token will be valid during **9 minutes**. | ||
64 | |||
65 | ```json | ||
66 | { | ||
67 | "iat": 1468663519 | ||
68 | } | ||
69 | ``` | ||
70 | |||
71 | See [RFC reference](https://tools.ietf.org/html/rfc7519#section-4.1.6). | ||
72 | |||
73 | 12 | ||
74 | #### Signature | 13 | The host where the API client is invoked should also be synchronized with NTP, see _payload/token expiration_ |
75 | |||
76 | The signature authenticate the token validity. It contains the base64 of the header and the body, separated by a dot `.`, hashed in SHA512 with the API secret available in Shaarli administration page. | ||
77 | |||
78 | Signature example with PHP: | ||
79 | |||
80 | ```php | ||
81 | $content = base64_encode($header) . '.' . base64_encode($payload); | ||
82 | $signature = hash_hmac('sha512', $content, $secret); | ||
83 | ``` | ||
84 | 14 | ||
85 | 15 | ||
86 | ## Clients and examples | 16 | ## Clients and examples |
87 | ### Android, Java, Kotlin | ||
88 | |||
89 | - [Android client example with Kotlin](https://gitlab.com/snippets/1665808) | ||
90 | by [Braincoke](https://github.com/Braincoke) | ||
91 | |||
92 | ### Javascript, NodeJS | ||
93 | 17 | ||
94 | - [shaarli-client](https://www.npmjs.com/package/shaarli-client) | 18 | - **[python-shaarli-client](https://github.com/shaarli/python-shaarli-client)** - the reference API client ([Documentation](http://python-shaarli-client.readthedocs.io/en/latest/)) |
95 | ([source code](https://github.com/laBecasse/shaarli-client)) | 19 | - [shaarli-client](https://www.npmjs.com/package/shaarli-client) - NodeJs client ([source code](https://github.com/laBecasse/shaarli-client)) by [laBecasse](https://github.com/laBecasse) |
96 | by [laBecasse](https://github.com/laBecasse) | 20 | - [Android client example with Kotlin](https://gitlab.com/snippets/1665808) by [Braincoke](https://github.com/Braincoke) |
97 | 21 | ||
98 | ### PHP | ||
99 | 22 | ||
100 | This example uses the [PHP cURL](http://php.net/manual/en/book.curl.php) library. | 23 | This example uses the [PHP cURL](http://php.net/manual/en/book.curl.php) library. |
101 | 24 | ||
@@ -145,13 +68,57 @@ function getInfo($baseUrl, $secret) { | |||
145 | var_dump(getInfo($baseUrl, $secret)); | 68 | var_dump(getInfo($baseUrl, $secret)); |
146 | ``` | 69 | ``` |
147 | 70 | ||
71 | ## Implementation | ||
72 | |||
73 | ### Authentication | ||
74 | |||
75 | - All requests to Shaarli's API must include a **JWT token** to verify their authenticity. | ||
76 | - This token must be included as an HTTP header called `Authentication: Bearer <jwt token>`. | ||
77 | - JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64: | ||
78 | |||
79 | ``` | ||
80 | [header].[payload].[signature] | ||
81 | ``` | ||
82 | |||
83 | ##### Header | ||
84 | |||
85 | Shaarli only allow one hash algorithm, so the header will always be the same: | ||
86 | |||
87 | ```json | ||
88 | { | ||
89 | "typ": "JWT", | ||
90 | "alg": "HS512" | ||
91 | } | ||
92 | ``` | ||
93 | |||
94 | Encoded in base64, it gives: | ||
148 | 95 | ||
149 | ### Python | 96 | ``` |
97 | ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ== | ||
98 | ``` | ||
99 | |||
100 | ##### Payload | ||
101 | |||
102 | Token expiration: To avoid infinite token validity, JWT tokens must include their creation date in UNIX timestamp format (timezone independent - UTC) under the key `iat` (issued at) field ([1](https://tools.ietf.org/html/rfc7519#section-4.1.6)). This token will be valid during **9 minutes**. | ||
103 | |||
104 | ```json | ||
105 | { | ||
106 | "iat": 1468663519 | ||
107 | } | ||
108 | ``` | ||
109 | |||
110 | ##### Signature | ||
111 | |||
112 | The signature authenticates the token validity. It contains the base64 of the header and the body, separated by a dot `.`, hashed in SHA512 with the API secret available in Shaarli administration page. | ||
113 | |||
114 | Example signature with PHP: | ||
115 | |||
116 | ```php | ||
117 | $content = base64_encode($header) . '.' . base64_encode($payload); | ||
118 | $signature = hash_hmac('sha512', $content, $secret); | ||
119 | ``` | ||
150 | 120 | ||
151 | See the reference API client: | ||
152 | 121 | ||
153 | - [Documentation](http://python-shaarli-client.readthedocs.io/en/latest/) on ReadTheDocs | ||
154 | - [python-shaarli-client](https://github.com/shaarli/python-shaarli-client) on Github | ||
155 | 122 | ||
156 | ## Troubleshooting | 123 | ## Troubleshooting |
157 | 124 | ||
@@ -171,3 +138,13 @@ to get the actual error message in the HTTP response body with: | |||
171 | } | 138 | } |
172 | } | 139 | } |
173 | ``` | 140 | ``` |
141 | |||
142 | ## References | ||
143 | |||
144 | - [jwt.io](https://jwt.io) (including a list of client per language). | ||
145 | - [RFC - JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) | ||
146 | - [JSON Web Tokens (JWT) vs Sessions](https://float-middle.com/json-web-tokens-jwt-vs-sessions/), [HackerNews thread](https://news.ycombinator.com/item?id=11929267) | ||
147 | |||
148 | |||
149 | |||
150 | |||
diff --git a/doc/md/RSS-feeds.md b/doc/md/RSS-feeds.md deleted file mode 100644 index d943218e..00000000 --- a/doc/md/RSS-feeds.md +++ /dev/null | |||
@@ -1,28 +0,0 @@ | |||
1 | ### Feeds options | ||
2 | |||
3 | Feeds are available in ATOM with `?do=atom` and RSS with `do=RSS`. | ||
4 | |||
5 | Options: | ||
6 | |||
7 | - You can use `permalinks` in the feed URL to get permalink to Shaares instead of direct link to shaared URL. | ||
8 | - E.G. `https://my.shaarli.domain/?do=atom&permalinks`. | ||
9 | - You can use `nb` parameter in the feed URL to specify the number of Shaares you want in a feed (default if not specified: `50`). The keyword `all` is available if you want everything. | ||
10 | - `https://my.shaarli.domain/?do=atom&permalinks&nb=42` | ||
11 | - `https://my.shaarli.domain/?do=atom&permalinks&nb=all` | ||
12 | |||
13 | ### RSS Feeds or Picture Wall for a specific search/tag | ||
14 | |||
15 | It is possible to filter RSS/ATOM feeds and Picture Wall on a Shaarli to **only display results of a specific search, or for a specific tag**. | ||
16 | |||
17 | For example, if you want to subscribe only to links tagged `photography`: | ||
18 | |||
19 | - Go to the desired Shaarli instance. | ||
20 | - Search for the `photography` tag in the _Filter by tag_ box. Links tagged `photography` are displayed. | ||
21 | - Click on the `RSS Feed` button. | ||
22 | - You are presented with an RSS feed showing only these links. Subscribe to it to receive only updates with this tag. | ||
23 | - The same method **also works for a full-text search** (_Search_ box) **and for the Picture Wall** (want to only see pictures about `nature`?) | ||
24 | - You can also build the URLs manually: | ||
25 | - `https://my.shaarli.domain/?do=rss&searchtags=nature` | ||
26 | - `https://my.shaarli.domain/links/?do=picwall&searchterm=poney` | ||
27 | |||
28 |   | ||
diff --git a/doc/md/Release-Shaarli.md b/doc/md/Release-Shaarli.md deleted file mode 100644 index e22eabc9..00000000 --- a/doc/md/Release-Shaarli.md +++ /dev/null | |||
@@ -1,161 +0,0 @@ | |||
1 | See [Git - Maintaining a project - Tagging your | ||
2 | releases](http://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project#Tagging-Your-Releases). | ||
3 | |||
4 | ## Prerequisites | ||
5 | This guide assumes that you have: | ||
6 | |||
7 | - a GPG key matching your GitHub authentication credentials | ||
8 | - i.e., the email address identified by the GPG key is the same as the one in your `~/.gitconfig` | ||
9 | - a GitHub fork of Shaarli | ||
10 | - a local clone of your Shaarli fork, with the following remotes: | ||
11 | - `origin` pointing to your GitHub fork | ||
12 | - `upstream` pointing to the main Shaarli repository | ||
13 | - maintainer permissions on the main Shaarli repository, to: | ||
14 | - push the signed tag | ||
15 | - create a new release | ||
16 | - [Composer](https://getcomposer.org/) needs to be installed | ||
17 | - The [venv](https://docs.python.org/3/library/venv.html) Python 3 module needs to be installed for HTML documentation generation. | ||
18 | |||
19 | ## GitHub release draft and `CHANGELOG.md` | ||
20 | See http://keepachangelog.com/en/0.3.0/ for changelog formatting. | ||
21 | |||
22 | ### GitHub release draft | ||
23 | GitHub allows drafting the release note for the upcoming release, from the [Releases](https://github.com/shaarli/Shaarli/releases) page. This way, the release note can be drafted while contributions are merged to `master`. | ||
24 | |||
25 | ### `CHANGELOG.md` | ||
26 | This file should contain the same information as the release note draft for the upcoming version. | ||
27 | |||
28 | Update it to: | ||
29 | |||
30 | - add new entries (additions, fixes, etc.) | ||
31 | - mark the current version as released by setting its date and link | ||
32 | - add a new section for the future unreleased version | ||
33 | |||
34 | ```bash | ||
35 | $ cd /path/to/shaarli | ||
36 | |||
37 | $ nano CHANGELOG.md | ||
38 | |||
39 | [...] | ||
40 | ## vA.B.C - UNRELEASED | ||
41 | TBA | ||
42 | |||
43 | ## [vX.Y.Z](https://github.com/shaarli/Shaarli/releases/tag/vX.Y.Z) - YYYY-MM-DD | ||
44 | [...] | ||
45 | ``` | ||
46 | |||
47 | |||
48 | ## Increment the version code, update docs, create and push a signed tag | ||
49 | ### Update the list of Git contributors | ||
50 | ```bash | ||
51 | $ make authors | ||
52 | $ git commit -s -m "Update AUTHORS" | ||
53 | ``` | ||
54 | |||
55 | ### Create and merge a Pull Request | ||
56 | This one is pretty straightforward ;-) | ||
57 | |||
58 | ### Bump Shaarli version to v0.x branch | ||
59 | |||
60 | ```bash | ||
61 | $ git checkout master | ||
62 | $ git fetch upstream | ||
63 | $ git pull upstream master | ||
64 | |||
65 | # IF the branch doesn't exists | ||
66 | $ git checkout -b v0.5 | ||
67 | # OR if the branch already exists | ||
68 | $ git checkout v0.5 | ||
69 | $ git rebase upstream/master | ||
70 | |||
71 | # Bump shaarli version from dev to 0.5.0, **without the `v`** | ||
72 | $ vim shaarli_version.php | ||
73 | $ git add shaarli_version | ||
74 | $ git commit -s -m "Bump Shaarli version to v0.5.0" | ||
75 | $ git push upstream v0.5 | ||
76 | ``` | ||
77 | |||
78 | ### Create and push a signed tag | ||
79 | ```bash | ||
80 | # update your local copy | ||
81 | $ git checkout v0.5 | ||
82 | $ git fetch upstream | ||
83 | $ git pull upstream v0.5 | ||
84 | |||
85 | # create a signed tag | ||
86 | $ git tag -s -m "Release v0.5.0" v0.5.0 | ||
87 | |||
88 | # push it to "upstream" | ||
89 | $ git push --tags upstream | ||
90 | ``` | ||
91 | |||
92 | ### Verify a signed tag | ||
93 | [`v0.5.0`](https://github.com/shaarli/Shaarli/releases/tag/v0.5.0) is the first GPG-signed tag pushed on the Community Shaarli. | ||
94 | |||
95 | Let's have a look at its signature! | ||
96 | |||
97 | ```bash | ||
98 | $ cd /path/to/shaarli | ||
99 | $ git fetch upstream | ||
100 | |||
101 | # get the SHA1 reference of the tag | ||
102 | $ git show-ref tags/v0.5.0 | ||
103 | f7762cf803f03f5caf4b8078359a63783d0090c1 refs/tags/v0.5.0 | ||
104 | |||
105 | # verify the tag signature information | ||
106 | $ git verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1 | ||
107 | gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F | ||
108 | gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate] | ||
109 | ``` | ||
110 | |||
111 | ## Publish the GitHub release | ||
112 | ### Update release badges | ||
113 | Update `README.md` so version badges display and point to the newly released Shaarli version(s), in the `master` branch. | ||
114 | |||
115 | ### Create a GitHub release from a Git tag | ||
116 | From the previously drafted release: | ||
117 | |||
118 | - edit the release notes (if needed) | ||
119 | - specify the appropriate Git tag | ||
120 | - publish the release | ||
121 | - profit! | ||
122 | |||
123 | ### Generate and upload all-in-one release archives | ||
124 | Users with a shared hosting may have: | ||
125 | |||
126 | - no SSH access | ||
127 | - no possibility to install PHP packages or server extensions | ||
128 | - no possibility to run scripts | ||
129 | |||
130 | To ease Shaarli installations, it is possible to generate and upload additional release archives, | ||
131 | that will contain Shaarli code plus all required third-party libraries. | ||
132 | |||
133 | **From the `v0.5` branch:** | ||
134 | |||
135 | ```bash | ||
136 | $ make release_archive | ||
137 | ``` | ||
138 | |||
139 | This will create the following archives: | ||
140 | |||
141 | - `shaarli-vX.Y.Z-full.tar` | ||
142 | - `shaarli-vX.Y.Z-full.zip` | ||
143 | |||
144 | The archives need to be manually uploaded on the previously created GitHub release. | ||
145 | |||
146 | ### Update `stable` and `latest` branches | ||
147 | |||
148 | ``` | ||
149 | $ git checkout latest | ||
150 | # latest release | ||
151 | $ git merge v0.5.0 | ||
152 | # fix eventual conflicts | ||
153 | $ make test | ||
154 | $ git push upstream latest | ||
155 | $ git checkout stable | ||
156 | # latest previous major | ||
157 | $ git merge v0.4.5 | ||
158 | # fix eventual conflicts | ||
159 | $ make test | ||
160 | $ git push upstream stable | ||
161 | ``` | ||
diff --git a/doc/md/Reverse-proxy.md b/doc/md/Reverse-proxy.md new file mode 100644 index 00000000..b7e347d5 --- /dev/null +++ b/doc/md/Reverse-proxy.md | |||
@@ -0,0 +1,141 @@ | |||
1 | # Reverse proxy | ||
2 | |||
3 | If Shaarli is hosted on a server behind a [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) (i.e. there is a proxy server between clients and the web server hosting Shaarli), configure it accordingly. See [Reverse proxy](Reverse-proxy.md) configuration. In this example: | ||
4 | |||
5 | - The Shaarli application server exposes port `10080` to the proxy (for example docker container started with `--publish 127.0.0.1:10080:80`). | ||
6 | - The Shaarli application server runs at `127.0.0.1` (container). Replace with the server's IP address if running on a different machine. | ||
7 | - Shaarli's Fully Qualified Domain Name (FQDN) is `shaarli.mydomain.org`. | ||
8 | - No HTTPS is setup on the application server, SSL termination is done at the reverse proxy. | ||
9 | |||
10 | In your [Shaarli configuration](Shaarli-configuration) `data/config.json.php`, add the public IP of your proxy under `security.trusted_proxies`. | ||
11 | |||
12 | See also [proxy-related](https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&q=label%3Aproxy+) issues. | ||
13 | |||
14 | |||
15 | ## Apache | ||
16 | |||
17 | ```apache | ||
18 | <VirtualHost *:80> | ||
19 | ServerName shaarli.mydomain.org | ||
20 | |||
21 | # For SSL/TLS certificates acquired with certbot or self-signed certificates | ||
22 | # Redirect HTTP requests to HTTPS, except Let's Encrypt ACME challenge requests | ||
23 | RewriteEngine on | ||
24 | RewriteRule ^.well-known/acme-challenge/ - [L] | ||
25 | RewriteCond %{HTTP_HOST} =shaarli.mydomain.org | ||
26 | RewriteRule ^ https://shaarli.mydomain.org%{REQUEST_URI} [END,NE,R=permanent] | ||
27 | </VirtualHost> | ||
28 | |||
29 | # SSL/TLS configuration for Let's Encrypt certificates managed with mod_md | ||
30 | #MDomain shaarli.mydomain.org | ||
31 | #MDCertificateAgreement accepted | ||
32 | #MDContactEmail admin@shaarli.mydomain.org | ||
33 | #MDPrivateKeys RSA 4096 | ||
34 | |||
35 | <VirtualHost *:443> | ||
36 | ServerName shaarli.mydomain.org | ||
37 | |||
38 | # SSL/TLS configuration for Let's Encrypt certificates acquired with certbot standalone | ||
39 | SSLEngine on | ||
40 | SSLCertificateFile /etc/letsencrypt/live/shaarli.mydomain.org/fullchain.pem | ||
41 | SSLCertificateKeyFile /etc/letsencrypt/live/shaarli.mydomain.org/privkey.pem | ||
42 | # Let's Encrypt settings from https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/_internal/tls_configs/current-options-ssl-apache.conf | ||
43 | SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 | ||
44 | SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 | ||
45 | SSLHonorCipherOrder off | ||
46 | SSLSessionTickets off | ||
47 | SSLOptions +StrictRequire | ||
48 | |||
49 | # SSL/TLS configuration for self-signed certificates | ||
50 | #SSLEngine on | ||
51 | #SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem | ||
52 | #SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key | ||
53 | |||
54 | # let the proxied shaarli server/container know HTTPS URLs should be served | ||
55 | RequestHeader set X-Forwarded-Proto "https" | ||
56 | |||
57 | # send the original SERVER_NAME to the proxied host | ||
58 | ProxyPreserveHost On | ||
59 | |||
60 | # pass requests to the proxied host | ||
61 | # sets X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Server headers | ||
62 | ProxyPass / http://127.0.0.1:10080/ | ||
63 | ProxyPassReverse / http://127.0.0.1:10080/ | ||
64 | </VirtualHost> | ||
65 | ``` | ||
66 | |||
67 | |||
68 | ## HAProxy | ||
69 | |||
70 | |||
71 | ```conf | ||
72 | global | ||
73 | [...] | ||
74 | |||
75 | defaults | ||
76 | [...] | ||
77 | |||
78 | frontend http-in | ||
79 | bind :80 | ||
80 | redirect scheme https code 301 if !{ ssl_fc } | ||
81 | bind :443 ssl crt /path/to/cert.pem | ||
82 | default_backend shaarli | ||
83 | |||
84 | backend shaarli | ||
85 | mode http | ||
86 | option http-server-close | ||
87 | option forwardfor | ||
88 | reqadd X-Forwarded-Proto: https | ||
89 | server shaarli1 127.0.0.1:10080 | ||
90 | ``` | ||
91 | |||
92 | - [HAProxy documentation](https://cbonte.github.io/haproxy-dconv/) | ||
93 | |||
94 | ## Nginx | ||
95 | |||
96 | |||
97 | ```nginx | ||
98 | http { | ||
99 | [...] | ||
100 | |||
101 | index index.html index.php; | ||
102 | |||
103 | root /home/john/web; | ||
104 | access_log /var/log/nginx/access.log combined; | ||
105 | error_log /var/log/nginx/error.log; | ||
106 | |||
107 | server { | ||
108 | listen 80; | ||
109 | server_name shaarli.mydomain.org; | ||
110 | # redirect HTTP to HTTPS | ||
111 | return 301 https://shaarli.mydomain.org$request_uri; | ||
112 | } | ||
113 | |||
114 | server { | ||
115 | listen 443 ssl http2; | ||
116 | server_name shaarli.mydomain.org; | ||
117 | |||
118 | ssl_certificate /path/to/certificate | ||
119 | ssl_certificate_key /path/to/private/key | ||
120 | |||
121 | location / { | ||
122 | proxy_set_header X-Real-IP $remote_addr; | ||
123 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
124 | proxy_set_header X-Forwarded-Proto $scheme; | ||
125 | proxy_set_header X-Forwarded-Host $host; | ||
126 | |||
127 | # pass requests to the proxied host | ||
128 | proxy_pass http://localhost:10080/; | ||
129 | proxy_set_header Host $host; | ||
130 | proxy_connect_timeout 30s; | ||
131 | proxy_read_timeout 120s; | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | ``` | ||
136 | |||
137 | ## References | ||
138 | |||
139 | - [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) | ||
140 | - [`X-Forwarded-Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) | ||
141 | - [`X-Forwarded-For`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) | ||
diff --git a/doc/md/Security.md b/doc/md/Security.md deleted file mode 100644 index 65db4225..00000000 --- a/doc/md/Security.md +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | ## Client browser | ||
2 | - Shaarli relies on `HTTP_REFERER` for some functions (like redirects and clicking on tags). If you have disabled or masqueraded `HTTP_REFERER` in your browser, some features of Shaarli may not work | ||
3 | |||
4 | ## Server and sessions | ||
5 | - Directories are protected using `.htaccess` files | ||
6 | - Forms are protected against XSRF (Cross-site requests forgery): | ||
7 | - Forms which act on data (save,delete…) contain a token generated by the server. | ||
8 | - Any posted form which does not contain a valid token is rejected. | ||
9 | - Any token can only be used once. | ||
10 | - Tokens are attached to the session and cannot be reused in another session. | ||
11 | - Sessions automatically expire after 60 minutes. | ||
12 | - Sessions are protected against hijacking: the session ID cannot be used from a different IP address. | ||
13 | |||
14 | ## Shaarli datastore and configuration | ||
15 | - The password is salted, hashed and stored in the data subdirectory, in a PHP file, and protected by htaccess. Even if the webserver does not support htaccess, the hash is not readable by URL. Even if the .php file is stolen, the password cannot deduced from the hash. The salt prevents rainbow-tables attacks. | ||
16 | - Links are stored as an associative array which is serialized, compressed (with deflate), base64-encoded and saved as a comment in a `.php` file. | ||
17 | - Even if the server does not support `.htaccess` files, the data file will still not be readable by URL. | ||
18 | - The database looks like this: | ||
19 | |||
20 | ```php | ||
21 | <?php /* zP1ZjxxJtiYIvvevEPJ2lDOaLrZv7o... | ||
22 | ...ka7gaco/Z+TFXM2i7BlfMf8qxpaSSYfKlvqv/x8= */ ?> | ||
23 | ``` | ||
24 | |||
25 | - Small hashes are used to make a link to an entry in Shaarli. They are unique. In fact, the date of the items (eg. `20110923_150523`) is hashed with CRC32, then converted to base64 and some characters are replaced. They are always 6 characters longs and use only `A-Z a-z 0-9 - _` and `@`. | ||
diff --git a/doc/md/Server-configuration.md b/doc/md/Server-configuration.md index 88eed8e6..297d7c29 100644 --- a/doc/md/Server-configuration.md +++ b/doc/md/Server-configuration.md | |||
@@ -1,20 +1,46 @@ | |||
1 | # Server configuration | ||
1 | 2 | ||
2 | - [Prerequisites](#prerequisistes) | 3 | ## Requirements |
3 | - [Apache](#apache) | ||
4 | - [Nginx](#nginx) | ||
5 | - [Proxies](#proxies) | ||
6 | - [See also](#see-also) | ||
7 | 4 | ||
8 | ## Prerequisites | 5 | ### Operating system and web server |
9 | ### Shaarli | ||
10 | 6 | ||
11 | - A web server and PHP interpreter module/service have been installed. | 7 | Shaarli can be hosted on dedicated/virtual servers, or shared hosting. |
12 | - You have write access to the Shaarli installation directory. | 8 | |
13 | - The correct read/write permissions have been granted to the web server user and group. | 9 | You need write access to the Shaarli installation directory - you should have received instructions from your hosting provider on how to connect to the server using SSH (or FTP for shared hosts). |
14 | - Your PHP interpreter is compatible with supported PHP versions: | 10 | |
11 | Examples in this documentation are given for [Debian](https://www.debian.org/), a GNU/Linux distribution widely used in server environments. Please adapt them to your specific Linux distribution. | ||
12 | |||
13 | A $5/month VPS (1 CPU, 1 GiB RAM and 25 GiB SSD) will run any Shaarli installation without problems. Some hosting providers: [DigitalOcean](https://www.digitalocean.com/) ([1](https://www.digitalocean.com/docs/droplets/overview/), [2](https://www.digitalocean.com/pricing/), [3](https://www.digitalocean.com/docs/droplets/how-to/create/), [4](https://www.digitalocean.com/docs/droplets/how-to/add-ssh-keys/), [5](https://www.digitalocean.com/community/tutorials/initial-server-setup-with-debian-8), [6](https://www.digitalocean.com/community/tutorials/an-introduction-to-securing-your-linux-vps)), [Gandi](https://www.gandi.net/en), [OVH](https://www.ovh.co.uk/), [RackSpace](https://www.rackspace.com/), etc. | ||
14 | |||
15 | |||
16 | ### Network and domain name | ||
17 | |||
18 | Try to host the server in a region that is geographically close to your users. | ||
19 | |||
20 | A **domain name** ([DNS record](https://opensource.com/article/17/4/introduction-domain-name-system-dns)) pointing to the server's public IP address is required to obtain a SSL/TLS certificate and setup HTTPS to secure client traffic to your Shaarli instance. | ||
21 | |||
22 | You can obtain a domain name from a [registrar](https://en.wikipedia.org/wiki/Domain_name_registrar) ([1](https://www.ovh.co.uk/domains), [2](https://www.gandi.net/en/domain)), or from free subdomain providers ([1](https://freedns.afraid.org/)). If you don't have a domain name, please set up a private domain name ([FQDN](ttps://en.wikipedia.org/wiki/Fully_qualified_domain_name)) in your clients' [hosts files](https://en.wikipedia.org/wiki/Hosts_(file)) to access the server (direct access by IP address can result in unexpected behavior). | ||
23 | |||
24 | Setup a **firewall** (using `iptables`, [ufw](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-debian-10), [fireHOL](https://firehol.org/) or any frontend of your choice) to deny all incoming traffic except `tcp/80` and `tcp/443`, which are needed to access the web server (and any other posrts you might need, like SSH). If the server is in a private network behind a NAT, ensure these **ports are forwarded** to the server. | ||
25 | |||
26 | Shaarli makes outbound HTTP/HTTPS connections to websites you bookmark to fetch page information (title, thumbnails), the server must then have access to the Internet as well, and a working DNS resolver. | ||
27 | |||
28 | |||
29 | ### Screencast | ||
30 | |||
31 | Here is a screencast of the installation procedure | ||
32 | |||
33 | [](https://asciinema.org/a/z3RXxcJIRgWk0jM2ws6EnUFgO) | ||
34 | |||
35 | -------------------------------------------------------------------------------- | ||
36 | |||
37 | ### PHP | ||
38 | |||
39 | Supported PHP versions: | ||
15 | 40 | ||
16 | Version | Status | Shaarli compatibility | 41 | Version | Status | Shaarli compatibility |
17 | :---:|:---:|:---: | 42 | :---:|:---:|:---: |
43 | 7.3 | Supported | Yes | ||
18 | 7.2 | Supported | Yes | 44 | 7.2 | Supported | Yes |
19 | 7.1 | Supported | Yes | 45 | 7.1 | Supported | Yes |
20 | 7.0 | EOL: 2018-12-03 | Yes (up to Shaarli 0.10.x) | 46 | 7.0 | EOL: 2018-12-03 | Yes (up to Shaarli 0.10.x) |
@@ -23,71 +49,132 @@ Version | Status | Shaarli compatibility | |||
23 | 5.4 | EOL: 2015-09-14 | Yes (up to Shaarli 0.8.x) | 49 | 5.4 | EOL: 2015-09-14 | Yes (up to Shaarli 0.8.x) |
24 | 5.3 | EOL: 2014-08-14 | Yes (up to Shaarli 0.8.x) | 50 | 5.3 | EOL: 2014-08-14 | Yes (up to Shaarli 0.8.x) |
25 | 51 | ||
26 | - The following PHP extensions are installed on the server: | 52 | Required PHP extensions: |
27 | 53 | ||
28 | Extension | Required? | Usage | 54 | Extension | Required? | Usage |
29 | ---|:---:|--- | 55 | ---|:---:|--- |
30 | [`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS | 56 | [`openssl`](http://php.net/manual/en/book.openssl.php) | requires | OpenSSL, HTTPS |
57 | [`php-json`](http://php.net/manual/en/book.json.php) | required | configuration parsing | ||
58 | [`php-simplexml`](https://www.php.net/manual/en/book.simplexml.php) | required | REST API (Slim framework) | ||
31 | [`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows, some hosting providers | multibyte (Unicode) string support | 59 | [`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows, some hosting providers | multibyte (Unicode) string support |
32 | [`php-gd`](http://php.net/manual/en/book.image.php) | optional | required to use thumbnails | 60 | [`php-gd`](http://php.net/manual/en/book.image.php) | optional | required to use thumbnails |
33 | [`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`) | 61 | [`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`) |
34 | [`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way | 62 | [`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way |
35 | [`php-gettext`](http://php.net/manual/en/book.gettext.php) | optional | Use the translation system in gettext mode (faster) | 63 | [`php-gettext`](http://php.net/manual/en/book.gettext.php) | optional | Use the translation system in gettext mode (faster) |
36 | 64 | ||
37 | -------------------------------------------------------------------------------- | 65 | Some [plugins](Plugins.md) may require additional configuration. |
66 | |||
67 | - [PHP: Supported versions](http://php.net/supported-versions.php) | ||
68 | - [PHP: Unsupported versions (EOL/End-of-life)](http://php.net/eol.php) | ||
69 | - [PHP 7 Changelog](http://php.net/ChangeLog-7.php) | ||
70 | - [PHP 5 Changelog](http://php.net/ChangeLog-5.php) | ||
71 | - [PHP: Bugs](https://bugs.php.net/) | ||
38 | 72 | ||
39 | ### SSL/TLS configuration | ||
40 | 73 | ||
41 | To setup HTTPS / SSL on your webserver (recommended), you must generate a public/private **key pair** and a **certificate**, and install, configure and activate the appropriate **webserver SSL extension**. | 74 | ## SSL/TLS (HTTPS) |
42 | 75 | ||
43 | #### Let's Encrypt | 76 | We recommend setting up [HTTPS](https://en.wikipedia.org/wiki/HTTPS) (SSL/[TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security)) on your webserver for secure communication between clients and the server. |
44 | 77 | ||
45 | [Let's Encrypt](https://en.wikipedia.org/wiki/Let%27s_Encrypt) is a certificate authority that provides free TLS/X.509 certificates via an automated process. | 78 | ### Let's Encrypt |
46 | 79 | ||
47 | * Install `certbot` using the appropriate method described on https://certbot.eff.org/. | 80 | For public-facing web servers this can be done using free SSL/TLS certificates from [Let's Encrypt](https://en.wikipedia.org/wiki/Let's_Encrypt), a non-profit certificate authority provididing free certificates. |
48 | |||
49 | Location of the `certbot` program and template configuration files may vary depending on which installation method was used. Change the file paths below accordingly. Here is an easy way to create a signed certificate using `certbot`, it assumes `certbot` was installed through APT on a Debian-based distribution: | ||
50 | 81 | ||
51 | * Stop the apache2/nginx service. | 82 | - [How to secure Apache with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-debian-10) |
52 | * Run `certbot --agree-tos --standalone --preferred-challenges tls-sni --email "youremail@example.com" --domain yourdomain.example.com` | 83 | - [How to secure Nginx with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-debian-10) |
53 | * For the Apache webserver, copy `/usr/lib/python2.7/dist-packages/certbot_apache/options-ssl-apache.conf` to `/etc/letsencrypt/options-ssl-apache.conf` (paths may vary depending on installation method) | 84 | - [How To Use Certbot Standalone Mode to Retrieve Let's Encrypt SSL Certificates](https://www.digitalocean.com/community/tutorials/how-to-use-certbot-standalone-mode-to-retrieve-let-s-encrypt-ssl-certificates-on-debian-10). |
54 | * For Nginx: TODO | ||
55 | * Setup your webserver as described below | ||
56 | * Restart the apache2/nginx service. | ||
57 | 85 | ||
58 | #### Self-signed certificates | 86 | In short: |
59 | 87 | ||
60 | If you don't want to request a certificate from Let's Encrypt, or are unable to (for example, webserver on a LAN, or domain name not registered in the public DNS system), you can generate a self-signed certificate. This certificate will trigger security warnings in web browsers, unless you add it to the browser's SSL store manually. | 88 | ```bash |
89 | # install certbot | ||
90 | sudo apt install certbot | ||
61 | 91 | ||
62 | * Apache: run `make-ssl-cert generate-default-snakeoil --force-overwrite` | 92 | # stop your webserver if you already have one running |
63 | * Nginx: TODO | 93 | # certbot in standalone mode needs to bind to port 80 (only needed on initial generation) |
94 | sudo systemctl stop apache2 | ||
95 | sudo systemctl stop nginx | ||
96 | |||
97 | # generate initial certificates | ||
98 | # Let's Encrypt ACME servers must be able to access your server! port forwarding and firewall must be properly configured | ||
99 | sudo certbot certonly --standalone --noninteractive --agree-tos --email "admin@shaarli.mydomain.org" -d shaarli.mydomain.org | ||
100 | # this will generate a private key and certificate at /etc/letsencrypt/live/shaarli.mydomain.org/{privkey,fullchain}.pem | ||
101 | |||
102 | # restart the web server | ||
103 | sudo systemctl start apache2 | ||
104 | sudo systemctl start nginx | ||
105 | ``` | ||
106 | |||
107 | On apache `2.4.43+`, you can also delegate LE certificate management to [mod_md](https://httpd.apache.org/docs/2.4/mod/mod_md.html) [[1](https://www.cyberciti.biz/faq/how-to-secure-apache-with-mod_md-lets-encrypt-on-ubuntu-20-04-lts/)] in which case you don't need certbot and manual SSL configuration in virtualhosts. | ||
108 | |||
109 | ### Self-signed | ||
110 | |||
111 | If you don't want to rely on a certificate authority, or the server can only be accessed from your own network, you can also generate self-signed certificates. Not that this will generate security warnings in web browsers/clients trying to access Shaarli: | ||
112 | |||
113 | - [How To Create a Self-Signed SSL Certificate for Apache](https://www.digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-apache-on-debian-10) | ||
114 | - [How To Create a Self-Signed SSL Certificate for Nginx](https://www.digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-nginx-on-debian-10) | ||
115 | - [How to Create Self-Signed SSL Certificates with OpenSSL](http://www.xenocafe.com/tutorials/linux/centos/openssl/self_signed_certificates/index.php) | ||
116 | - [How do I create my own Certificate Authority?](https://workaround.org/certificate-authority) | ||
64 | 117 | ||
65 | -------------------------------------------------------------------------------- | 118 | -------------------------------------------------------------------------------- |
66 | 119 | ||
67 | ## Apache | 120 | ## Examples |
121 | |||
122 | The following examples assume a Debian-based operating system is installed. On other distributions you may have to adapt details such as package installation procedures, configuration file locations, and webserver username/group (`www-data` or `httpd` are common values). In these examples we assume the document root for your web server/virtualhost is at `/var/www/shaarli.mydomain.org/`: | ||
123 | |||
124 | ```bash | ||
125 | # create the document root (replace with your own domain name) | ||
126 | sudo mkdir -p /var/www/shaarli.mydomain.org/ | ||
127 | ``` | ||
128 | |||
129 | You can install Shaarli at the root of your virtualhost, or in a subdirectory as well. See [Directory structure](Directory-structure) | ||
68 | 130 | ||
69 | Here is a basic configuration example for the Apache web server with `mod_php`. | ||
70 | 131 | ||
71 | In `/etc/apache2/sites-available/shaarli.conf`: | 132 | ### Apache |
133 | |||
134 | ```bash | ||
135 | # Install apache + mod_php and PHP modules | ||
136 | sudo apt update | ||
137 | sudo apt install apache2 libapache2-mod-php php-json php-mbstring php-gd php-intl php-curl php-gettext | ||
138 | |||
139 | # Edit the virtualhost configuration file with your favorite editor (replace the example domain name) | ||
140 | sudo nano /etc/apache2/sites-available/shaarli.mydomain.org.conf | ||
141 | ``` | ||
72 | 142 | ||
73 | ```apache | 143 | ```apache |
74 | <VirtualHost *:443> | 144 | <VirtualHost *:80> |
75 | ServerName shaarli.my-domain.org | 145 | ServerName shaarli.mydomain.org |
76 | DocumentRoot /absolute/path/to/shaarli/ | 146 | DocumentRoot /var/www/shaarli.mydomain.org/ |
147 | |||
148 | # For SSL/TLS certificates acquired with certbot or self-signed certificates | ||
149 | # Redirect HTTP requests to HTTPS, except Let's Encrypt ACME challenge requests | ||
150 | RewriteEngine on | ||
151 | RewriteRule ^.well-known/acme-challenge/ - [L] | ||
152 | RewriteCond %{HTTP_HOST} =shaarli.mydomain.org | ||
153 | RewriteRule ^ https://shaarli.mydomain.org%{REQUEST_URI} [END,NE,R=permanent] | ||
154 | </VirtualHost> | ||
77 | 155 | ||
78 | # Logging | 156 | # SSL/TLS configuration for Let's Encrypt certificates managed with mod_md |
79 | # Possible values include: debug, info, notice, warn, error, crit, alert, emerg. | 157 | #MDomain shaarli.mydomain.org |
80 | LogLevel warn | 158 | #MDCertificateAgreement accepted |
81 | ErrorLog /var/log/apache2/shaarli-error.log | 159 | #MDContactEmail admin@shaarli.mydomain.org |
82 | CustomLog /var/log/apache2/shaarli-access.log combined | 160 | #MDPrivateKeys RSA 4096 |
83 | 161 | ||
84 | # Let's Encrypt SSL configuration (recommended) | 162 | <VirtualHost *:443> |
85 | SSLEngine on | 163 | ServerName shaarli.mydomain.org |
86 | SSLCertificateFile /etc/letsencrypt/live/yourdomain.example.com/fullchain.pem | 164 | DocumentRoot /var/www/shaarli.mydomain.org/ |
87 | SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.example.com/privkey.pem | ||
88 | Include /etc/letsencrypt/options-ssl-apache.conf | ||
89 | 165 | ||
90 | # Self-signed SSL cert configuration | 166 | # SSL/TLS configuration for Let's Encrypt certificates acquired with certbot standalone |
167 | SSLEngine on | ||
168 | SSLCertificateFile /etc/letsencrypt/live/shaarli.mydomain.org/fullchain.pem | ||
169 | SSLCertificateKeyFile /etc/letsencrypt/live/shaarli.mydomain.org/privkey.pem | ||
170 | # Let's Encrypt settings from https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/_internal/tls_configs/current-options-ssl-apache.conf | ||
171 | SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 | ||
172 | SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 | ||
173 | SSLHonorCipherOrder off | ||
174 | SSLSessionTickets off | ||
175 | SSLOptions +StrictRequire | ||
176 | |||
177 | # SSL/TLS configuration for self-signed certificates | ||
91 | #SSLEngine on | 178 | #SSLEngine on |
92 | #SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem | 179 | #SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem |
93 | #SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key | 180 | #SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key |
@@ -98,345 +185,262 @@ In `/etc/apache2/sites-available/shaarli.conf`: | |||
98 | #php_value error_reporting 2147483647 | 185 | #php_value error_reporting 2147483647 |
99 | #php_value error_log /var/log/apache2/shaarli-php-error.log | 186 | #php_value error_log /var/log/apache2/shaarli-php-error.log |
100 | 187 | ||
101 | <Directory /absolute/path/to/shaarli/> | 188 | <Directory /var/www/shaarli.mydomain.org/> |
102 | #Required for .htaccess support | 189 | # Required for .htaccess support |
103 | AllowOverride All | 190 | AllowOverride All |
104 | Order allow,deny | 191 | Require all granted |
105 | Allow from all | ||
106 | |||
107 | Options Indexes FollowSymLinks MultiViews #TODO is Indexes/Multiviews required? | ||
108 | |||
109 | # Optional - required for playvideos plugin | ||
110 | #Header set Content-Security-Policy "script-src 'self' 'unsafe-inline' https://www.youtube.com https://s.ytimg.com 'unsafe-eval'" | ||
111 | </Directory> | 192 | </Directory> |
112 | 193 | ||
113 | </VirtualHost> | 194 | <LocationMatch "/\."> |
114 | ``` | 195 | # Prevent accessing dotfiles |
115 | 196 | RedirectMatch 404 ".*" | |
116 | Enable this configuration with `sudo a2ensite shaarli` | 197 | </LocationMatch> |
117 | |||
118 | _Note: If you use Apache 2.2 or lower, you need [mod_version](https://httpd.apache.org/docs/current/mod/mod_version.html) to be installed and enabled._ | ||
119 | 198 | ||
120 | _Note: Apache module `mod_rewrite` must be enabled to use the REST API._ | 199 | <LocationMatch "\.(?:ico|css|js|gif|jpe?g|png)$"> |
200 | # allow client-side caching of static files | ||
201 | Header set Cache-Control "max-age=2628000, public, must-revalidate, proxy-revalidate" | ||
202 | </LocationMatch> | ||
121 | 203 | ||
204 | # serve the Shaarli favicon from its custom location | ||
205 | Alias favicon.ico /var/www/shaarli.mydomain.org/images/favicon.ico | ||
122 | 206 | ||
123 | ## Nginx | 207 | </VirtualHost> |
208 | ``` | ||
124 | 209 | ||
125 | Here is a basic configuration example for the Nginx web server, using the [php-fpm](http://php-fpm.org) PHP FastCGI Process Manager, and Nginx's [FastCGI](https://en.wikipedia.org/wiki/FastCGI) module. | 210 | ```bash |
211 | # Enable the virtualhost | ||
212 | sudo a2ensite shaarli.mydomain.org | ||
126 | 213 | ||
127 | <!--- TODO refactor everything below this point ---> | 214 | # mod_ssl must be enabled to use TLS/SSL certificates |
215 | # https://httpd.apache.org/docs/current/mod/mod_ssl.html | ||
216 | sudo a2enmod ssl | ||
128 | 217 | ||
129 | ### Common setup | 218 | # mod_rewrite must be enabled to use the REST API |
130 | Once Nginx and PHP-FPM are installed, we need to ensure: | 219 | # https://httpd.apache.org/docs/current/mod/mod_rewrite.html |
220 | sudo a2enmod rewrite | ||
131 | 221 | ||
132 | - Nginx and PHP-FPM are running using the _same user and group_ | 222 | # mod_headers must be enabled to set custom headers from the server config |
133 | - both these user and group have | 223 | sudo a2enmod headers |
134 | - `read` permissions for Shaarli resources | ||
135 | - `execute` permissions for Shaarli directories _AND_ their parent directories | ||
136 | 224 | ||
137 | On a production server: | 225 | # mod_version must only be enabled if you use Apache 2.2 or lower |
226 | # https://httpd.apache.org/docs/current/mod/mod_version.html | ||
227 | # sudo a2enmod version | ||
138 | 228 | ||
139 | - `user:group` will likely be `http:http`, `www:www` or `www-data:www-data` | 229 | # restart the apache service |
140 | - files will be located under `/var/www`, `/var/http` or `/usr/share/nginx` | 230 | sudo systemctl restart apache2 |
231 | ``` | ||
141 | 232 | ||
142 | On a development server: | 233 | - [How to install the Apache web server](https://www.digitalocean.com/community/tutorials/how-to-install-the-apache-web-server-on-debian-10) |
234 | - [Apache/PHP - error log per VirtualHost - StackOverflow](http://stackoverflow.com/q/176) | ||
235 | - [Apache - PHP: php_value vs php_admin_value and the use of php_flag explained](https://ma.ttias.be/php-php_value-vs-php_admin_value-and-the-use-of-php_flag-explained/) | ||
236 | - [Server-side TLS (Apache) - Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS#Apache) | ||
237 | - [Apache 2.4 documentation](https://httpd.apache.org/docs/2.4/) | ||
238 | - [Apache mod_proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html) | ||
239 | - [Apache Reverse Proxy Request Headers](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#x-headers) | ||
143 | 240 | ||
144 | - files may be located in a user's home directory | ||
145 | - in this case, make sure both Nginx and PHP-FPM are running as the local user/group! | ||
146 | 241 | ||
147 | For all following configuration examples, this user/group pair will be used: | 242 | ### Nginx |
148 | 243 | ||
149 | - `user:group = john:users`, | 244 | This examples uses nginx and the [PHP-FPM](https://www.digitalocean.com/community/tutorials/how-to-install-linux-nginx-mariadb-php-lemp-stack-on-debian-10#step-3-%E2%80%94-installing-php-for-processing) PHP interpreter. Nginx and PHP-FPM must be running using the same user and group, here we assume the user/group to be `www-data:www-data`. |
150 | 245 | ||
151 | which corresponds to the following service configuration: | ||
152 | 246 | ||
153 | ```ini | 247 | ```bash |
154 | ; /etc/php/php-fpm.conf | 248 | # install nginx and php-fpm |
155 | user = john | 249 | sudo apt update |
156 | group = users | 250 | sudo apt install nginx php-fpm |
157 | 251 | ||
158 | [...] | 252 | # Edit the virtualhost configuration file with your favorite editor |
159 | listen.owner = john | 253 | sudo nano /etc/nginx/sites-available/shaarli.mydomain.org |
160 | listen.group = users | ||
161 | ``` | 254 | ``` |
162 | 255 | ||
163 | ```nginx | 256 | ```nginx |
164 | # /etc/nginx/nginx.conf | 257 | server { |
165 | user john users; | 258 | listen 80; |
259 | server_name shaarli.mydomain.org; | ||
166 | 260 | ||
167 | http { | 261 | # redirect all plain HTTP requests to HTTPS |
168 | [...] | 262 | return 301 https://shaarli.mydomain.org$request_uri; |
169 | } | 263 | } |
170 | ``` | ||
171 | |||
172 | ### (Optional) Increase the maximum file upload size | ||
173 | Some bookmark dumps generated by web browsers can be _huge_ due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders. | ||
174 | |||
175 | To increase upload size, you will need to modify both nginx and PHP configuration: | ||
176 | |||
177 | ```nginx | ||
178 | # /etc/nginx/nginx.conf | ||
179 | |||
180 | http { | ||
181 | [...] | ||
182 | |||
183 | client_max_body_size 10m; | ||
184 | |||
185 | [...] | ||
186 | } | ||
187 | ``` | ||
188 | |||
189 | ```ini | ||
190 | # /etc/php5/fpm/php.ini | ||
191 | |||
192 | [...] | ||
193 | post_max_size = 10M | ||
194 | [...] | ||
195 | upload_max_filesize = 10M | ||
196 | ``` | ||
197 | 264 | ||
198 | ### Minimal | 265 | server { |
199 | _WARNING: Use for development only!_ | 266 | # ipv4 listening port/protocol |
200 | 267 | listen 443 ssl http2; | |
201 | ```nginx | 268 | # ipv6 listening port/protocol |
202 | user john users; | 269 | listen [::]:443 ssl http2; |
203 | worker_processes 1; | 270 | server_name shaarli.mydomain.org; |
204 | events { | 271 | root /var/www/shaarli.mydomain.org; |
205 | worker_connections 1024; | 272 | |
206 | } | 273 | # log file locations |
274 | # combined log format prepends the virtualhost/domain name to log entries | ||
275 | access_log /var/log/nginx/access.log combined; | ||
276 | error_log /var/log/nginx/error.log; | ||
207 | 277 | ||
208 | http { | 278 | # paths to private key and certificates for SSL/TLS |
209 | include mime.types; | 279 | ssl_certificate /etc/ssl/shaarli.mydomain.org.crt; |
210 | default_type application/octet-stream; | 280 | ssl_certificate_key /etc/ssl/private/shaarli.mydomain.org.key; |
211 | keepalive_timeout 20; | 281 | |
212 | 282 | # Let's Encrypt SSL settings from https://github.com/certbot/certbot/blob/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf | |
213 | index index.html index.php; | 283 | ssl_session_cache shared:le_nginx_SSL:10m; |
214 | 284 | ssl_session_timeout 1440m; | |
215 | server { | 285 | ssl_session_tickets off; |
216 | listen 80; | 286 | ssl_protocols TLSv1.2 TLSv1.3; |
217 | server_name localhost; | 287 | ssl_prefer_server_ciphers off; |
218 | root /home/john/web; | 288 | ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; |
219 | 289 | ||
220 | access_log /var/log/nginx/access.log; | 290 | # increase the maximum file upload size if needed: by default nginx limits file upload to 1MB (413 Entity Too Large error) |
221 | error_log /var/log/nginx/error.log; | 291 | client_max_body_size 100m; |
222 | 292 | ||
223 | location /shaarli/ { | 293 | # relative path to shaarli from the root of the webserver |
224 | try_files $uri /shaarli/index.php$is_args$args; | 294 | location / { |
225 | access_log /var/log/nginx/shaarli.access.log; | 295 | # default index file when no file URI is requested |
226 | error_log /var/log/nginx/shaarli.error.log; | 296 | index index.php; |
227 | } | 297 | try_files $uri /index.php$is_args$args; |
228 | |||
229 | location ~ (index)\.php$ { | ||
230 | try_files $uri =404; | ||
231 | fastcgi_split_path_info ^(.+\.php)(/.+)$; | ||
232 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; | ||
233 | fastcgi_index index.php; | ||
234 | include fastcgi.conf; | ||
235 | } | ||
236 | } | 298 | } |
237 | } | ||
238 | ``` | ||
239 | 299 | ||
240 | ### Modular | 300 | location ~ (index)\.php$ { |
241 | The previous setup is sufficient for development purposes, but has several major caveats: | 301 | try_files $uri =404; |
302 | # slim API - split URL path into (script_filename, path_info) | ||
303 | fastcgi_split_path_info ^(.+\.php)(/.+)$; | ||
304 | # pass PHP requests to PHP-FPM | ||
305 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; | ||
306 | fastcgi_index index.php; | ||
307 | include fastcgi.conf; | ||
308 | } | ||
242 | 309 | ||
243 | - every content that does not match the PHP rule will be sent to client browsers: | 310 | location ~ \.php$ { |
244 | - dotfiles - in our case, `.htaccess` | 311 | # deny access to all other PHP scripts |
245 | - temporary files, e.g. Vim or Emacs files: `index.php~` | 312 | # disable this if you host other PHP applications on the same virtualhost |
246 | - asset / static resource caching is not optimized | 313 | deny all; |
247 | - if serving several PHP sites, there will be a lot of duplication: `location /shaarli/`, `location /mysite/`, etc. | 314 | } |
248 | 315 | ||
249 | To solve this, we will split Nginx configuration in several parts, that will be included when needed: | 316 | location ~ /\. { |
317 | # deny access to dotfiles | ||
318 | deny all; | ||
319 | } | ||
250 | 320 | ||
251 | ```nginx | 321 | location ~ ~$ { |
252 | # /etc/nginx/deny.conf | 322 | # deny access to temp editor files, e.g. "script.php~" |
253 | location ~ /\. { | 323 | deny all; |
254 | # deny access to dotfiles | 324 | } |
255 | access_log off; | ||
256 | log_not_found off; | ||
257 | deny all; | ||
258 | } | ||
259 | 325 | ||
260 | location ~ ~$ { | 326 | location = /favicon.ico { |
261 | # deny access to temp editor files, e.g. "script.php~" | 327 | # serve the Shaarli favicon from its custom location |
262 | access_log off; | 328 | alias /var/www/shaarli/images/favicon.ico; |
263 | log_not_found off; | 329 | } |
264 | deny all; | ||
265 | } | ||
266 | ``` | ||
267 | 330 | ||
268 | ```nginx | 331 | # allow client-side caching of static files |
269 | # /etc/nginx/php.conf | 332 | location ~* \.(?:ico|css|js|gif|jpe?g|png)$ { |
270 | location ~ (index)\.php$ { | 333 | expires max; |
271 | # Slim - split URL path into (script_filename, path_info) | 334 | add_header Cache-Control "public, must-revalidate, proxy-revalidate"; |
272 | try_files $uri =404; | 335 | # HTTP 1.0 compatibility |
273 | fastcgi_split_path_info ^(.+\.php)(/.+)$; | 336 | add_header Pragma public; |
274 | 337 | } | |
275 | # filter and proxy PHP requests to PHP-FPM | ||
276 | fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; | ||
277 | fastcgi_index index.php; | ||
278 | include fastcgi.conf; | ||
279 | } | ||
280 | 338 | ||
281 | location ~ \.php$ { | ||
282 | # deny access to all other PHP scripts | ||
283 | deny all; | ||
284 | } | 339 | } |
285 | ``` | 340 | ``` |
286 | 341 | ||
287 | ```nginx | 342 | ```bash |
288 | # /etc/nginx/static_assets.conf | 343 | # enable the configuration/virtualhost |
289 | location ~* \.(?:ico|css|js|gif|jpe?g|png)$ { | 344 | sudo ln -s /etc/nginx/sites-available/shaarli.mydomain.org /etc/nginx/sites-enabled/shaarli.mydomain.org |
290 | expires max; | 345 | # reload nginx configuration |
291 | add_header Pragma public; | 346 | sudo systemctl reload nginx |
292 | add_header Cache-Control "public, must-revalidate, proxy-revalidate"; | ||
293 | } | ||
294 | ``` | 347 | ``` |
295 | 348 | ||
296 | ```nginx | 349 | - [How to install the Nginx web server](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-debian-10) |
297 | # /etc/nginx/nginx.conf | 350 | - [Nginx Beginner's guide](http://nginx.org/en/docs/beginners_guide.html) |
298 | [...] | 351 | - [Nginx documentation](https://nginx.org/en/docs/) |
299 | 352 | - [Nginx ngx_http_fastcgi_module](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html) | |
300 | http { | 353 | - [Nginx Pitfalls](http://wiki.nginx.org/Pitfalls) |
301 | [...] | 354 | - [Nginx PHP configuration examples - Karl Blessing](http://kbeezie.com/nginx-configuration-examples/) |
302 | 355 | - [Server-side TLS (Nginx) - Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS#Nginx) | |
303 | root /home/john/web; | ||
304 | access_log /var/log/nginx/access.log; | ||
305 | error_log /var/log/nginx/error.log; | ||
306 | 356 | ||
307 | server { | ||
308 | # virtual host for a first domain | ||
309 | listen 80; | ||
310 | server_name my.first.domain.org; | ||
311 | 357 | ||
312 | location /shaarli/ { | ||
313 | # Slim - rewrite URLs | ||
314 | try_files $uri /shaarli/index.php$is_args$args; | ||
315 | 358 | ||
316 | access_log /var/log/nginx/shaarli.access.log; | 359 | ## Reverse proxies |
317 | error_log /var/log/nginx/shaarli.error.log; | ||
318 | } | ||
319 | 360 | ||
320 | location = /shaarli/favicon.ico { | 361 | If Shaarli is hosted on a server behind a [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) (i.e. there is a proxy server between clients and the web server hosting Shaarli), configure it accordingly. See [Reverse proxy](Reverse-proxy.md) configuration. |
321 | # serve the Shaarli favicon from its custom location | ||
322 | alias /var/www/shaarli/images/favicon.ico; | ||
323 | } | ||
324 | 362 | ||
325 | include deny.conf; | ||
326 | include static_assets.conf; | ||
327 | include php.conf; | ||
328 | } | ||
329 | 363 | ||
330 | server { | ||
331 | # virtual host for a second domain | ||
332 | listen 80; | ||
333 | server_name second.domain.com; | ||
334 | 364 | ||
335 | location /minigal/ { | 365 | ## Allow import of large browser bookmarks export |
336 | access_log /var/log/nginx/minigal.access.log; | ||
337 | error_log /var/log/nginx/minigal.error.log; | ||
338 | } | ||
339 | 366 | ||
340 | include deny.conf; | 367 | Web browser bookmark exports can be large due to the presence of base64-encoded images and favicons/long subfolder names. Edit the PHP configuration file. |
341 | include static_assets.conf; | ||
342 | include php.conf; | ||
343 | } | ||
344 | } | ||
345 | ``` | ||
346 | 368 | ||
347 | ### Redirect HTTP to HTTPS | 369 | - Apache: `/etc/php/<PHP_VERSION>/apache2/php.ini` |
348 | Assuming you have generated a (self-signed) key and certificate, and they are | 370 | - Nginx + PHP-FPM: `/etc/php/<PHP_VERSION>/fpm/php.ini` (in addition to `client_max_body_size` in the [Nginx configuration](#nginx)) |
349 | located under `/home/john/ssl/localhost.{key,crt}`, it is pretty straightforward | ||
350 | to set an HTTP (:80) to HTTPS (:443) redirection to force SSL/TLS usage. | ||
351 | 371 | ||
352 | ```nginx | 372 | ```ini |
353 | # /etc/nginx/nginx.conf | ||
354 | [...] | 373 | [...] |
374 | # (optional) increase the maximum file upload size: | ||
375 | post_max_size = 100M | ||
376 | [...] | ||
377 | # (optional) increase the maximum file upload size: | ||
378 | upload_max_filesize = 100M | ||
379 | ``` | ||
355 | 380 | ||
356 | http { | 381 | To verify PHP settings currently set on the server, create a `phpinfo.php` in your webserver's document root |
357 | [...] | ||
358 | |||
359 | index index.html index.php; | ||
360 | |||
361 | root /home/john/web; | ||
362 | access_log /var/log/nginx/access.log; | ||
363 | error_log /var/log/nginx/error.log; | ||
364 | |||
365 | server { | ||
366 | listen 80; | ||
367 | server_name localhost; | ||
368 | 382 | ||
369 | return 301 https://localhost$request_uri; | 383 | ```bash |
370 | } | 384 | # example |
385 | echo '<?php phpinfo(); ?>' | sudo tee /var/www/shaarli.mydomain.org/phpinfo.php | ||
386 | #give read-only access to this file to the webserver user | ||
387 | sudo chown www-data:root /var/www/shaarli.mydomain.org/phpinfo.php | ||
388 | sudo chmod 0400 /var/www/shaarli.mydomain.org/phpinfo.php | ||
389 | ``` | ||
371 | 390 | ||
372 | server { | 391 | Access the file from a web browser (eg. <https://shaarli.mydomain.org/phpinfo.php> and look at the _Loaded Configuration File_ and _Scan this dir for additional .ini files_ entries |
373 | listen 443 ssl; | ||
374 | server_name localhost; | ||
375 | 392 | ||
376 | ssl_certificate /home/john/ssl/localhost.crt; | 393 | It is recommended to remove the `phpinfo.php` when no longer needed as it publicly discloses details about your webserver configuration. |
377 | ssl_certificate_key /home/john/ssl/localhost.key; | ||
378 | 394 | ||
379 | location /shaarli/ { | ||
380 | # Slim - rewrite URLs | ||
381 | try_files $uri /index.php$is_args$args; | ||
382 | 395 | ||
383 | access_log /var/log/nginx/shaarli.access.log; | 396 | ## Robots and crawlers |
384 | error_log /var/log/nginx/shaarli.error.log; | ||
385 | } | ||
386 | 397 | ||
387 | location = /shaarli/favicon.ico { | 398 | To opt-out of indexing your Shaarli instance by search engines, create a `robots.txt` file at the root of your virtualhost: |
388 | # serve the Shaarli favicon from its custom location | ||
389 | alias /var/www/shaarli/images/favicon.ico; | ||
390 | } | ||
391 | 399 | ||
392 | include deny.conf; | 400 | ``` |
393 | include static_assets.conf; | 401 | User-agent: * |
394 | include php.conf; | 402 | Disallow: / |
395 | } | ||
396 | } | ||
397 | ``` | 403 | ``` |
398 | 404 | ||
399 | ## Proxies | 405 | By default Shaarli already disallows indexing of your local copy of the documentation by default, using `<meta name="robots">` HTML tags. Your Shaarli instance may still be indexed by various robots on the public Internet, that do not respect this header or the robots standard. |
400 | |||
401 | If Shaarli is served behind a proxy (i.e. there is a proxy server between clients and the web server hosting Shaarli), please refer to the proxy server documentation for proper configuration. In particular, you have to ensure that the following server variables are properly set: | ||
402 | 406 | ||
403 | - `X-Forwarded-Proto` | 407 | - [Robots exclusion standard](https://en.wikipedia.org/wiki/Robots_exclusion_standard) |
404 | - `X-Forwarded-Host` | 408 | - [Introduction to robots.txt](https://support.google.com/webmasters/answer/6062608?hl=en) |
405 | - `X-Forwarded-For` | 409 | - [Robots meta tag, data-nosnippet, and X-Robots-Tag specifications](https://developers.google.com/search/reference/robots_meta_tag) |
410 | - [About robots.txt](http://www.robotstxt.org) | ||
411 | - [About the robots META tag](https://www.robotstxt.org/meta.html) | ||
406 | 412 | ||
407 | In you [Shaarli configuration](Shaarli-configuration) `data/config.json.php`, add the public IP of your proxy under `security.trusted_proxies`. | ||
408 | 413 | ||
409 | See also [proxy-related](https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&q=label%3Aproxy+) issues. | 414 | ## Fail2ban |
410 | 415 | ||
411 | ## Robots and crawlers | 416 | [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page) is an intrusion prevention framework that reads server (Apache, SSH, etc.) and uses `iptables` profiles to block brute-force attempts. You need to create a filter to detect shaarli login failures in logs, and a jail configuation to configure the behavior when failed login attempts are detected: |
412 | 417 | ||
413 | Shaarli disallows indexing and crawling of your local documentation pages by search engines, using `<meta name="robots">` HTML tags. | 418 | ```ini |
414 | Your Shaarli instance and other pages you host may still be indexed by various robots on the public Internet. | 419 | # /etc/fail2ban/filter.d/shaarli-auth.conf |
415 | You may want to setup a robots.txt file or other crawler control mechanism on your server. | 420 | [INCLUDES] |
416 | See [[1]](https://en.wikipedia.org/wiki/Robots_exclusion_standard), [[2]](https://support.google.com/webmasters/answer/6062608?hl=en) and [[3]](https://developers.google.com/search/reference/robots_meta_tag) | 421 | before = common.conf |
422 | [Definition] | ||
423 | failregex = \s-\s<HOST>\s-\sLogin failed for user.*$ | ||
424 | ignoreregex = | ||
425 | ``` | ||
417 | 426 | ||
418 | ## See also | 427 | ```ini |
428 | # /etc/fail2ban/jail.local | ||
429 | [shaarli-auth] | ||
430 | enabled = true | ||
431 | port = https,http | ||
432 | filter = shaarli-auth | ||
433 | logpath = /var/www/shaarli.mydomain.org/data/log.txt | ||
434 | # allow 3 login attempts per IP address | ||
435 | # (over a period specified by findtime = in /etc/fail2ban/jail.conf) | ||
436 | maxretry = 3 | ||
437 | # permanently ban the IP address after reaching the limit | ||
438 | bantime = -1 | ||
439 | ``` | ||
419 | 440 | ||
420 | * [Server security](Server-security.md) | 441 | Then restart the service: `sudo systemctl restart fail2ban` |
421 | 442 | ||
422 | #### Webservers | ||
423 | 443 | ||
424 | - [Apache/PHP - error log per VirtualHost](http://stackoverflow.com/q/176) (StackOverflow) | 444 | ## What next? |
425 | - [Apache - PHP: php_value vs php_admin_value and the use of php_flag explained](https://ma.ttias.be/php-php_value-vs-php_admin_value-and-the-use-of-php_flag-explained/) | ||
426 | - [Server-side TLS (Apache)](https://wiki.mozilla.org/Security/Server_Side_TLS#Apache) (Mozilla) | ||
427 | - [Nginx Beginner's guide](http://nginx.org/en/docs/beginners_guide.html) | ||
428 | - [Nginx ngx_http_fastcgi_module](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html) | ||
429 | - [Nginx Pitfalls](http://wiki.nginx.org/Pitfalls) | ||
430 | - [Nginx PHP configuration examples](http://kbeezie.com/nginx-configuration-examples/) (Karl Blessing) | ||
431 | - [Server-side TLS (Nginx)](https://wiki.mozilla.org/Security/Server_Side_TLS#Nginx) (Mozilla) | ||
432 | - [How to Create Self-Signed SSL Certificates with OpenSSL](http://www.xenocafe.com/tutorials/linux/centos/openssl/self_signed_certificates/index.php) | ||
433 | - [How do I create my own Certificate Authority?](https://workaround.org/certificate-authority) | ||
434 | |||
435 | #### PHP | ||
436 | 445 | ||
437 | - [Travis configuration](https://github.com/shaarli/Shaarli/blob/master/.travis.yml) | 446 | [Shaarli installation](Installation.md) |
438 | - [PHP: Supported versions](http://php.net/supported-versions.php) | ||
439 | - [PHP: Unsupported versions](http://php.net/eol.php) _(EOL - End Of Life)_ | ||
440 | - [PHP 7 Changelog](http://php.net/ChangeLog-7.php) | ||
441 | - [PHP 5 Changelog](http://php.net/ChangeLog-5.php) | ||
442 | - [PHP: Bugs](https://bugs.php.net/) | ||
diff --git a/doc/md/Server-security.md b/doc/md/Server-security.md deleted file mode 100644 index 700084e2..00000000 --- a/doc/md/Server-security.md +++ /dev/null | |||
@@ -1,76 +0,0 @@ | |||
1 | ## php.ini | ||
2 | PHP settings are defined in: | ||
3 | |||
4 | - a main configuration file, usually found under `/etc/php5/php.ini`; some distributions provide different configuration environments, e.g. | ||
5 | - `/etc/php5/php.ini` - used when running console scripts | ||
6 | - `/etc/php5/apache2/php.ini` - used when a client requests PHP resources from Apache | ||
7 | - `/etc/php5/php-fpm.conf` - used when PHP requests are proxied to PHP-FPM | ||
8 | - additional configuration files/entries, depending on the installed/enabled extensions: | ||
9 | - `/etc/php/conf.d/xdebug.ini` | ||
10 | |||
11 | ### Locate .ini files | ||
12 | #### Console environment | ||
13 | ```bash | ||
14 | $ php --ini | ||
15 | Configuration File (php.ini) Path: /etc/php | ||
16 | Loaded Configuration File: /etc/php/php.ini | ||
17 | Scan for additional .ini files in: /etc/php/conf.d | ||
18 | Additional .ini files parsed: /etc/php/conf.d/xdebug.ini | ||
19 | ``` | ||
20 | |||
21 | #### Server environment | ||
22 | - create a `phpinfo.php` script located in a path supported by the web server, e.g. | ||
23 | - Apache (with user dirs enabled): `/home/myself/public_html/phpinfo.php` | ||
24 | - `/var/www/test/phpinfo.php` | ||
25 | - make sure the script is readable by the web server user/group (usually, `www`, `www-data` or `httpd`) | ||
26 | - access the script from a web browser | ||
27 | - look at the _Loaded Configuration File_ and _Scan this dir for additional .ini files_ entries | ||
28 | ```php | ||
29 | <?php phpinfo(); ?> | ||
30 | ``` | ||
31 | |||
32 | ## fail2ban | ||
33 | `fail2ban` is an intrusion prevention framework that reads server (Apache, SSH, etc.) and uses `iptables` profiles to block brute-force attempts: | ||
34 | |||
35 | - [Official website](http://www.fail2ban.org/wiki/index.php/Main_Page) | ||
36 | - [Source code](https://github.com/fail2ban/fail2ban) | ||
37 | |||
38 | ### Read Shaarli logs to ban IPs | ||
39 | Example configuration: | ||
40 | - allow 3 login attempts per IP address | ||
41 | - after 3 failures, permanently ban the corresponding IP adddress | ||
42 | |||
43 | `/etc/fail2ban/jail.local` | ||
44 | ```ini | ||
45 | [shaarli-auth] | ||
46 | enabled = true | ||
47 | port = https,http | ||
48 | filter = shaarli-auth | ||
49 | logpath = /var/www/path/to/shaarli/data/log.txt | ||
50 | maxretry = 3 | ||
51 | bantime = -1 | ||
52 | ``` | ||
53 | |||
54 | `/etc/fail2ban/filter.d/shaarli-auth.conf` | ||
55 | ```ini | ||
56 | [INCLUDES] | ||
57 | before = common.conf | ||
58 | [Definition] | ||
59 | failregex = \s-\s<HOST>\s-\sLogin failed for user.*$ | ||
60 | ignoreregex = | ||
61 | ``` | ||
62 | |||
63 | ## Robots - Restricting search engines and web crawler traffic | ||
64 | |||
65 | Creating a `robots.txt` with the following contents at the root of your Shaarli installation will prevent _honest_ web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic. | ||
66 | |||
67 | ``` | ||
68 | User-agent: * | ||
69 | Disallow: / | ||
70 | ``` | ||
71 | |||
72 | See: | ||
73 | |||
74 | - http://www.robotstxt.org | ||
75 | - http://www.robotstxt.org/robotstxt.html | ||
76 | - http://www.robotstxt.org/meta.html | ||
diff --git a/doc/md/Shaarli-configuration.md b/doc/md/Shaarli-configuration.md index 664e36dd..263fb761 100644 --- a/doc/md/Shaarli-configuration.md +++ b/doc/md/Shaarli-configuration.md | |||
@@ -1,126 +1,24 @@ | |||
1 | ## Foreword | 1 | # Shaarli configuration |
2 | |||
3 | **Do not edit configuration options in index.php! Your changes would be lost.** | ||
4 | 2 | ||
5 | Once your Shaarli instance is installed, the file `data/config.json.php` is generated: | 3 | Once your Shaarli instance is installed, the file `data/config.json.php` is generated: |
6 | * it contains all settings in JSON format, and can be edited to customize values | ||
7 | * it defines which [plugins](Plugin-System) are enabled | ||
8 | * its values override those defined in `index.php` | ||
9 | * it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration | ||
10 | |||
11 | ## File and directory permissions | ||
12 | |||
13 | The server process running Shaarli must have: | ||
14 | |||
15 | - `read` access to the following resources: | ||
16 | - PHP scripts: `index.php`, `application/*.php`, `plugins/*.php` | ||
17 | - 3rd party PHP and Javascript libraries: `inc/*.php`, `inc/*.js` | ||
18 | - static assets: | ||
19 | - CSS stylesheets: `inc/*.css` | ||
20 | - `images/*` | ||
21 | - RainTPL templates: `tpl/*.html` | ||
22 | - `read`, `write` and `execution` access to the following directories: | ||
23 | - `cache` - thumbnail cache | ||
24 | - `data` - link data store, configuration options | ||
25 | - `pagecache` - Atom/RSS feed cache | ||
26 | - `tmp` - RainTPL page cache | ||
27 | |||
28 | On a Linux distribution: | ||
29 | |||
30 | - the web server user will likely be `www` or `http` (for Apache2) | ||
31 | - it will be a member of a group of the same name: `www:www`, `http:http` | ||
32 | - to give it access to Shaarli, either: | ||
33 | - unzip Shaarli in the default web server location (usually `/var/www/`) and set the web server user as the owner | ||
34 | - put users in the same group as the web server, and set the appropriate access rights | ||
35 | - if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly | ||
36 | |||
37 | ## Configuration | ||
38 | |||
39 | In `data/config.json.php`. | ||
40 | |||
41 | See also [Plugin System](Plugin-System). | ||
42 | |||
43 | ### Credentials | ||
44 | |||
45 | _These settings should not be edited_ | ||
46 | |||
47 | - **login**: Login username. | ||
48 | - **hash**: Generated password hash. | ||
49 | - **salt**: Password salt. | ||
50 | |||
51 | ### General | ||
52 | |||
53 | - **title**: Shaarli's instance title. | ||
54 | - **header_link**: Link to the homepage. | ||
55 | - **links_per_page**: Number of shaares displayed per page. | ||
56 | - **timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php). | ||
57 | - **enabled_plugins**: List of enabled plugins. | ||
58 | - **default_note_title**: Default title of a new note. | ||
59 | - **retrieve_description** (boolean): If set to true, for every new links Shaarli will try | ||
60 | to retrieve the description and keywords from the HTML meta tags. | ||
61 | |||
62 | ### Security | ||
63 | |||
64 | - **session_protection_disabled**: Disable session cookie hijacking protection (not recommended). | ||
65 | It might be useful if your IP adress often changes. | ||
66 | - **ban_after**: Failed login attempts before being IP banned. | ||
67 | - **ban_duration**: IP ban duration in seconds. | ||
68 | - **open_shaarli**: Anyone can add a new link while logged out if enabled. | ||
69 | - **trusted_proxies**: List of trusted IP which won't be banned after failed login attemps. Useful if Shaarli is behind a reverse proxy. | ||
70 | - **allowed_protocols**: List of allowed protocols in shaare URLs or markdown-rendered descriptions. Useful if you want to store `javascript:` links (bookmarklets) in Shaarli (default: `["ftp", "ftps", "magnet"]`). | ||
71 | |||
72 | ### Resources | ||
73 | 4 | ||
74 | - **data_dir**: Data directory. | 5 | - it contains all settings in JSON format, and can be edited to customize values |
75 | - **datastore**: Shaarli's links database file path. | 6 | - it defines which [plugins](Plugins.md) are enabled |
76 | - **history**: Shaarli's operation history file path. | 7 | - its values override those defined in `index.php` |
77 | - **updates**: File path for the ran updates file. | 8 | - it is wrapped in a PHP comment so that its contents are never served by the web server, regardless of configuration |
78 | - **log**: Log file path. | ||
79 | - **update_check**: Last update check file path. | ||
80 | - **raintpl_tpl**: Templates directory. | ||
81 | - **raintpl_tmp**: Template engine cache directory. | ||
82 | - **thumbnails_cache**: Thumbnails cache directory. | ||
83 | - **page_cache**: Shaarli's internal cache directory. | ||
84 | - **ban_file**: Banned IP file path. | ||
85 | 9 | ||
86 | ### Translation | 10 | **Do not edit configuration options in index.php! Your changes would be lost.** |
87 | 11 | ||
88 | - **language**: translation language (also see [Translations](Translations)) | 12 | ## Tools menu |
89 | - **auto** (default): The translation language is chosen from the browser locale. | ||
90 | It means that the language can be different for 2 different visitors depending on their locale. | ||
91 | - **en**: Use the English translation. | ||
92 | - **fr**: Use the French translation. | ||
93 | - **mode**: | ||
94 | - **auto** or **php** (default): Use the PHP implementation of gettext (slower) | ||
95 | - **gettext**: Use PHP builtin gettext extension | ||
96 | (faster, but requires `php-gettext` to be installed and to reload the web server on update) | ||
97 | - **extension**: Translation extensions for custom themes or plugins. | ||
98 | Must be an associative array: `translation domain => translation path`. | ||
99 | 13 | ||
100 | ### Updates | 14 | Some settings can be configured directly from a web browser by accesing the `Tools` menu. Values are read/written to/from the configuration file. |
101 | 15 | ||
102 | - **check_updates**: Enable or disable update check to the git repository. | 16 |  |
103 | - **check_updates_branch**: Git branch used to check updates (e.g. `stable` or `master`). | ||
104 | - **check_updates_interval**: Look for new version every N seconds (default: every day). | ||
105 | 17 | ||
106 | ### Privacy | 18 | ### LDAP |
107 | 19 | ||
108 | - **default_private_links**: Check the private checkbox by default for every new link. | 20 | - **host**: LDAP host used for user authentication |
109 | - **hide_public_links**: All links are hidden while logged out. | 21 | - **dn**: user DN template (`sprintf` format, `%s` being replaced by user login) |
110 | - **force_login**: if **hide_public_links** and this are set to `true`, all anonymous users are redirected to the login page. | ||
111 | - **hide_timestamps**: Timestamps are hidden. | ||
112 | - **remember_user_default**: Default state of the login page's *remember me* checkbox | ||
113 | - `true`: checked by default, `false`: unchecked by default | ||
114 | |||
115 | ### Feed | ||
116 | |||
117 | - **rss_permalinks**: Enable this to redirect RSS links to Shaarli's permalinks instead of shaared URL. | ||
118 | - **show_atom**: Display ATOM feed button. | ||
119 | |||
120 | ### Thumbnail | ||
121 | |||
122 | - **enable_thumbnails**: Enable or disable thumbnail display. | ||
123 | - **enable_localcache**: Enable or disable local cache. | ||
124 | 22 | ||
125 | ## Configuration file example | 23 | ## Configuration file example |
126 | 24 | ||
@@ -177,6 +75,9 @@ Must be an associative array: `translation domain => translation path`. | |||
177 | "title": "My Shaarli", | 75 | "title": "My Shaarli", |
178 | "header_link": "?" | 76 | "header_link": "?" |
179 | }, | 77 | }, |
78 | "dev": { | ||
79 | "debug": false, | ||
80 | } | ||
180 | "extras": { | 81 | "extras": { |
181 | "show_atom": false, | 82 | "show_atom": false, |
182 | "hide_public_links": false, | 83 | "hide_public_links": false, |
@@ -223,13 +124,98 @@ Must be an associative array: `translation domain => translation path`. | |||
223 | "extensions": { | 124 | "extensions": { |
224 | "demo": "plugins/demo_plugin/languages/" | 125 | "demo": "plugins/demo_plugin/languages/" |
225 | } | 126 | } |
127 | }, | ||
128 | "ldap": { | ||
129 | "host": "ldap://localhost", | ||
130 | "dn": "uid=%s,ou=people,dc=example,dc=org" | ||
226 | } | 131 | } |
227 | } ?> | 132 | } ?> |
228 | ``` | 133 | ``` |
229 | 134 | ||
230 | ## Additional configuration | 135 | ## Settings |
136 | |||
137 | ### Credentials | ||
138 | |||
139 | _These settings should not be edited_ | ||
140 | |||
141 | - **login**: Login username. | ||
142 | - **hash**: Generated password hash. | ||
143 | - **salt**: Password salt. | ||
144 | |||
145 | ### General | ||
146 | |||
147 | - **title**: Shaarli's instance title. | ||
148 | - **header_link**: Link to the homepage. | ||
149 | - **links_per_page**: Number of Shaares displayed per page. | ||
150 | - **timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php). | ||
151 | - **enabled_plugins**: List of enabled plugins. | ||
152 | - **default_note_title**: Default title of a new note. | ||
153 | - **retrieve_description** (boolean): If set to true, for every new Shaare Shaarli will try to retrieve the description and keywords from the HTML meta tags. | ||
154 | - **root_url**: Overrides automatic discovery of Shaarli instance's URL (e.g.) `https://sub.domain.tld/shaarli-folder/`. | ||
155 | |||
156 | ### Security | ||
157 | |||
158 | - **session_protection_disabled**: Disable session cookie hijacking protection (not recommended). | ||
159 | It might be useful if your IP adress often changes. | ||
160 | - **ban_after**: Failed login attempts before being IP banned. | ||
161 | - **ban_duration**: IP ban duration in seconds. | ||
162 | - **open_shaarli**: Anyone can add a new Shaare while logged out if enabled. | ||
163 | - **trusted_proxies**: List of trusted IP which won't be banned after failed login attemps. Useful if Shaarli is behind a reverse proxy. | ||
164 | - **allowed_protocols**: List of allowed protocols in shaare URLs or markdown-rendered descriptions. Useful if you want to store `javascript:` links (bookmarklets) in Shaarli (default: `["ftp", "ftps", "magnet"]`). | ||
165 | |||
166 | ### Resources | ||
167 | |||
168 | - **data_dir**: Data directory. | ||
169 | - **datastore**: Shaarli's Shaares database file path. | ||
170 | - **history**: Shaarli's operation history file path. | ||
171 | - **updates**: File path for the ran updates file. | ||
172 | - **log**: Log file path. | ||
173 | - **update_check**: Last update check file path. | ||
174 | - **raintpl_tpl**: Templates directory. | ||
175 | - **raintpl_tmp**: Template engine cache directory. | ||
176 | - **thumbnails_cache**: Thumbnails cache directory. | ||
177 | - **page_cache**: Shaarli's internal cache directory. | ||
178 | - **ban_file**: Banned IP file path. | ||
179 | |||
180 | ### Translation | ||
181 | |||
182 | - **language**: translation language (also see [Translations](Translations)) | ||
183 | - **auto** (default): The translation language is chosen from the browser locale. | ||
184 | It means that the language can be different for 2 different visitors depending on their locale. | ||
185 | - **en**: Use the English translation. | ||
186 | - **fr**: Use the French translation. | ||
187 | - **mode**: | ||
188 | - **auto** or **php** (default): Use the PHP implementation of gettext (slower) | ||
189 | - **gettext**: Use PHP builtin gettext extension | ||
190 | (faster, but requires `php-gettext` to be installed and to reload the web server on update) | ||
191 | - **extension**: Translation extensions for custom themes or plugins. | ||
192 | Must be an associative array: `translation domain => translation path`. | ||
193 | |||
194 | ### Updates | ||
195 | |||
196 | - **check_updates**: Enable or disable update check to the git repository. | ||
197 | - **check_updates_branch**: Git branch used to check updates (e.g. `stable` or `master`). | ||
198 | - **check_updates_interval**: Look for new version every N seconds (default: every day). | ||
199 | |||
200 | ### Privacy | ||
201 | |||
202 | - **default_private_links**: Check the private checkbox by default for every new Shaare. | ||
203 | - **hide_public_links**: All Shaares are hidden while logged out. | ||
204 | - **force_login**: if **hide_public_links** and this are set to `true`, all anonymous users are redirected to the login page. | ||
205 | - **hide_timestamps**: Timestamps are hidden. | ||
206 | - **remember_user_default**: Default state of the login page's *remember me* checkbox | ||
207 | - `true`: checked by default, `false`: unchecked by default | ||
208 | |||
209 | ### Feed | ||
210 | |||
211 | - **rss_permalinks**: Enable this to redirect RSS links to Shaarli's permalinks instead of shaared URL. | ||
212 | - **show_atom**: Display ATOM feed button. | ||
213 | |||
214 | ### Thumbnail | ||
215 | |||
216 | - **enable_thumbnails**: Enable or disable thumbnail display. | ||
217 | - **enable_localcache**: Enable or disable local cache. | ||
231 | 218 | ||
232 | The `playvideos` plugin may require that you adapt your server's | 219 | ## Plugins configuration |
233 | [Content Security Policy](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md#troubleshooting) | ||
234 | configuration to work properly. | ||
235 | 220 | ||
221 | See [Plugins](Plugins.md) | ||
diff --git a/doc/md/Sharing-content.md b/doc/md/Sharing-content.md deleted file mode 100644 index 9a16fc62..00000000 --- a/doc/md/Sharing-content.md +++ /dev/null | |||
@@ -1,71 +0,0 @@ | |||
1 | Content posted to Shaarli is separated in items called _Shaares_. For each Shaare, | ||
2 | you can customize the following aspects: | ||
3 | |||
4 | * URL to link to | ||
5 | * Title | ||
6 | * Free-text description | ||
7 | * Tags | ||
8 | * Public/private status | ||
9 | |||
10 | -------------------------------------------------------------------------------- | ||
11 | |||
12 | ## Adding new Shaares | ||
13 | |||
14 | While logged in to your Shaarli, you can add new Shaares in several ways: | ||
15 | |||
16 | * [+Shaare button](#shaare-button) | ||
17 | * [Bookmarklet](#bookmarklet) | ||
18 | * Third-party [apps and browser addons](Community-&-Related-software.md#mobile-apps) | ||
19 | * [REST API](https://shaarli.github.io/api-documentation/) | ||
20 | |||
21 | ### +Shaare button | ||
22 | |||
23 | * While logged in to your Shaarli, click the **`+Shaare`** button located in the toolbar. | ||
24 | * Enter the URL of a link you want to share. | ||
25 | * Click `Add link` | ||
26 | * The `New Shaare` dialog appears, allowing you to fill in the details of your Shaare. | ||
27 | * The Description, Title, and Tags will help you find your Shaare later using tags or full-text search. | ||
28 | * You can also check the “Private†box so that the link is saved but only visible to you (the logged-in user). | ||
29 | * Click `Save`. | ||
30 | |||
31 | <!-- TODO Add screenshot of add/edit link dialog --> | ||
32 | |||
33 | ### Bookmarklet | ||
34 | |||
35 | The _Bookmarklet_ \[[1](https://en.wikipedia.org/wiki/Bookmarklet)\] is a special | ||
36 | browser bookmark you can use to add new content to your Shaarli. This bookmarklet is | ||
37 | compatible with Firefox, Opera, Chrome and Safari. To set it up: | ||
38 | |||
39 | * Access the `Tools` page from the button in the toolbar. | ||
40 | * Drag the **`✚Shaare link` button** to your browser's bookmarks bar. | ||
41 | |||
42 | Once this is done, you can shaare any URL you are visiting simply by clicking the | ||
43 | bookmarklet in your browser! The same `New Shaare` dialog as above is displayed. | ||
44 | |||
45 | | Note | Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunately, there is nothing Shaarli can do about it. \[[1](https://github.com/shaarli/Shaarli/issues/196)]\ \[[2](https://bugzilla.mozilla.org/show_bug.cgi?id=866522)]\ \[[3](https://code.google.com/p/chromium/issues/detail?id=233903)]\ | | ||
46 | |---------|---------| | ||
47 | |||
48 | | Note | Under Opera, you can't drag'n drop the button: You have to right-click on it and add a bookmark to your personal toolbar. | | ||
49 | |---------|---------| | ||
50 | |||
51 |  | ||
52 | |||
53 | |||
54 | -------------------------------------------------------------------------------- | ||
55 | |||
56 | ## Editing Shaares | ||
57 | |||
58 | Any Shaare can edited by clicking its  `Edit` button. | ||
59 | |||
60 | Editing a Shaare will not change it's permalink, each permalink always points to the | ||
61 | latest revision of a Shaare. | ||
62 | |||
63 | -------------------------------------------------------------------------------- | ||
64 | |||
65 | ## Using shaarli as a blog, notepad, pastebin... | ||
66 | |||
67 | While adding or editing a link, leave the URL field blank to create a text-only | ||
68 | ("note") post. This allows you to post any kind of text content, such as blog | ||
69 | articles, private or public notes, snippets... There is no character limit! You can | ||
70 | access your Shaare from its permalink. | ||
71 | |||
diff --git a/doc/md/Static-analysis.md b/doc/md/Static-analysis.md deleted file mode 100644 index 29d98362..00000000 --- a/doc/md/Static-analysis.md +++ /dev/null | |||
@@ -1,13 +0,0 @@ | |||
1 | ## WIP | ||
2 | This topic is currently being discussed here: | ||
3 | |||
4 | - [Fix coding style (static analysis)](https://github.com/shaarli/Shaarli/issues/95) (#95) | ||
5 | - [Continuous Integration tools & features](https://github.com/shaarli/Shaarli/issues/130) (#130) | ||
6 | |||
7 | ### Usage | ||
8 | Static analysis tools can be installed with Composer, and used through Shaarli's [Makefile](https://github.com/shaarli/Shaarli/blob/master/Makefile). | ||
9 | |||
10 | For an overview of the available features, see: | ||
11 | |||
12 | - [Code quality: Makefile to run static code checkers](https://github.com/shaarli/Shaarli/pull/124) (#124) | ||
13 | - [Run PHPCS against different coding standards](https://github.com/shaarli/Shaarli/pull/276) (#276) | ||
diff --git a/doc/md/Troubleshooting.md b/doc/md/Troubleshooting.md index 570f6956..e1ed5e00 100644 --- a/doc/md/Troubleshooting.md +++ b/doc/md/Troubleshooting.md | |||
@@ -1,97 +1,58 @@ | |||
1 | # Troubleshooting | 1 | # Troubleshooting |
2 | 2 | ||
3 | ## Browser | 3 | First of all, ensure that both the [web server](Server-configuration.md) and [Shaarli](Shaarli-configuration.md) are correctly configured. |
4 | 4 | ||
5 | ### Redirection issues (HTTP Referer) | ||
6 | |||
7 | Depending on its configuration and installed plugins, the browser may remove or alter (spoof) HTTP referers, thus preventing Shaarli from properly redirecting between pages. | ||
8 | 5 | ||
9 | See: | 6 | ## Login |
10 | 7 | ||
11 | - [HTTP referer](https://en.wikipedia.org/wiki/HTTP_referer) (Wikipedia) | 8 | ### I forgot my password! |
12 | - [Improve online privacy by controlling referrer information](http://www.ghacks.net/2015/01/22/improve-online-privacy-by-controlling-referrer-information/) | ||
13 | - [Better security, privacy and anonymity in Firefox](http://b.agilob.net/better-security-privacy-and-anonymity-in-firefox/) | ||
14 | 9 | ||
15 | ### Firefox HTTP Referer options | 10 | Delete the file `data/config.json.php` and display the page again. You will be asked for a new login/password. |
16 | 11 | ||
17 | HTTP settings are available by browsing `about:config`, here are the available settings and their values. | 12 | ### I'm locked out - Login bruteforce protection |
18 | 13 | ||
19 | `network.http.sendRefererHeader` - determines when to send the Referer HTTP header | 14 | Login form is protected against brute force attacks: 4 failed logins will ban the IP address from login for 30 minutes. Banned IPs can still browse Shaares. |
20 | 15 | ||
21 | - `0`: Never send the referring URL | 16 | - To remove the current IP bans, delete the file `data/ipbans.php` |
22 | - not recommended, may break some sites | 17 | - To list all login attempts, see `data/log.txt` (succesful/failed logins, bans/lifted bans) |
23 | - `1`: Send only on clicked links | ||
24 | - `2` (default): Send for links and images | ||
25 | 18 | ||
26 | `network.http.referer.XOriginPolicy` - Cross-domain origin policy | 19 | -------------------------------------- |
27 | 20 | ||
28 | - `0` (default): Always send | 21 | ## Browser issues |
29 | - `1`: Send if base domains match | ||
30 | - `2`: Send if hosts match | ||
31 | 22 | ||
32 | `network.http.referer.spoofSource` - Referer spoofing (~faking) | 23 | ### Redirection issues (HTTP Referer) |
33 | 24 | ||
34 | - `false` (default): real referer | 25 | Shaarli relies on `HTTP_REFERER` for some functions (like redirects and clicking on tags). If you have disabled or altered/spoofed [HTTP referers](https://en.wikipedia.org/wiki/HTTP_referer) in your browser, some features of Shaarli may not work as expected (depending on configuration and installed plugins), notably redirections between pages. |
35 | - `true`: spoof referer (use target URI as referer) | ||
36 | - known to break some functionality in Shaarli | ||
37 | 26 | ||
38 | `network.http.referer.trimmingPolicy` - trim the URI not to send a full Referer | 27 | Firefox Referer settings are available by browsing `about:config` and are documented [here](https://wiki.mozilla.org/Security/Referrer). `network.http.referer.spoofSource = true` in particular is known to break some functionality in Shaarli. |
39 | 28 | ||
40 | - `0`: (default): send full URI | ||
41 | - `1`: scheme+host+port+path | ||
42 | - `2`: scheme+host+port | ||
43 | 29 | ||
44 | ### Firefox, localhost and redirections | 30 | ### Firefox, localhost and redirections |
45 | 31 | ||
46 | `localhost` is not a proper Fully Qualified Domain Name (FQDN); if Firefox has | 32 | `localhost` is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or only accept requests from the same base domain/host, |
47 | been set up to spoof referers, or only accept requests from the same base domain/host, | 33 | Shaarli redirections will not work properly. To solve this, assign a local domain to your host, e.g. `localhost.lan` in your [hosts file](https://en.wikipedia.org/wiki/Hosts_(file)) and browse Shaarli at http://localhost.lan/. |
48 | Shaarli redirections will not work properly. | ||
49 | |||
50 | To solve this, assign a local domain to your host, e.g. | ||
51 | ``` | ||
52 | 127.0.0.1 localhost desktop localhost.lan | ||
53 | ::1 localhost desktop localhost.lan | ||
54 | ``` | ||
55 | 34 | ||
56 | and browse Shaarli at http://localhost.lan/. | 35 | ----------------------------------------- |
57 | |||
58 | Related threads: | ||
59 | - [What is localhost.localdomain for?](https://bbs.archlinux.org/viewtopic.php?id=156064) | ||
60 | - [Stop returning to the first page after editing a bookmark from another page](https://github.com/shaarli/Shaarli/issues/311) | ||
61 | |||
62 | ## Login | ||
63 | |||
64 | ### I forgot my password! | ||
65 | |||
66 | Delete the file `data/config.json.php` and display the page again. You will be asked for a new login/password. | ||
67 | |||
68 | ### I'm locked out - Login bruteforce protection | ||
69 | |||
70 | Login form is protected against brute force attacks: 4 failed logins will ban the IP address from login for 30 minutes. Banned IPs can still browse links. | ||
71 | |||
72 | To remove the current IP bans, delete the file `data/ipbans.php` | ||
73 | |||
74 | ### List of all login attempts | ||
75 | |||
76 | The file `data/log.txt` shows all logins (successful or failed) and bans/lifted bans. | ||
77 | Search for `failed` in this file to look for unauthorized login attempts. | ||
78 | 36 | ||
79 | ## Hosting problems | 37 | ## Hosting problems |
80 | 38 | ||
81 | ### Old PHP versions | 39 | ### Old PHP versions |
82 | 40 | ||
83 | On **free.fr**: free.fr now supports php 5.6.x([link](http://les.pages.perso.chez.free.fr/migrations/php5v6.io)) | 41 | - On hosts (such as **free.fr**) which only support PHP 5.6, Shaarli [v0.10.4](https://github.com/shaarli/Shaarli/releases/tag/v0.10.4) is the maximum supported version. At the root of your webspace create a `sessions` directory and a `.htaccess` file containing: |
84 | and so support now the tag autocompletion but you have to do the following. | ||
85 | |||
86 | At the root of your webspace create a `sessions` directory and a `.htaccess` file containing: | ||
87 | 42 | ||
88 | ```xml | 43 | ```xml |
89 | <IfDefine Free> | 44 | <IfDefine Free> |
90 | php56 1 | 45 | php56 1 |
91 | </IfDefine> | 46 | </IfDefine> |
47 | <Files ".ht*"> | ||
48 | Order allow,deny | ||
49 | Deny from all | ||
50 | Satisfy all | ||
51 | </Files> | ||
52 | Options -Indexes | ||
92 | ``` | 53 | ``` |
93 | 54 | ||
94 | - If you have an error such as: `Parse error: syntax error, unexpected '=', expecting '(' in /links/index.php on line xxx`, it means that your host is using php4, not php5. Shaarli requires php 5.1. Try changing the file extension to `.php5` | 55 | - If you have an error such as: `Parse error: syntax error, unexpected '=', expecting '(' in /links/index.php on line xxx`, it means that your host is using PHP 4, not PHP 5. Shaarli requires PHP 5.1. Try changing the file extension to `.php5` |
95 | - On **1and1** : If you add the link from the page (and not from the bookmarklet), Shaarli will no be able to get the title of the page. You will have to enter it manually. (Because they have disabled the ability to download a file through HTTP). | 56 | - On **1and1** : If you add the link from the page (and not from the bookmarklet), Shaarli will no be able to get the title of the page. You will have to enter it manually. (Because they have disabled the ability to download a file through HTTP). |
96 | - If you have the error `Warning: file_get_contents() [function.file-get-contents]: URL file-access is disabled in the server configuration in /…/index.php on line xxx`, it means that your host has disabled the ability to fetch a file by HTTP in the php config (Typically in 1and1 hosting). Bad host. Change host. Or comment the following lines: | 57 | - If you have the error `Warning: file_get_contents() [function.file-get-contents]: URL file-access is disabled in the server configuration in /…/index.php on line xxx`, it means that your host has disabled the ability to fetch a file by HTTP in the php config (Typically in 1and1 hosting). Bad host. Change host. Or comment the following lines: |
97 | 58 | ||
@@ -101,9 +62,11 @@ php56 1 | |||
101 | //if (strpos($status,'200 OK')) $title=html_extract_title($data); | 62 | //if (strpos($status,'200 OK')) $title=html_extract_title($data); |
102 | ``` | 63 | ``` |
103 | 64 | ||
104 | - On hosts which forbid outgoing HTTP requests (such as free.fr), some thumbnails will not work. | 65 | - On hosts (such as **free.fr**) which forbid outgoing HTTP requests, some thumbnails will not work. |
66 | - On hosts (such as **free.fr**) which limit the number of FTP connections, setup your FTP client accordingly (else some files may be missing after upload). | ||
105 | - On **lost-oasis**, RSS doesn't work correctly, because of this message at the begining of the RSS/ATOM feed : `<? // tout ce qui est charge ici (generalement des includes et require) est charge en permanence. ?>`. To fix this, remove this message from `php-include/prepend.php` | 67 | - On **lost-oasis**, RSS doesn't work correctly, because of this message at the begining of the RSS/ATOM feed : `<? // tout ce qui est charge ici (generalement des includes et require) est charge en permanence. ?>`. To fix this, remove this message from `php-include/prepend.php` |
106 | 68 | ||
69 | |||
107 | ### Dates are not properly formatted | 70 | ### Dates are not properly formatted |
108 | 71 | ||
109 | Shaarli tries to sniff the language of the browser (using `HTTP_ACCEPT_LANGUAGE` headers) | 72 | Shaarli tries to sniff the language of the browser (using `HTTP_ACCEPT_LANGUAGE` headers) |
@@ -123,6 +86,118 @@ This can be caused by several things: | |||
123 | - You may be using OperaTurbo or OperaMini, which use their own proxies which may change from time to time. | 86 | - You may be using OperaTurbo or OperaMini, which use their own proxies which may change from time to time. |
124 | - If you have another application on the same webserver where Shaarli is installed, these application may forcefully expire php sessions. | 87 | - If you have another application on the same webserver where Shaarli is installed, these application may forcefully expire php sessions. |
125 | 88 | ||
126 | ## Sessions do not seem to work correctly on your server | 89 | |
90 | ### Old apache versions, Internal Server Error | ||
91 | |||
92 | If you hosting provider only provides apache 2.2 and no support for `mod_version`, `.htaccess` files may cause 500 errors (Internal Server Error). See [this workaround](https://github.com/shaarli/Shaarli/issues/1196#issuecomment-412271085). | ||
93 | |||
94 | |||
95 | ### Sessions do not seem to work correctly on your server | ||
127 | 96 | ||
128 | Follow the instructions in the error message. Make sure you are accessing shaarli via a direct IP address or a proper hostname. If you have **no dots** in the hostname (e.g. `localhost` or `http://my-webserver/shaarli/`), some browsers will not store cookies at all (this respects the [HTTP cookie specification](http://curl.haxx.se/rfc/cookie_spec.html)). | 97 | Follow the instructions in the error message. Make sure you are accessing shaarli via a direct IP address or a proper hostname. If you have **no dots** in the hostname (e.g. `localhost` or `http://my-webserver/shaarli/`), some browsers will not store cookies at all (this respects the [HTTP cookie specification](http://curl.haxx.se/rfc/cookie_spec.html)). |
98 | |||
99 | ---------------------------------------------------------- | ||
100 | |||
101 | ## Upgrades | ||
102 | |||
103 | ### You must specify an integer as a key | ||
104 | |||
105 | In `v0.8.1` we changed how Shaare keys are handled (from timestamps to incremental integers). Take a look at `data/updates.txt` content. | ||
106 | |||
107 | |||
108 | ### `updates.txt` contains `updateMethodDatastoreIds` | ||
109 | |||
110 | Try to delete it and refresh your page while being logged in. | ||
111 | |||
112 | ### `updates.txt` doesn't exist or doesn't contain `updateMethodDatastoreIds` | ||
113 | |||
114 | 1. Create `data/updates.txt` if it doesn't exist | ||
115 | 2. Paste this string in the update file `;updateMethodRenameDashTags;` | ||
116 | 3. Login to Shaarli | ||
117 | 4. Delete the update file | ||
118 | 5. Refresh | ||
119 | |||
120 | |||
121 | |||
122 | -------------------------------------------------------- | ||
123 | |||
124 | ## Import/export | ||
125 | |||
126 | ### Importing shaarli data to Firefox | ||
127 | |||
128 | - In Firefox, open the bookmark manager (`Bookmarks menu > Show all bookmarks` or `Ctrl+Shift+B`), select `Import and Backup > Import bookmarks in HTML format` | ||
129 | - Make sure the `Prepend note permalinks with this Shaarli instance's URL` box is checked when exporting, so that text-only/notes Shaares still point to the Shaarli instance you exported them from. | ||
130 | - Depending on the number of bookmarks, the import can take some time. | ||
131 | |||
132 | You may be interested in these Firefox addons to manage bookmarks imported from Shaarli | ||
133 | |||
134 | - [Bookmark Deduplicator](https://addons.mozilla.org/en-US/firefox/addon/bookmark-deduplicator/) - provides an easy way to deduplicate your bookmarks | ||
135 | - [TagSieve](https://addons.mozilla.org/en-US/firefox/addon/tagsieve/) - browse your bookmarks by their tags | ||
136 | |||
137 | ### Diigo | ||
138 | |||
139 | If you export your bookmark from Diigo, make sure you use the Delicious export, not the Netscape export. (Their Netscape export is broken, and they don't seem to be interested in fixing it.) | ||
140 | |||
141 | ### Mister Wong | ||
142 | |||
143 | See [this issue](https://github.com/sebsauvage/Shaarli/issues/146) for import tweaks. | ||
144 | |||
145 | ### SemanticScuttle | ||
146 | |||
147 | To correctly import the tags from a [SemanticScuttle](http://semanticscuttle.sourceforge.net/) HTML export, edit the HTML file before importing and replace all occurences of `tags=` (lowercase) to `TAGS=` (uppercase). | ||
148 | |||
149 | ### Scuttle | ||
150 | |||
151 | Shaarli cannot import data directly from [Scuttle](https://github.com/scronide/scuttle). | ||
152 | |||
153 | However, you can use the third-party [scuttle-to-shaarli](https://github.com/q2apro/scuttle-to-shaarli) | ||
154 | tool to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer. | ||
155 | |||
156 | ### Refind.com | ||
157 | |||
158 | 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. | ||
159 | |||
160 | |||
161 | ------------------------------------------------------- | ||
162 | |||
163 | ## Other | ||
164 | |||
165 | ### The bookmarklet doesn't work | ||
166 | |||
167 | Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunately, there is nothing Shaarli can do about it ([1](https://github.com/shaarli/Shaarli/issues/196), [2](https://bugzilla.mozilla.org/show_bug.cgi?id=866522), [3](https://code.google.com/p/chromium/issues/detail?id=233903). | ||
168 | |||
169 | Under Opera, you can't drag'n drop the button: You have to right-click on it and add a bookmark to your personal toolbar. | ||
170 | |||
171 | |||
172 | ### Changing the timestamp for a shaare | ||
173 | |||
174 | - Look for `<input type="hidden" name="lf_linkdate" value="{$link.linkdate}">` in `tpl/editlink.tpl` (line 14) | ||
175 | - Replace `type="hidden"` with `type="text"` from this line | ||
176 | - A new date/time field becomes available in the edit/new Shaare dialog. | ||
177 | - You can set the timestamp manually by entering it in the format `YYYMMDD_HHMMS`. | ||
178 | |||
179 | ### Clearing Shaarli caches | ||
180 | |||
181 | For debugging purposes: | ||
182 | |||
183 | ```bash | ||
184 | # clear raintpl cache and temporary files | ||
185 | find /var/www/links/cache/ /var/www/links/pagecache/ /var/www/links/tmp/ -type f -exec rm -v '{}' \; | ||
186 | # if you have a php accelerator such as php-apcu, restart the webserver | ||
187 | sudo systemctl restart apache2 | ||
188 | ``` | ||
189 | |||
190 | ------------------------------------------------------- | ||
191 | |||
192 | ## Support | ||
193 | |||
194 | If the solutions above did not help, please: | ||
195 | |||
196 | - Come and ask question on the [Gitter chat](https://gitter.im/shaarli/Shaarli) (also reachable via [IRC](https://irc.gitter.im/)) | ||
197 | - Search for [issues](https://github.com/shaarli/Shaarli/issues) and [Pull Requests](https://github.com/shaarli/Shaarli/pulls) | ||
198 | - if you find one that is related to the issue, feel free to comment and provide additional details (host/Shaarli setup...) | ||
199 | - check issues labeled [`feature`](https://github.com/shaarli/Shaarli/labels/feature), [`enhancement`](https://github.com/shaarli/Shaarli/labels/enhancement), and [`plugin`](https://github.com/shaarli/Shaarli/labels/plugin) if you would like a feature added to Shaarli. | ||
200 | - else, [open a new issue](https://github.com/shaarli/Shaarli/issues/new), and provide information about the problem: | ||
201 | - _what happens?_ - display glitches, invalid data, security flaws... | ||
202 | - _what is your configuration?_ - OS, server version, activated extensions, web browser... | ||
203 | - _is it reproducible?_ \ No newline at end of file | ||
diff --git a/doc/md/Unit-tests-Docker.md b/doc/md/Unit-tests-Docker.md deleted file mode 100644 index 59bd5b45..00000000 --- a/doc/md/Unit-tests-Docker.md +++ /dev/null | |||
@@ -1,56 +0,0 @@ | |||
1 | ## Running tests inside Docker containers | ||
2 | |||
3 | Read first: | ||
4 | |||
5 | - [Docker 101](docker/docker-101.md) | ||
6 | - [Docker resources](docker/resources.md) | ||
7 | - [Unit tests](Unit-tests.md) | ||
8 | |||
9 | ### Docker test images | ||
10 | |||
11 | Test Dockerfiles are located under `tests/docker/<distribution>/Dockerfile`, | ||
12 | and can be used to build Docker images to run Shaarli test suites under common | ||
13 | Linux environments. | ||
14 | |||
15 | Dockerfiles are provided for the following environments: | ||
16 | |||
17 | - `alpine36` - [Alpine 3.6](https://www.alpinelinux.org/downloads/) | ||
18 | - `debian8` - [Debian 8 Jessie](https://www.debian.org/DebianJessie) (oldstable) | ||
19 | - `debian9` - [Debian 9 Stretch](https://wiki.debian.org/DebianStretch) (stable) | ||
20 | - `ubuntu16` - [Ubuntu 16.04 Xenial Xerus](http://releases.ubuntu.com/16.04/) (LTS) | ||
21 | |||
22 | What's behind the curtains: | ||
23 | |||
24 | - each image provides: | ||
25 | - a base Linux OS | ||
26 | - Shaarli PHP dependencies (OS packages) | ||
27 | - test PHP dependencies (OS packages) | ||
28 | - Composer | ||
29 | - the local workspace is mapped to the container's `/shaarli/` directory, | ||
30 | - the files are rsync'd so tests are run using a standard Linux user account | ||
31 | (running tests as `root` would bypass permission checks and may hide issues) | ||
32 | - the tests are run inside the container. | ||
33 | |||
34 | ### Building test images | ||
35 | |||
36 | ```bash | ||
37 | # build the Debian 9 Docker image | ||
38 | $ cd /path/to/shaarli | ||
39 | $ cd tests/docker/debian9 | ||
40 | $ docker build -t shaarli-test:debian9 . | ||
41 | ``` | ||
42 | |||
43 | ### Running tests | ||
44 | |||
45 | ```bash | ||
46 | $ cd /path/to/shaarli | ||
47 | |||
48 | # install/update 3rd-party test dependencies | ||
49 | $ composer install --prefer-dist | ||
50 | |||
51 | # run tests using the freshly built image | ||
52 | $ docker run -v $PWD:/shaarli shaarli-test:debian9 docker_test | ||
53 | |||
54 | # run the full test campaign | ||
55 | $ docker run -v $PWD:/shaarli shaarli-test:debian9 docker_all_tests | ||
56 | ``` | ||
diff --git a/doc/md/Unit-tests.md b/doc/md/Unit-tests.md deleted file mode 100644 index f6030d5c..00000000 --- a/doc/md/Unit-tests.md +++ /dev/null | |||
@@ -1,157 +0,0 @@ | |||
1 | ### Setup your environment for tests | ||
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. | ||
4 | |||
5 | ### Install composer | ||
6 | |||
7 | You can either use: | ||
8 | |||
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 | |||
12 | ```bash | ||
13 | # system-wide version | ||
14 | $ composer install | ||
15 | $ composer update | ||
16 | |||
17 | # local version | ||
18 | $ php composer.phar self-update | ||
19 | $ php composer.phar install | ||
20 | $ php composer.phar update | ||
21 | ``` | ||
22 | |||
23 | #### Install Shaarli dev dependencies | ||
24 | |||
25 | ```bash | ||
26 | $ cd /path/to/shaarli | ||
27 | $ composer update | ||
28 | ``` | ||
29 | |||
30 | #### Install and enable Xdebug to generate PHPUnit coverage reports | ||
31 | |||
32 | See http://xdebug.org/docs/install | ||
33 | |||
34 | For Debian-based distros: | ||
35 | ```bash | ||
36 | $ aptitude install php5-xdebug | ||
37 | ``` | ||
38 | For ArchLinux: | ||
39 | ```bash | ||
40 | $ pacman -S xdebug | ||
41 | ``` | ||
42 | |||
43 | Then add the following line to `/etc/php/php.ini`: | ||
44 | ```ini | ||
45 | zend_extension=xdebug.so | ||
46 | ``` | ||
47 | |||
48 | #### Run unit tests | ||
49 | |||
50 | Successful test suite: | ||
51 | ```bash | ||
52 | $ make test | ||
53 | |||
54 | ------- | ||
55 | PHPUNIT | ||
56 | ------- | ||
57 | PHPUnit 4.6.9 by Sebastian Bergmann and contributors. | ||
58 | |||
59 | Configuration read from /home/virtualtam/public_html/shaarli/phpunit.xml | ||
60 | |||
61 | .................................... | ||
62 | |||
63 | Time: 759 ms, Memory: 8.25Mb | ||
64 | |||
65 | OK (36 tests, 65 assertions) | ||
66 | ``` | ||
67 | |||
68 | Test suite with failures and errors: | ||
69 | ```bash | ||
70 | $ make test | ||
71 | ------- | ||
72 | PHPUNIT | ||
73 | ------- | ||
74 | PHPUnit 4.6.9 by Sebastian Bergmann and contributors. | ||
75 | |||
76 | Configuration read from /home/virtualtam/public_html/shaarli/phpunit.xml | ||
77 | |||
78 | E..FF............................... | ||
79 | |||
80 | Time: 802 ms, Memory: 8.25Mb | ||
81 | |||
82 | There was 1 error: | ||
83 | |||
84 | 1) LinkDBTest::testConstructLoggedIn | ||
85 | Missing argument 2 for LinkDB::__construct(), called in /home/virtualtam/public_html/shaarli/tests/Link\ | ||
86 | DBTest.php on line 79 and defined | ||
87 | |||
88 | /home/virtualtam/public_html/shaarli/application/LinkDB.php:58 | ||
89 | /home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:79 | ||
90 | |||
91 | -- | ||
92 | |||
93 | There were 2 failures: | ||
94 | |||
95 | 1) LinkDBTest::testCheckDBNew | ||
96 | Failed asserting that two strings are equal. | ||
97 | --- Expected | ||
98 | +++ Actual | ||
99 | @@ @@ | ||
100 | -'e3edea8ea7bb50be4bcb404df53fbb4546a7156e' | ||
101 | +'85eab0c610d4f68025f6ed6e6b6b5fabd4b55834' | ||
102 | |||
103 | /home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:121 | ||
104 | |||
105 | 2) LinkDBTest::testCheckDBLoad | ||
106 | Failed asserting that two strings are equal. | ||
107 | --- Expected | ||
108 | +++ Actual | ||
109 | @@ @@ | ||
110 | -'e3edea8ea7bb50be4bcb404df53fbb4546a7156e' | ||
111 | +'85eab0c610d4f68025f6ed6e6b6b5fabd4b55834' | ||
112 | |||
113 | /home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:133 | ||
114 | |||
115 | FAILURES! | ||
116 | Tests: 36, Assertions: 63, Errors: 1, Failures: 2. | ||
117 | ``` | ||
118 | |||
119 | #### Test results and coverage | ||
120 | |||
121 | By default, PHPUnit will run all suitable tests found under the `tests` directory. | ||
122 | |||
123 | Each test has 3 possible outcomes: | ||
124 | |||
125 | - `.` - success | ||
126 | - `F` - failure: the test was run but its results are invalid | ||
127 | - the code does not behave as expected | ||
128 | - dependencies to external elements: globals, session, cache... | ||
129 | - `E` - error: something went wrong and the tested code has crashed | ||
130 | - typos in the code, or in the test code | ||
131 | - dependencies to missing external elements | ||
132 | |||
133 | If Xdebug has been installed and activated, two coverage reports will be generated: | ||
134 | |||
135 | - a summary in the console | ||
136 | - a detailed HTML report with metrics for tested code | ||
137 | - to open it in a web browser: `firefox coverage/index.html &` | ||
138 | |||
139 | ### Executing specific tests | ||
140 | |||
141 | Add a [`@group`](https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.group) annotation in a test class or method comment: | ||
142 | |||
143 | ```php | ||
144 | /** | ||
145 | * Netscape bookmark import | ||
146 | * @group WIP | ||
147 | */ | ||
148 | class BookmarkImportTest extends PHPUnit_Framework_TestCase | ||
149 | { | ||
150 | [...] | ||
151 | } | ||
152 | ``` | ||
153 | |||
154 | To run all tests annotated with `@group WIP`: | ||
155 | ```bash | ||
156 | $ vendor/bin/phpunit --group WIP tests/ | ||
157 | ``` | ||
diff --git a/doc/md/Upgrade-and-migration.md b/doc/md/Upgrade-and-migration.md index d5682a34..bfef3e8c 100644 --- a/doc/md/Upgrade-and-migration.md +++ b/doc/md/Upgrade-and-migration.md | |||
@@ -1,96 +1,83 @@ | |||
1 | ## Preparation | 1 | # Upgrade and migration |
2 | 2 | ||
3 | ### Note your current version | 3 | ## Note your current version |
4 | 4 | ||
5 | If anything goes wrong, it's important for us to know which version you're upgrading from. | 5 | If anything goes wrong, it's important for us to know which version you're upgrading from. |
6 | The current version is present in the `shaarli_version.php` file. | 6 | The current version is present in the `shaarli_version.php` file. |
7 | 7 | ||
8 | ### Backup your data | ||
9 | 8 | ||
10 | Shaarli stores all user data under the `data` directory: | 9 | ## Backup your data |
11 | 10 | ||
12 | - `data/config.json.php` (or `data/config.php` for older Shaarli versions) - main configuration file | 11 | Shaarli stores all user data and [configuration](Shaarli-configuration.md) under the `data` directory. [Backup](Backup-and-restore.md) this repository _before_ upgrading Shaarli. You will need to restore it after the following upgrade steps. |
13 | - `data/datastore.php` - bookmarked links | ||
14 | - `data/ipbans.php` - banned IP addresses | ||
15 | - `data/updates.txt` - contains all automatic update to the configuration and datastore files already run | ||
16 | 12 | ||
17 | See [Shaarli configuration](Shaarli-configuration) for more information about Shaarli resources. | 13 | ```bash |
18 | 14 | sudo cp -r /var/www/shaarli.mydomain.org/data ~/shaarli-data-backup | |
19 | It is recommended to backup this repository _before_ starting updating/upgrading Shaarli: | 15 | ``` |
20 | |||
21 | - users with SSH access: copy or archive the directory to a temporary location | ||
22 | - users with FTP access: download a local copy of your Shaarli installation using your favourite client | ||
23 | 16 | ||
24 | ### Migrating data from a previous installation | 17 | ## Upgrading from ZIP archives |
25 | 18 | ||
26 | As all user data is kept under `data`, this is the only directory you need to worry about when migrating to a new installation, which corresponds to the following steps: | 19 | If you installed Shaarli from a [release ZIP archive](Installation.md#from-release-zip): |
27 | 20 | ||
28 | - backup the `data` directory | 21 | ```bash |
29 | - install or update Shaarli: | 22 | # Download the archive to the server, and extract it |
30 | - fresh installation - see [Download and Installation](Download-and-Installation) | 23 | cd ~ |
31 | - update - see the following sections | 24 | wget https://github.com/shaarli/Shaarli/releases/download/v0.X.Y/shaarli-v0.X.Y-full.zip |
32 | - check or restore the `data` directory | 25 | unzip shaarli-v0.X.Y-full.zip |
33 | 26 | ||
34 | ## Recommended : Upgrading from release archives | 27 | # overwrite your Shaarli installation with the new release **All data will be lost, see _Backup your data_ above.** |
28 | sudo rsync -avP --delete Shaarli/ /var/www/shaarli.mydomain.org/ | ||
35 | 29 | ||
36 | All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page. | 30 | # restore file permissions as described on the installation page |
31 | sudo chown -R root:www-data /var/www/shaarli.mydomain.org | ||
32 | sudo chmod -R g+rX /var/www/shaarli.mydomain.org | ||
33 | sudo chmod -R g+rwX /var/www/shaarli.mydomain.org/{cache/,data/,pagecache/,tmp/} | ||
37 | 34 | ||
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. | 35 | # restore backups of the data directory |
36 | sudo cp -r ~/shaarli-data-backup/* /var/www/shaarli.mydomain.org/data/ | ||
39 | 37 | ||
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! | 38 | # If you use gettext mode for translations (not the default), reload your web server. |
39 | sudo systemctl restart apache2 | ||
40 | sudo systemctl restart nginx | ||
41 | ``` | ||
41 | 42 | ||
42 | If you use translations in gettext mode - meaning you manually changed the default mode -, | 43 | If you don't have shell access (eg. on shared hosting), backup the shaarli data directory, download the ZIP archive locally, extract it, upload it to the server using file transfer, and restore the data directory backup. |
43 | reload your web server. | ||
44 | 44 | ||
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). | 45 | 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.md) for more details). |
46 | 46 | ||
47 | ## Upgrading with Git | ||
48 | 47 | ||
49 | ### Updating a community Shaarli | 48 | ## Upgrading from Git |
50 | 49 | ||
51 | If you have installed Shaarli from the [community Git repository](Download#clone-with-git-recommended), simply [pull new changes](https://www.git-scm.com/docs/git-pull) from your local clone: | 50 | If you have installed Shaarli [from sources](Installation.md#from-sources): |
52 | 51 | ||
53 | ```bash | 52 | ```bash |
54 | $ cd /path/to/shaarli | 53 | # pull new changes from your local clone |
55 | $ git pull | 54 | cd /var/www/shaarli.mydomain.org/ |
56 | 55 | sudo git pull | |
57 | From github.com:shaarli/Shaarli | ||
58 | * branch master -> FETCH_HEAD | ||
59 | Updating ebd67c6..521f0e6 | ||
60 | Fast-forward | ||
61 | application/Url.php | 1 + | ||
62 | shaarli_version.php | 2 +- | ||
63 | tests/Url/UrlTest.php | 1 + | ||
64 | 3 files changed, 3 insertions(+), 1 deletion(-) | ||
65 | ``` | ||
66 | 56 | ||
67 | Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/): | 57 | # update PHP dependencies (Shaarli >= v0.8) |
58 | sudo composer install --no-dev | ||
68 | 59 | ||
69 | ```bash | 60 | # update translations (Shaarli >= v0.9.2) |
70 | $ composer install --no-dev | 61 | sudo make translate |
71 | 62 | ||
72 | Loading composer repositories with package information | 63 | # If you use translations in gettext mode (not the default), reload your web server. |
73 | Updating dependencies | 64 | sudo systemctl reload apache |
74 | - Installing shaarli/netscape-bookmark-parser (v1.0.1) | 65 | sudo systemctl reload nginx |
75 | Downloading: 100% | ||
76 | ``` | ||
77 | 66 | ||
78 | Shaarli >= `v0.9.2` supports translations: | 67 | # update front-end dependencies (Shaarli >= v0.10.0) |
68 | sudo make build_frontend | ||
79 | 69 | ||
80 | ```bash | 70 | # restore file permissions as described on the installation page |
81 | $ make translate | 71 | sudo chown -R root:www-data /var/www/shaarli.mydomain.org |
82 | ``` | 72 | sudo chmod -R g+rX /var/www/shaarli.mydomain.org |
73 | sudo chmod -R g+rwX /var/www/shaarli.mydomain.org/{cache/,data/,pagecache/,tmp/} | ||
74 | ``` | ||
83 | 75 | ||
84 | If you use translations in gettext mode, reload your web server. | 76 | 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.md) for more details). |
85 | 77 | ||
86 | Shaarli >= `v0.10.0` manages its front-end dependencies with nodejs. You need to install | 78 | --------------------------------------------------------------- |
87 | [yarn](https://yarnpkg.com/lang/en/docs/install/): | ||
88 | 79 | ||
89 | ```bash | 80 | ## Migrating and upgrading from Sebsauvage's repository |
90 | $ make build_frontend | ||
91 | ``` | ||
92 | |||
93 | ### Migrating and upgrading from Sebsauvage's repository | ||
94 | 81 | ||
95 | 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. | 82 | 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. |
96 | 83 | ||
@@ -104,7 +91,7 @@ The following guide assumes that: | |||
104 | - no versioned file has been locally modified | 91 | - no versioned file has been locally modified |
105 | - no untracked files are present | 92 | - no untracked files are present |
106 | 93 | ||
107 | #### Step 0: show repository information | 94 | ### Step 0: show repository information |
108 | 95 | ||
109 | ```bash | 96 | ```bash |
110 | $ cd /path/to/shaarli | 97 | $ cd /path/to/shaarli |
@@ -122,7 +109,7 @@ Your branch is up-to-date with 'origin/master'. | |||
122 | nothing to commit, working directory clean | 109 | nothing to commit, working directory clean |
123 | ``` | 110 | ``` |
124 | 111 | ||
125 | #### Step 1: update Git remotes | 112 | ### Step 1: update Git remotes |
126 | 113 | ||
127 | ``` | 114 | ``` |
128 | $ git remote rename origin sebsauvage | 115 | $ git remote rename origin sebsauvage |
@@ -146,7 +133,7 @@ From https://github.com/shaarli/Shaarli | |||
146 | * [new tag] v0.7.0 -> v0.7.0 | 133 | * [new tag] v0.7.0 -> v0.7.0 |
147 | ``` | 134 | ``` |
148 | 135 | ||
149 | #### Step 2: use the stable community branch | 136 | ### Step 2: use the stable community branch |
150 | 137 | ||
151 | ```bash | 138 | ```bash |
152 | $ git checkout origin/stable -b stable | 139 | $ git checkout origin/stable -b stable |
@@ -177,8 +164,7 @@ $ make translate | |||
177 | 164 | ||
178 | If you use translations in gettext mode, reload your web server. | 165 | If you use translations in gettext mode, reload your web server. |
179 | 166 | ||
180 | Shaarli >= `v0.10.0` manages its front-end dependencies with nodejs. You need to install | 167 | Shaarli >= `v0.10.0` manages its front-end dependencies with nodejs. You need to install [yarn](https://yarnpkg.com/lang/en/docs/install/): |
181 | [yarn](https://yarnpkg.com/lang/en/docs/install/): | ||
182 | 168 | ||
183 | ```bash | 169 | ```bash |
184 | $ make build_frontend | 170 | $ make build_frontend |
@@ -204,30 +190,14 @@ Writing objects: 100% (3317/3317), done. | |||
204 | Total 3317 (delta 2050), reused 3301 (delta 2034)to | 190 | Total 3317 (delta 2050), reused 3301 (delta 2034)to |
205 | ``` | 191 | ``` |
206 | 192 | ||
207 | #### Step 3: configuration | 193 | ### Step 3: configuration |
208 | 194 | ||
209 | After migrating, access your fresh Shaarli installation from a web browser; the | 195 | After migrating, access your fresh Shaarli installation from a web browser; the |
210 | configuration will then be automatically updated, and new settings added to | 196 | configuration will then be automatically updated, and new settings added to |
211 | `data/config.json.php` (see [Shaarli configuration](Shaarli-configuration) for more | 197 | `data/config.json.php` (see [Shaarli configuration](Shaarli-configuration.md) for more |
212 | details). | 198 | details). |
213 | 199 | ||
214 | ## Troubleshooting | 200 | ## Troubleshooting |
215 | 201 | ||
216 | If the solutions provided here don't work, please open an issue specifying which version you're upgrading from and to. | 202 | If the solutions provided here don't work, see [Troubleshooting](Troubleshooting.md) and/or open an issue specifying which version you're upgrading from and to. |
217 | |||
218 | ### You must specify an integer as a key | ||
219 | |||
220 | In `v0.8.1` we changed how link keys are handled (from timestamps to incremental integers). | ||
221 | Take a look at `data/updates.txt` content. | ||
222 | |||
223 | #### `updates.txt` contains `updateMethodDatastoreIds` | ||
224 | |||
225 | Try to delete it and refresh your page while being logged in. | ||
226 | |||
227 | #### `updates.txt` doesn't exist or doesn't contain `updateMethodDatastoreIds` | ||
228 | 203 | ||
229 | 1. Create `data/updates.txt` if it doesn't exist | ||
230 | 2. Paste this string in the update file `;updateMethodRenameDashTags;` | ||
231 | 3. Login to Shaarli | ||
232 | 4. Delete the update file | ||
233 | 5. Refresh | ||
diff --git a/doc/md/Usage.md b/doc/md/Usage.md new file mode 100644 index 00000000..6dadde0a --- /dev/null +++ b/doc/md/Usage.md | |||
@@ -0,0 +1,111 @@ | |||
1 | ## Features | ||
2 | |||
3 | For any item posted to Shaarli (called a _Shaare_), you can customize the following aspects: | ||
4 | |||
5 | - URL to link to | ||
6 | - Title | ||
7 | - Free-text description | ||
8 | - Tags | ||
9 | - Public/private status | ||
10 | |||
11 | |||
12 | ### Adding/editing Shaares | ||
13 | |||
14 | While logged in to your Shaarli, you can add, edit or delete Shaares: | ||
15 | |||
16 | - Using the **+Shaare** button: enter the URL you want to share, click `Add link`, fill in the details of your Shaare, and `Save` | ||
17 | - Using the [Bookmarklet](https://en.wikipedia.org/wiki/Bookmarklet): drag the `✚Shaare link` button from the `Tools` page to your browser's bookmarks bar, click it to share the current page. | ||
18 | - Using [apps and browser addons](Community-and-related-software.md#mobile-apps) | ||
19 | - Using the [REST API](https://shaarli.github.io/api-documentation/) | ||
20 | - Any Shaare can edited by clicking its  `Edit` button. | ||
21 | |||
22 | |||
23 | ### Tags | ||
24 | |||
25 | Tags can be be used to organize and categorize your Shaares: | ||
26 | |||
27 | - You can rename, merge and delete tags from the _Tools_ menu or the [tag cloud/list](#tag-cloud) | ||
28 | - Tags are auto-completed (from the list of existing tags) in all dialogs | ||
29 | - Tags can be combined with text in [search](#search) queries | ||
30 | |||
31 | |||
32 | ### Public/private Shaares | ||
33 | |||
34 | Additional filter buttons can be found at the top left of the Shaare list **only when logged in**: | ||
35 | |||
36 | - **Only show private Shaares:** Private shares can be searched by clicking the `only show private links` toggle button top left of the Shaares list (only when logged in) | ||
37 | |||
38 | |||
39 | ### Permalinks | ||
40 | |||
41 | Permalinks are fixed, short links attached to each Shaare. Editing a Shaare will not change it's permalink, each permalink always points to the latest revision of a Shaare. | ||
42 | |||
43 | |||
44 | ### Text-only (note) Shaares | ||
45 | |||
46 | Shaarli can be used as a minimal blog, notepad, pastebin...: While adding or editing a Shaare, leave the URL field blank to create a text-only ("note") post. This allows you to post any kind of text content, such as blog articles, private or public notes, snippets... There is no character limit! You can access your post from its permalink. | ||
47 | |||
48 | |||
49 | ### Search | ||
50 | |||
51 | - **Plain text search:** Use `Search text` to search in all fields of all Shaares (Title, URL, Description...). Use double-quotes (example `"exact search"`) to search for the exact expression. | ||
52 | - **Tags search:** `Filter by tags` allow only displaying Shaares tagged with one or multiple tags (use space to separate tags). | ||
53 | - **Hidden tags:** tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in. | ||
54 | - **Exclude text/tags:** Use the `-` operator before a word or tag to exclude Shaares matching this word from search results (`NOT` operator). | ||
55 | - **Untagged links:** Shaares without tags can be searched by clicking the `untagged` toggle button top left of the Shaares list (only when logged in). | ||
56 | |||
57 | Both exclude patterns and exact searches can be combined with normal searches (example `"exact search" term otherterm -notthis "very exact" stuff -notagain`). Only AND (and NOT) search is currrently supported. | ||
58 | |||
59 | Active search terms are displayed on top of the link list. To remove terms/tags from the curent search, click the `x` next to any of them, or simply clear text/tag search fields. | ||
60 | |||
61 | |||
62 | ### Tag cloud | ||
63 | |||
64 | The `Tag cloud` page diplays a "cloud" or list view of all tags in your Shaarli (most frequently used tags are displayed with a bigger font size) | ||
65 | |||
66 | |||
67 | - **Tags list:** click on `Most used` or `Alphabetical` to display tags as a list. You can also edit/delete tags for this page. | ||
68 | - Click on any tag to search all Shaares matching this tag. | ||
69 | - **Filtering the tag cloud/list:** Click on the counter next to a tag to show other tags of Shaares with this tag. 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 set. | ||
70 | |||
71 | |||
72 | |||
73 | ### RSS feeds | ||
74 | |||
75 | RSS/ATOM feeds feeds are available (in ATOM with `/feed/atom` and RSS with `/feed/rss`) | ||
76 | |||
77 | - **Filtering RSS feeds:** RSS feeds and picture wall can also be restricted to only return items matching a text/tag search. For example, search for `photography` (text or tags) in Shaarli, then click the `RSS Feed` button. A feed with only matching results is displayed. | ||
78 | - Add the `&nb` parameter in feed URLs to specify the number of Shaares you want in a feed (default if not specified: `50`). The keyword `all` is available if you want everything. | ||
79 | - Add the `&permalinks` parameter in feed URLs to point permalinks to the corresponding shaarly entry/link instead of the direct, Shaare URL attribute | ||
80 | |||
81 |   | ||
82 | |||
83 | ```bash | ||
84 | # examples | ||
85 | https://shaarli.mydomain.org/feed/atom?permalinks | ||
86 | https://shaarli.mydomain.org/feed/atom?permalinks&nb=42 | ||
87 | https://shaarli.mydomain.org/feed/atom?permalinks&nb=all | ||
88 | https://shaarli.mydomain.org/feed/rss?searchtags=nature | ||
89 | https://shaarli.mydomain.org/links/picture-wall?searchterm=poney | ||
90 | ``` | ||
91 | |||
92 | |||
93 | ### Picture wall | ||
94 | |||
95 | - The picture wall can be filtered by text or tags search in the same way as [RSS feeds](#rss-feeds) | ||
96 | |||
97 | |||
98 | ### Import/export | ||
99 | |||
100 | To **export Shaares as a HTML file**, under _Tools > Export_, choose: | ||
101 | |||
102 | - `Export all` to export both public and private Shaares | ||
103 | - `Export public` to export public Shaares only | ||
104 | - `Export private` to export private Shaares only | ||
105 | |||
106 | Restore by using the `Import` feature. | ||
107 | |||
108 | - These exports contain the full data (URL, title, tags, date, description, public/private status of your Shaares) | ||
109 | - They can also be imported to your web browser bookmarks. | ||
110 | |||
111 | To **import a HTML bookmarks file** exported from your browser, just use the `Import` feature. For each "folder" in the bookmarks you imported, a new tag will be created (for example a bookmark in `Movies > Sci-fi` folder will be tagged `Movies` `Sci-fi`). | ||
diff --git a/doc/md/dev/Development.md b/doc/md/dev/Development.md new file mode 100644 index 00000000..5c085e03 --- /dev/null +++ b/doc/md/dev/Development.md | |||
@@ -0,0 +1,179 @@ | |||
1 | # Development | ||
2 | |||
3 | Please read [Contributing to Shaarli](https://github.com/shaarli/Shaarli/tree/master/CONTRIBUTING.md) | ||
4 | |||
5 | ## Guidelines | ||
6 | |||
7 | |||
8 | - [Unit tests](Unit-tests) | ||
9 | - Javascript linting - Shaarli uses [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript). | ||
10 | Run `make eslint` to check JS style. | ||
11 | - [GnuPG signature](GnuPG-signature) for tags/releases | ||
12 | |||
13 | |||
14 | ## Third-party libraries | ||
15 | |||
16 | CSS: | ||
17 | |||
18 | - Yahoo UI [CSS Reset](http://yuilibrary.com/yui/docs/cssreset/) - standardize cross-browser rendering | ||
19 | |||
20 | Javascript: | ||
21 | |||
22 | - [Awesomeplete](https://leaverou.github.io/awesomplete/) ([GitHub](https://github.com/LeaVerou/awesomplete)) - autocompletion in input forms | ||
23 | - [bLazy](http://dinbror.dk/blazy/) ([GitHub](https://github.com/dinbror/blazy)) - lazy loading for thumbnails | ||
24 | - [qr.js](http://neocotic.com/qr.js/) ([GitHub](https://github.com/neocotic/qr.js)) - QR code generation | ||
25 | |||
26 | PHP (managed through [`composer.json`](https://github.com/shaarli/Shaarli/blob/master/composer.json)): | ||
27 | |||
28 | - [RainTPL](https://github.com/rainphp/raintpl) - HTML templating for PHP | ||
29 | - [`shaarli/netscape-bookmark-parser`](https://packagist.org/packages/shaarli/netscape-bookmark-parser) - Import bookmarks from Netscape files | ||
30 | - [`erusev/parsedown`](https://packagist.org/packages/erusev/parsedown) - Parse MarkDown syntax for the MarkDown plugin | ||
31 | - [`slim/slim`](https://packagist.org/packages/slim/slim) - Handle routes and middleware for the REST API | ||
32 | - [`ArthurHoaro/web-thumbnailer`](https://github.com/ArthurHoaro/web-thumbnailer) - PHP library which will retrieve a thumbnail for any given URL | ||
33 | - [`pubsubhubbub/publisher`](https://github.com/pubsubhubbub/php-publisher) - A PubSubHubbub publisher module for PHP. | ||
34 | - [`gettext/gettext`](https://github.com/php-gettext/Gettext) - PHP library to collect and manipulate gettext (.po, .mo, .php, .json, etc) | ||
35 | |||
36 | |||
37 | ## Security | ||
38 | |||
39 | - The password is salted, hashed and stored in the data subdirectory, in a PHP file, and protected by htaccess. Even if the webserver does not support htaccess, the hash is not readable by URL. Even if the .php file is stolen, the password cannot deduced from the hash. The salt prevents rainbow-tables attacks. | ||
40 | - Directories are protected using `.htaccess` files | ||
41 | - Forms are protected against [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery): | ||
42 | - Forms which act on data (save,delete…) contain a token generated by the server. | ||
43 | - Any posted form which does not contain a valid token is rejected. | ||
44 | - Any token can only be used once. | ||
45 | - Tokens are attached to the session and cannot be reused in another session. | ||
46 | - Sessions automatically expire after 60 minutes. | ||
47 | - Sessions are protected against hijacking: the session ID cannot be used from a different IP address. | ||
48 | - Links are stored as an associative array which is serialized, compressed (with deflate), base64-encoded and saved as a comment in a `.php` file - even if the server does not support `.htaccess` files, the data file will still not be readable by URL. | ||
49 | - Bruteforce protection: Successful and failed login attempts are logged - IP bans are enforced after a configurable amount of failures. Logs can also be used consumed by [fail2ban](../Server-configuration.md#fail2ban) | ||
50 | - A pop-up notification is shown when a new release is available. | ||
51 | |||
52 | ## Link structure | ||
53 | |||
54 | Every link available through the `LinkDB` object is represented as an array | ||
55 | containing the following fields: | ||
56 | |||
57 | * `id` (integer): Unique identifier. | ||
58 | * `title` (string): Title of the link. | ||
59 | * `url` (string): URL of the link. Used for displayable links (without redirector, url encoding, etc.). | ||
60 | Can be absolute or relative for Notes. | ||
61 | * `real_url` (string): Real destination URL, can be redirected, encoded, etc. | ||
62 | * `shorturl` (string): Permalink small hash. | ||
63 | * `description` (string): Link text description. | ||
64 | * `private` (boolean): whether the link is private or not. | ||
65 | * `tags` (string): all link tags separated by a single space | ||
66 | * `thumbnail` (string|boolean): relative path of the thumbnail cache file, or false if there isn't any. | ||
67 | * `created` (DateTime): link creation date time. | ||
68 | * `updated` (DateTime): last modification date time. | ||
69 | |||
70 | Small hashes are used to make a link to an entry in Shaarli. They are unique: the date of the item (eg. `20110923_150523`) is hashed with CRC32, then converted to base64 and some characters are replaced. They are always 6 characters longs and use only `A-Z a-z 0-9 - _` and `@`. | ||
71 | |||
72 | |||
73 | ## Directory structure | ||
74 | |||
75 | Here is the directory structure of Shaarli and the purpose of the different files: | ||
76 | |||
77 | ```bash | ||
78 | index.php # Main program | ||
79 | application/ # Shaarli classes | ||
80 | ├── LinkDB.php | ||
81 | |||
82 | ... | ||
83 | |||
84 | └── Utils.php | ||
85 | tests/ # Shaarli unitary & functional tests | ||
86 | ├── LinkDBTest.php | ||
87 | |||
88 | ... | ||
89 | |||
90 | ├── utils # utilities to ease testing | ||
91 | │ └── ReferenceLinkDB.php | ||
92 | └── UtilsTest.php | ||
93 | assets/ | ||
94 | ├── common/ # Assets shared by multiple themes | ||
95 | ├── ... | ||
96 | ├── default/ # Assets for the default template, before compilation | ||
97 | ├── fonts/ # Font files | ||
98 | ├── img/ # Images used by the default theme | ||
99 | ├── js/ # JavaScript files in ES6 syntax | ||
100 | ├── scss/ # SASS files | ||
101 | └── vintage/ # Assets for the vintage template, before compilation | ||
102 | └── ... | ||
103 | COPYING # Shaarli license | ||
104 | inc/ # static assets and 3rd party libraries | ||
105 | └── rain.tpl.class.php # RainTPL templating library | ||
106 | images/ # Images and icons used in Shaarli | ||
107 | data/ # data storage: bookmark database, configuration, logs, banlist... | ||
108 | ├── config.json.php # Shaarli configuration (login, password, timezone, title...) | ||
109 | ├── datastore.php # Your link database (compressed). | ||
110 | ├── ipban.php # IP address ban system data | ||
111 | ├── lastupdatecheck.txt # Update check timestamp file | ||
112 | └── log.txt # login/IPban log. | ||
113 | tpl/ # RainTPL templates for Shaarli. They are used to build the pages. | ||
114 | ├── default/ # Default Shaarli theme | ||
115 | ├── fonts/ # Font files | ||
116 | ├── img/ # Images | ||
117 | ├── js/ # JavaScript files compiled by Babel and compatible with all browsers | ||
118 | ├── css/ # CSS files compiled with SASS | ||
119 | └── vintage/ # Legacy Shaarli theme | ||
120 | └── ... | ||
121 | cache/ # thumbnails cache | ||
122 | # This directory is automatically created. You can erase it anytime you want. | ||
123 | tmp/ # Temporary directory for compiled RainTPL templates. | ||
124 | # This directory is automatically created. You can erase it anytime you want. | ||
125 | vendor/ # Third-party dependencies. This directory is created by Composer | ||
126 | ``` | ||
127 | |||
128 | Shaarli needs read access to: | ||
129 | |||
130 | - the root index.php file | ||
131 | - the `application/`, `plugins/` and `inc/` directories (recursively) | ||
132 | |||
133 | Shaarli needs read/write access to the `cache/`, `data/`, `pagecache/`, and `tmp/` directories | ||
134 | |||
135 | |||
136 | ## Automation | ||
137 | |||
138 | A [`Makefile`](https://github.com/shaarli/Shaarli/blob/master/Makefile) is available to perform project-related operations: | ||
139 | |||
140 | - [Static analysis](#Static-analysis) - check that the code is compliant to PHP conventions | ||
141 | - [Unit tests](#Unit-tests) - ensure there are no regressions introduced by new commits | ||
142 | - Documentation - generate a local HTML copy of the markdown documentation | ||
143 | |||
144 | ### Continuous Integration | ||
145 | |||
146 | [Travis CI](http://docs.travis-ci.com/) is a Continuous Integration build server, that runs a build: | ||
147 | |||
148 | - each time a commit is merged to the mainline (`master` branch) | ||
149 | - each time a Pull Request is submitted or updated | ||
150 | |||
151 | After all jobs have finished, Travis returns the results to GitHub: | ||
152 | |||
153 | - a status icon represents the result for the `master` branch: [](https://travis-ci.org/shaarli/Shaarli) | ||
154 | - Pull Requests are updated with the Travis build result. | ||
155 | |||
156 | See [`.travis.yml`](https://github.com/shaarli/Shaarli/blob/master/.travis.yml). | ||
157 | |||
158 | |||
159 | ### Documentation | ||
160 | |||
161 | [mkdocs](https://www.mkdocs.org/) is used to convert markdown documentation to HTML pages. The [public documentation](https://shaarli.readthedocs.io/en/master/) website is rendered and hosted by [readthedocs.org](https://readthedocs.org/). A copy of the documentation is also included in prebuilt [release archives](https://github.com/shaarli/Shaarli/releases) (`doc/html/` path in your Shaarli installation). To generate the HTML documentation locally, install a recent version of Python `setuptools` and run `make doc`. | ||
162 | |||
163 | |||
164 | ## Static analysis | ||
165 | |||
166 | Patches should try to stick to the [PHP Standard Recommendations](http://www.php-fig.org/psr/) (PSR), especially: | ||
167 | |||
168 | - [PSR-1](http://www.php-fig.org/psr/psr-1/) - Basic Coding Standard | ||
169 | - [PSR-2](http://www.php-fig.org/psr/psr-2/) - Coding Style Guide | ||
170 | |||
171 | |||
172 | **Work in progress:** Static analysis is currently being discussed here: in [#95 - Fix coding style (static analysis)](https://github.com/shaarli/Shaarli/issues/95), [#130 - Continuous Integration tools & features](https://github.com/shaarli/Shaarli/issues/130) | ||
173 | |||
174 | Static analysis tools can be installed with Composer, and used through Shaarli's [Makefile](https://github.com/shaarli/Shaarli/blob/master/Makefile). | ||
175 | |||
176 | For an overview of the available features, see: | ||
177 | |||
178 | - [Code quality: Makefile to run static code checkers](https://github.com/shaarli/Shaarli/pull/124) (#124) | ||
179 | - [Run PHPCS against different coding standards](https://github.com/shaarli/Shaarli/pull/276) (#276) | ||
diff --git a/doc/md/GnuPG-signature.md b/doc/md/dev/GnuPG-signature.md index d1fc10a5..25578001 100644 --- a/doc/md/GnuPG-signature.md +++ b/doc/md/dev/GnuPG-signature.md | |||
@@ -1,24 +1,16 @@ | |||
1 | ## Introduction | 1 | ## Introduction |
2 | ### PGP and GPG | 2 | ### PGP and GPG |
3 | [Gnu Privacy Guard](https://gnupg.org/) (GnuPG) is an Open Source implementation of the | 3 | [Gnu Privacy Guard](https://gnupg.org/) (GnuPG) is an Open Source implementation of the [Pretty Good Privacy](https://en.wikipedia.org/wiki/Pretty_Good_Privacy#OpenPGP) (OpenPGP) specification. Its main purposes are digital authentication, signature and encryption. It is often used by the [FLOSS](https://en.wikipedia.org/wiki/Free_and_open-source_software) community to verify: |
4 | [Pretty Good Privacy](https://en.wikipedia.org/wiki/Pretty_Good_Privacy#OpenPGP) | ||
5 | (OpenPGP) specification. Its main purposes are digital authentication, signature and encryption. | ||
6 | 4 | ||
7 | It is often used by the [FLOSS](https://en.wikipedia.org/wiki/Free_and_open-source_software) community to verify: | 5 | - Linux package signatures: Debian [SecureApt](https://wiki.debian.org/SecureApt), ArchLinux [Master Keys](https://www.archlinux.org/master-keys/) |
6 | - [Version control](https://en.wikipedia.org/wiki/Revision_control) releases & maintainer identity | ||
8 | 7 | ||
9 | - Linux package signatures: Debian [SecureApt](https://wiki.debian.org/SecureApt), ArchLinux [Master | 8 | > You MUST understand that presence of data in the keyserver (pools) in no way connotes trust. Anyone can generate a key, with any name or email address, and upload it. All security and trust comes from evaluating security at the “object levelâ€, via PGP [Web of trust](https://en.wikipedia.org/wiki/Web_of_trust) signatures. This keyserver makes it possible to retrieve keys, looking them up via various indices, but the collection of keys in this public pool is KNOWN to contain malicious and fraudulent keys. It is the common expectation of server operators that users understand this and use software which, like all known common OpenPGP implementations, evaluates trust accordingly. This expectation is so common that it is not normally explicitly stated. |
10 | Keys](https://www.archlinux.org/master-keys/) | ||
11 | - [SCM](https://en.wikipedia.org/wiki/Revision_control) releases & maintainer identity | ||
12 | 9 | ||
13 | ### Trust | 10 | -- Phil Pennock (author of the [SKS](https://bitbucket.org/skskeyserver/sks-keyserver/wiki/Home) key server - http://sks.spodhuis.org/) |
14 | To quote Phil Pennock (the author of the [SKS](https://bitbucket.org/skskeyserver/sks-keyserver/wiki/Home) key server - http://sks.spodhuis.org/): | ||
15 | 11 | ||
16 | > You MUST understand that presence of data in the keyserver (pools) in no way connotes trust. Anyone can generate a key, with any name or email address, and upload it. All security and trust comes from evaluating security at the “object levelâ€, via PGP Web-Of-Trust signatures. This keyserver makes it possible to retrieve keys, looking them up via various indices, but the collection of keys in this public pool is KNOWN to contain malicious and fraudulent keys. It is the common expectation of server operators that users understand this and use software which, like all known common OpenPGP implementations, evaluates trust accordingly. This expectation is so common that it is not normally explicitly stated. | 12 | Trust can be gained by having your key signed by other people (and signing their key back, too :) ), for instance during [key signing parties](https://en.wikipedia.org/wiki/Key_signing_party): [Keysigning party HOWTO](http://www.cryptnet.net/fdp/crypto/keysigning_party/en/keysigning_party.html), |
17 | 13 | ||
18 | Trust can be gained by having your key signed by other people (and signing their key back, too :) ), for instance during [key signing parties](https://en.wikipedia.org/wiki/Key_signing_party), see: | ||
19 | |||
20 | - [The Keysigning party HOWTO](http://www.cryptnet.net/fdp/crypto/keysigning_party/en/keysigning_party.html) | ||
21 | - [Web of trust](https://en.wikipedia.org/wiki/Web_of_trust) | ||
22 | 14 | ||
23 | ## Generate a GPG key | 15 | ## Generate a GPG key |
24 | - [Generating a GPG key for Git tagging](http://stackoverflow.com/a/16725717) (StackOverflow) | 16 | - [Generating a GPG key for Git tagging](http://stackoverflow.com/a/16725717) (StackOverflow) |
diff --git a/doc/md/Plugin-System.md b/doc/md/dev/Plugin-system.md index 9b0d3a7d..c29774de 100644 --- a/doc/md/Plugin-System.md +++ b/doc/md/dev/Plugin-system.md | |||
@@ -1,24 +1,21 @@ | |||
1 | [**I am a developer: ** Developer API](#developer-api) | 1 | # Plugin system |
2 | |||
3 | [**I am a template designer: ** Guide for template designers](#guide-for-template-designer) | ||
4 | |||
5 | --- | ||
6 | 2 | ||
7 | ## Developer API | 3 | ## Developer API |
8 | 4 | ||
9 | ### What can I do with plugins? | 5 | ### What can I do with plugins? |
10 | 6 | ||
11 | The plugin system let you: | 7 | The plugin system lets you: |
12 | 8 | ||
13 | - insert content into specific places across templates. | 9 | - insert content into specific places across templates. |
14 | - alter data before templates rendering. | 10 | - alter data before templates rendering. |
15 | - alter data before saving new links. | 11 | - alter data before saving new links. |
16 | 12 | ||
13 | |||
17 | ### How can I create a plugin for Shaarli? | 14 | ### How can I create a plugin for Shaarli? |
18 | 15 | ||
19 | First, chose a plugin name, such as `demo_plugin`. | 16 | First, chose a plugin name, such as `demo_plugin`. |
20 | 17 | ||
21 | Under `plugin` folder, create a folder named with your plugin name. Then create a <plugin_name>.php file in that folder. | 18 | Under `plugin` folder, create a folder named with your plugin name. Then create a <plugin_name>.meta file and a <plugin_name>.php file in that folder. |
22 | 19 | ||
23 | You should have the following tree view: | 20 | You should have the following tree view: |
24 | 21 | ||
@@ -26,17 +23,23 @@ You should have the following tree view: | |||
26 | | index.php | 23 | | index.php |
27 | | plugins/ | 24 | | plugins/ |
28 | |---| demo_plugin/ | 25 | |---| demo_plugin/ |
26 | | |---| demo_plugin.meta | ||
29 | | |---| demo_plugin.php | 27 | | |---| demo_plugin.php |
30 | ``` | 28 | ``` |
31 | 29 | ||
30 | |||
32 | ### Plugin initialization | 31 | ### Plugin initialization |
33 | 32 | ||
34 | At the beginning of Shaarli execution, all enabled plugins are loaded. At this point, the plugin system looks for an `init()` function to execute and run it if it exists. This function must be named this way, and takes the `ConfigManager` as parameter. | 33 | At the beginning of Shaarli execution, all enabled plugins are loaded. At this point, the plugin system looks for an `init()` function in the <plugin_name>.php to execute and run it if it exists. This function must be named this way, and takes the `ConfigManager` as parameter. |
35 | 34 | ||
36 | <plugin_name>_init($conf) | 35 | <plugin_name>_init($conf) |
37 | 36 | ||
38 | This function can be used to create initial data, load default settings, etc. But also to set *plugin errors*. If the initialization function returns an array of strings, they will be understand as errors, and displayed in the header to logged in users. | 37 | This function can be used to create initial data, load default settings, etc. But also to set *plugin errors*. If the initialization function returns an array of strings, they will be understand as errors, and displayed in the header to logged in users. |
39 | 38 | ||
39 | The plugin system also looks for a `description` variable in the <plugin_name>.meta file, to be displayed in the plugin administration page. | ||
40 | |||
41 | description="The plugin does this and that." | ||
42 | |||
40 | ### Understanding hooks | 43 | ### Understanding hooks |
41 | 44 | ||
42 | A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution. | 45 | A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution. |
@@ -58,6 +61,7 @@ For example, if my plugin want to add data to the header, this function is neede | |||
58 | 61 | ||
59 | If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header. | 62 | If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header. |
60 | 63 | ||
64 | |||
61 | ### Plugin's data | 65 | ### Plugin's data |
62 | 66 | ||
63 | #### Parameters | 67 | #### Parameters |
@@ -68,6 +72,26 @@ Every hook function has a `$data` parameter. Its content differs for each hooks. | |||
68 | 72 | ||
69 | return $data; | 73 | return $data; |
70 | 74 | ||
75 | #### Special data | ||
76 | |||
77 | Special additional data are passed to every hook through the | ||
78 | `$data` parameter to give you access to additional context, and services. | ||
79 | |||
80 | Complete list: | ||
81 | |||
82 | * `_PAGE_` (string): if the current hook is used to render a template, its name is passed through this additional parameter. | ||
83 | * `_LOGGEDIN_` (bool): whether the user is logged in or not. | ||
84 | * `_BASE_PATH_` (string): if Shaarli instance is hosted under a subfolder, contains the subfolder path to `index.php` (e.g. `https://domain.tld/shaarli/` -> `/shaarli/`). | ||
85 | * `_BOOKMARK_SERVICE_` (`BookmarkServiceInterface`): bookmark service instance, for advanced usage. | ||
86 | |||
87 | Example: | ||
88 | |||
89 | ```php | ||
90 | if ($data['_PAGE_'] === TemplatePage::LINKLIST && $data['LOGGEDIN'] === true) { | ||
91 | // Do something for logged in users when the link list is rendered | ||
92 | } | ||
93 | ``` | ||
94 | |||
71 | #### Filling templates placeholder | 95 | #### Filling templates placeholder |
72 | 96 | ||
73 | Template placeholders are displayed in template in specific places. | 97 | Template placeholders are displayed in template in specific places. |
@@ -84,13 +108,14 @@ array_push($data['top_placeholder'], 'My', 'content'); | |||
84 | return $data; | 108 | return $data; |
85 | ``` | 109 | ``` |
86 | 110 | ||
111 | |||
87 | #### Data manipulation | 112 | #### Data manipulation |
88 | 113 | ||
89 | When a page is displayed, every variable send to the template engine is passed to plugins before that in `$data`. | 114 | When a page is displayed, every variable send to the template engine is passed to plugins before that in `$data`. |
90 | 115 | ||
91 | The data contained by this array can be altered before template rendering. | 116 | The data contained by this array can be altered before template rendering. |
92 | 117 | ||
93 | For exemple, in linklist, it is possible to alter every title: | 118 | For example, in linklist, it is possible to alter every title: |
94 | 119 | ||
95 | ```php | 120 | ```php |
96 | // mind the reference if you want $data to be altered | 121 | // mind the reference if you want $data to be altered |
@@ -114,19 +139,35 @@ Each file contain two keys: | |||
114 | 139 | ||
115 | > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. | 140 | > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. |
116 | 141 | ||
142 | ### Understanding relative paths | ||
143 | |||
144 | Because Shaarli is a self-hosted tool, an instance can either be installed at the root directory, or under a subfolder. | ||
145 | This means that you can *never* use absolute paths (eg `/plugins/mything/file.png`). | ||
146 | |||
147 | If a file needs to be included in server end, use simple relative path: | ||
148 | `PluginManager::$PLUGINS_PATH . '/mything/template.html'`. | ||
149 | |||
150 | If it needs to be included in front end side (e.g. an image), | ||
151 | the relative path must be prefixed with special data `_BASE_PATH_`: | ||
152 | `($data['_BASE_PATH_'] ?? '') . '/' . PluginManager::$PLUGINS_PATH . '/mything/picture.png`. | ||
153 | |||
154 | Note that special placeholders for CSS and JS files (respectively `css_files` and `js_files`) are already prefixed | ||
155 | with the base path in template files. | ||
156 | |||
117 | ### It's not working! | 157 | ### It's not working! |
118 | 158 | ||
119 | Use `demo_plugin` as a functional example. It covers most of the plugin system features. | 159 | Use `demo_plugin` as a functional example. It covers most of the plugin system features. |
120 | 160 | ||
121 | If it's still not working, please [open an issue](https://github.com/shaarli/Shaarli/issues/new). | 161 | If it's still not working, please [open an issue](https://github.com/shaarli/Shaarli/issues/new). |
122 | 162 | ||
163 | |||
123 | ### Hooks | 164 | ### Hooks |
124 | 165 | ||
125 | | Hooks | Description | | 166 | | Hooks | Description | |
126 | | ------------- |:-------------:| | 167 | | ------------- |:-------------:| |
127 | | [render_header](#render_header) | Allow plugin to add content in page headers. | | 168 | | [render_header](#render_header) | Allow plugin to add content in page headers. | |
128 | | [render_includes](#render_includes) | Allow plugin to include their own CSS files. | | 169 | | [render_includes](#render_includes) | Allow plugin to include their own CSS files. | |
129 | | [render_footer](#render_footer) | Allow plugin to add content in page footer and include their own JS files. | | 170 | | [render_footer](#render_footer) | Allow plugin to add content in page footer and include their own JS files. | |
130 | | [render_linklist](#render_linklist) | It allows to add content at the begining and end of the page, after every link displayed and to alter link data. | | 171 | | [render_linklist](#render_linklist) | It allows to add content at the begining and end of the page, after every link displayed and to alter link data. | |
131 | | [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. | | 172 | | [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. | |
132 | | [render_tools](#render_tools) | Allow to add content at the end of the page. | | 173 | | [render_tools](#render_tools) | Allow to add content at the end of the page. | |
@@ -140,19 +181,16 @@ If it's still not working, please [open an issue](https://github.com/shaarli/Sha | |||
140 | | [save_plugin_parameters](#save_plugin_parameters) | Allow to manipulate plugin parameters before they're saved. | | 181 | | [save_plugin_parameters](#save_plugin_parameters) | Allow to manipulate plugin parameters before they're saved. | |
141 | 182 | ||
142 | 183 | ||
143 | |||
144 | #### render_header | 184 | #### render_header |
145 | 185 | ||
146 | Triggered on every page. | 186 | Triggered on every page - allows plugins to add content in page headers. |
147 | 187 | ||
148 | Allow plugin to add content in page headers. | ||
149 | 188 | ||
150 | ##### Data | 189 | ##### Data |
151 | 190 | ||
152 | `$data` is an array containing: | 191 | `$data` is an array containing: |
153 | 192 | ||
154 | - `_PAGE_`: current target page (eg: `linklist`, `picwall`, etc.). | 193 | - [Special data](#special-data) |
155 | - `_LOGGEDIN_`: true if user is logged in, false otherwise. | ||
156 | 194 | ||
157 | ##### Template placeholders | 195 | ##### Template placeholders |
158 | 196 | ||
@@ -170,18 +208,16 @@ List of placeholders: | |||
170 | 208 | ||
171 |  | 209 |  |
172 | 210 | ||
173 | #### render_includes | ||
174 | 211 | ||
175 | Triggered on every page. | 212 | #### render_includes |
176 | 213 | ||
177 | Allow plugin to include their own CSS files. | 214 | Triggered on every page - allows plugins to include their own CSS files. |
178 | 215 | ||
179 | ##### Data | 216 | ##### data |
180 | 217 | ||
181 | `$data` is an array containing: | 218 | `$data` is an array containing: |
182 | 219 | ||
183 | - `_PAGE_`: current target page (eg: `linklist`, `picwall`, etc.). | 220 | - [Special data](#special-data) |
184 | - `_LOGGEDIN_`: true if user is logged in, false otherwise. | ||
185 | 221 | ||
186 | ##### Template placeholders | 222 | ##### Template placeholders |
187 | 223 | ||
@@ -193,18 +229,18 @@ List of placeholders: | |||
193 | 229 | ||
194 | > Note: only add the path of the CSS file. E.g: `plugins/demo_plugin/custom_demo.css`. | 230 | > Note: only add the path of the CSS file. E.g: `plugins/demo_plugin/custom_demo.css`. |
195 | 231 | ||
232 | |||
196 | #### render_footer | 233 | #### render_footer |
197 | 234 | ||
198 | Triggered on every page. | 235 | Triggered on every page. |
199 | 236 | ||
200 | Allow plugin to add content in page footer and include their own JS files. | 237 | Allow plugin to add content in page footer and include their own JS files. |
201 | 238 | ||
202 | ##### Data | 239 | ##### data |
203 | 240 | ||
204 | `$data` is an array containing: | 241 | `$data` is an array containing: |
205 | 242 | ||
206 | - `_PAGE_`: current target page (eg: `linklist`, `picwall`, etc.). | 243 | - [Special data](#special-data) |
207 | - `_LOGGEDIN_`: true if user is logged in, false otherwise. | ||
208 | 244 | ||
209 | ##### Template placeholders | 245 | ##### Template placeholders |
210 | 246 | ||
@@ -221,20 +257,21 @@ List of placeholders: | |||
221 | 257 | ||
222 | > Note: only add the path of the JS file. E.g: `plugins/demo_plugin/custom_demo.js`. | 258 | > Note: only add the path of the JS file. E.g: `plugins/demo_plugin/custom_demo.js`. |
223 | 259 | ||
260 | |||
224 | #### render_linklist | 261 | #### render_linklist |
225 | 262 | ||
226 | Triggered when `linklist` is displayed (list of links, permalink, search, tag filtered, etc.). | 263 | Triggered when `linklist` is displayed (list of links, permalink, search, tag filtered, etc.). |
227 | 264 | ||
228 | It allows to add content at the begining and end of the page, after every link displayed and to alter link data. | 265 | It allows to add content at the begining and end of the page, after every link displayed and to alter link data. |
229 | 266 | ||
230 | ##### Data | 267 | ##### data |
231 | 268 | ||
232 | `$data` is an array containing: | 269 | `$data` is an array containing: |
233 | 270 | ||
234 | - `_LOGGEDIN_`: true if user is logged in, false otherwise. | 271 | - All templates data, including links. |
235 | - All templates data, including links. | 272 | - [Special data](#special-data) |
236 | 273 | ||
237 | ##### Template placeholders | 274 | ##### template placeholders |
238 | 275 | ||
239 | Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array. | 276 | Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array. |
240 | 277 | ||
@@ -256,19 +293,21 @@ List of placeholders: | |||
256 | 293 | ||
257 |  | 294 |  |
258 | 295 | ||
296 | |||
259 | #### render_editlink | 297 | #### render_editlink |
260 | 298 | ||
261 | Triggered when the link edition form is displayed. | 299 | Triggered when the link edition form is displayed. |
262 | 300 | ||
263 | Allow to add fields in the form, or display elements. | 301 | Allow to add fields in the form, or display elements. |
264 | 302 | ||
265 | ##### Data | 303 | ##### data |
266 | 304 | ||
267 | `$data` is an array containing: | 305 | `$data` is an array containing: |
268 | 306 | ||
269 | - All templates data. | 307 | - All templates data. |
308 | - [Special data](#special-data) | ||
270 | 309 | ||
271 | ##### Template placeholders | 310 | ##### template placeholders |
272 | 311 | ||
273 | Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array. | 312 | Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array. |
274 | 313 | ||
@@ -278,19 +317,21 @@ List of placeholders: | |||
278 | 317 | ||
279 |  | 318 |  |
280 | 319 | ||
320 | |||
281 | #### render_tools | 321 | #### render_tools |
282 | 322 | ||
283 | Triggered when the "tools" page is displayed. | 323 | Triggered when the "tools" page is displayed. |
284 | 324 | ||
285 | Allow to add content at the end of the page. | 325 | Allow to add content at the end of the page. |
286 | 326 | ||
287 | ##### Data | 327 | ##### data |
288 | 328 | ||
289 | `$data` is an array containing: | 329 | `$data` is an array containing: |
290 | 330 | ||
291 | - All templates data. | 331 | - All templates data. |
332 | - [Special data](#special-data) | ||
292 | 333 | ||
293 | ##### Template placeholders | 334 | ##### template placeholders |
294 | 335 | ||
295 | Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array. | 336 | Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array. |
296 | 337 | ||
@@ -300,20 +341,21 @@ List of placeholders: | |||
300 | 341 | ||
301 |  | 342 |  |
302 | 343 | ||
344 | |||
303 | #### render_picwall | 345 | #### render_picwall |
304 | 346 | ||
305 | Triggered when picwall is displayed. | 347 | Triggered when picwall is displayed. |
306 | 348 | ||
307 | Allow to add content at the top and bottom of the page. | 349 | Allow to add content at the top and bottom of the page. |
308 | 350 | ||
309 | ##### Data | 351 | ##### data |
310 | 352 | ||
311 | `$data` is an array containing: | 353 | `$data` is an array containing: |
312 | 354 | ||
313 | - `_LOGGEDIN_`: true if user is logged in, false otherwise. | 355 | - All templates data. |
314 | - All templates data. | 356 | - [Special data](#special-data) |
315 | 357 | ||
316 | ##### Template placeholders | 358 | ##### template placeholders |
317 | 359 | ||
318 | Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array. | 360 | Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array. |
319 | 361 | ||
@@ -324,18 +366,19 @@ List of placeholders: | |||
324 | 366 | ||
325 |  | 367 |  |
326 | 368 | ||
369 | |||
327 | #### render_tagcloud | 370 | #### render_tagcloud |
328 | 371 | ||
329 | Triggered when tagcloud is displayed. | 372 | Triggered when tagcloud is displayed. |
330 | 373 | ||
331 | Allow to add content at the top and bottom of the page. | 374 | Allow to add content at the top and bottom of the page. |
332 | 375 | ||
333 | ##### Data | 376 | ##### data |
334 | 377 | ||
335 | `$data` is an array containing: | 378 | `$data` is an array containing: |
336 | 379 | ||
337 | - `_LOGGEDIN_`: true if user is logged in, false otherwise. | 380 | - All templates data. |
338 | - All templates data. | 381 | - [Special data](#special-data) |
339 | 382 | ||
340 | ##### Template placeholders | 383 | ##### Template placeholders |
341 | 384 | ||
@@ -355,16 +398,14 @@ For each tag, the following placeholder can be used: | |||
355 | 398 | ||
356 | #### render_taglist | 399 | #### render_taglist |
357 | 400 | ||
358 | Triggered when taglist is displayed. | 401 | Triggered when taglist is displayed - allows to add content at the top and bottom of the page. |
359 | |||
360 | Allow to add content at the top and bottom of the page. | ||
361 | 402 | ||
362 | ##### Data | 403 | ##### data |
363 | 404 | ||
364 | `$data` is an array containing: | 405 | `$data` is an array containing: |
365 | 406 | ||
366 | - `_LOGGEDIN_`: true if user is logged in, false otherwise. | 407 | - All templates data. |
367 | - All templates data. | 408 | - [Special data](#special-data) |
368 | 409 | ||
369 | ##### Template placeholders | 410 | ##### Template placeholders |
370 | 411 | ||
@@ -385,12 +426,13 @@ Triggered when tagcloud is displayed. | |||
385 | 426 | ||
386 | Allow to add content at the top and bottom of the page, the bottom of each link and to alter data. | 427 | Allow to add content at the top and bottom of the page, the bottom of each link and to alter data. |
387 | 428 | ||
388 | ##### Data | 429 | |
430 | ##### data | ||
389 | 431 | ||
390 | `$data` is an array containing: | 432 | `$data` is an array containing: |
391 | 433 | ||
392 | - `_LOGGEDIN_`: true if user is logged in, false otherwise. | 434 | - All templates data, including links. |
393 | - All templates data, including links. | 435 | - [Special data](#special-data) |
394 | 436 | ||
395 | ##### Template placeholders | 437 | ##### Template placeholders |
396 | 438 | ||
@@ -405,19 +447,19 @@ List of placeholders: | |||
405 | - `plugin_start_zone`: before displaying the template content. | 447 | - `plugin_start_zone`: before displaying the template content. |
406 | - `plugin_end_zone`: after displaying the template content. | 448 | - `plugin_end_zone`: after displaying the template content. |
407 | 449 | ||
450 | |||
408 | #### render_feed | 451 | #### render_feed |
409 | 452 | ||
410 | Triggered when the ATOM or RSS feed is displayed. | 453 | Triggered when the ATOM or RSS feed is displayed. |
411 | 454 | ||
412 | Allow to add tags in the feed, either in the header or for each items. Items (links) can also be altered before being rendered. | 455 | Allow to add tags in the feed, either in the header or for each items. Items (links) can also be altered before being rendered. |
413 | 456 | ||
414 | ##### Data | 457 | ##### data |
415 | 458 | ||
416 | `$data` is an array containing: | 459 | `$data` is an array containing: |
417 | 460 | ||
418 | - `_LOGGEDIN_`: true if user is logged in, false otherwise. | 461 | - All templates data, including links. |
419 | - `_PAGE_`: containing either `rss` or `atom`. | 462 | - [Special data](#special-data) |
420 | - All templates data, including links. | ||
421 | 463 | ||
422 | ##### Template placeholders | 464 | ##### Template placeholders |
423 | 465 | ||
@@ -431,13 +473,14 @@ For each links: | |||
431 | 473 | ||
432 | - `feed_plugins`: additional tag for every link entry. | 474 | - `feed_plugins`: additional tag for every link entry. |
433 | 475 | ||
476 | |||
434 | #### save_link | 477 | #### save_link |
435 | 478 | ||
436 | Triggered when a link is save (new link or edit). | 479 | Triggered when a link is save (new link or edit). |
437 | 480 | ||
438 | Allow to alter the link being saved in the datastore. | 481 | Allow to alter the link being saved in the datastore. |
439 | 482 | ||
440 | ##### Data | 483 | ##### data |
441 | 484 | ||
442 | `$data` is an array containing the link being saved: | 485 | `$data` is an array containing the link being saved: |
443 | 486 | ||
@@ -451,6 +494,8 @@ Allow to alter the link being saved in the datastore. | |||
451 | - created | 494 | - created |
452 | - updated | 495 | - updated |
453 | 496 | ||
497 | Also [special data](#special-data). | ||
498 | |||
454 | 499 | ||
455 | #### delete_link | 500 | #### delete_link |
456 | 501 | ||
@@ -458,9 +503,9 @@ Triggered when a link is deleted. | |||
458 | 503 | ||
459 | Allow to execute any action before the link is actually removed from the datastore | 504 | Allow to execute any action before the link is actually removed from the datastore |
460 | 505 | ||
461 | ##### Data | 506 | ##### data |
462 | 507 | ||
463 | `$data` is an array containing the link being saved: | 508 | `$data` is an array containing the link being deleted: |
464 | 509 | ||
465 | - id | 510 | - id |
466 | - title | 511 | - title |
@@ -472,6 +517,7 @@ Allow to execute any action before the link is actually removed from the datasto | |||
472 | - created | 517 | - created |
473 | - updated | 518 | - updated |
474 | 519 | ||
520 | Also [special data](#special-data). | ||
475 | 521 | ||
476 | #### save_plugin_parameters | 522 | #### save_plugin_parameters |
477 | 523 | ||
@@ -480,15 +526,16 @@ Triggered when the plugin parameters are saved from the plugin administration pa | |||
480 | Plugins can perform an action every times their settings are updated. | 526 | Plugins can perform an action every times their settings are updated. |
481 | For example it is used to update the CSS file of the `default_colors` plugins. | 527 | For example it is used to update the CSS file of the `default_colors` plugins. |
482 | 528 | ||
483 | ##### Data | 529 | ##### data |
484 | 530 | ||
485 | `$data` input contains the `$_POST` array. | 531 | `$data` input contains the `$_POST` array. |
486 | 532 | ||
487 | So if the plugin has a parameter called `MYPLUGIN_PARAMETER`, | 533 | So if the plugin has a parameter called `MYPLUGIN_PARAMETER`, |
488 | the array will contain an entry with `MYPLUGIN_PARAMETER` as a key. | 534 | the array will contain an entry with `MYPLUGIN_PARAMETER` as a key. |
489 | 535 | ||
536 | Also [special data](#special-data). | ||
490 | 537 | ||
491 | ## Guide for template designer | 538 | ## Guide for template designers |
492 | 539 | ||
493 | ### Plugin administration | 540 | ### Plugin administration |
494 | 541 | ||
@@ -510,7 +557,7 @@ Otherwise, you can use your own JS as long as this field is send by the form: | |||
510 | 557 | ||
511 | ### Placeholder system | 558 | ### Placeholder system |
512 | 559 | ||
513 | In order to make plugins work with every custom themes, you need to add variable placeholder in your templates. | 560 | In order to make plugins work with every custom themes, you need to add variable placeholder in your templates. |
514 | 561 | ||
515 | It's a RainTPL loop like this: | 562 | It's a RainTPL loop like this: |
516 | 563 | ||
@@ -532,7 +579,7 @@ At the end of the menu: | |||
532 | 579 | ||
533 | At the end of file, before clearing floating blocks: | 580 | At the end of file, before clearing floating blocks: |
534 | 581 | ||
535 | {if="!empty($plugin_errors) && isLoggedIn()"} | 582 | {if="!empty($plugin_errors) && $is_logged_in"} |
536 | <ul class="errors"> | 583 | <ul class="errors"> |
537 | {loop="plugin_errors"} | 584 | {loop="plugin_errors"} |
538 | <li>{$value}</li> | 585 | <li>{$value}</li> |
diff --git a/doc/md/dev/Release-Shaarli.md b/doc/md/dev/Release-Shaarli.md new file mode 100644 index 00000000..2c772406 --- /dev/null +++ b/doc/md/dev/Release-Shaarli.md | |||
@@ -0,0 +1,145 @@ | |||
1 | # Release Shaarli | ||
2 | |||
3 | ## Requirements | ||
4 | |||
5 | This guide assumes that you have: | ||
6 | |||
7 | - a GPG key matching your GitHub authentication credentials/email (the email address identified by the GPG key is the same as the one in your `~/.gitconfig`) | ||
8 | - a GitHub fork of Shaarli | ||
9 | - a local clone of your Shaarli fork, with the following remotes: | ||
10 | - `origin` pointing to your GitHub fork | ||
11 | - `upstream` pointing to the main Shaarli repository | ||
12 | - maintainer permissions on the main Shaarli repository, to: | ||
13 | - push the signed tag | ||
14 | - create a new release | ||
15 | - [Composer](https://getcomposer.org/) needs to be installed | ||
16 | - The [venv](https://docs.python.org/3/library/venv.html) Python 3 module needs to be installed for HTML documentation generation. | ||
17 | |||
18 | ## Release notes and `CHANGELOG.md` | ||
19 | |||
20 | GitHub allows drafting the release notes for the upcoming release, from the [Releases](https://github.com/shaarli/Shaarli/releases) page. This way, the release note can be drafted while contributions are merged to `master`. See http://keepachangelog.com/en/0.3.0/ for changelog formatting. | ||
21 | |||
22 | `CHANGELOG.md` should contain the same information as the release note draft for the upcoming version. Update it to: | ||
23 | |||
24 | - add new entries (additions, fixes, etc.) | ||
25 | - mark the current version as released by setting its date and link | ||
26 | - add a new section for the future unreleased version | ||
27 | |||
28 | ```bash | ||
29 | ## [v0.x.y](https://github.com/shaarli/Shaarli/releases/tag/v0.x.y) - UNRELEASES | ||
30 | |||
31 | ### Added | ||
32 | |||
33 | ### Changed | ||
34 | |||
35 | ### Fixed | ||
36 | |||
37 | ### Removed | ||
38 | |||
39 | ### Deprecated | ||
40 | |||
41 | ### Security | ||
42 | |||
43 | ``` | ||
44 | |||
45 | |||
46 | ## Update the list of Git contributors | ||
47 | |||
48 | ```bash | ||
49 | $ make authors | ||
50 | $ git commit -s -m "Update AUTHORS" | ||
51 | ``` | ||
52 | |||
53 | ## Create and merge a Pull Request | ||
54 | |||
55 | Create a Pull Request to marge changes from your remote, into `master` in the community Shaarli repository, and have it merged. | ||
56 | |||
57 | |||
58 | ## Create the release branch and update shaarli_version.php | ||
59 | |||
60 | ```bash | ||
61 | # fetch latest changes from master to your local copy | ||
62 | git checkout master | ||
63 | git pull upstream master | ||
64 | |||
65 | # If releasing a new minor version, create a release branch | ||
66 | $ git checkout -b v0.x | ||
67 | |||
68 | # Bump shaarli_version.php from dev to 0.x.0, **without the v** | ||
69 | $ vim shaarli_version.php | ||
70 | $ git add shaarli_version | ||
71 | $ git commit -s -m "Bump Shaarli version to v0.x.0" | ||
72 | $ git push upstream v0.x | ||
73 | ``` | ||
74 | |||
75 | ## Create and push a signed tag | ||
76 | |||
77 | Git [tags](http://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project#Tagging-Your-Releases) are used to identify specific revisions with a unique version number that follows [semantic versioning](https://semver.org/) | ||
78 | |||
79 | ```bash | ||
80 | # update your local copy | ||
81 | git checkout v0.5 | ||
82 | git pull upstream v0.5 | ||
83 | |||
84 | # create a signed tag | ||
85 | git tag -s -m "Release v0.5.0" v0.5.0 | ||
86 | |||
87 | # push the tag to upstream | ||
88 | git push --tags upstream | ||
89 | ``` | ||
90 | |||
91 | Here is how to verify a signed tag. [`v0.5.0`](https://github.com/shaarli/Shaarli/releases/tag/v0.5.0) is the first GPG-signed tag pushed on the Community Shaarli. Let's have a look at its signature! | ||
92 | |||
93 | ```bash | ||
94 | # update the list of available tags | ||
95 | git fetch upstream | ||
96 | |||
97 | # get the SHA1 reference of the tag | ||
98 | git show-ref tags/v0.5.0 | ||
99 | # gives: f7762cf803f03f5caf4b8078359a63783d0090c1 refs/tags/v0.5.0 | ||
100 | |||
101 | # verify the tag signature information | ||
102 | git verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1 | ||
103 | # gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F | ||
104 | # gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate] | ||
105 | ``` | ||
106 | |||
107 | ## Publish the GitHub release | ||
108 | |||
109 | - In the `master` banch, update version badges in `README.md` to point to the newly released Shaarli version | ||
110 | - Update the previously drafted [release](https://github.com/shaarli/Shaarli/releases) (notes, tag) and publish it | ||
111 | - Profit! | ||
112 | |||
113 | |||
114 | ## Generate full release zip archives | ||
115 | |||
116 | Release archives will contain Shaarli code plus all required third-party libraries. They are useful for users who: | ||
117 | |||
118 | - have no SSH access, no possibility to install PHP packages/server extensions, no possibility to run scripts (shared hosting) | ||
119 | - do not want to install build/dev dependencies on their server | ||
120 | |||
121 | `git checkout` the appropriate branch, then: | ||
122 | |||
123 | ```bash | ||
124 | # checkout the appropriate branch | ||
125 | git checkout 0.x.y | ||
126 | # generate zip archives | ||
127 | make release_archive | ||
128 | ``` | ||
129 | |||
130 | This will create `shaarli-v0.x.y-full.tar`, `shaarli-v0.x.y-full.zip`. These archives need to be manually uploaded on the previously created GitHub [release](https://github.com/shaarli/Shaarli/releases). | ||
131 | |||
132 | |||
133 | ### Update the `latest` branch | ||
134 | |||
135 | ```bash | ||
136 | # checkout the 'latest' branch | ||
137 | git checkout latest | ||
138 | # merge changes from your newly published release branch | ||
139 | git merge v0.x.y | ||
140 | # fix eventual conflicts with git mergetool... | ||
141 | # run tests | ||
142 | make test | ||
143 | # push the latest branch | ||
144 | git push upstream latest | ||
145 | ``` | ||
diff --git a/doc/md/Theming.md b/doc/md/dev/Theming.md index bd400776..1ad30465 100644 --- a/doc/md/Theming.md +++ b/doc/md/dev/Theming.md | |||
@@ -1,3 +1,5 @@ | |||
1 | # Theming | ||
2 | |||
1 | ## Foreword | 3 | ## Foreword |
2 | 4 | ||
3 | There are two ways of customizing how Shaarli looks: | 5 | There are two ways of customizing how Shaarli looks: |
@@ -16,8 +18,6 @@ This file allows overriding rules defined in the template CSS files (only add ch | |||
16 | 18 | ||
17 | **Note**: Do not edit `tpl/default/css/shaarli.css`! Your changes would be overridden when updating Shaarli. | 19 | **Note**: Do not edit `tpl/default/css/shaarli.css`! Your changes would be overridden when updating Shaarli. |
18 | 20 | ||
19 | See also [Download CSS styles from an OPML list](Download CSS styles from an OPML list) | ||
20 | |||
21 | ## Themes | 21 | ## Themes |
22 | 22 | ||
23 | Installation: | 23 | Installation: |
@@ -45,6 +45,7 @@ Installation: | |||
45 | - [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli | 45 | - [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli |
46 | - [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone | 46 | - [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone |
47 | - [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site | 47 | - [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site |
48 | - [xfnw/shaarli-default-dark](https://github.com/xfnw/shaarli-default-dark) - The default theme but nice and dark for your eyeballs | ||
48 | 49 | ||
49 | ### Shaarli forks | 50 | ### Shaarli forks |
50 | 51 | ||
diff --git a/doc/md/Translations.md b/doc/md/dev/Translations.md index c7d33855..8f3b8f10 100644 --- a/doc/md/Translations.md +++ b/doc/md/dev/Translations.md | |||
@@ -7,87 +7,80 @@ Note that only the `default` theme supports translations. | |||
7 | 7 | ||
8 | ### Contributing | 8 | ### Contributing |
9 | 9 | ||
10 | We encourage the community to contribute to Shaarli's translation either by improving existing | 10 | We encourage the community to contribute to Shaarli translations, either by improving existing translations or submitting a new language. |
11 | translations or submitting a new language. | ||
12 | 11 | ||
13 | Contributing to the translation does not require development skill. | 12 | Contributing to the translation does not require software development knowledge. |
14 | 13 | ||
15 | Please submit a pull request with the `.po` file updated/created. Note that the compiled file (`.mo`) | 14 | Please submit a pull request with the `.po` file updated/created. Note that the compiled file (`.mo`) is not stored on the repository, and is generated during the release process. |
16 | is not stored on the repository, and is generated during the release process. | ||
17 | 15 | ||
18 | ### How to | ||
19 | |||
20 | First, install [Poedit](https://poedit.net/) tool. | ||
21 | 16 | ||
22 | Poedit will extract strings to translate from the PHP source code. | 17 | ### How to |
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 | 18 | ||
27 | You can either use [this script](https://gist.github.com/ArthurHoaro/5d0323f758ab2401ef444a53f54e9a07) (recommended) | 19 | Install [Poedit](https://poedit.net/) (used to extract strings to translate from the PHP source code, and generate `.po` files). |
28 | or visit every template page in your browser to generate cache files, while logged in. | ||
29 | 20 | ||
30 | Here is a list : | 21 | Due to the usage of a template engine, it's important to generate PHP cache files to extract every translatable string. You can either use [this script](https://gist.github.com/ArthurHoaro/5d0323f758ab2401ef444a53f54e9a07) (recommended) or visit every template page in your browser to generate cache files, while logged in. Here is a list : |
31 | 22 | ||
32 | ``` | 23 | ``` |
33 | http://<replace_domain>/ | 24 | http://<replace_domain>/ |
25 | http://<replace_domain>/login | ||
26 | http://<replace_domain>/daily | ||
27 | http://<replace_domain>/tags/cloud | ||
28 | http://<replace_domain>/tags/list | ||
29 | http://<replace_domain>/picture-wall | ||
34 | http://<replace_domain>/?nonope | 30 | http://<replace_domain>/?nonope |
35 | http://<replace_domain>/?do=addlink | 31 | http://<replace_domain>/admin/add-shaare |
36 | http://<replace_domain>/?do=changepasswd | 32 | http://<replace_domain>/admin/password |
37 | http://<replace_domain>/?do=changetag | 33 | http://<replace_domain>/admin/tags |
38 | http://<replace_domain>/?do=configure | 34 | http://<replace_domain>/admin/configure |
39 | http://<replace_domain>/?do=tools | 35 | http://<replace_domain>/admin/tools |
40 | http://<replace_domain>/?do=daily | 36 | http://<replace_domain>/admin/shaare |
41 | http://<replace_domain>/?post | 37 | http://<replace_domain>/admin/export |
42 | http://<replace_domain>/?do=export | 38 | http://<replace_domain>/admin/import |
43 | http://<replace_domain>/?do=import | 39 | http://<replace_domain>/admin/plugins |
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 | ``` | 40 | ``` |
50 | 41 | ||
51 | #### Improve existing translation | ||
52 | 42 | ||
53 | In Poedit, click on "Edit a Translation", and from Shaarli's directory open | 43 | #### Improve existing translations |
54 | `inc/languages/<lang>/LC_MESSAGES/shaarli.po`. | ||
55 | 44 | ||
56 | The existing list of translatable strings should have been loaded, then click on the "Update" button. | 45 | - In Poedit, click on "Edit a Translation |
57 | 46 | - Open `inc/languages/<lang>/LC_MESSAGES/shaarli.po` under Shaarli's directory | |
58 | You can start editing the translation. | 47 | - The existing list of translatable strings should load |
48 | - Click on the "Update" button. | ||
49 | - Start editing translations. | ||
59 | 50 | ||
60 |  | 51 |  |
61 | 52 | ||
62 | Save when you're done, then you can submit a pull request containing the updated `shaarli.po`. | 53 | Save when you're done, then you can submit a pull request containing the updated `shaarli.po`. |
63 | 54 | ||
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 | 55 | ||
71 | Click on `File > Save as...`, and save your file in `<shaarli directory>/inc/language/<new language>/LC_MESSAGES/shaarli.po`. | 56 | #### Add a new language |
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 | 57 | ||
75 | Then click on the "Update" button, and you can start to translate every available string. | 58 | - In Poedit select "Create New Translation" |
59 | - Open `inc/languages/<lang>/LC_MESSAGES/shaarli.po` under Shaarli's directory | ||
60 | - Select the language you want to create. | ||
61 | - Click on `File > Save as...`, save your file in `<shaarli directory>/inc/language/<new language>/LC_MESSAGES/shaarli.po` (`<new language>` here should be the language code respecting the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-2) format in lowercase - e.g. `de` for German) | ||
62 | - Click on the "Update" button | ||
63 | - Start editing translations. | ||
76 | 64 | ||
77 | Save when you're done, then you can submit a pull request containing the new `shaarli.po`. | 65 | Save when you're done, then you can submit a pull request containing the new `shaarli.po`. |
78 | 66 | ||
79 | ### Theme translations | ||
80 | 67 | ||
81 | Theme translation extensions are loaded automatically if they're present. | 68 | ### Theme translations |
69 | |||
70 | [Theme](Theming) translation extensions are loaded automatically if they're present. | ||
82 | 71 | ||
83 | As a theme developer, all you have to do is to add the `.po` and `.mo` compiled file like this: | 72 | As a theme developer, all you have to do is to add the `.po` and `.mo` compiled file like this: |
84 | 73 | ||
85 | tpl/<theme name>/language/<lang>/LC_MESSAGES/<theme name>.po | 74 | ``` |
86 | tpl/<theme name>/language/<lang>/LC_MESSAGES/<theme name>.mo | 75 | tpl/<theme name>/language/<lang>/LC_MESSAGES/<theme name>.po |
76 | tpl/<theme name>/language/<lang>/LC_MESSAGES/<theme name>.mo | ||
77 | ``` | ||
78 | |||
79 | Where `<lang>` is the ISO 3166-1 alpha-2 language code. | ||
87 | 80 | ||
88 | Where `<lang>` is the ISO 3166-1 alpha-2 language code. | ||
89 | Read the following section "Extend Shaarli's translation" to learn how to generate those files. | 81 | Read the following section "Extend Shaarli's translation" to learn how to generate those files. |
90 | 82 | ||
83 | |||
91 | ### Extend Shaarli's translation | 84 | ### Extend Shaarli's translation |
92 | 85 | ||
93 | If you're writing a custom theme, or a non official plugin, you might want to use the translation system, | 86 | If you're writing a custom theme, or a non official plugin, you might want to use the translation system, |
@@ -106,7 +99,7 @@ First, create your translation files tree directory: | |||
106 | Your `.po` files must be named like your domain. E.g. if your translation domain is `my_theme`, then your file will be | 99 | Your `.po` files must be named like your domain. E.g. if your translation domain is `my_theme`, then your file will be |
107 | `my_theme.po`. | 100 | `my_theme.po`. |
108 | 101 | ||
109 | Users have to register your extension in their configuration with the parameter | 102 | Users have to register your extension in their configuration with the parameter |
110 | `translation.extensions.<domain>: <translation files path>`. | 103 | `translation.extensions.<domain>: <translation files path>`. |
111 | 104 | ||
112 | Example: | 105 | Example: |
@@ -151,11 +144,11 @@ When you're done, open Poedit and load translation strings from sources: | |||
151 | 1. `File > New` | 144 | 1. `File > New` |
152 | 2. Choose your language | 145 | 2. Choose your language |
153 | 3. Save your `PO` file in `<your_module>/languages/<language code>/LC_MESSAGES/my_theme.po`. | 146 | 3. Save your `PO` file in `<your_module>/languages/<language code>/LC_MESSAGES/my_theme.po`. |
154 | 4. Go to `Catalog > Properties...` | 147 | 4. Go to `Catalog > Properties...` |
155 | 5. Fill the `Translation Properties` tab | 148 | 5. Fill the `Translation Properties` tab |
156 | 6. Add your source path in the `Sources Paths` tab | 149 | 6. Add your source path in the `Sources Paths` tab |
157 | 7. In the `Sources Keywords` tab uncheck "Also use default keywords" and add the following lines: | 150 | 7. In the `Sources Keywords` tab uncheck "Also use default keywords" and add the following lines: |
158 | 151 | ||
159 | ``` | 152 | ``` |
160 | my_theme_t | 153 | my_theme_t |
161 | my_theme_t:1,2 | 154 | my_theme_t:1,2 |
diff --git a/doc/md/dev/Unit-tests.md b/doc/md/dev/Unit-tests.md new file mode 100644 index 00000000..fd286bf0 --- /dev/null +++ b/doc/md/dev/Unit-tests.md | |||
@@ -0,0 +1,133 @@ | |||
1 | # Unit tests | ||
2 | |||
3 | Shaarli uses the [PHPUnit](https://phpunit.de/) test framework; it can be installed with [Composer](https://getcomposer.org/), which is a dependency management tool. | ||
4 | |||
5 | ## Install composer | ||
6 | |||
7 | You can either use: | ||
8 | |||
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 | |||
12 | ```bash | ||
13 | # for Debian-based distros | ||
14 | sudo apt install composer | ||
15 | ``` | ||
16 | |||
17 | |||
18 | ## Install Shaarli dev dependencies | ||
19 | |||
20 | ```bash | ||
21 | $ cd /path/to/shaarli | ||
22 | $ make composer_dependencies_dev | ||
23 | ``` | ||
24 | |||
25 | ## Install and enable Xdebug to generate PHPUnit coverage reports | ||
26 | |||
27 | |||
28 | [Xdebug](http://xdebug.org/docs/install) is a PHP extension which provides debugging and profiling capabilities. Install Xdebug: | ||
29 | |||
30 | ```bash | ||
31 | # for Debian-based distros: | ||
32 | sudo apt install php-xdebug | ||
33 | |||
34 | # for ArchLinux: | ||
35 | pacman -S xdebug | ||
36 | |||
37 | # then add the following line to /etc/php/php.ini | ||
38 | zend_extension=xdebug.so | ||
39 | ``` | ||
40 | |||
41 | ## Run unit tests | ||
42 | |||
43 | Ensure tests pass successuflly: | ||
44 | |||
45 | ```bash | ||
46 | make test | ||
47 | # ... | ||
48 | # OK (36 tests, 65 assertions) | ||
49 | ``` | ||
50 | |||
51 | In case of failure the test suite will point you to actual errors and output a summary: | ||
52 | |||
53 | ```bash | ||
54 | make test | ||
55 | # ... | ||
56 | # FAILURES! | ||
57 | # Tests: 36, Assertions: 63, Errors: 1, Failures: 2. | ||
58 | ``` | ||
59 | |||
60 | By default, PHPUnit will run all suitable tests found under the `tests` directory. Each test has 3 possible outcomes: | ||
61 | |||
62 | - `.` - success | ||
63 | - `F` - failure: the test was run but its results are invalid | ||
64 | - the code does not behave as expected | ||
65 | - dependencies to external elements: globals, session, cache... | ||
66 | - `E` - error: something went wrong and the tested code has crashed | ||
67 | - typos in the code, or in the test code | ||
68 | - dependencies to missing external elements | ||
69 | |||
70 | If Xdebug has been installed and activated, two coverage reports will be generated: | ||
71 | |||
72 | - a summary in the console | ||
73 | - a detailed HTML report with metrics for tested code | ||
74 | - to open it in a web browser: `firefox coverage/index.html &` | ||
75 | |||
76 | |||
77 | ### Executing specific tests | ||
78 | |||
79 | Add a [`@group`](https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.group) annotation in a test class or method comment: | ||
80 | |||
81 | ```php | ||
82 | /** | ||
83 | * Netscape bookmark import | ||
84 | * @group WIP | ||
85 | */ | ||
86 | class BookmarkImportTest extends PHPUnit_Framework_TestCase | ||
87 | { | ||
88 | [...] | ||
89 | } | ||
90 | ``` | ||
91 | |||
92 | To run all tests annotated with `@group WIP`: | ||
93 | ```bash | ||
94 | $ vendor/bin/phpunit --group WIP tests/ | ||
95 | ``` | ||
96 | |||
97 | ## Running tests inside Docker containers | ||
98 | |||
99 | Unit tests can be run inside [Docker](../Docker.md) containers. | ||
100 | |||
101 | Test Dockerfiles are located under `tests/docker/<distribution>/Dockerfile`, and can be used to build Docker images to run Shaarli test suites under commonLinux environments. Dockerfiles are provided for the following environments: | ||
102 | |||
103 | - [`alpine36`](https://github.com/shaarli/Shaarli/blob/master/tests/docker/alpine36/Dockerfile) - [Alpine Linux 3.6](https://www.alpinelinux.org/downloads/) | ||
104 | - [`debian8`](https://github.com/shaarli/Shaarli/blob/master/tests/docker/debian8/Dockerfile) - [Debian 8 Jessie](https://www.debian.org/DebianJessie) (oldoldstable) | ||
105 | - [`debian9`](https://github.com/shaarli/Shaarli/blob/master/tests/docker/debian9/Dockerfile) - [Debian 9 Stretch](https://wiki.debian.org/DebianStretch) (oldstable) | ||
106 | - [`ubuntu16`](https://github.com/shaarli/Shaarli/blob/master/tests/docker/ubuntu16/Dockerfile) - [Ubuntu 16.04 Xenial Xerus](http://releases.ubuntu.com/16.04/) (old LTS) | ||
107 | |||
108 | Each image provides: | ||
109 | - a base Linux OS | ||
110 | - Shaarli PHP dependencies (OS packages) | ||
111 | - test PHP dependencies (OS packages) | ||
112 | - Composer | ||
113 | - Tests that run inside the conatiner using a standard Linux user account (running tests as `root` would bypass permission checks and may hide issues) | ||
114 | |||
115 | Build a test image: | ||
116 | |||
117 | ```bash | ||
118 | # build the Debian 9 Docker image | ||
119 | cd /path/to/shaarli/tests/docker/debian9 | ||
120 | docker build -t shaarli-test:debian9 . | ||
121 | ``` | ||
122 | |||
123 | Run unit tests in a container: | ||
124 | |||
125 | ```bash | ||
126 | cd /path/to/shaarli | ||
127 | # install/update 3rd-party test dependencies | ||
128 | composer install --prefer-dist | ||
129 | # run tests using the freshly built image | ||
130 | docker run -v $PWD:/shaarli shaarli-test:debian9 docker_test | ||
131 | # run the full test campaign | ||
132 | docker run -v $PWD:/shaarli shaarli-test:debian9 docker_all_tests | ||
133 | ``` | ||
diff --git a/doc/md/Versioning-and-Branches.md b/doc/md/dev/Versioning.md index 7097ca0a..32c80a5c 100644 --- a/doc/md/Versioning-and-Branches.md +++ b/doc/md/dev/Versioning.md | |||
@@ -1,6 +1,7 @@ | |||
1 | **WORK IN PROGRESS** | 1 | # Versioning |
2 | |||
3 | If you're maintaining a 3rd party tool for Shaarli (theme, plugin, etc.), It's important to understand how Shaarli branches work ensure your tool stays compatible. | ||
2 | 4 | ||
3 | It's important to understand how Shaarli branches work, especially if you're maintaining a 3rd party tools for Shaarli (theme, plugin, etc.), to be sure stay compatible. | ||
4 | 5 | ||
5 | ## `master` branch | 6 | ## `master` branch |
6 | 7 | ||
@@ -11,39 +12,26 @@ Remarks: | |||
11 | - This branch shouldn't be used for production as it isn't necessary stable. | 12 | - This branch shouldn't be used for production as it isn't necessary stable. |
12 | - 3rd party aren't required to be compatible with the latest changes. | 13 | - 3rd party aren't required to be compatible with the latest changes. |
13 | - Official plugins, themes and libraries (contained within Shaarli organization repos) must be compatible with the master branch. | 14 | - Official plugins, themes and libraries (contained within Shaarli organization repos) must be compatible with the master branch. |
14 | - The version in this branch is always `dev`. | ||
15 | 15 | ||
16 | ## `v0.x` branch | ||
17 | 16 | ||
18 | This `v0.x` branch, points to the latest `v0.x.y` release. | 17 | ## `v0.x` branch |
19 | 18 | ||
20 | Explanation: | 19 | The `v0.x` branch points to the latest `v0.x.y` release. |
21 | 20 | ||
22 | When a new version is released, it might contains a major bug which isn't detected right away. For example, a new PHP version is released, containing backward compatibility issue which doesn't work with Shaarli. | 21 | If a major bug affects the original `v0.x.0` release, we may [backport](https://en.wikipedia.org/wiki/Backporting) a fix for this bug from master, to the `v0.x` branch, and create a new bugfix release (eg. `v0.x.1`) from this branch. |
23 | 22 | ||
24 | In this case, the issue is fixed in the `master` branch, and the fix is backported the to the `v0.x` branch. Then a new release is made from the `v0.x` branch. | 23 | This allows users of the original release to upgrade to the fixed version, without having to upgrade to a completely new minor/major release. |
25 | 24 | ||
26 | This workflow allow us to fix any major bug detected, without having to release bleeding edge feature too soon. | ||
27 | 25 | ||
28 | ## `latest` branch | 26 | ## `latest` branch |
29 | 27 | ||
30 | This branch point the latest release. It recommended to use it to get the latest tested changes. | 28 | This branch point the latest release. It recommended to use it to get the latest tested changes. |
31 | 29 | ||
32 | ## `stable` branch | ||
33 | |||
34 | The `stable` branch doesn't contain any major bug, and is one major digit version behind the latest release. | ||
35 | |||
36 | For example, the current latest release is `v0.8.3`, the stable branch is an alias to the latest `v0.7.x` release. When the `v0.9.0` version will be released, the stable will move to the latest `v0.8.x` release. | ||
37 | |||
38 | Remarks: | ||
39 | |||
40 | - Shaarli release pace isn't fast, and the stable branch might be a few months behind the latest release. | ||
41 | 30 | ||
42 | ## Releases | 31 | ## Releases |
43 | 32 | ||
44 | Releases are always made from the latest `v0.x` branch. | 33 | For every release, we manually generate a .zip file which contains all Shaarli dependencies, making Shaarli's installation only one step. |
45 | 34 | ||
46 | Note that for every release, we manually generate a tarball which contains all Shaarli dependencies, making Shaarli's installation only one step. | ||
47 | 35 | ||
48 | ## Advices on 3rd party git repos workflow | 36 | ## Advices on 3rd party git repos workflow |
49 | 37 | ||
diff --git a/doc/md/images/poedit-1.jpg b/doc/md/dev/images/poedit-1.jpg index 673ae6d6..673ae6d6 100644 --- a/doc/md/images/poedit-1.jpg +++ b/doc/md/dev/images/poedit-1.jpg | |||
Binary files differ | |||
diff --git a/doc/md/docker/docker-101.md b/doc/md/docker/docker-101.md deleted file mode 100644 index a9c00b85..00000000 --- a/doc/md/docker/docker-101.md +++ /dev/null | |||
@@ -1,140 +0,0 @@ | |||
1 | ## Basics | ||
2 | Install [Docker](https://www.docker.com/), by following the instructions relevant | ||
3 | to your OS / distribution, and start the service. | ||
4 | |||
5 | ### Search an image on [DockerHub](https://hub.docker.com/) | ||
6 | |||
7 | ```bash | ||
8 | $ docker search debian | ||
9 | |||
10 | NAME DESCRIPTION STARS OFFICIAL AUTOMATED | ||
11 | ubuntu Ubuntu is a Debian-based Linux operating s... 2065 [OK] | ||
12 | debian Debian is a Linux distribution that's comp... 603 [OK] | ||
13 | google/debian 47 [OK] | ||
14 | ``` | ||
15 | |||
16 | ### Show available tags for a repository | ||
17 | ```bash | ||
18 | $ curl https://index.docker.io/v1/repositories/debian/tags | python -m json.tool | ||
19 | |||
20 | % Total % Received % Xferd Average Speed Time Time Time Current | ||
21 | Dload Upload Total Spent Left Speed | ||
22 | 100 1283 0 1283 0 0 433 0 --:--:-- 0:00:02 --:--:-- 433 | ||
23 | ``` | ||
24 | |||
25 | Sample output: | ||
26 | ```json | ||
27 | [ | ||
28 | { | ||
29 | "layer": "85a02782", | ||
30 | "name": "stretch" | ||
31 | }, | ||
32 | { | ||
33 | "layer": "59abecbc", | ||
34 | "name": "testing" | ||
35 | }, | ||
36 | { | ||
37 | "layer": "bf0fd686", | ||
38 | "name": "unstable" | ||
39 | }, | ||
40 | { | ||
41 | "layer": "60c52dbe", | ||
42 | "name": "wheezy" | ||
43 | }, | ||
44 | { | ||
45 | "layer": "c5b806fe", | ||
46 | "name": "wheezy-backports" | ||
47 | } | ||
48 | ] | ||
49 | |||
50 | ``` | ||
51 | |||
52 | ### Pull an image from DockerHub | ||
53 | ```bash | ||
54 | $ docker pull repository[:tag] | ||
55 | |||
56 | $ docker pull debian:wheezy | ||
57 | wheezy: Pulling from debian | ||
58 | 4c8cbfd2973e: Pull complete | ||
59 | 60c52dbe9d91: Pull complete | ||
60 | Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe | ||
61 | Status: Downloaded newer image for debian:wheezy | ||
62 | ``` | ||
63 | |||
64 | Docker re-uses layers already downloaded. In other words if you have images based on Alpine or some Ubuntu version for example, those can share disk space. | ||
65 | |||
66 | ### Start a container | ||
67 | A container is an instance created from an image, that can be run and that keeps running until its main process exits. Or until the user stops the container. | ||
68 | |||
69 | The simplest way to start a container from image is ``docker run``. It also pulls the image for you if it is not locally available. For more advanced use, refer to ``docker create``. | ||
70 | |||
71 | Stopped containers are not destroyed, unless you specify ``--rm``. To view all created, running and stopped containers, enter: | ||
72 | ```bash | ||
73 | $ docker ps -a | ||
74 | ``` | ||
75 | |||
76 | Some containers may be designed or configured to be restarted, others are not. Also remember both network ports and volumes of a container are created on start, and not editable later. | ||
77 | |||
78 | ### Access a running container | ||
79 | A running container is accessible using ``docker exec``, or ``docker copy``. You can use ``exec`` to start a root shell in the Shaarli container: | ||
80 | ```bash | ||
81 | $ docker exec -ti <container-name-or-id> bash | ||
82 | ``` | ||
83 | Note the names and ID's of containers are listed in ``docker ps``. You can even type only one or two letters of the ID, given they are unique. | ||
84 | |||
85 | Access can also be through one or more network ports, or disk volumes. Both are specified on and fixed on ``docker create`` or ``run``. | ||
86 | |||
87 | You can view the console output of the main container process too: | ||
88 | ```bash | ||
89 | $ docker logs -f <container-name-or-id> | ||
90 | ``` | ||
91 | |||
92 | ### Docker disk use | ||
93 | Trying out different images can fill some gigabytes of disk quickly. Besides images, the docker volumes usually take up most disk space. | ||
94 | |||
95 | If you care only about trying out docker and not about what is running or saved, the following commands should help you out quickly if you run low on disk space: | ||
96 | |||
97 | ```bash | ||
98 | $ docker rmi -f $(docker images -aq) # remove or mark all images for disposal | ||
99 | $ docker volume rm $(docker volume ls -q) # remove all volumes | ||
100 | ``` | ||
101 | |||
102 | ### Systemd config | ||
103 | Systemd is the process manager of choice on Debian-based distributions. Once you have a ``docker`` service installed, you can use the following steps to set up Shaarli to run on system start. | ||
104 | |||
105 | ```bash | ||
106 | systemctl enable /etc/systemd/system/docker.shaarli.service | ||
107 | systemctl start docker.shaarli | ||
108 | systemctl status docker.* | ||
109 | journalctl -f # inspect system log if needed | ||
110 | ``` | ||
111 | |||
112 | You will need sudo or a root terminal to perform some or all of the steps above. Here are the contents for the service file: | ||
113 | ``` | ||
114 | [Unit] | ||
115 | Description=Shaarli Bookmark Manager Container | ||
116 | After=docker.service | ||
117 | Requires=docker.service | ||
118 | |||
119 | |||
120 | [Service] | ||
121 | Restart=always | ||
122 | |||
123 | # Put any environment you want in an included file, like $host- or $domainname in this example | ||
124 | EnvironmentFile=/etc/sysconfig/box-environment | ||
125 | |||
126 | # It's just an example.. | ||
127 | ExecStart=/usr/bin/docker run \ | ||
128 | -p 28010:80 \ | ||
129 | --name ${hostname}-shaarli \ | ||
130 | --hostname shaarli.${domainname} \ | ||
131 | -v /srv/docker-volumes-local/shaarli-data:/var/www/shaarli/data:rw \ | ||
132 | -v /etc/localtime:/etc/localtime:ro \ | ||
133 | shaarli/shaarli:latest | ||
134 | |||
135 | ExecStop=/usr/bin/docker rm -f ${hostname}-shaarli | ||
136 | |||
137 | |||
138 | [Install] | ||
139 | WantedBy=multi-user.target | ||
140 | ``` | ||
diff --git a/doc/md/docker/resources.md b/doc/md/docker/resources.md deleted file mode 100644 index 082d4a46..00000000 --- a/doc/md/docker/resources.md +++ /dev/null | |||
@@ -1,19 +0,0 @@ | |||
1 | ### Docker | ||
2 | |||
3 | - [Interactive Docker training portal](https://www.katacoda.com/courses/docker/) on [Katakoda](https://www.katacoda.com/) | ||
4 | - [Where are Docker images stored?](http://blog.thoward37.me/articles/where-are-docker-images-stored/) | ||
5 | - [Dockerfile reference](https://docs.docker.com/reference/builder/) | ||
6 | - [Dockerfile best practices](https://docs.docker.com/articles/dockerfile_best-practices/) | ||
7 | - [Volumes](https://docs.docker.com/userguide/dockervolumes/) | ||
8 | |||
9 | ### DockerHub | ||
10 | |||
11 | - [Repositories](https://docs.docker.com/userguide/dockerrepos/) | ||
12 | - [Teams and organizations](https://docs.docker.com/docker-hub/orgs/) | ||
13 | - [GitHub automated build](https://docs.docker.com/docker-hub/github/) | ||
14 | |||
15 | ### Service management | ||
16 | |||
17 | - [Using supervisord](https://docs.docker.com/articles/using_supervisord/) | ||
18 | - [Nginx in the foreground](http://nginx.org/en/docs/ngx_core_module.html#daemon) | ||
19 | - [supervisord](http://supervisord.org/) | ||
diff --git a/doc/md/docker/reverse-proxy-configuration.md b/doc/md/docker/reverse-proxy-configuration.md deleted file mode 100644 index e53c9422..00000000 --- a/doc/md/docker/reverse-proxy-configuration.md +++ /dev/null | |||
@@ -1,123 +0,0 @@ | |||
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 when the `ProxyPass` directive is set: | ||
17 | |||
18 | - `X-Forwarded-For` | ||
19 | - `X-Forwarded-Host` | ||
20 | - `X-Forwarded-Server` | ||
21 | |||
22 | The original `SERVER_NAME` can be sent to the proxied host by setting the [`ProxyPreserveHost`](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#ProxyPreserveHost) directive to `On`. | ||
23 | |||
24 | ```apache | ||
25 | <VirtualHost *:80> | ||
26 | ServerName shaarli.domain.tld | ||
27 | Redirect permanent / https://shaarli.domain.tld | ||
28 | </VirtualHost> | ||
29 | |||
30 | <VirtualHost *:443> | ||
31 | ServerName shaarli.domain.tld | ||
32 | |||
33 | SSLEngine on | ||
34 | SSLCertificateFile /path/to/cert | ||
35 | SSLCertificateKeyFile /path/to/certkey | ||
36 | |||
37 | LogLevel warn | ||
38 | ErrorLog /var/log/apache2/shaarli-error.log | ||
39 | CustomLog /var/log/apache2/shaarli-access.log combined | ||
40 | |||
41 | RequestHeader set X-Forwarded-Proto "https" | ||
42 | ProxyPreserveHost On | ||
43 | |||
44 | ProxyPass / http://127.0.0.1:10080/ | ||
45 | ProxyPassReverse / http://127.0.0.1:10080/ | ||
46 | </VirtualHost> | ||
47 | ``` | ||
48 | |||
49 | |||
50 | ## HAProxy | ||
51 | |||
52 | - [HAProxy documentation](https://cbonte.github.io/haproxy-dconv/) | ||
53 | |||
54 | ```conf | ||
55 | global | ||
56 | [...] | ||
57 | |||
58 | defaults | ||
59 | [...] | ||
60 | |||
61 | frontend http-in | ||
62 | bind :80 | ||
63 | redirect scheme https code 301 if !{ ssl_fc } | ||
64 | |||
65 | bind :443 ssl crt /path/to/cert.pem | ||
66 | |||
67 | default_backend shaarli | ||
68 | |||
69 | |||
70 | backend shaarli | ||
71 | mode http | ||
72 | option http-server-close | ||
73 | option forwardfor | ||
74 | reqadd X-Forwarded-Proto: https | ||
75 | |||
76 | server shaarli1 127.0.0.1:10080 | ||
77 | ``` | ||
78 | |||
79 | |||
80 | ## Nginx | ||
81 | |||
82 | - [Nginx documentation](https://nginx.org/en/docs/) | ||
83 | |||
84 | ```nginx | ||
85 | http { | ||
86 | [...] | ||
87 | |||
88 | index index.html index.php; | ||
89 | |||
90 | root /home/john/web; | ||
91 | access_log /var/log/nginx/access.log; | ||
92 | error_log /var/log/nginx/error.log; | ||
93 | |||
94 | server { | ||
95 | listen 80; | ||
96 | server_name shaarli.domain.tld; | ||
97 | return 301 https://shaarli.domain.tld$request_uri; | ||
98 | } | ||
99 | |||
100 | server { | ||
101 | listen 443 ssl http2; | ||
102 | server_name shaarli.domain.tld; | ||
103 | |||
104 | ssl_certificate /path/to/cert | ||
105 | ssl_certificate_key /path/to/certkey | ||
106 | |||
107 | location / { | ||
108 | proxy_set_header X-Real-IP $remote_addr; | ||
109 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
110 | proxy_set_header X-Forwarded-Proto $scheme; | ||
111 | proxy_set_header X-Forwarded-Host $host; | ||
112 | |||
113 | proxy_pass http://localhost:10080/; | ||
114 | proxy_set_header Host $host; | ||
115 | proxy_connect_timeout 30s; | ||
116 | proxy_read_timeout 120s; | ||
117 | |||
118 | access_log /var/log/nginx/shaarli.access.log; | ||
119 | error_log /var/log/nginx/shaarli.error.log; | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | ``` | ||
diff --git a/doc/md/docker/shaarli-images.md b/doc/md/docker/shaarli-images.md deleted file mode 100644 index 5948949a..00000000 --- a/doc/md/docker/shaarli-images.md +++ /dev/null | |||
@@ -1,124 +0,0 @@ | |||
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 | |||
4 | ## Get and run a Shaarli image | ||
5 | |||
6 | ### DockerHub repository | ||
7 | The images can be found in the [`shaarli/shaarli`](https://hub.docker.com/r/shaarli/shaarli/) | ||
8 | repository. | ||
9 | |||
10 | ### Available image tags | ||
11 | - `latest`: latest branch | ||
12 | - `master`: master branch | ||
13 | - `stable`: stable branch | ||
14 | |||
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 | |||
23 | - [Debian 8 Jessie](https://hub.docker.com/_/debian/) | ||
24 | - [PHP5-FPM](http://php-fpm.org/) | ||
25 | - [Nginx](http://nginx.org/) | ||
26 | |||
27 | Additional Dockerfiles are provided for the `arm32v7` platform, relying on | ||
28 | [Linuxserver.io Alpine armhf | ||
29 | images](https://hub.docker.com/r/lsiobase/alpine.armhf/). These images must be | ||
30 | built using [`docker | ||
31 | build`](https://docs.docker.com/engine/reference/commandline/build/) on an | ||
32 | `arm32v7` machine or using an emulator such as | ||
33 | [qemu](https://resin.io/blog/building-arm-containers-on-any-x86-machine-even-dockerhub/). | ||
34 | |||
35 | ### Download from Docker Hub | ||
36 | ```shell | ||
37 | $ docker pull shaarli/shaarli | ||
38 | |||
39 | latest: Pulling from shaarli/shaarli | ||
40 | 32716d9fcddb: Pull complete | ||
41 | 84899d045435: Pull complete | ||
42 | 4b6ad7444763: Pull complete | ||
43 | e0345ef7a3e0: Pull complete | ||
44 | 5c1dd344094f: Pull complete | ||
45 | 6422305a200b: Pull complete | ||
46 | 7d63f861dbef: Pull complete | ||
47 | 3eb97210645c: Pull complete | ||
48 | 869319d746ff: Already exists | ||
49 | 869319d746ff: Pulling fs layer | ||
50 | 902b87aaaec9: Already exists | ||
51 | Digest: sha256:f836b4627b958b3f83f59c332f22f02fcd495ace3056f2be2c4912bd8704cc98 | ||
52 | Status: Downloaded newer image for shaarli/shaarli:latest | ||
53 | ``` | ||
54 | |||
55 | ### Create and start a new container from the image | ||
56 | ```shell | ||
57 | # map the host's :8000 port to the container's :80 port | ||
58 | $ docker create -p 8000:80 shaarli/shaarli | ||
59 | d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101 | ||
60 | |||
61 | # launch the container in the background | ||
62 | $ docker start d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101 | ||
63 | d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101 | ||
64 | |||
65 | # list active containers | ||
66 | $ docker ps | ||
67 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES | ||
68 | d40b7af693d6 shaarli/shaarli /usr/bin/supervisor 15 seconds ago Up 4 seconds 0.0.0.0:8000->80/tcp backstabbing_galileo | ||
69 | ``` | ||
70 | |||
71 | ### Stop and destroy a container | ||
72 | ```shell | ||
73 | $ docker stop backstabbing_galileo # those docker guys are really rude to physicists! | ||
74 | backstabbing_galileo | ||
75 | |||
76 | # check the container is stopped | ||
77 | $ docker ps | ||
78 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES | ||
79 | |||
80 | # list ALL containers | ||
81 | $ docker ps -a | ||
82 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES | ||
83 | d40b7af693d6 shaarli/shaarli /usr/bin/supervisor 5 minutes ago Exited (0) 48 seconds ago backstabbing_galileo | ||
84 | |||
85 | # destroy the container | ||
86 | $ docker rm backstabbing_galileo # let's put an end to these barbarian practices | ||
87 | backstabbing_galileo | ||
88 | |||
89 | $ docker ps -a | ||
90 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES | ||
91 | ``` | ||
92 | |||
93 | ### Automatic builds | ||
94 | Docker users can start a personal instance from an | ||
95 | [autobuild image](https://hub.docker.com/r/shaarli/shaarli/). | ||
96 | For example to start a temporary Shaarli at ``localhost:8000``, and keep session | ||
97 | data (config, storage): | ||
98 | |||
99 | ```shell | ||
100 | MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P) | ||
101 | docker run -ti --rm \ | ||
102 | -p 8000:80 \ | ||
103 | -v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \ | ||
104 | shaarli/shaarli | ||
105 | ``` | ||
106 | |||
107 | ### Volumes and data persistence | ||
108 | Data can be persisted by [using volumes](https://docs.docker.com/storage/volumes/). | ||
109 | Volumes allow to keep your data when renewing and/or updating container images: | ||
110 | |||
111 | ```shell | ||
112 | # Create data volumes | ||
113 | $ docker volume create shaarli-data | ||
114 | $ docker volume create shaarli-cache | ||
115 | |||
116 | # Create and start a Shaarli container using these volumes to persist data | ||
117 | $ docker create \ | ||
118 | --name shaarli \ | ||
119 | -v shaarli-cache:/var/www/shaarli/cache \ | ||
120 | -v shaarli-data:/var/www/shaarli/data \ | ||
121 | -p 8000:80 \ | ||
122 | shaarli/shaarli:master | ||
123 | $ docker start shaarli | ||
124 | ``` | ||
diff --git a/doc/md/guides/backup-restore-import-export.md b/doc/md/guides/backup-restore-import-export.md deleted file mode 100644 index bb790074..00000000 --- a/doc/md/guides/backup-restore-import-export.md +++ /dev/null | |||
@@ -1,64 +0,0 @@ | |||
1 | ## Backup and restore the datastore file | ||
2 | |||
3 | Backup the file `data/datastore.php` (by FTP or SSH). Restore by putting the file back in place. | ||
4 | |||
5 | Example command: | ||
6 | ```bash | ||
7 | rsync -avzP my.server.com:/var/www/shaarli/data/datastore.php datastore-$(date +%Y-%m-%d_%H%M).php | ||
8 | ``` | ||
9 | |||
10 | ## Export links as... | ||
11 | |||
12 | To export links as an HTML file, under _Tools > Export_, choose: | ||
13 | |||
14 | - _Export all_ to export both public and private links | ||
15 | - _Export public_ to export public links only | ||
16 | - _Export private_ to export private links only | ||
17 | |||
18 | Restore by using the `Import` feature. | ||
19 | |||
20 | - This can be done using the [shaarchiver](https://github.com/nodiscc/shaarchiver) tool. | ||
21 | |||
22 | Example command: | ||
23 | ```bash | ||
24 | ./export-bookmarks.py --url=https://my.server.com/shaarli --username=myusername --password=mysupersecretpassword --download-dir=./ --type=all | ||
25 | ``` | ||
26 | |||
27 | ## Import links from... | ||
28 | |||
29 | ### Diigo | ||
30 | |||
31 | If you export your bookmark from Diigo, make sure you use the Delicious export, not the Netscape export. (Their Netscape export is broken, and they don't seem to be interested in fixing it.) | ||
32 | |||
33 | ### Mister Wong | ||
34 | |||
35 | See [this issue](https://github.com/sebsauvage/Shaarli/issues/146) for import tweaks. | ||
36 | |||
37 | ### SemanticScuttle | ||
38 | |||
39 | To correctly import the tags from a [SemanticScuttle](http://semanticscuttle.sourceforge.net/) HTML export, edit the HTML file before importing and replace all occurences of `tags=` (lowercase) to `TAGS=` (uppercase). | ||
40 | |||
41 | ### Scuttle | ||
42 | |||
43 | Shaarli cannot import data directly from [Scuttle](https://github.com/scronide/scuttle). | ||
44 | |||
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. | ||
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 | |||
52 | ## Import Shaarli links to Firefox | ||
53 | |||
54 | - Export your Shaarli links as described above. | ||
55 | - For compatibility reasons, check `Prepend note permalinks with this Shaarli instance's URL (useful to import bookmarks in a web browser)` | ||
56 | - In Firefox, open the bookmark manager (not the sidebar! `Bookmarks menu > Show all bookmarks` or `Ctrl+Shift+B`) | ||
57 | - Select `Import and Backup > Import bookmarks in HTML format` | ||
58 | |||
59 | Your bookmarks will be imported in Firefox, ready to use, with tags and descriptions retained. "Self" (notes) shaares will still point to the Shaarli instance you exported them from, but the note text can be viewed directly in the bookmark properties inside your browser. Depending on the number of bookmarks, the import can take some time. | ||
60 | |||
61 | You may be interested in these Firefox addons to manage links imported from Shaarli | ||
62 | |||
63 | - [Bookmark Deduplicator](https://addons.mozilla.org/en-US/firefox/addon/bookmark-deduplicator/) - provides an easy way to deduplicate your bookmarks | ||
64 | - [TagSieve](https://addons.mozilla.org/en-US/firefox/addon/tagsieve/) - browse your bookmarks by their tags | ||
diff --git a/doc/md/guides/images/01-create-droplet-distro.jpg b/doc/md/guides/images/01-create-droplet-distro.jpg deleted file mode 100644 index 63682ba8..00000000 --- a/doc/md/guides/images/01-create-droplet-distro.jpg +++ /dev/null | |||
Binary files differ | |||
diff --git a/doc/md/guides/images/02-create-droplet-region.jpg b/doc/md/guides/images/02-create-droplet-region.jpg deleted file mode 100644 index 135a78be..00000000 --- a/doc/md/guides/images/02-create-droplet-region.jpg +++ /dev/null | |||
Binary files differ | |||
diff --git a/doc/md/guides/images/03-create-droplet-size.jpg b/doc/md/guides/images/03-create-droplet-size.jpg deleted file mode 100644 index aa5b2fd2..00000000 --- a/doc/md/guides/images/03-create-droplet-size.jpg +++ /dev/null | |||
Binary files differ | |||
diff --git a/doc/md/guides/images/04-finalize.jpg b/doc/md/guides/images/04-finalize.jpg deleted file mode 100644 index 68ec0dc5..00000000 --- a/doc/md/guides/images/04-finalize.jpg +++ /dev/null | |||
Binary files differ | |||
diff --git a/doc/md/guides/images/05-droplet.jpg b/doc/md/guides/images/05-droplet.jpg deleted file mode 100644 index 44e93a1e..00000000 --- a/doc/md/guides/images/05-droplet.jpg +++ /dev/null | |||
Binary files differ | |||
diff --git a/doc/md/guides/images/06-domain.jpg b/doc/md/guides/images/06-domain.jpg deleted file mode 100644 index 5827dd93..00000000 --- a/doc/md/guides/images/06-domain.jpg +++ /dev/null | |||
Binary files differ | |||
diff --git a/doc/md/guides/install-shaarli-with-debian9-and-docker.md b/doc/md/guides/install-shaarli-with-debian9-and-docker.md deleted file mode 100644 index f1b26d47..00000000 --- a/doc/md/guides/install-shaarli-with-debian9-and-docker.md +++ /dev/null | |||
@@ -1,257 +0,0 @@ | |||
1 | _Last updated on 2018-07-01._ | ||
2 | |||
3 | ## Goals | ||
4 | - Getting a Virtual Private Server (VPS) | ||
5 | - Running Shaarli: | ||
6 | - as a Docker container, | ||
7 | - using the Træfik reverse proxy, | ||
8 | - securized with TLS certificates from Let's Encrypt. | ||
9 | |||
10 | |||
11 | The following components and tools will be used: | ||
12 | |||
13 | - [Debian](https://www.debian.org/), a GNU/Linux distribution widely used in | ||
14 | server environments; | ||
15 | - [Docker](https://docs.docker.com/engine/docker-overview/), an open platform | ||
16 | for developing, shipping, and running applications; | ||
17 | - [Docker Compose](https://docs.docker.com/compose/), a tool for defining and | ||
18 | running multi-container Docker applications. | ||
19 | |||
20 | |||
21 | More information can be found in the [Resources](#resources) section at the | ||
22 | bottom of the guide. | ||
23 | |||
24 | ## Getting a Virtual Private Server | ||
25 | For this guide, I went for the smallest VPS available from DigitalOcean, | ||
26 | a Droplet with 1 CPU, 1 GiB RAM and 25 GiB SSD storage, which costs | ||
27 | $5/month ($0.007/hour): | ||
28 | |||
29 | - [Droplets Overview](https://www.digitalocean.com/docs/droplets/overview/) | ||
30 | - [Pricing](https://www.digitalocean.com/pricing/) | ||
31 | - [How to Create a Droplet from the DigitalOcean Control Panel](https://www.digitalocean.com/docs/droplets/how-to/create/) | ||
32 | - [How to Add SSH Keys to Droplets](https://www.digitalocean.com/docs/droplets/how-to/add-ssh-keys/) | ||
33 | - [Initial Server Setup with Debian 8](https://www.digitalocean.com/community/tutorials/initial-server-setup-with-debian-8) (also applies to Debian 9) | ||
34 | - [An Introduction to Securing your Linux VPS](https://www.digitalocean.com/community/tutorials/an-introduction-to-securing-your-linux-vps) | ||
35 | |||
36 | ### Creating a Droplet | ||
37 | Select `Debian 9` as the Droplet distribution: | ||
38 | |||
39 | <img src="../images/01-create-droplet-distro.jpg" | ||
40 | width="500px" | ||
41 | alt="Droplet distribution" /> | ||
42 | |||
43 | Choose a region that is geographically close to you: | ||
44 | |||
45 | <img src="../images/02-create-droplet-region.jpg" | ||
46 | width="500px" | ||
47 | alt="Droplet region" /> | ||
48 | |||
49 | Choose a Droplet size that corresponds to your usage and budget: | ||
50 | |||
51 | <img src="../images/03-create-droplet-size.jpg" | ||
52 | width="500px" | ||
53 | alt="Droplet size" /> | ||
54 | |||
55 | Finalize the Droplet creation: | ||
56 | |||
57 | <img src="../images/04-finalize.jpg" | ||
58 | width="500px" | ||
59 | alt="Droplet finalization" /> | ||
60 | |||
61 | Droplet information is displayed on the Control Panel: | ||
62 | |||
63 | <img src="../images/05-droplet.jpg" | ||
64 | width="500px" | ||
65 | alt="Droplet summary" /> | ||
66 | |||
67 | Once your VPS has been created, you will receive an e-mail with connection | ||
68 | instructions. | ||
69 | |||
70 | ## Obtaining a domain name | ||
71 | After creating your VPS, it will be reachable using its IP address; some hosting | ||
72 | providers also create a DNS record, e.g. `ns4853142.ip-01-47-127.eu`. | ||
73 | |||
74 | A domain name (DNS record) is required to obtain a certificate and setup HTTPS | ||
75 | (HTTP with TLS encryption). | ||
76 | |||
77 | Domain names can be obtained from registrars through hosting providers such as | ||
78 | [Gandi](https://www.gandi.net/en/domain). | ||
79 | |||
80 | Once you have your own domain, you need to create a new DNS record that points | ||
81 | to your VPS' IP address: | ||
82 | |||
83 | <img src="../images/06-domain.jpg" | ||
84 | width="650px" | ||
85 | alt="Domain configuration" /> | ||
86 | |||
87 | ## Host setup | ||
88 | Now's the time to connect to your freshly created VPS! | ||
89 | |||
90 | ```shell | ||
91 | $ ssh root@188.166.85.8 | ||
92 | |||
93 | Linux stretch-shaarli-02 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07) x86_64 | ||
94 | |||
95 | The programs included with the Debian GNU/Linux system are free software; | ||
96 | the exact distribution terms for each program are described in the | ||
97 | individual files in /usr/share/doc/*/copyright. | ||
98 | |||
99 | Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent | ||
100 | permitted by applicable law. | ||
101 | Last login: Sun Jul 1 11:20:18 2018 from <REDACTED> | ||
102 | |||
103 | root@stretch-shaarli-02:~$ | ||
104 | ``` | ||
105 | |||
106 | ### Updating the system | ||
107 | ```shell | ||
108 | root@stretch-shaarli-02:~$ apt update && apt upgrade -y | ||
109 | ``` | ||
110 | |||
111 | ### Setting up Docker | ||
112 | _The following instructions are from the | ||
113 | [Get Docker CE for Debian](https://docs.docker.com/install/linux/docker-ce/debian/) | ||
114 | guide._ | ||
115 | |||
116 | Install package dependencies: | ||
117 | |||
118 | ```shell | ||
119 | root@stretch-shaarli-02:~$ apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common | ||
120 | ``` | ||
121 | |||
122 | Add Docker's package repository GPG key: | ||
123 | |||
124 | ```shell | ||
125 | root@stretch-shaarli-02:~$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - | ||
126 | ``` | ||
127 | |||
128 | Add Docker's package repository: | ||
129 | |||
130 | ```shell | ||
131 | root@stretch-shaarli-02:~$ add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian stretch stable" | ||
132 | ``` | ||
133 | |||
134 | Update package lists and install Docker: | ||
135 | |||
136 | ```shell | ||
137 | root@stretch-shaarli-02:~$ apt update && apt install -y docker-ce | ||
138 | ``` | ||
139 | |||
140 | Verify Docker is properly configured by running the `hello-world` image: | ||
141 | |||
142 | ```shell | ||
143 | root@stretch-shaarli-02:~$ docker run hello-world | ||
144 | ``` | ||
145 | |||
146 | ### Setting up Docker Compose | ||
147 | _The following instructions are from the | ||
148 | [Install Docker Compose](https://docs.docker.com/compose/install/) | ||
149 | guide._ | ||
150 | |||
151 | Download the current version from the release page: | ||
152 | |||
153 | ```shell | ||
154 | root@stretch-shaarli-02:~$ curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose | ||
155 | root@stretch-shaarli-02:~$ chmod +x /usr/local/bin/docker-compose | ||
156 | ``` | ||
157 | |||
158 | ## Running Shaarli | ||
159 | Shaarli comes with a configuration file for Docker Compose, that will setup: | ||
160 | |||
161 | - a local Docker network | ||
162 | - a Docker [volume](https://docs.docker.com/storage/volumes/) to store Shaarli data | ||
163 | - a Docker [volume](https://docs.docker.com/storage/volumes/) to store Træfik TLS configuration and certificates | ||
164 | - a [Shaarli](https://hub.docker.com/r/shaarli/shaarli/) instance | ||
165 | - a [Træfik](https://hub.docker.com/_/traefik/) instance | ||
166 | |||
167 | [Træfik](https://docs.traefik.io/) is a modern HTTP reverse proxy, with native | ||
168 | support for Docker and [Let's Encrypt](https://letsencrypt.org/). | ||
169 | |||
170 | ### Compose configuration | ||
171 | Create a new directory to store the configuration: | ||
172 | |||
173 | ```shell | ||
174 | root@stretch-shaarli-02:~$ mkdir shaarli && cd shaarli | ||
175 | root@stretch-shaarli-02:~/shaarli$ | ||
176 | ``` | ||
177 | |||
178 | Download the current version of Shaarli's `docker-compose.yml`: | ||
179 | |||
180 | ```shell | ||
181 | root@stretch-shaarli-02:~/shaarli$ curl -L https://raw.githubusercontent.com/shaarli/Shaarli/master/docker-compose.yml -o docker-compose.yml | ||
182 | ``` | ||
183 | |||
184 | Create the `.env` file and fill in your VPS and domain information (replace | ||
185 | `<MY_SHAARLI_DOMAIN>` and `<MY_CONTACT_EMAIL>` with your actual information): | ||
186 | |||
187 | ```shell | ||
188 | root@stretch-shaarli-02:~/shaarli$ vim .env | ||
189 | ``` | ||
190 | |||
191 | ```shell | ||
192 | SHAARLI_VIRTUAL_HOST=<MY_SHAARLI_DOMAIN> | ||
193 | SHAARLI_LETSENCRYPT_EMAIL=<MY_CONTACT_EMAIL> | ||
194 | ``` | ||
195 | |||
196 | ### Pull the Docker images | ||
197 | ```shell | ||
198 | root@stretch-shaarli-02:~/shaarli$ docker-compose pull | ||
199 | Pulling shaarli ... done | ||
200 | Pulling traefik ... done | ||
201 | ``` | ||
202 | |||
203 | ### Run! | ||
204 | ```shell | ||
205 | root@stretch-shaarli-02:~/shaarli$ docker-compose up -d | ||
206 | Creating network "shaarli_http-proxy" with the default driver | ||
207 | Creating volume "shaarli_traefik-acme" with default driver | ||
208 | Creating volume "shaarli_shaarli-data" with default driver | ||
209 | Creating shaarli_shaarli_1 ... done | ||
210 | Creating shaarli_traefik_1 ... done | ||
211 | ``` | ||
212 | |||
213 | ## Conclusion | ||
214 | Congratulations! Your Shaarli instance should be up and running, and available | ||
215 | at `https://<MY_SHAARLI_DOMAIN>`. | ||
216 | |||
217 | <img src="../images/07-installation.jpg" | ||
218 | width="500px" | ||
219 | alt="Shaarli installation page" /> | ||
220 | |||
221 | ## Resources | ||
222 | ### Related Shaarli documentation | ||
223 | - [Docker 101](../docker/docker-101.md) | ||
224 | - [Shaarli images](../docker/shaarli-images.md) | ||
225 | |||
226 | ### Hosting providers | ||
227 | - [DigitalOcean](https://www.digitalocean.com/) | ||
228 | - [Gandi](https://www.gandi.net/en) | ||
229 | - [OVH](https://www.ovh.co.uk/) | ||
230 | - [RackSpace](https://www.rackspace.com/) | ||
231 | - etc. | ||
232 | |||
233 | ### Domain Names and Registrars | ||
234 | - [Introduction to the Domain Name System (DNS)](https://opensource.com/article/17/4/introduction-domain-name-system-dns) | ||
235 | - [ICANN](https://www.icann.org/) | ||
236 | - [Domain name registrar](https://en.wikipedia.org/wiki/Domain_name_registrar) | ||
237 | - [OVH Domain Registration](https://www.ovh.co.uk/domains/) | ||
238 | - [Gandi Domain Registration](https://www.gandi.net/en/domain) | ||
239 | |||
240 | ### HTTPS and Security | ||
241 | - [Transport Layer Security](https://en.wikipedia.org/wiki/Transport_Layer_Security) | ||
242 | - [Let's Encrypt](https://letsencrypt.org/) | ||
243 | |||
244 | ### Docker | ||
245 | - [Docker Overview](https://docs.docker.com/engine/docker-overview/) | ||
246 | - [Docker Documentation](https://docs.docker.com/) | ||
247 | - [Get Docker CE for Debian](https://docs.docker.com/install/linux/docker-ce/debian/) | ||
248 | - [docker logs](https://docs.docker.com/engine/reference/commandline/logs/) | ||
249 | - [Volumes](https://docs.docker.com/storage/volumes/) | ||
250 | - [Install Docker Compose](https://docs.docker.com/compose/install/) | ||
251 | - [docker-compose logs](https://docs.docker.com/compose/reference/logs/) | ||
252 | |||
253 | ### Træfik | ||
254 | - [Getting Started](https://docs.traefik.io/) | ||
255 | - [Docker backend](https://docs.traefik.io/configuration/backends/docker/) | ||
256 | - [Let's Encrypt and Docker](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/) | ||
257 | - [traefik](https://hub.docker.com/_/traefik/) Docker image | ||
diff --git a/doc/md/guides/various-hacks.md b/doc/md/guides/various-hacks.md deleted file mode 100644 index 0074ae9f..00000000 --- a/doc/md/guides/various-hacks.md +++ /dev/null | |||
@@ -1,33 +0,0 @@ | |||
1 | ### Decode datastore content | ||
2 | |||
3 | To display the array representing the data saved in `data/datastore.php`, use the following snippet: | ||
4 | |||
5 | ```php | ||
6 | $data = "tZNdb9MwFIb... <Commented content inside datastore.php>"; | ||
7 | $out = unserialize(gzinflate(base64_decode($data))); | ||
8 | echo "<pre>"; // Pretty printing is love, pretty printing is life | ||
9 | print_r($out); | ||
10 | echo "</pre>"; | ||
11 | exit; | ||
12 | ``` | ||
13 | This will output the internal representation of the datastore, "unobfuscated" (if this can really be considered obfuscation). | ||
14 | |||
15 | Alternatively, you can transform to JSON format (and pretty-print if you have `jq` installed): | ||
16 | ``` | ||
17 | php -r 'print(json_encode(unserialize(gzinflate(base64_decode(preg_replace("!.*/\* (.+) \*/.*!", "$1", file_get_contents("data/datastore.php")))))));' | jq . | ||
18 | ``` | ||
19 | |||
20 | ### Changing the timestamp for a shaare | ||
21 | |||
22 | - Look for `<input type="hidden" name="lf_linkdate" value="{$link.linkdate}">` in `tpl/editlink.tpl` (line 14) | ||
23 | - Replace `type="hidden"` with `type="text"` from this line | ||
24 | - A new date/time field becomes available in the edit/new link dialog. | ||
25 | - You can set the timestamp manually by entering it in the format `YYYMMDD_HHMMS`. | ||
26 | |||
27 | |||
28 | ### See also | ||
29 | |||
30 | - [Add a new custom field to shaares (example patch)](https://gist.github.com/nodiscc/8b0194921f059d7b9ad89a581ecd482c) | ||
31 | - [Download CSS styles for shaarlis listed in an opml file](https://gist.github.com/nodiscc/dede231c92cab22c3ad2cc24d5035012) | ||
32 | - [Copy an existing Shaarli installation over SSH, and serve it locally](https://gist.github.com/nodiscc/ed161c66e5b028b5299b0a3733d01c77) | ||
33 | - [Create multiple Shaarli instances, generate an HTML index of them](https://gist.github.com/nodiscc/52e711cda3bc47717c16065231cf6b20) | ||
diff --git a/doc/md/guides/images/07-installation.jpg b/doc/md/images/07-installation.jpg index 42cc9f10..42cc9f10 100644 --- a/doc/md/guides/images/07-installation.jpg +++ b/doc/md/images/07-installation.jpg | |||
Binary files differ | |||
diff --git a/doc/md/images/bookmarklet.png b/doc/md/images/bookmarklet.png deleted file mode 100644 index 0262578e..00000000 --- a/doc/md/images/bookmarklet.png +++ /dev/null | |||
Binary files differ | |||
diff --git a/doc/md/images/firefoxshare.png b/doc/md/images/firefoxshare.png deleted file mode 100644 index 8f8fdba4..00000000 --- a/doc/md/images/firefoxshare.png +++ /dev/null | |||
Binary files differ | |||
diff --git a/doc/md/images/install-shaarli.png b/doc/md/images/install-shaarli.png deleted file mode 100644 index d5d5baa7..00000000 --- a/doc/md/images/install-shaarli.png +++ /dev/null | |||
Binary files differ | |||
diff --git a/doc/md/index.md b/doc/md/index.md index 220eeec1..2c4995f8 100644 --- a/doc/md/index.md +++ b/doc/md/index.md | |||
@@ -2,113 +2,101 @@ | |||
2 | 2 | ||
3 | The personal, minimalist, super-fast, database free, bookmarking service. | 3 | The personal, minimalist, super-fast, database free, bookmarking service. |
4 | 4 | ||
5 | Do you want to share the links you discover? | 5 | Do you want to share the links you discover? Shaarli is a minimalist bookmark manager and link sharing service that you can install on your own server. It is designed to be personal (single-user), fast and handy. |
6 | Shaarli is a minimalist bookmark manager and link sharing service that you can install on your own server. | ||
7 | It is designed to be personal (single-user), fast and handy. | ||
8 | |||
9 | <!-- TODO screenshots --> | ||
10 | 6 | ||
11 | Visit the pages in the sidebar to find information on how to setup, use, configure, tweak and troubleshoot Shaarli. | 7 | Visit the pages in the sidebar to find information on how to setup, use, configure, tweak and troubleshoot Shaarli. |
12 | 8 | ||
13 | |||
14 | * [GitHub project page](https://github.com/shaarli/Shaarli) | 9 | * [GitHub project page](https://github.com/shaarli/Shaarli) |
15 | * [Online documentation](https://shaarli.readthedocs.io/) | 10 | * [Documentation](https://shaarli.readthedocs.io/) |
16 | * [Latest releases](https://github.com/shaarli/Shaarli/releases) | ||
17 | * [Changelog](https://github.com/shaarli/Shaarli/blob/master/CHANGELOG.md) | 11 | * [Changelog](https://github.com/shaarli/Shaarli/blob/master/CHANGELOG.md) |
18 | 12 | ||
19 | 13 | ||
20 | ### Demo | 14 | [](https://i.imgur.com/WWPfSj0.png) [](https://i.imgur.com/V09kAQt.png) [](https://i.imgur.com/TZzGHMs.png) [](https://i.imgur.com/sfJJ6NT.png) [](https://i.imgur.com/QsedIuJ.png) [](https://i.imgur.com/KdtF8Ll.png) [](https://i.imgur.com/27wYsbC.png) [](https://i.imgur.com/zGF4d6L.jpg) |
15 | |||
16 | |||
17 | |||
18 | ## Demo | ||
21 | 19 | ||
22 | You can use this [public demo instance of Shaarli](https://demo.shaarli.org). | 20 | You can use this [public demo instance of Shaarli](https://demo.shaarli.org). |
23 | It runs the latest development version of Shaarli and is updated/reset daily. | 21 | It runs the latest development version of Shaarli and is updated/reset daily. |
24 | 22 | ||
25 | Login: `demo`; Password: `demo` | 23 | Login: `demo`; Password: `demo` |
26 | 24 | ||
25 | |||
26 | ## Getting started | ||
27 | |||
28 | - [Configure your server](Server-configuration.md) | ||
29 | - [Install Shaarli](Installation.md) | ||
30 | - Or install Shaarli using [Docker](Docker.md) | ||
31 | |||
32 | |||
27 | ## Features | 33 | ## Features |
28 | 34 | ||
29 | Shaarli can be used: | 35 | Shaarli can be used: |
30 | 36 | ||
31 | - to share, comment and save interesting links and news | 37 | - to share, comment and save interesting links |
32 | - to bookmark useful/frequent links and share them between computers | 38 | - to bookmark useful/frequent links and share them between computers |
33 | - as a minimal blog/microblog/writing platform | 39 | - as a minimal blog/microblog/writing platform |
34 | - as a read-it-later list | 40 | - as a read-it-later/todo list |
35 | - to draft and save articles/posts/ideas | 41 | - as a notepad to draft and save articles/posts/ideas |
36 | - to keep notes, documentation and code snippets | 42 | - as a knowledge base to keep notes, documentation and code snippets |
37 | - as a shared clipboard/notepad/pastebin between machines | 43 | - as a shared clipboard/notepad/pastebin between computers |
38 | - as a todo list | 44 | - as playlist manager for online media |
39 | - to store media playlists | 45 | - to feed other blogs, aggregators, social networks... |
40 | - to keep extracts/comments from webpages that may disappear. | ||
41 | - to keep track of ongoing discussions | ||
42 | - to feed other blogs, aggregators, social networks... using RSS feeds | ||
43 | 46 | ||
44 | ### Edit, view and search your links | 47 | ### Edit, view and search your links |
45 | 48 | ||
46 | - Minimalist design | 49 | - Editable URL, title, description, tags, private/public status for all your [Shaares](Usage.md) |
47 | - FAST | 50 | - [Tags](Usage.md#tags) to organize your Shaares |
48 | - Customizable link titles and descriptions | 51 | - [Search](Usage.md#search) in all fields |
49 | - Tags to organize your links (features tag autocompletion, renaming, merging and deletion) | 52 | - Unique [permalinks](Usage.md#permalinks) for easy reference |
50 | - Search by tag or using the full-text search | 53 | - Paginated Shaares list view (with image and video thumbnails) |
51 | - Public and private links (visible only to logged-in users) | 54 | - [Tag cloud/list](Usage#tag-cloud) views |
52 | - Unique permalinks for easy reference | 55 | - [Picture wall](Usage#picture-wall)/thumbnails view (with lazy loading) |
53 | - Paginated link list (with image and video thumbnails) | 56 | - [ATOM and RSS feeds](Usage.md#rss-feeds) (can also be filtered using tags or text search) |
54 | - Tag cloud and list views | 57 | - [Daily](Usage.md#daily): newspaper-like daily digest (and daily RSS feed) |
55 | - Picture wall: image and video thumbnails view (with lazy loading) | 58 | - URL cleanup: automatic removal of `?utm_source=...`, `fb=...` tracking parameters |
56 | - ATOM and RSS feeds (can also be filtered using tags or text search) | 59 | - Extensible through [plugins](Plugins.md) |
57 | - Daily: newspaper-like daily digest (and daily RSS feed) | 60 | - Easily extensible by any client using the [REST API](REST-API.md) exposed by Shaarli |
58 | - URL cleanup: automatic removal of `?utm_source=...`, `fb=...` | 61 | - Bookmarklet and [other tools](Community-and-related-software.md) to share links in one click |
59 | - Extensible through [plugins](https://shaarli.readthedocs.io/en/master/Plugins/#plugin-usage) | 62 | - Responsive/support for mobile browsers, degrades gracefully with Javascript disabled |
63 | |||
60 | 64 | ||
61 | ### Easy setup | 65 | ### Easy setup |
62 | 66 | ||
63 | - Dead-simple installation: drop the files, open the page | 67 | - Dead-simple [installation](Installation.md): drop the files on your server, open the page |
64 | - Links are stored in a file (no database required, easy backup: simply copy the datastore file) | 68 | - Shaares are stored in a file (no database required, easy [backup](Backup-and-restore.md)) |
65 | - Import and export links as Netscape bookmarks compatible with most Web browsers | 69 | - [Configurable](Shaarli-configuration.md) from dialog and configuration file |
70 | - Extensible through third-party [plugins and themes](Community-and-related-software.md) | ||
66 | 71 | ||
67 | ### Accessibility | ||
68 | 72 | ||
69 | - Bookmarklet and other tools to share links in one click | 73 | ### Fast |
70 | - Support for mobile browsers | ||
71 | - Degrades gracefully with Javascript disabled | ||
72 | - Easy page customization through HTML/CSS/RainTPL | ||
73 | 74 | ||
74 | ### Security | 75 | - Fast! Small datastore file, write-once/read-many, served most of the time from OS disk caches (no disk I/O) |
76 | - Stays fast with even tens of thousands shaares! | ||
75 | 77 | ||
76 | - Discreet pop-up notification when a new release is available | ||
77 | - Bruteforce protection on the login form | ||
78 | - Protected against [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) and session cookie hijacking | ||
79 | 78 | ||
80 | <!-- TODO Limitations --> | 79 | ### Self-hosted |
81 | 80 | ||
82 | ### REST API | 81 | - Shaarli is an alternative to commercial services such as StumbleUpon, Delicio.us, Diigo... |
82 | - The data is yours, [import and export](Usage#import-export) it to HTML bookmarksformat compatible with most web browser, and from a variety of formats | ||
83 | - Shaarli does not send any telemetry/metrics/private information to developers | ||
84 | - Shaarli is Free and Open-Source software, inspect and change how the program works in the [source code](https://github.com/shaarli/Shaarli) | ||
85 | - Built-in [Security](dev/Development.md#security) features to help you protect your Shaarli instance | ||
83 | 86 | ||
84 | - Easily extensible by any client using the REST API exposed by Shaarli ([API documentation](http://shaarli.github.io/api-documentation/)). | ||
85 | 87 | ||
86 | ## About | 88 | ## About |
87 | 89 | ||
88 | ### Shaarli community fork | 90 | This [community fork](https://github.com/shaarli/Shaarli) of the original [Shaarli](https://github.com/sebsauvage/Shaarli/) project by [Sébastien Sauvage](http://sebsauvage.net/) (now [unmaintained](https://github.com/sebsauvage/Shaarli/issues/191)) has carried on the work to provide [many patches](https://github.com/shaarli/Shaarli/compare/sebsauvage:master...master) for [bug fixes and enhancements](https://github.com/shaarli/Shaarli/issues?q=is%3Aclosed+) in this repository, and will keep maintaining the project for the foreseeable future, while keeping Shaarli simple and efficient. |
89 | |||
90 | This friendly fork is maintained by the Shaarli community at <https://github.com/shaarli/Shaarli> | ||
91 | |||
92 | This is a community fork of the original [Shaarli](https://github.com/sebsauvage/Shaarli/) project by [Sébastien Sauvage](http://sebsauvage.net/). | ||
93 | |||
94 | The original project is currently unmaintained, and the developer [has informed us](https://github.com/sebsauvage/Shaarli/issues/191) that he would have no time to work on Shaarli in the near future. | ||
95 | 91 | ||
96 | The Shaarli community has carried on the work to provide [many | 92 | The original Shaarli instance is still available [here](https://sebsauvage.net/links/) (+25000 shaares!) |
97 | patches](https://github.com/shaarli/Shaarli/compare/sebsauvage:master...master) for | ||
98 | [bug fixes and enhancements](https://github.com/shaarli/Shaarli/issues?q=is%3Aclosed+) | ||
99 | in this repository, and will keep maintaining the project for the foreseeable | ||
100 | future, while keeping Shaarli simple and efficient. | ||
101 | 93 | ||
102 | 94 | ||
103 | ### Contributing and getting help | 95 | ### Contributing and getting help |
104 | 96 | ||
105 | Feedback is very appreciated! | 97 | Feedback is very appreciated! Feel free to propose solutions to existing problems, help us improve the documentation and translations, and submit pull requests :-) |
106 | 98 | ||
107 | - If you have any questions or ideas, please join the [chat](https://gitter.im/shaarli/Shaarli) (also reachable via [IRC](https://irc.gitter.im/)), post them in our [general discussion](https://github.com/shaarli/Shaarli/issues/308) or read the current [issues](https://github.com/shaarli/Shaarli/issues). | 99 | See [Support](Troubleshooting.md#support) to get in touch with the Shaarli community. |
108 | - Have a look at the open [issues](https://github.com/shaarli/Shaarli/issues) and [pull requests](https://github.com/shaarli/Shaarli/pulls) | ||
109 | - If you would like a feature added to Shaarli, check the issues labeled [`feature`](https://github.com/shaarli/Shaarli/labels/feature), [`enhancement`](https://github.com/shaarli/Shaarli/labels/enhancement), and [`plugin`](https://github.com/shaarli/Shaarli/labels/plugin). | ||
110 | - If you've found a bug, please create a [new issue](https://github.com/shaarli/Shaarli/issues/new). | ||
111 | - Feel free to propose solutions to existing problems, help us improve the documentation and translations, and submit pull requests :-) | ||
112 | 100 | ||
113 | 101 | ||
114 | ### License | 102 | ### License |
diff --git a/docker-compose.yml b/docker-compose.yml index e8ea4271..a3de4b1c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml | |||
@@ -33,7 +33,7 @@ services: | |||
33 | traefik.frontend.rule: "Host:${SHAARLI_VIRTUAL_HOST}" | 33 | traefik.frontend.rule: "Host:${SHAARLI_VIRTUAL_HOST}" |
34 | 34 | ||
35 | traefik: | 35 | traefik: |
36 | image: traefik | 36 | image: traefik:1.7-alpine |
37 | command: | 37 | command: |
38 | - "--defaultentrypoints=http,https" | 38 | - "--defaultentrypoints=http,https" |
39 | - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https" | 39 | - "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https" |
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po index 026d0101..9a6e3958 100644 --- a/inc/languages/fr/LC_MESSAGES/shaarli.po +++ b/inc/languages/fr/LC_MESSAGES/shaarli.po | |||
@@ -1,24 +1,26 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: Shaarli\n" | 3 | "Project-Id-Version: Shaarli\n" |
4 | "POT-Creation-Date: 2019-07-13 10:45+0200\n" | 4 | "POT-Creation-Date: 2020-09-10 16:06+0200\n" |
5 | "PO-Revision-Date: 2019-07-13 10:49+0200\n" | 5 | "PO-Revision-Date: 2020-09-10 16:07+0200\n" |
6 | "Last-Translator: \n" | 6 | "Last-Translator: \n" |
7 | "Language-Team: Shaarli\n" | 7 | "Language-Team: Shaarli\n" |
8 | "Language: fr_FR\n" | 8 | "Language: fr_FR\n" |
9 | "MIME-Version: 1.0\n" | 9 | "MIME-Version: 1.0\n" |
10 | "Content-Type: text/plain; charset=UTF-8\n" | 10 | "Content-Type: text/plain; charset=UTF-8\n" |
11 | "Content-Transfer-Encoding: 8bit\n" | 11 | "Content-Transfer-Encoding: 8bit\n" |
12 | "X-Generator: Poedit 2.2.1\n" | 12 | "X-Generator: Poedit 2.3\n" |
13 | "X-Poedit-Basepath: ../../../..\n" | 13 | "X-Poedit-Basepath: ../../../..\n" |
14 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" | 14 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" |
15 | "X-Poedit-SourceCharset: UTF-8\n" | 15 | "X-Poedit-SourceCharset: UTF-8\n" |
16 | "X-Poedit-KeywordsList: t:1,2;t\n" | 16 | "X-Poedit-KeywordsList: t:1,2;t\n" |
17 | "X-Poedit-SearchPath-0: .\n" | 17 | "X-Poedit-SearchPath-0: application\n" |
18 | "X-Poedit-SearchPathExcluded-0: node_modules\n" | 18 | "X-Poedit-SearchPath-1: tmp\n" |
19 | "X-Poedit-SearchPathExcluded-1: vendor\n" | 19 | "X-Poedit-SearchPath-2: index.php\n" |
20 | "X-Poedit-SearchPath-3: init.php\n" | ||
21 | "X-Poedit-SearchPath-4: plugins\n" | ||
20 | 22 | ||
21 | #: application/ApplicationUtils.php:159 | 23 | #: application/ApplicationUtils.php:161 |
22 | #, php-format | 24 | #, php-format |
23 | msgid "" | 25 | msgid "" |
24 | "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " | 26 | "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " |
@@ -29,27 +31,27 @@ msgstr "" | |||
29 | "peut donc pas fonctionner. Votre version de PHP a des failles de sécurités " | 31 | "peut donc pas fonctionner. Votre version de PHP a des failles de sécurités " |
30 | "connues et devrait être mise à jour au plus tôt." | 32 | "connues et devrait être mise à jour au plus tôt." |
31 | 33 | ||
32 | #: application/ApplicationUtils.php:189 application/ApplicationUtils.php:201 | 34 | #: application/ApplicationUtils.php:192 application/ApplicationUtils.php:204 |
33 | msgid "directory is not readable" | 35 | msgid "directory is not readable" |
34 | msgstr "le répertoire n'est pas accessible en lecture" | 36 | msgstr "le répertoire n'est pas accessible en lecture" |
35 | 37 | ||
36 | #: application/ApplicationUtils.php:204 | 38 | #: application/ApplicationUtils.php:207 |
37 | msgid "directory is not writable" | 39 | msgid "directory is not writable" |
38 | msgstr "le répertoire n'est pas accessible en écriture" | 40 | msgstr "le répertoire n'est pas accessible en écriture" |
39 | 41 | ||
40 | #: application/ApplicationUtils.php:222 | 42 | #: application/ApplicationUtils.php:225 |
41 | msgid "file is not readable" | 43 | msgid "file is not readable" |
42 | msgstr "le fichier n'est pas accessible en lecture" | 44 | msgstr "le fichier n'est pas accessible en lecture" |
43 | 45 | ||
44 | #: application/ApplicationUtils.php:225 | 46 | #: application/ApplicationUtils.php:228 |
45 | msgid "file is not writable" | 47 | msgid "file is not writable" |
46 | msgstr "le fichier n'est pas accessible en écriture" | 48 | msgstr "le fichier n'est pas accessible en écriture" |
47 | 49 | ||
48 | #: application/History.php:178 | 50 | #: application/History.php:179 |
49 | msgid "History file isn't readable or writable" | 51 | msgid "History file isn't readable or writable" |
50 | msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture" | 52 | msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture" |
51 | 53 | ||
52 | #: application/History.php:189 | 54 | #: application/History.php:190 |
53 | msgid "Could not parse history file" | 55 | msgid "Could not parse history file" |
54 | msgstr "Format incorrect pour le fichier d'historique" | 56 | msgstr "Format incorrect pour le fichier d'historique" |
55 | 57 | ||
@@ -58,16 +60,20 @@ msgid "Automatic" | |||
58 | msgstr "Automatique" | 60 | msgstr "Automatique" |
59 | 61 | ||
60 | #: application/Languages.php:182 | 62 | #: application/Languages.php:182 |
63 | msgid "German" | ||
64 | msgstr "Allemand" | ||
65 | |||
66 | #: application/Languages.php:183 | ||
61 | msgid "English" | 67 | msgid "English" |
62 | msgstr "Anglais" | 68 | msgstr "Anglais" |
63 | 69 | ||
64 | #: application/Languages.php:183 | 70 | #: application/Languages.php:184 |
65 | msgid "French" | 71 | msgid "French" |
66 | msgstr "Français" | 72 | msgstr "Français" |
67 | 73 | ||
68 | #: application/Languages.php:184 | 74 | #: application/Languages.php:185 |
69 | msgid "German" | 75 | msgid "Japanese" |
70 | msgstr "Allemand" | 76 | msgstr "Japonais" |
71 | 77 | ||
72 | #: application/Thumbnailer.php:62 | 78 | #: application/Thumbnailer.php:62 |
73 | msgid "" | 79 | msgid "" |
@@ -77,50 +83,144 @@ msgstr "" | |||
77 | "l'extension php-gd doit être chargée pour utiliser les miniatures. Les " | 83 | "l'extension php-gd doit être chargée pour utiliser les miniatures. Les " |
78 | "miniatures sont désormais désactivées. Rechargez la page." | 84 | "miniatures sont désormais désactivées. Rechargez la page." |
79 | 85 | ||
80 | #: application/Utils.php:379 tests/UtilsTest.php:343 | 86 | #: application/Utils.php:383 |
81 | msgid "Setting not set" | 87 | msgid "Setting not set" |
82 | msgstr "Paramètre non défini" | 88 | msgstr "Paramètre non défini" |
83 | 89 | ||
84 | #: application/Utils.php:386 tests/UtilsTest.php:341 tests/UtilsTest.php:342 | 90 | #: application/Utils.php:390 |
85 | msgid "Unlimited" | 91 | msgid "Unlimited" |
86 | msgstr "Illimité" | 92 | msgstr "Illimité" |
87 | 93 | ||
88 | #: application/Utils.php:389 tests/UtilsTest.php:338 tests/UtilsTest.php:339 | 94 | #: application/Utils.php:393 |
89 | #: tests/UtilsTest.php:353 | ||
90 | msgid "B" | 95 | msgid "B" |
91 | msgstr "o" | 96 | msgstr "o" |
92 | 97 | ||
93 | #: application/Utils.php:389 tests/UtilsTest.php:332 tests/UtilsTest.php:333 | 98 | #: application/Utils.php:393 |
94 | #: tests/UtilsTest.php:340 | ||
95 | msgid "kiB" | 99 | msgid "kiB" |
96 | msgstr "ko" | 100 | msgstr "ko" |
97 | 101 | ||
98 | #: application/Utils.php:389 tests/UtilsTest.php:334 tests/UtilsTest.php:335 | 102 | #: application/Utils.php:393 |
99 | #: tests/UtilsTest.php:351 tests/UtilsTest.php:352 | ||
100 | msgid "MiB" | 103 | msgid "MiB" |
101 | msgstr "Mo" | 104 | msgstr "Mo" |
102 | 105 | ||
103 | #: application/Utils.php:389 tests/UtilsTest.php:336 tests/UtilsTest.php:337 | 106 | #: application/Utils.php:393 |
104 | msgid "GiB" | 107 | msgid "GiB" |
105 | msgstr "Go" | 108 | msgstr "Go" |
106 | 109 | ||
107 | #: application/bookmark/LinkDB.php:128 | 110 | #: application/bookmark/BookmarkFileService.php:174 |
108 | msgid "You are not authorized to add a link." | 111 | #: application/bookmark/BookmarkFileService.php:199 |
109 | msgstr "Vous n'êtes pas autorisé à ajouter un lien." | 112 | #: application/bookmark/BookmarkFileService.php:224 |
113 | #: application/bookmark/BookmarkFileService.php:241 | ||
114 | msgid "You're not authorized to alter the datastore" | ||
115 | msgstr "Vous n'êtes pas autorisé à modifier les données" | ||
110 | 116 | ||
111 | #: application/bookmark/LinkDB.php:131 | 117 | #: application/bookmark/BookmarkFileService.php:177 |
112 | msgid "Internal Error: A link should always have an id and URL." | 118 | #: application/bookmark/BookmarkFileService.php:202 |
113 | msgstr "Erreur interne : un lien devrait toujours avoir un ID et une URL." | 119 | #: application/bookmark/BookmarkFileService.php:244 |
120 | msgid "Provided data is invalid" | ||
121 | msgstr "Les informations fournies ne sont pas valides" | ||
114 | 122 | ||
115 | #: application/bookmark/LinkDB.php:134 | 123 | #: application/bookmark/BookmarkFileService.php:205 |
116 | msgid "You must specify an integer as a key." | 124 | msgid "This bookmarks already exists" |
117 | msgstr "Vous devez utiliser un entier comme clé." | 125 | msgstr "Ce marque-page existe déjà ." |
118 | 126 | ||
119 | #: application/bookmark/LinkDB.php:137 | 127 | #: application/bookmark/BookmarkInitializer.php:37 |
120 | msgid "Array offset and link ID must be equal." | 128 | msgid "(private bookmark with thumbnail demo)" |
121 | msgstr "La clé du tableau et l'ID du lien doivent être identiques." | 129 | msgstr "(marque page privé avec une miniature)" |
130 | |||
131 | #: application/bookmark/BookmarkInitializer.php:40 | ||
132 | msgid "" | ||
133 | "Shaarli will automatically pick up the thumbnail for links to a variety of " | ||
134 | "websites.\n" | ||
135 | "\n" | ||
136 | "Explore your new Shaarli instance by trying out controls and menus.\n" | ||
137 | "Visit the project on [Github](https://github.com/shaarli/Shaarli) or [the " | ||
138 | "documentation](https://shaarli.readthedocs.io/en/master/) to learn more " | ||
139 | "about Shaarli.\n" | ||
140 | "\n" | ||
141 | "Now you can edit or delete the default shaares.\n" | ||
142 | msgstr "" | ||
143 | "Shaarli récupérera automatiquement la miniature associée au liens pour de " | ||
144 | "nombreux sites web.\n" | ||
145 | "\n" | ||
146 | "Explorez votre nouvelle instance de Shaarli en essayant les différents " | ||
147 | "contrôles et menus.\n" | ||
148 | "Visitez le projet sur [Github](https://github.com/shaarli/Shaarli) ou [la " | ||
149 | "documentation](https://shaarli.readthedocs.io/en/master/) pour en apprendre " | ||
150 | "plus sur Shaarli.\n" | ||
151 | "\n" | ||
152 | "Maintenant, vous pouvez modifier ou supprimer les shaares créés par défaut.\n" | ||
122 | 153 | ||
123 | #: application/bookmark/LinkDB.php:243 | 154 | #: application/bookmark/BookmarkInitializer.php:53 |
155 | msgid "Note: Shaare descriptions" | ||
156 | msgstr "Note : Description des Shaares" | ||
157 | |||
158 | #: application/bookmark/BookmarkInitializer.php:55 | ||
159 | msgid "" | ||
160 | "Adding a shaare without entering a URL creates a text-only \"note\" post " | ||
161 | "such as this one.\n" | ||
162 | "This note is private, so you are the only one able to see it while logged " | ||
163 | "in.\n" | ||
164 | "\n" | ||
165 | "You can use this to keep notes, post articles, code snippets, and much " | ||
166 | "more.\n" | ||
167 | "\n" | ||
168 | "The Markdown formatting setting allows you to format your notes and bookmark " | ||
169 | "description:\n" | ||
170 | "\n" | ||
171 | "### Title headings\n" | ||
172 | "\n" | ||
173 | "#### Multiple headings levels\n" | ||
174 | " * bullet lists\n" | ||
175 | " * _italic_ text\n" | ||
176 | " * **bold** text\n" | ||
177 | " * ~~strike through~~ text\n" | ||
178 | " * `code` blocks\n" | ||
179 | " * images\n" | ||
180 | " * [links](https://en.wikipedia.org/wiki/Markdown)\n" | ||
181 | "\n" | ||
182 | "Markdown also supports tables:\n" | ||
183 | "\n" | ||
184 | "| Name | Type | Color | Qty |\n" | ||
185 | "| ------- | --------- | ------ | ----- |\n" | ||
186 | "| Orange | Fruit | Orange | 126 |\n" | ||
187 | "| Apple | Fruit | Any | 62 |\n" | ||
188 | "| Lemon | Fruit | Yellow | 30 |\n" | ||
189 | "| Carrot | Vegetable | Red | 14 |\n" | ||
190 | msgstr "" | ||
191 | "Ajouter un shaare sans préciser d'URL créé une « note » textuelle, telle que " | ||
192 | "celle-ci.\n" | ||
193 | "Cette note est privée, donc vous êtes seul à pouvoir la voir lorsque vous " | ||
194 | "êtes connecté.\n" | ||
195 | "\n" | ||
196 | "Vous pouvez utiliser cette fonctionnalité pour prendre des notes, publier " | ||
197 | "des articles, des extraits de code, et bien plus.\n" | ||
198 | "\n" | ||
199 | "L'option du formatage par Markdown vous permet de formater vos description " | ||
200 | "de notes et marque-pages :\n" | ||
201 | "\n" | ||
202 | "### Titre d'en-tête\n" | ||
203 | "\n" | ||
204 | "#### Sur plusieurs niveaux\n" | ||
205 | " * liste à puce\n" | ||
206 | " * texte en _italique_\n" | ||
207 | " * texte en **gras**\n" | ||
208 | " * texte ~~barré~~\n" | ||
209 | " * blocs de `code`\n" | ||
210 | " * images\n" | ||
211 | " * [liens](https://en.wikipedia.org/wiki/Markdown)\n" | ||
212 | "\n" | ||
213 | "Markdown supporte aussi les tableaux :\n" | ||
214 | "\n" | ||
215 | "| Nom | Type | Couleur | Qte |\n" | ||
216 | "| ------- | --------- | ------ | ----- |\n" | ||
217 | "| Orange | Fruit | Orange | 126 |\n" | ||
218 | "| Pomme | Fruit | Multiple | 62 |\n" | ||
219 | "| Citron | Fruit | Jaune | 30 |\n" | ||
220 | "| Carotte | Légume | Orange | 14 |\n" | ||
221 | |||
222 | #: application/bookmark/BookmarkInitializer.php:89 | ||
223 | #: application/legacy/LegacyLinkDB.php:246 | ||
124 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | 224 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 |
125 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49 | 225 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49 |
126 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15 | 226 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15 |
@@ -131,37 +231,56 @@ msgstr "" | |||
131 | "Le gestionnaire de marque-pages personnel, minimaliste, et sans base de " | 231 | "Le gestionnaire de marque-pages personnel, minimaliste, et sans base de " |
132 | "données" | 232 | "données" |
133 | 233 | ||
134 | #: application/bookmark/LinkDB.php:246 | 234 | #: application/bookmark/BookmarkInitializer.php:92 |
135 | msgid "" | 235 | msgid "" |
136 | "Welcome to Shaarli! This is your first public bookmark. To edit or delete " | 236 | "Welcome to Shaarli!\n" |
137 | "me, you must first login.\n" | ||
138 | "\n" | 237 | "\n" |
139 | "To learn how to use Shaarli, consult the link \"Documentation\" at the " | 238 | "Shaarli allows you to bookmark your favorite pages, and share them with " |
140 | "bottom of this page.\n" | 239 | "others or store them privately.\n" |
240 | "You can add a description to your bookmarks, such as this one, and tag " | ||
241 | "them.\n" | ||
141 | "\n" | 242 | "\n" |
142 | "You use the community supported version of the original Shaarli project, by " | 243 | "Create a new shaare by clicking the `+Shaare` button, or using any of the " |
143 | "Sebastien Sauvage." | 244 | "recommended tools (browser extension, mobile app, bookmarklet, REST API, " |
144 | msgstr "" | 245 | "etc.).\n" |
145 | "Bienvenue sur Shaarli ! Ceci est votre premier marque-page public. Pour me " | ||
146 | "modifier ou me supprimer, vous devez d'abord vous connecter.\n" | ||
147 | "\n" | 246 | "\n" |
148 | "Pour apprendre à utiliser Shaarli, consultez le lien « Documentation » en " | 247 | "You can easily retrieve your links, even with thousands of them, using the " |
149 | "bas de page.\n" | 248 | "internal search engine, or search through tags (e.g. this Shaare is tagged " |
249 | "with `shaarli` and `help`).\n" | ||
250 | "Hashtags such as #shaarli #help are also supported.\n" | ||
251 | "You can also filter the available [RSS feed](/feed/atom) and picture wall by " | ||
252 | "tag or plaintext search.\n" | ||
150 | "\n" | 253 | "\n" |
151 | "Vous utilisez la version supportée par la communauté du projet original " | 254 | "We hope that you will enjoy using Shaarli, maintained with â¤ï¸ by the " |
152 | "Shaarli de Sébastien Sauvage." | 255 | "community!\n" |
153 | 256 | "Feel free to open [an issue](https://github.com/shaarli/Shaarli/issues) if " | |
154 | #: application/bookmark/LinkDB.php:263 | 257 | "you have a suggestion or encounter an issue.\n" |
155 | msgid "My secret stuff... - Pastebin.com" | ||
156 | msgstr "Mes trucs secrets... - Pastebin.com" | ||
157 | |||
158 | #: application/bookmark/LinkDB.php:265 | ||
159 | msgid "Shhhh! I'm a private link only YOU can see. You can delete me too." | ||
160 | msgstr "" | 258 | msgstr "" |
161 | "Pssst ! Je suis un lien privé que VOUS êtes le seul à voir. Vous pouvez me " | 259 | "Bienvenue sur Shaarli !\n" |
162 | "supprimer aussi." | 260 | "\n" |
261 | "Shaarli vous permet de sauvegarder des marque-pages de vos pages favorites, " | ||
262 | "et de les partager avec d'autres, ou de les enregistrer en privé.\n" | ||
263 | "Vous pouvez ajouter une description à vos marque-pages, comme celle-ci, et y " | ||
264 | "ajouter des tags.\n" | ||
265 | "\n" | ||
266 | "Créez un nouveau shaare en cliquant sur le bouton `+Shaare`, ou en utilisant " | ||
267 | "l'un des outils recommandés (extension de navigateur, application mobile, " | ||
268 | "bookmarklet, REST API, etc.).\n" | ||
269 | "\n" | ||
270 | "Vous pouvez facilement retrouver vos liens, même parmi des milliers, en " | ||
271 | "utilisant le moteur de recherche interne, ou en filtrant par tags (par " | ||
272 | "exemple ce Shaare est taggé avec `shaarli` et `help`).\n" | ||
273 | "Les hashtags comme #shaarli #help sont aussi supportés.\n" | ||
274 | "Vous pouvez aussi filtrer les [flux RSS](/feed/atom) et [mur d'images]() par " | ||
275 | "tag ou par texte brut.\n" | ||
276 | "\n" | ||
277 | "Nous espérons que vous apprécierez utiliser Shaarli, maintenu avec â¤ï¸ par la " | ||
278 | "communauté !\n" | ||
279 | "N'hésitez pas à ouvrir [un ticket (en)](https://github.com/shaarli/Shaarli/" | ||
280 | "issues) si vous avez une suggestion ou si vous rencontrez un problème.\n" | ||
281 | " \n" | ||
163 | 282 | ||
164 | #: application/bookmark/exception/LinkNotFoundException.php:13 | 283 | #: application/bookmark/exception/BookmarkNotFoundException.php:13 |
165 | msgid "The link you are trying to reach does not exist or has been deleted." | 284 | msgid "The link you are trying to reach does not exist or has been deleted." |
166 | msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé." | 285 | msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé." |
167 | 286 | ||
@@ -173,8 +292,8 @@ msgstr "" | |||
173 | "Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que " | 292 | "Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que " |
174 | "Shaarli a les droits d'écriture dans le dossier dans lequel il est installé." | 293 | "Shaarli a les droits d'écriture dans le dossier dans lequel il est installé." |
175 | 294 | ||
176 | #: application/config/ConfigManager.php:135 | 295 | #: application/config/ConfigManager.php:136 |
177 | #: application/config/ConfigManager.php:162 | 296 | #: application/config/ConfigManager.php:163 |
178 | msgid "Invalid setting key parameter. String expected, got: " | 297 | msgid "Invalid setting key parameter. String expected, got: " |
179 | msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : " | 298 | msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : " |
180 | 299 | ||
@@ -196,268 +315,376 @@ msgstr "Vous n'êtes pas autorisé à modifier la configuration." | |||
196 | msgid "Error accessing" | 315 | msgid "Error accessing" |
197 | msgstr "Une erreur s'est produite en accédant à " | 316 | msgstr "Une erreur s'est produite en accédant à " |
198 | 317 | ||
199 | #: application/feed/Cache.php:16 | 318 | #: application/feed/FeedBuilder.php:179 |
200 | #, php-format | ||
201 | msgid "Cannot purge %s: no directory" | ||
202 | msgstr "Impossible de purger %s : le répertoire n'existe pas" | ||
203 | |||
204 | #: application/feed/FeedBuilder.php:155 | ||
205 | msgid "Direct link" | 319 | msgid "Direct link" |
206 | msgstr "Liens directs" | 320 | msgstr "Liens directs" |
207 | 321 | ||
208 | #: application/feed/FeedBuilder.php:157 | 322 | #: application/feed/FeedBuilder.php:181 |
209 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 | 323 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 |
210 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:177 | 324 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179 |
211 | msgid "Permalink" | 325 | msgid "Permalink" |
212 | msgstr "Permalien" | 326 | msgstr "Permalien" |
213 | 327 | ||
214 | #: application/netscape/NetscapeBookmarkUtils.php:42 | 328 | #: application/front/controller/admin/ConfigureController.php:54 |
215 | msgid "Invalid export selection:" | 329 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 |
216 | msgstr "Sélection d'export invalide :" | 330 | msgid "Configure" |
331 | msgstr "Configurer" | ||
217 | 332 | ||
218 | #: application/netscape/NetscapeBookmarkUtils.php:87 | 333 | #: application/front/controller/admin/ConfigureController.php:102 |
219 | #, php-format | 334 | #: application/legacy/LegacyUpdater.php:537 |
220 | msgid "File %s (%d bytes) " | 335 | msgid "You have enabled or changed thumbnails mode." |
221 | msgstr "Le fichier %s (%d octets) " | 336 | msgstr "Vous avez activé ou changé le mode de miniatures." |
222 | 337 | ||
223 | #: application/netscape/NetscapeBookmarkUtils.php:89 | 338 | #: application/front/controller/admin/ConfigureController.php:103 |
224 | msgid "has an unknown file format. Nothing was imported." | 339 | #: application/legacy/LegacyUpdater.php:538 |
225 | msgstr "a un format inconnu. Rien n'a été importé." | 340 | msgid "Please synchronize them." |
341 | msgstr "Merci de les synchroniser." | ||
342 | |||
343 | #: application/front/controller/admin/ConfigureController.php:113 | ||
344 | #: application/front/controller/visitor/InstallController.php:136 | ||
345 | msgid "Error while writing config file after configuration update." | ||
346 | msgstr "" | ||
347 | "Une erreur s'est produite lors de la sauvegarde du fichier de configuration." | ||
226 | 348 | ||
227 | #: application/netscape/NetscapeBookmarkUtils.php:93 | 349 | #: application/front/controller/admin/ConfigureController.php:122 |
350 | msgid "Configuration was saved." | ||
351 | msgstr "La configuration a été sauvegardée." | ||
352 | |||
353 | #: application/front/controller/admin/ExportController.php:26 | ||
354 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64 | ||
355 | msgid "Export" | ||
356 | msgstr "Exporter" | ||
357 | |||
358 | #: application/front/controller/admin/ExportController.php:42 | ||
359 | msgid "Please select an export mode." | ||
360 | msgstr "Merci de choisir un mode d'export." | ||
361 | |||
362 | #: application/front/controller/admin/ImportController.php:41 | ||
363 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 | ||
364 | msgid "Import" | ||
365 | msgstr "Importer" | ||
366 | |||
367 | #: application/front/controller/admin/ImportController.php:55 | ||
368 | msgid "No import file provided." | ||
369 | msgstr "Aucun fichier à importer n'a été fourni." | ||
370 | |||
371 | #: application/front/controller/admin/ImportController.php:66 | ||
228 | #, php-format | 372 | #, php-format |
229 | msgid "" | 373 | msgid "" |
230 | "was successfully processed in %d seconds: %d links imported, %d links " | 374 | "The file you are trying to upload is probably bigger than what this " |
231 | "overwritten, %d links skipped." | 375 | "webserver can accept (%s). Please upload in smaller chunks." |
232 | msgstr "" | 376 | msgstr "" |
233 | "a été importé avec succès en %d secondes : %d liens importés, %d liens " | 377 | "Le fichier que vous essayer d'envoyer est probablement plus lourd que ce que " |
234 | "écrasés, %d liens ignorés." | 378 | "le serveur web peut accepter (%s). Merci de l'envoyer en parties plus " |
379 | "légères." | ||
235 | 380 | ||
236 | #: application/plugin/exception/PluginFileNotFoundException.php:21 | 381 | #: application/front/controller/admin/ManageShaareController.php:29 |
382 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
383 | msgid "Shaare a new link" | ||
384 | msgstr "Partager un nouveau lien" | ||
385 | |||
386 | #: application/front/controller/admin/ManageShaareController.php:78 | ||
387 | msgid "Note: " | ||
388 | msgstr "Note : " | ||
389 | |||
390 | #: application/front/controller/admin/ManageShaareController.php:109 | ||
391 | #: application/front/controller/admin/ManageShaareController.php:206 | ||
392 | #: application/front/controller/admin/ManageShaareController.php:275 | ||
393 | #: application/front/controller/admin/ManageShaareController.php:315 | ||
237 | #, php-format | 394 | #, php-format |
238 | msgid "Plugin \"%s\" files not found." | 395 | msgid "Bookmark with identifier %s could not be found." |
239 | msgstr "Les fichiers de l'extension \"%s\" sont introuvables." | 396 | msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé." |
240 | 397 | ||
241 | #: application/render/PageBuilder.php:209 | 398 | #: application/front/controller/admin/ManageShaareController.php:194 |
242 | msgid "The page you are trying to reach does not exist or has been deleted." | 399 | #: application/front/controller/admin/ManageShaareController.php:252 |
243 | msgstr "La page que vous essayez de consulter n'existe pas ou a été supprimée." | 400 | msgid "Invalid bookmark ID provided." |
401 | msgstr "ID du lien non valide." | ||
244 | 402 | ||
245 | #: application/render/PageBuilder.php:211 | 403 | #: application/front/controller/admin/ManageShaareController.php:260 |
246 | msgid "404 Not Found" | 404 | msgid "Invalid visibility provided." |
247 | msgstr "404 Introuvable" | 405 | msgstr "Visibilité du lien non valide." |
248 | 406 | ||
249 | #: application/updater/Updater.php:99 | 407 | #: application/front/controller/admin/ManageShaareController.php:363 |
250 | #, fuzzy | 408 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 |
251 | #| msgid "Couldn't retrieve Updater class methods." | 409 | msgid "Edit" |
252 | msgid "Couldn't retrieve updater class methods." | 410 | msgstr "Modifier" |
253 | msgstr "Impossible de récupérer les méthodes de la classe Updater." | ||
254 | 411 | ||
255 | #: application/updater/Updater.php:526 index.php:1034 | 412 | #: application/front/controller/admin/ManageShaareController.php:366 |
256 | msgid "" | 413 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 |
257 | "You have enabled or changed thumbnails mode. <a href=\"?do=thumbs_update" | 414 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 |
258 | "\">Please synchronize them</a>." | 415 | msgid "Shaare" |
259 | msgstr "" | 416 | msgstr "Shaare" |
260 | "Vous avez activé ou changé le mode de miniatures. <a href=\"?do=thumbs_update" | ||
261 | "\">Merci de les synchroniser</a>." | ||
262 | 417 | ||
263 | #: application/updater/UpdaterUtils.php:32 | 418 | #: application/front/controller/admin/ManageTagController.php:29 |
264 | msgid "Updates file path is not set, can't write updates." | 419 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 |
420 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
421 | msgid "Manage tags" | ||
422 | msgstr "Gérer les tags" | ||
423 | |||
424 | #: application/front/controller/admin/ManageTagController.php:48 | ||
425 | msgid "Invalid tags provided." | ||
426 | msgstr "Les tags fournis ne sont pas valides." | ||
427 | |||
428 | #: application/front/controller/admin/ManageTagController.php:72 | ||
429 | #, php-format | ||
430 | msgid "The tag was removed from %d bookmark." | ||
431 | msgid_plural "The tag was removed from %d bookmarks." | ||
432 | msgstr[0] "Le tag a été supprimé du %d lien." | ||
433 | msgstr[1] "Le tag a été supprimé de %d liens." | ||
434 | |||
435 | #: application/front/controller/admin/ManageTagController.php:77 | ||
436 | #, php-format | ||
437 | msgid "The tag was renamed in %d bookmark." | ||
438 | msgid_plural "The tag was renamed in %d bookmarks." | ||
439 | msgstr[0] "Le tag a été renommé dans %d lien." | ||
440 | msgstr[1] "Le tag a été renommé dans %d liens." | ||
441 | |||
442 | #: application/front/controller/admin/PasswordController.php:28 | ||
443 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
444 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
445 | msgid "Change password" | ||
446 | msgstr "Modifier le mot de passe" | ||
447 | |||
448 | #: application/front/controller/admin/PasswordController.php:55 | ||
449 | msgid "You must provide the current and new password to change it." | ||
265 | msgstr "" | 450 | msgstr "" |
266 | "Le chemin vers le fichier de mise à jour n'est pas défini, impossible " | 451 | "Vous devez fournir les mots de passe actuel et nouveau pour pouvoir le " |
267 | "d'écrire les mises à jour." | 452 | "modifier." |
268 | 453 | ||
269 | #: application/updater/UpdaterUtils.php:37 | 454 | #: application/front/controller/admin/PasswordController.php:71 |
270 | msgid "Unable to write updates in " | 455 | msgid "The old password is not correct." |
271 | msgstr "Impossible d'écrire les mises à jour dans " | 456 | msgstr "L'ancien mot de passe est incorrect." |
272 | 457 | ||
273 | #: application/updater/exception/UpdaterException.php:51 | 458 | #: application/front/controller/admin/PasswordController.php:97 |
274 | msgid "An error occurred while running the update " | 459 | msgid "Your password has been changed" |
275 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " | 460 | msgstr "Votre mot de passe a été modifié" |
276 | 461 | ||
277 | #: index.php:145 | 462 | #: application/front/controller/admin/PluginsController.php:45 |
278 | msgid "Shared links on " | 463 | msgid "Plugin Administration" |
279 | msgstr "Liens partagés sur " | 464 | msgstr "Administration des plugins" |
280 | 465 | ||
281 | #: index.php:167 | 466 | #: application/front/controller/admin/PluginsController.php:75 |
282 | msgid "Insufficient permissions:" | 467 | msgid "Setting successfully saved." |
283 | msgstr "Permissions insuffisantes :" | 468 | msgstr "Les paramètres ont été sauvegardés avec succès." |
284 | 469 | ||
285 | #: index.php:203 | 470 | #: application/front/controller/admin/PluginsController.php:78 |
286 | msgid "I said: NO. You are banned for the moment. Go away." | 471 | msgid "Error while saving plugin configuration: " |
287 | msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard." | 472 | msgstr "" |
473 | "Une erreur s'est produite lors de la sauvegarde de la configuration des " | ||
474 | "plugins : " | ||
288 | 475 | ||
289 | #: index.php:275 | 476 | #: application/front/controller/admin/ThumbnailsController.php:37 |
290 | msgid "Wrong login/password." | 477 | #: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 |
291 | msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." | 478 | msgid "Thumbnails update" |
479 | msgstr "Mise à jour des miniatures" | ||
480 | |||
481 | #: application/front/controller/admin/ToolsController.php:31 | ||
482 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33 | ||
483 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:33 | ||
484 | msgid "Tools" | ||
485 | msgstr "Outils" | ||
486 | |||
487 | #: application/front/controller/visitor/BookmarkListController.php:115 | ||
488 | msgid "Search: " | ||
489 | msgstr "Recherche : " | ||
292 | 490 | ||
293 | #: index.php:398 index.php:404 | 491 | #: application/front/controller/visitor/DailyController.php:45 |
294 | msgid "Today" | 492 | msgid "Today" |
295 | msgstr "Aujourd'hui" | 493 | msgstr "Aujourd'hui" |
296 | 494 | ||
297 | #: index.php:400 | 495 | #: application/front/controller/visitor/DailyController.php:47 |
298 | msgid "Yesterday" | 496 | msgid "Yesterday" |
299 | msgstr "Hier" | 497 | msgstr "Hier" |
300 | 498 | ||
301 | #: index.php:484 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | 499 | #: application/front/controller/visitor/DailyController.php:85 |
302 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:46 | 500 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 |
501 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:48 | ||
303 | msgid "Daily" | 502 | msgid "Daily" |
304 | msgstr "Quotidien" | 503 | msgstr "Quotidien" |
305 | 504 | ||
306 | #: index.php:593 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | 505 | #: application/front/controller/visitor/ErrorController.php:36 |
307 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | 506 | msgid "An unexpected error occurred." |
308 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 | 507 | msgstr "Une erreur inattendue s'est produite." |
309 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 | 508 | |
310 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:75 | 509 | #: application/front/controller/visitor/InstallController.php:73 |
311 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:99 | 510 | #, php-format |
511 | msgid "" | ||
512 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " | ||
513 | "variable \"session.save_path\" is set correctly in your PHP config, and that " | ||
514 | "you have write access to it.<br>It currently points to %s.<br>On some " | ||
515 | "browsers, accessing your server via a hostname like 'localhost' or any " | ||
516 | "custom hostname without a dot causes cookie storage to fail. We recommend " | ||
517 | "accessing your server via it's IP address or Fully Qualified Domain Name.<br>" | ||
518 | msgstr "" | ||
519 | "<pre>Les sesssions ne semblent pas fonctionner sur ce serveur.<br>Assurez " | ||
520 | "vous que la variable « session.save_path » est correctement définie dans " | ||
521 | "votre fichier de configuration PHP, et que vous avez les droits d'écriture " | ||
522 | "dessus.<br>Ce paramètre pointe actuellement sur %s.<br>Sur certains " | ||
523 | "navigateurs, accéder à votre serveur depuis un nom d'hôte comme « localhost " | ||
524 | "» ou autre nom personnalisé sans point '.' entraine l'échec de la sauvegarde " | ||
525 | "des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " | ||
526 | "adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>" | ||
527 | |||
528 | #: application/front/controller/visitor/InstallController.php:144 | ||
529 | msgid "" | ||
530 | "Shaarli is now configured. Please login and start shaaring your bookmarks!" | ||
531 | msgstr "" | ||
532 | "Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à " | ||
533 | "shaare vos liens !" | ||
534 | |||
535 | #: application/front/controller/visitor/InstallController.php:158 | ||
536 | msgid "Insufficient permissions:" | ||
537 | msgstr "Permissions insuffisantes :" | ||
538 | |||
539 | #: application/front/controller/visitor/LoginController.php:46 | ||
540 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
541 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
542 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | ||
543 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101 | ||
544 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:77 | ||
545 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:101 | ||
312 | msgid "Login" | 546 | msgid "Login" |
313 | msgstr "Connexion" | 547 | msgstr "Connexion" |
314 | 548 | ||
315 | #: index.php:608 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | 549 | #: application/front/controller/visitor/LoginController.php:78 |
316 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:41 | 550 | msgid "Wrong login/password." |
551 | msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." | ||
552 | |||
553 | #: application/front/controller/visitor/PictureWallController.php:29 | ||
554 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | ||
555 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:43 | ||
317 | msgid "Picture wall" | 556 | msgid "Picture wall" |
318 | msgstr "Mur d'images" | 557 | msgstr "Mur d'images" |
319 | 558 | ||
320 | #: index.php:683 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 559 | #: application/front/controller/visitor/TagCloudController.php:80 |
321 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36 | 560 | #, fuzzy |
322 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | 561 | #| msgid "Tag list" |
323 | msgid "Tag cloud" | 562 | msgid "Tag " |
324 | msgstr "Nuage de tags" | ||
325 | |||
326 | #: index.php:715 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
327 | msgid "Tag list" | ||
328 | msgstr "Liste des tags" | 563 | msgstr "Liste des tags" |
329 | 564 | ||
330 | #: index.php:944 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | 565 | #: application/front/exceptions/AlreadyInstalledException.php:11 |
331 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31 | 566 | msgid "Shaarli has already been installed. Login to edit the configuration." |
332 | msgid "Tools" | 567 | msgstr "" |
333 | msgstr "Outils" | 568 | "Shaarli est déjà installé. Connectez-vous pour modifier la configuration." |
569 | |||
570 | #: application/front/exceptions/LoginBannedException.php:11 | ||
571 | msgid "" | ||
572 | "You have been banned after too many failed login attempts. Try again later." | ||
573 | msgstr "" | ||
574 | "Vous avez été banni après trop d'échecs d'authentification. Merci de " | ||
575 | "réessayer plus tard." | ||
334 | 576 | ||
335 | #: index.php:952 | 577 | #: application/front/exceptions/OpenShaarliPasswordException.php:16 |
336 | msgid "You are not supposed to change a password on an Open Shaarli." | 578 | msgid "You are not supposed to change a password on an Open Shaarli." |
337 | msgstr "" | 579 | msgstr "" |
338 | "Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert." | 580 | "Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert." |
339 | 581 | ||
340 | #: index.php:957 index.php:1007 index.php:1094 index.php:1124 index.php:1234 | 582 | #: application/front/exceptions/ThumbnailsDisabledException.php:11 |
341 | #: index.php:1281 | 583 | msgid "Picture wall unavailable (thumbnails are disabled)." |
584 | msgstr "" | ||
585 | "Le mur d'images n'est pas disponible (les miniatures sont désactivées)." | ||
586 | |||
587 | #: application/front/exceptions/WrongTokenException.php:16 | ||
342 | msgid "Wrong token." | 588 | msgid "Wrong token." |
343 | msgstr "Jeton invalide." | 589 | msgstr "Jeton invalide." |
344 | 590 | ||
345 | #: index.php:966 | 591 | #: application/legacy/LegacyLinkDB.php:131 |
346 | msgid "The old password is not correct." | 592 | msgid "You are not authorized to add a link." |
347 | msgstr "L'ancien mot de passe est incorrect." | 593 | msgstr "Vous n'êtes pas autorisé à ajouter un lien." |
348 | |||
349 | #: index.php:993 | ||
350 | msgid "Your password has been changed" | ||
351 | msgstr "Votre mot de passe a été modifié" | ||
352 | |||
353 | #: index.php:997 | ||
354 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
355 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
356 | msgid "Change password" | ||
357 | msgstr "Modifier le mot de passe" | ||
358 | |||
359 | #: index.php:1054 | ||
360 | msgid "Configuration was saved." | ||
361 | msgstr "La configuration a été sauvegardée." | ||
362 | 594 | ||
363 | #: index.php:1078 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | 595 | #: application/legacy/LegacyLinkDB.php:134 |
364 | msgid "Configure" | 596 | msgid "Internal Error: A link should always have an id and URL." |
365 | msgstr "Configurer" | 597 | msgstr "Erreur interne : un lien devrait toujours avoir un ID et une URL." |
366 | 598 | ||
367 | #: index.php:1088 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | 599 | #: application/legacy/LegacyLinkDB.php:137 |
368 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 600 | msgid "You must specify an integer as a key." |
369 | msgid "Manage tags" | 601 | msgstr "Vous devez utiliser un entier comme clé." |
370 | msgstr "Gérer les tags" | ||
371 | 602 | ||
372 | #: index.php:1107 | 603 | #: application/legacy/LegacyLinkDB.php:140 |
373 | #, php-format | 604 | msgid "Array offset and link ID must be equal." |
374 | msgid "The tag was removed from %d link." | 605 | msgstr "La clé du tableau et l'ID du lien doivent être identiques." |
375 | msgid_plural "The tag was removed from %d links." | ||
376 | msgstr[0] "Le tag a été supprimé de %d lien." | ||
377 | msgstr[1] "Le tag a été supprimé de %d liens." | ||
378 | 606 | ||
379 | #: index.php:1108 | 607 | #: application/legacy/LegacyLinkDB.php:249 |
380 | #, php-format | 608 | msgid "" |
381 | msgid "The tag was renamed in %d link." | 609 | "Welcome to Shaarli! This is your first public bookmark. To edit or delete " |
382 | msgid_plural "The tag was renamed in %d links." | 610 | "me, you must first login.\n" |
383 | msgstr[0] "Le tag a été renommé dans %d lien." | 611 | "\n" |
384 | msgstr[1] "Le tag a été renommé dans %d liens." | 612 | "To learn how to use Shaarli, consult the link \"Documentation\" at the " |
613 | "bottom of this page.\n" | ||
614 | "\n" | ||
615 | "You use the community supported version of the original Shaarli project, by " | ||
616 | "Sebastien Sauvage." | ||
617 | msgstr "" | ||
618 | "Bienvenue sur Shaarli ! Ceci est votre premier marque-page public. Pour me " | ||
619 | "modifier ou me supprimer, vous devez d'abord vous connecter.\n" | ||
620 | "\n" | ||
621 | "Pour apprendre à utiliser Shaarli, consultez le lien « Documentation » en " | ||
622 | "bas de page.\n" | ||
623 | "\n" | ||
624 | "Vous utilisez la version supportée par la communauté du projet original " | ||
625 | "Shaarli de Sébastien Sauvage." | ||
385 | 626 | ||
386 | #: index.php:1115 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | 627 | #: application/legacy/LegacyLinkDB.php:266 |
387 | msgid "Shaare a new link" | 628 | msgid "My secret stuff... - Pastebin.com" |
388 | msgstr "Partager un nouveau lien" | 629 | msgstr "Mes trucs secrets... - Pastebin.com" |
389 | 630 | ||
390 | #: index.php:1344 tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | 631 | #: application/legacy/LegacyLinkDB.php:268 |
391 | msgid "Edit" | 632 | msgid "Shhhh! I'm a private link only YOU can see. You can delete me too." |
392 | msgstr "Modifier" | 633 | msgstr "" |
634 | "Pssst ! Je suis un lien privé que VOUS êtes le seul à voir. Vous pouvez me " | ||
635 | "supprimer aussi." | ||
393 | 636 | ||
394 | #: index.php:1344 index.php:1416 | 637 | #: application/legacy/LegacyUpdater.php:104 |
395 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | 638 | msgid "Couldn't retrieve updater class methods." |
396 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26 | 639 | msgstr "Impossible de récupérer les méthodes de la classe Updater." |
397 | msgid "Shaare" | ||
398 | msgstr "Shaare" | ||
399 | 640 | ||
400 | #: index.php:1385 | 641 | #: application/legacy/LegacyUpdater.php:538 |
401 | msgid "Note: " | 642 | msgid "<a href=\"./admin/thumbnails\">" |
402 | msgstr "Note : " | 643 | msgstr "<a href=\"./admin/thumbnails\">" |
403 | 644 | ||
404 | #: index.php:1424 | 645 | #: application/netscape/NetscapeBookmarkUtils.php:63 |
405 | msgid "Invalid link ID provided" | 646 | msgid "Invalid export selection:" |
406 | msgstr "ID du lien non valide" | 647 | msgstr "Sélection d'export invalide :" |
407 | 648 | ||
408 | #: index.php:1444 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 | 649 | #: application/netscape/NetscapeBookmarkUtils.php:215 |
409 | msgid "Export" | 650 | #, php-format |
410 | msgstr "Exporter" | 651 | msgid "File %s (%d bytes) " |
652 | msgstr "Le fichier %s (%d octets) " | ||
411 | 653 | ||
412 | #: index.php:1506 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 | 654 | #: application/netscape/NetscapeBookmarkUtils.php:217 |
413 | msgid "Import" | 655 | msgid "has an unknown file format. Nothing was imported." |
414 | msgstr "Importer" | 656 | msgstr "a un format inconnu. Rien n'a été importé." |
415 | 657 | ||
416 | #: index.php:1516 | 658 | #: application/netscape/NetscapeBookmarkUtils.php:221 |
417 | #, php-format | 659 | #, php-format |
418 | msgid "" | 660 | msgid "" |
419 | "The file you are trying to upload is probably bigger than what this " | 661 | "was successfully processed in %d seconds: %d bookmarks imported, %d " |
420 | "webserver can accept (%s). Please upload in smaller chunks." | 662 | "bookmarks overwritten, %d bookmarks skipped." |
421 | msgstr "" | 663 | msgstr "" |
422 | "Le fichier que vous essayer d'envoyer est probablement plus lourd que ce que " | 664 | "a été importé avec succès en %d secondes : %d liens importés, %d liens " |
423 | "le serveur web peut accepter (%s). Merci de l'envoyer en parties plus " | 665 | "écrasés, %d liens ignorés." |
424 | "légères." | ||
425 | 666 | ||
426 | #: index.php:1561 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | 667 | #: application/plugin/PluginManager.php:122 |
427 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | 668 | msgid " [plugin incompatibility]: " |
428 | msgid "Plugin administration" | 669 | msgstr " [incompatibilité de l'extension] : " |
429 | msgstr "Administration des plugins" | ||
430 | 670 | ||
431 | #: index.php:1616 tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 671 | #: application/plugin/exception/PluginFileNotFoundException.php:21 |
432 | msgid "Thumbnails update" | 672 | #, php-format |
433 | msgstr "Mise à jour des miniatures" | 673 | msgid "Plugin \"%s\" files not found." |
434 | 674 | msgstr "Les fichiers de l'extension \"%s\" sont introuvables." | |
435 | #: index.php:1782 | ||
436 | msgid "Search: " | ||
437 | msgstr "Recherche : " | ||
438 | 675 | ||
439 | #: index.php:1825 | 676 | #: application/render/PageCacheManager.php:32 |
440 | #, php-format | 677 | #, php-format |
441 | msgid "" | 678 | msgid "Cannot purge %s: no directory" |
442 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " | 679 | msgstr "Impossible de purger %s : le répertoire n'existe pas" |
443 | "variable \"session.save_path\" is set correctly in your PHP config, and that " | 680 | |
444 | "you have write access to it.<br>It currently points to %s.<br>On some " | 681 | #: application/updater/exception/UpdaterException.php:51 |
445 | "browsers, accessing your server via a hostname like 'localhost' or any " | 682 | msgid "An error occurred while running the update " |
446 | "custom hostname without a dot causes cookie storage to fail. We recommend " | 683 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " |
447 | "accessing your server via it's IP address or Fully Qualified Domain Name.<br>" | ||
448 | msgstr "" | ||
449 | "<pre>Les sesssions ne semblent pas fonctionner sur ce serveur.<br>Assurez " | ||
450 | "vous que la variable « session.save_path » est correctement définie dans " | ||
451 | "votre fichier de configuration PHP, et que vous avez les droits d'écriture " | ||
452 | "dessus.<br>Ce paramètre pointe actuellement sur %s.<br>Sur certains " | ||
453 | "navigateurs, accéder à votre serveur depuis un nom d'hôte comme « localhost " | ||
454 | "» ou autre nom personnalisé sans point '.' entraine l'échec de la sauvegarde " | ||
455 | "des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " | ||
456 | "adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>" | ||
457 | 684 | ||
458 | #: index.php:1835 | 685 | #: index.php:62 |
459 | msgid "Click to try again." | 686 | msgid "Shared bookmarks on " |
460 | msgstr "Cliquer ici pour réessayer." | 687 | msgstr "Liens partagés sur " |
461 | 688 | ||
462 | #: plugins/addlink_toolbar/addlink_toolbar.php:31 | 689 | #: plugins/addlink_toolbar/addlink_toolbar.php:31 |
463 | msgid "URI" | 690 | msgid "URI" |
@@ -472,15 +699,15 @@ msgstr "Shaare" | |||
472 | msgid "Adds the addlink input on the linklist page." | 699 | msgid "Adds the addlink input on the linklist page." |
473 | msgstr "Ajoute le formulaire d'ajout de liens sur la page principale." | 700 | msgstr "Ajoute le formulaire d'ajout de liens sur la page principale." |
474 | 701 | ||
475 | #: plugins/archiveorg/archiveorg.php:25 | 702 | #: plugins/archiveorg/archiveorg.php:26 |
476 | msgid "View on archive.org" | 703 | msgid "View on archive.org" |
477 | msgstr "Voir sur archive.org" | 704 | msgstr "Voir sur archive.org" |
478 | 705 | ||
479 | #: plugins/archiveorg/archiveorg.php:38 | 706 | #: plugins/archiveorg/archiveorg.php:39 |
480 | msgid "For each link, add an Archive.org icon." | 707 | msgid "For each link, add an Archive.org icon." |
481 | msgstr "Pour chaque lien, ajoute une icône pour Archive.org." | 708 | msgstr "Pour chaque lien, ajoute une icône pour Archive.org." |
482 | 709 | ||
483 | #: plugins/default_colors/default_colors.php:33 | 710 | #: plugins/default_colors/default_colors.php:38 |
484 | msgid "" | 711 | msgid "" |
485 | "Default colors plugin error: This plugin is active and no custom color is " | 712 | "Default colors plugin error: This plugin is active and no custom color is " |
486 | "configured." | 713 | "configured." |
@@ -488,25 +715,25 @@ msgstr "" | |||
488 | "Erreur du plugin default colors : ce plugin est actif et aucune couleur " | 715 | "Erreur du plugin default colors : ce plugin est actif et aucune couleur " |
489 | "n'est configurée." | 716 | "n'est configurée." |
490 | 717 | ||
491 | #: plugins/default_colors/default_colors.php:107 | 718 | #: plugins/default_colors/default_colors.php:113 |
492 | msgid "Override default theme colors. Use any CSS valid color." | 719 | msgid "Override default theme colors. Use any CSS valid color." |
493 | msgstr "" | 720 | msgstr "" |
494 | "Remplacer les couleurs du thème par défaut. Utiliser n'importe quelle " | 721 | "Remplacer les couleurs du thème par défaut. Utiliser n'importe quelle " |
495 | "couleur CSS valide." | 722 | "couleur CSS valide." |
496 | 723 | ||
497 | #: plugins/default_colors/default_colors.php:108 | 724 | #: plugins/default_colors/default_colors.php:114 |
498 | msgid "Main color (navbar green)" | 725 | msgid "Main color (navbar green)" |
499 | msgstr "Couleur principale (vert de la barre de navigation)" | 726 | msgstr "Couleur principale (vert de la barre de navigation)" |
500 | 727 | ||
501 | #: plugins/default_colors/default_colors.php:109 | 728 | #: plugins/default_colors/default_colors.php:115 |
502 | msgid "Background color (light grey)" | 729 | msgid "Background color (light grey)" |
503 | msgstr "Couleur de fond (gris léger)" | 730 | msgstr "Couleur de fond (gris léger)" |
504 | 731 | ||
505 | #: plugins/default_colors/default_colors.php:110 | 732 | #: plugins/default_colors/default_colors.php:116 |
506 | msgid "Dark main color (e.g. visited links)" | 733 | msgid "Dark main color (e.g. visited links)" |
507 | msgstr "Couleur principale sombre (ex : les liens visités)" | 734 | msgstr "Couleur principale sombre (ex : les liens visités)" |
508 | 735 | ||
509 | #: plugins/demo_plugin/demo_plugin.php:482 | 736 | #: plugins/demo_plugin/demo_plugin.php:477 |
510 | msgid "" | 737 | msgid "" |
511 | "A demo plugin covering all use cases for template designers and plugin " | 738 | "A demo plugin covering all use cases for template designers and plugin " |
512 | "developers." | 739 | "developers." |
@@ -514,11 +741,11 @@ msgstr "" | |||
514 | "Une extension de démonstration couvrant tous les cas d'utilisation pour les " | 741 | "Une extension de démonstration couvrant tous les cas d'utilisation pour les " |
515 | "designers de thèmes et les développeurs d'extensions." | 742 | "designers de thèmes et les développeurs d'extensions." |
516 | 743 | ||
517 | #: plugins/demo_plugin/demo_plugin.php:483 | 744 | #: plugins/demo_plugin/demo_plugin.php:478 |
518 | msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed." | 745 | msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed." |
519 | msgstr "Ceci est un paramètre dédié au plugin de démo. Il sera suffixé." | 746 | msgstr "Ceci est un paramètre dédié au plugin de démo. Il sera suffixé." |
520 | 747 | ||
521 | #: plugins/demo_plugin/demo_plugin.php:484 | 748 | #: plugins/demo_plugin/demo_plugin.php:479 |
522 | msgid "Other demo parameter" | 749 | msgid "Other demo parameter" |
523 | msgstr "Un autre paramètre de démo" | 750 | msgstr "Un autre paramètre de démo" |
524 | 751 | ||
@@ -540,36 +767,6 @@ msgstr "" | |||
540 | msgid "Isso server URL (without 'http://')" | 767 | msgid "Isso server URL (without 'http://')" |
541 | msgstr "URL du serveur Isso (sans 'http://')" | 768 | msgstr "URL du serveur Isso (sans 'http://')" |
542 | 769 | ||
543 | #: plugins/markdown/markdown.php:163 | ||
544 | msgid "Description will be rendered with" | ||
545 | msgstr "La description sera générée avec" | ||
546 | |||
547 | #: plugins/markdown/markdown.php:164 | ||
548 | msgid "Markdown syntax documentation" | ||
549 | msgstr "Documentation sur la syntaxe Markdown" | ||
550 | |||
551 | #: plugins/markdown/markdown.php:165 | ||
552 | msgid "Markdown syntax" | ||
553 | msgstr "la syntaxe Markdown" | ||
554 | |||
555 | #: plugins/markdown/markdown.php:361 | ||
556 | msgid "" | ||
557 | "Render shaare description with Markdown syntax.<br><strong>Warning</" | ||
558 | "strong>:\n" | ||
559 | "If your shaared descriptions contained HTML tags before enabling the " | ||
560 | "markdown plugin,\n" | ||
561 | "enabling it might break your page.\n" | ||
562 | "See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
563 | "markdown#html-rendering\">README</a>." | ||
564 | msgstr "" | ||
565 | "Utilise la syntaxe Markdown pour la description des liens." | ||
566 | "<br><strong>Attention</strong> :\n" | ||
567 | "Si vous aviez des descriptions contenant du HTML avant d'activer cette " | ||
568 | "extension,\n" | ||
569 | "l'activer pourrait déformer vos pages.\n" | ||
570 | "Voir le <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
571 | "markdown#html-rendering\">README</a>." | ||
572 | |||
573 | #: plugins/piwik/piwik.php:23 | 770 | #: plugins/piwik/piwik.php:23 |
574 | msgid "" | 771 | msgid "" |
575 | "Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin " | 772 | "Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin " |
@@ -626,7 +823,7 @@ msgstr "Mauvaise réponse du hub %s" | |||
626 | msgid "Enable PubSubHubbub feed publishing." | 823 | msgid "Enable PubSubHubbub feed publishing." |
627 | msgstr "Active la publication de flux vers PubSubHubbub." | 824 | msgstr "Active la publication de flux vers PubSubHubbub." |
628 | 825 | ||
629 | #: plugins/qrcode/qrcode.php:72 plugins/wallabag/wallabag.php:68 | 826 | #: plugins/qrcode/qrcode.php:73 plugins/wallabag/wallabag.php:70 |
630 | msgid "For each link, add a QRCode icon." | 827 | msgid "For each link, add a QRCode icon." |
631 | msgstr "Pour chaque lien, ajouter une icône de QRCode." | 828 | msgstr "Pour chaque lien, ajouter une icône de QRCode." |
632 | 829 | ||
@@ -642,24 +839,14 @@ msgstr "" | |||
642 | msgid "Save to wallabag" | 839 | msgid "Save to wallabag" |
643 | msgstr "Sauvegarder dans Wallabag" | 840 | msgstr "Sauvegarder dans Wallabag" |
644 | 841 | ||
645 | #: plugins/wallabag/wallabag.php:69 | 842 | #: plugins/wallabag/wallabag.php:71 |
646 | msgid "Wallabag API URL" | 843 | msgid "Wallabag API URL" |
647 | msgstr "URL de l'API Wallabag" | 844 | msgstr "URL de l'API Wallabag" |
648 | 845 | ||
649 | #: plugins/wallabag/wallabag.php:70 | 846 | #: plugins/wallabag/wallabag.php:72 |
650 | msgid "Wallabag API version (1 or 2)" | 847 | msgid "Wallabag API version (1 or 2)" |
651 | msgstr "Version de l'API Wallabag (1 ou 2)" | 848 | msgstr "Version de l'API Wallabag (1 ou 2)" |
652 | 849 | ||
653 | #: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227 | ||
654 | #: tests/languages/fr/LanguagesFrTest.php:159 | ||
655 | #: tests/languages/fr/LanguagesFrTest.php:172 | ||
656 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85 | ||
657 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:85 | ||
658 | msgid "Search" | ||
659 | msgid_plural "Search" | ||
660 | msgstr[0] "Rechercher" | ||
661 | msgstr[1] "Rechercher" | ||
662 | |||
663 | #: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12 | 850 | #: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12 |
664 | msgid "Sorry, nothing to see here." | 851 | msgid "Sorry, nothing to see here." |
665 | msgstr "Désolé, il y a rien à voir ici." | 852 | msgstr "Désolé, il y a rien à voir ici." |
@@ -698,10 +885,11 @@ msgid "Rename" | |||
698 | msgstr "Renommer" | 885 | msgstr "Renommer" |
699 | 886 | ||
700 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | 887 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 |
701 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | 888 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:93 |
702 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 | 889 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 |
703 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:145 | 890 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 |
704 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:145 | 891 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147 |
892 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67 | ||
705 | msgid "Delete" | 893 | msgid "Delete" |
706 | msgstr "Supprimer" | 894 | msgstr "Supprimer" |
707 | 895 | ||
@@ -713,33 +901,6 @@ msgstr "Vous pouvez aussi modifier les tags dans la" | |||
713 | msgid "tag list" | 901 | msgid "tag list" |
714 | msgstr "liste des tags" | 902 | msgstr "liste des tags" |
715 | 903 | ||
716 | #: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:143 | ||
717 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:312 | ||
718 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | ||
719 | msgid "All" | ||
720 | msgstr "Tous" | ||
721 | |||
722 | #: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:147 | ||
723 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:316 | ||
724 | msgid "Only common media hosts" | ||
725 | msgstr "Seulement les hébergeurs de média connus" | ||
726 | |||
727 | #: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:151 | ||
728 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320 | ||
729 | msgid "None" | ||
730 | msgstr "Aucune" | ||
731 | |||
732 | #: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:158 | ||
733 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:297 | ||
734 | msgid "You need to enable the extension <code>php-gd</code> to use thumbnails." | ||
735 | msgstr "" | ||
736 | "Vous devez activer l'extension <code>php-gd</code> pour utiliser les " | ||
737 | "miniatures." | ||
738 | |||
739 | #: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:162 | ||
740 | msgid "Synchonize thumbnails" | ||
741 | msgstr "Synchroniser les miniatures" | ||
742 | |||
743 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | 904 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 |
744 | msgid "title" | 905 | msgid "title" |
745 | msgstr "titre" | 906 | msgstr "titre" |
@@ -756,109 +917,132 @@ msgstr "Valeur par défaut" | |||
756 | msgid "Theme" | 917 | msgid "Theme" |
757 | msgstr "Thème" | 918 | msgstr "Thème" |
758 | 919 | ||
759 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87 | 920 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85 |
760 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78 | 921 | msgid "Description formatter" |
922 | msgstr "Format des descriptions" | ||
923 | |||
924 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114 | ||
925 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | ||
761 | msgid "Language" | 926 | msgid "Language" |
762 | msgstr "Langue" | 927 | msgstr "Langue" |
763 | 928 | ||
764 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116 | 929 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143 |
765 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 | 930 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101 |
766 | msgid "Timezone" | 931 | msgid "Timezone" |
767 | msgstr "Fuseau horaire" | 932 | msgstr "Fuseau horaire" |
768 | 933 | ||
769 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | 934 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144 |
770 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 | 935 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 |
771 | msgid "Continent" | 936 | msgid "Continent" |
772 | msgstr "Continent" | 937 | msgstr "Continent" |
773 | 938 | ||
774 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | 939 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144 |
775 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 | 940 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 |
776 | msgid "City" | 941 | msgid "City" |
777 | msgstr "Ville" | 942 | msgstr "Ville" |
778 | 943 | ||
779 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164 | 944 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:191 |
780 | msgid "Disable session cookie hijacking protection" | 945 | msgid "Disable session cookie hijacking protection" |
781 | msgstr "Désactiver la protection contre le détournement de cookies" | 946 | msgstr "Désactiver la protection contre le détournement de cookies" |
782 | 947 | ||
783 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166 | 948 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:193 |
784 | msgid "Check this if you get disconnected or if your IP address changes often" | 949 | msgid "Check this if you get disconnected or if your IP address changes often" |
785 | msgstr "" | 950 | msgstr "" |
786 | "Cocher cette case si vous êtes souvent déconnecté ou si votre adresse IP " | 951 | "Cocher cette case si vous êtes souvent déconnecté ou si votre adresse IP " |
787 | "change souvent" | 952 | "change souvent" |
788 | 953 | ||
789 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183 | 954 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:210 |
790 | msgid "Private links by default" | 955 | msgid "Private links by default" |
791 | msgstr "Liens privés par défaut" | 956 | msgstr "Liens privés par défaut" |
792 | 957 | ||
793 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:184 | 958 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:211 |
794 | msgid "All new links are private by default" | 959 | msgid "All new links are private by default" |
795 | msgstr "Tous les nouveaux liens sont privés par défaut" | 960 | msgstr "Tous les nouveaux liens sont privés par défaut" |
796 | 961 | ||
797 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 | 962 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:226 |
798 | msgid "RSS direct links" | 963 | msgid "RSS direct links" |
799 | msgstr "Liens directs dans le flux RSS" | 964 | msgstr "Liens directs dans le flux RSS" |
800 | 965 | ||
801 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:200 | 966 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:227 |
802 | msgid "Check this to use direct URL instead of permalink in feeds" | 967 | msgid "Check this to use direct URL instead of permalink in feeds" |
803 | msgstr "" | 968 | msgstr "" |
804 | "Cocher cette case pour utiliser des liens directs au lieu des permaliens " | 969 | "Cocher cette case pour utiliser des liens directs au lieu des permaliens " |
805 | "dans le flux RSS" | 970 | "dans le flux RSS" |
806 | 971 | ||
807 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:215 | 972 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:242 |
808 | msgid "Hide public links" | 973 | msgid "Hide public links" |
809 | msgstr "Cacher les liens publics" | 974 | msgstr "Cacher les liens publics" |
810 | 975 | ||
811 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:216 | 976 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:243 |
812 | msgid "Do not show any links if the user is not logged in" | 977 | msgid "Do not show any links if the user is not logged in" |
813 | msgstr "N'afficher aucun lien sans être connecté" | 978 | msgstr "N'afficher aucun lien sans être connecté" |
814 | 979 | ||
815 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231 | 980 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:258 |
816 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150 | 981 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:149 |
817 | msgid "Check updates" | 982 | msgid "Check updates" |
818 | msgstr "Vérifier les mises à jour" | 983 | msgstr "Vérifier les mises à jour" |
819 | 984 | ||
820 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232 | 985 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:259 |
821 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152 | 986 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151 |
822 | msgid "Notify me when a new release is ready" | 987 | msgid "Notify me when a new release is ready" |
823 | msgstr "Me notifier lorsqu'une nouvelle version est disponible" | 988 | msgstr "Me notifier lorsqu'une nouvelle version est disponible" |
824 | 989 | ||
825 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247 | 990 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274 |
826 | msgid "Automatically retrieve description for new bookmarks" | 991 | msgid "Automatically retrieve description for new bookmarks" |
827 | msgstr "Récupérer automatiquement la description" | 992 | msgstr "Récupérer automatiquement la description" |
828 | 993 | ||
829 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248 | 994 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:275 |
830 | msgid "Shaarli will try to retrieve the description from meta HTML headers" | 995 | msgid "Shaarli will try to retrieve the description from meta HTML headers" |
831 | msgstr "" | 996 | msgstr "" |
832 | "Shaarli essaiera de récupérer la description depuis les balises HTML meta " | 997 | "Shaarli essaiera de récupérer la description depuis les balises HTML meta " |
833 | "dans les entêtes" | 998 | "dans les entêtes" |
834 | 999 | ||
835 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263 | 1000 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:290 |
836 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | 1001 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168 |
837 | msgid "Enable REST API" | 1002 | msgid "Enable REST API" |
838 | msgstr "Activer l'API REST" | 1003 | msgstr "Activer l'API REST" |
839 | 1004 | ||
840 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:264 | 1005 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:291 |
841 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170 | 1006 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 |
842 | msgid "Allow third party software to use Shaarli such as mobile application" | 1007 | msgid "Allow third party software to use Shaarli such as mobile application" |
843 | msgstr "" | 1008 | msgstr "" |
844 | "Permet aux applications tierces d'utiliser Shaarli, par exemple les " | 1009 | "Permet aux applications tierces d'utiliser Shaarli, par exemple les " |
845 | "applications mobiles" | 1010 | "applications mobiles" |
846 | 1011 | ||
847 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:279 | 1012 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:306 |
848 | msgid "API secret" | 1013 | msgid "API secret" |
849 | msgstr "Clé d'API secrète" | 1014 | msgstr "Clé d'API secrète" |
850 | 1015 | ||
851 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:293 | 1016 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320 |
852 | msgid "Enable thumbnails" | 1017 | msgid "Enable thumbnails" |
853 | msgstr "Activer les miniatures" | 1018 | msgstr "Activer les miniatures" |
854 | 1019 | ||
855 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:301 | 1020 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:324 |
1021 | msgid "You need to enable the extension <code>php-gd</code> to use thumbnails." | ||
1022 | msgstr "" | ||
1023 | "Vous devez activer l'extension <code>php-gd</code> pour utiliser les " | ||
1024 | "miniatures." | ||
1025 | |||
1026 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328 | ||
856 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 | 1027 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 |
857 | msgid "Synchronize thumbnails" | 1028 | msgid "Synchronize thumbnails" |
858 | msgstr "Synchroniser les miniatures" | 1029 | msgstr "Synchroniser les miniatures" |
859 | 1030 | ||
860 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328 | 1031 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339 |
861 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 | 1032 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 |
1033 | msgid "All" | ||
1034 | msgstr "Tous" | ||
1035 | |||
1036 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343 | ||
1037 | msgid "Only common media hosts" | ||
1038 | msgstr "Seulement les hébergeurs de média connus" | ||
1039 | |||
1040 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347 | ||
1041 | msgid "None" | ||
1042 | msgstr "Aucune" | ||
1043 | |||
1044 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:355 | ||
1045 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 | ||
862 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 | 1046 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 |
863 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 | 1047 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 |
864 | msgid "Save" | 1048 | msgid "Save" |
@@ -884,27 +1068,27 @@ msgstr "Tous les liens d'un jour sur une page." | |||
884 | msgid "Next day" | 1068 | msgid "Next day" |
885 | msgstr "Jour suivant" | 1069 | msgstr "Jour suivant" |
886 | 1070 | ||
887 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 1071 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 |
888 | msgid "Edit Shaare" | 1072 | msgid "Edit Shaare" |
889 | msgstr "Modifier le Shaare" | 1073 | msgstr "Modifier le Shaare" |
890 | 1074 | ||
891 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 1075 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 |
892 | msgid "New Shaare" | 1076 | msgid "New Shaare" |
893 | msgstr "Nouveau Shaare" | 1077 | msgstr "Nouveau Shaare" |
894 | 1078 | ||
895 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | 1079 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 |
896 | msgid "Created:" | 1080 | msgid "Created:" |
897 | msgstr "Création :" | 1081 | msgstr "Création :" |
898 | 1082 | ||
899 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | 1083 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 |
900 | msgid "URL" | 1084 | msgid "URL" |
901 | msgstr "URL" | 1085 | msgstr "URL" |
902 | 1086 | ||
903 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32 | 1087 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 |
904 | msgid "Title" | 1088 | msgid "Title" |
905 | msgstr "Titre" | 1089 | msgstr "Titre" |
906 | 1090 | ||
907 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38 | 1091 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 |
908 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | 1092 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 |
909 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 | 1093 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 |
910 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 | 1094 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 |
@@ -912,17 +1096,29 @@ msgstr "Titre" | |||
912 | msgid "Description" | 1096 | msgid "Description" |
913 | msgstr "Description" | 1097 | msgstr "Description" |
914 | 1098 | ||
915 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | 1099 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 |
916 | msgid "Tags" | 1100 | msgid "Tags" |
917 | msgstr "Tags" | 1101 | msgstr "Tags" |
918 | 1102 | ||
919 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57 | 1103 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60 |
920 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 1104 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 |
921 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167 | 1105 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 |
922 | msgid "Private" | 1106 | msgid "Private" |
923 | msgstr "Privé" | 1107 | msgstr "Privé" |
924 | 1108 | ||
925 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 | 1109 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66 |
1110 | msgid "Description will be rendered with" | ||
1111 | msgstr "La description sera générée avec" | ||
1112 | |||
1113 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68 | ||
1114 | msgid "Markdown syntax documentation" | ||
1115 | msgstr "Documentation sur la syntaxe Markdown" | ||
1116 | |||
1117 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69 | ||
1118 | msgid "Markdown syntax" | ||
1119 | msgstr "la syntaxe Markdown" | ||
1120 | |||
1121 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 | ||
926 | msgid "Apply Changes" | 1122 | msgid "Apply Changes" |
927 | msgstr "Appliquer les changements" | 1123 | msgstr "Appliquer les changements" |
928 | 1124 | ||
@@ -930,19 +1126,19 @@ msgstr "Appliquer les changements" | |||
930 | msgid "Export Database" | 1126 | msgid "Export Database" |
931 | msgstr "Exporter les données" | 1127 | msgstr "Exporter les données" |
932 | 1128 | ||
933 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | 1129 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 |
934 | msgid "Selection" | 1130 | msgid "Selection" |
935 | msgstr "Choisir" | 1131 | msgstr "Choisir" |
936 | 1132 | ||
937 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | 1133 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40 |
938 | msgid "Public" | 1134 | msgid "Public" |
939 | msgstr "Publics" | 1135 | msgstr "Publics" |
940 | 1136 | ||
941 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52 | 1137 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51 |
942 | msgid "Prepend note permalinks with this Shaarli instance's URL" | 1138 | msgid "Prepend note permalinks with this Shaarli instance's URL" |
943 | msgstr "Préfixer les liens de note avec l'URL de l'instance de Shaarli" | 1139 | msgstr "Préfixer les liens de note avec l'URL de l'instance de Shaarli" |
944 | 1140 | ||
945 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53 | 1141 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52 |
946 | msgid "Useful to import bookmarks in a web browser" | 1142 | msgid "Useful to import bookmarks in a web browser" |
947 | msgstr "Utile pour importer les marques-pages dans un navigateur" | 1143 | msgstr "Utile pour importer les marques-pages dans un navigateur" |
948 | 1144 | ||
@@ -993,29 +1189,29 @@ msgstr "" | |||
993 | "Il semblerait que ça soit la première fois que vous lancez Shaarli. Merci de " | 1189 | "Il semblerait que ça soit la première fois que vous lancez Shaarli. Merci de " |
994 | "le configurer." | 1190 | "le configurer." |
995 | 1191 | ||
996 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33 | 1192 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32 |
997 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 | 1193 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 |
998 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:165 | 1194 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167 |
999 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:165 | 1195 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:167 |
1000 | msgid "Username" | 1196 | msgid "Username" |
1001 | msgstr "Nom d'utilisateur" | 1197 | msgstr "Nom d'utilisateur" |
1002 | 1198 | ||
1003 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | 1199 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 |
1004 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | 1200 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20 |
1005 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166 | 1201 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168 |
1006 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:166 | 1202 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:168 |
1007 | msgid "Password" | 1203 | msgid "Password" |
1008 | msgstr "Mot de passe" | 1204 | msgstr "Mot de passe" |
1009 | 1205 | ||
1010 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63 | 1206 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:62 |
1011 | msgid "Shaarli title" | 1207 | msgid "Shaarli title" |
1012 | msgstr "Titre du Shaarli" | 1208 | msgstr "Titre du Shaarli" |
1013 | 1209 | ||
1014 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69 | 1210 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68 |
1015 | msgid "My links" | 1211 | msgid "My links" |
1016 | msgstr "Mes liens" | 1212 | msgstr "Mes liens" |
1017 | 1213 | ||
1018 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182 | 1214 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181 |
1019 | msgid "Install" | 1215 | msgid "Install" |
1020 | msgstr "Installer" | 1216 | msgstr "Installer" |
1021 | 1217 | ||
@@ -1034,21 +1230,31 @@ msgstr[0] "lien privé" | |||
1034 | msgstr[1] "liens privés" | 1230 | msgstr[1] "liens privés" |
1035 | 1231 | ||
1036 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 | 1232 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 |
1037 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121 | 1233 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123 |
1038 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:121 | 1234 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:123 |
1039 | msgid "Search text" | 1235 | msgid "Search text" |
1040 | msgstr "Recherche texte" | 1236 | msgstr "Recherche texte" |
1041 | 1237 | ||
1042 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37 | 1238 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37 |
1043 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:128 | 1239 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130 |
1044 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:128 | 1240 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:130 |
1045 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 1241 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 |
1046 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64 | 1242 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 |
1047 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 1243 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 |
1048 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | 1244 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 |
1049 | msgid "Filter by tag" | 1245 | msgid "Filter by tag" |
1050 | msgstr "Filtrer par tag" | 1246 | msgstr "Filtrer par tag" |
1051 | 1247 | ||
1248 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
1249 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87 | ||
1250 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 | ||
1251 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:87 | ||
1252 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:139 | ||
1253 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
1254 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45 | ||
1255 | msgid "Search" | ||
1256 | msgstr "Rechercher" | ||
1257 | |||
1052 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110 | 1258 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110 |
1053 | msgid "Nothing found." | 1259 | msgid "Nothing found." |
1054 | msgstr "Aucun résultat." | 1260 | msgstr "Aucun résultat." |
@@ -1069,60 +1275,61 @@ msgid "tagged" | |||
1069 | msgstr "taggé" | 1275 | msgstr "taggé" |
1070 | 1276 | ||
1071 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:133 | 1277 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:133 |
1278 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 | ||
1072 | msgid "Remove tag" | 1279 | msgid "Remove tag" |
1073 | msgstr "Retirer le tag" | 1280 | msgstr "Retirer le tag" |
1074 | 1281 | ||
1075 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:142 | 1282 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144 |
1076 | msgid "with status" | 1283 | msgid "with status" |
1077 | msgstr "avec le statut" | 1284 | msgstr "avec le statut" |
1078 | 1285 | ||
1079 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153 | 1286 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155 |
1080 | msgid "without any tag" | 1287 | msgid "without any tag" |
1081 | msgstr "sans tag" | 1288 | msgstr "sans tag" |
1082 | 1289 | ||
1083 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 | 1290 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175 |
1084 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | 1291 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 |
1085 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43 | 1292 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43 |
1086 | msgid "Fold" | 1293 | msgid "Fold" |
1087 | msgstr "Replier" | 1294 | msgstr "Replier" |
1088 | 1295 | ||
1089 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175 | 1296 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:177 |
1090 | msgid "Edited: " | 1297 | msgid "Edited: " |
1091 | msgstr "Modifié : " | 1298 | msgstr "Modifié : " |
1092 | 1299 | ||
1093 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179 | 1300 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181 |
1094 | msgid "permalink" | 1301 | msgid "permalink" |
1095 | msgstr "permalien" | 1302 | msgstr "permalien" |
1096 | 1303 | ||
1097 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181 | 1304 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183 |
1098 | msgid "Add tag" | 1305 | msgid "Add tag" |
1099 | msgstr "Ajouter un tag" | 1306 | msgstr "Ajouter un tag" |
1100 | 1307 | ||
1101 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183 | 1308 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:185 |
1102 | msgid "Toggle sticky" | 1309 | msgid "Toggle sticky" |
1103 | msgstr "Changer statut épinglé" | 1310 | msgstr "Changer statut épinglé" |
1104 | 1311 | ||
1105 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:185 | 1312 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:187 |
1106 | msgid "Sticky" | 1313 | msgid "Sticky" |
1107 | msgstr "Épinglé" | 1314 | msgstr "Épinglé" |
1108 | 1315 | ||
1109 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7 | 1316 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5 |
1110 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:7 | 1317 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:5 |
1111 | msgid "Filters" | 1318 | msgid "Filters" |
1112 | msgstr "Filtres" | 1319 | msgstr "Filtres" |
1113 | 1320 | ||
1114 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12 | 1321 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:10 |
1115 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:12 | 1322 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:10 |
1116 | msgid "Only display private links" | 1323 | msgid "Only display private links" |
1117 | msgstr "Afficher uniquement les liens privés" | 1324 | msgstr "Afficher uniquement les liens privés" |
1118 | 1325 | ||
1119 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | 1326 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 |
1120 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:15 | 1327 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:13 |
1121 | msgid "Only display public links" | 1328 | msgid "Only display public links" |
1122 | msgstr "Afficher uniquement les liens publics" | 1329 | msgstr "Afficher uniquement les liens publics" |
1123 | 1330 | ||
1124 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20 | 1331 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 |
1125 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:20 | 1332 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:18 |
1126 | msgid "Filter untagged links" | 1333 | msgid "Filter untagged links" |
1127 | msgstr "Filtrer par liens privés" | 1334 | msgstr "Filtrer par liens privés" |
1128 | 1335 | ||
@@ -1131,30 +1338,23 @@ msgstr "Filtrer par liens privés" | |||
1131 | msgid "Select all" | 1338 | msgid "Select all" |
1132 | msgstr "Tout sélectionner" | 1339 | msgstr "Tout sélectionner" |
1133 | 1340 | ||
1134 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:27 | 1341 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 |
1135 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79 | 1342 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:89 |
1136 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:27 | 1343 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:29 |
1137 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:79 | 1344 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:89 |
1138 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | 1345 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 |
1139 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44 | 1346 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44 |
1140 | msgid "Fold all" | 1347 | msgid "Fold all" |
1141 | msgstr "Replier tout" | 1348 | msgstr "Replier tout" |
1142 | 1349 | ||
1143 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 | 1350 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76 |
1144 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:72 | 1351 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:76 |
1145 | msgid "Links per page" | 1352 | msgid "Links per page" |
1146 | msgstr "Liens par page" | 1353 | msgstr "Liens par page" |
1147 | 1354 | ||
1148 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | 1355 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 |
1149 | msgid "" | 1356 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 |
1150 | "You have been banned after too many failed login attempts. Try again later." | 1357 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:171 |
1151 | msgstr "" | ||
1152 | "Vous avez été banni après trop d'échecs d'authentification. Merci de " | ||
1153 | "réessayer plus tard." | ||
1154 | |||
1155 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
1156 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | ||
1157 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:169 | ||
1158 | msgid "Remember me" | 1358 | msgid "Remember me" |
1159 | msgstr "Rester connecté" | 1359 | msgstr "Rester connecté" |
1160 | 1360 | ||
@@ -1185,62 +1385,64 @@ msgstr "Déplier tout" | |||
1185 | msgid "Are you sure you want to delete this link?" | 1385 | msgid "Are you sure you want to delete this link?" |
1186 | msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?" | 1386 | msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?" |
1187 | 1387 | ||
1188 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 | 1388 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:11 |
1189 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:90 | 1389 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:11 |
1190 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:65 | 1390 | msgid "Menu" |
1191 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:90 | 1391 | msgstr "Menu" |
1392 | |||
1393 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38 | ||
1394 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:38 | ||
1395 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1396 | msgid "Tag cloud" | ||
1397 | msgstr "Nuage de tags" | ||
1398 | |||
1399 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:67 | ||
1400 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92 | ||
1401 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:67 | ||
1402 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:92 | ||
1192 | msgid "RSS Feed" | 1403 | msgid "RSS Feed" |
1193 | msgstr "Flux RSS" | 1404 | msgstr "Flux RSS" |
1194 | 1405 | ||
1195 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:70 | 1406 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 |
1196 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106 | 1407 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108 |
1197 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:70 | 1408 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:72 |
1198 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:106 | 1409 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:108 |
1199 | msgid "Logout" | 1410 | msgid "Logout" |
1200 | msgstr "Déconnexion" | 1411 | msgstr "Déconnexion" |
1201 | 1412 | ||
1202 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150 | 1413 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152 |
1203 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:150 | 1414 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:152 |
1204 | msgid "Set public" | 1415 | msgid "Set public" |
1205 | msgstr "Rendre public" | 1416 | msgstr "Rendre public" |
1206 | 1417 | ||
1207 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155 | 1418 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:157 |
1208 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:155 | 1419 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:157 |
1209 | msgid "Set private" | 1420 | msgid "Set private" |
1210 | msgstr "Rendre privé" | 1421 | msgstr "Rendre privé" |
1211 | 1422 | ||
1212 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:187 | 1423 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189 |
1213 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:187 | 1424 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:189 |
1214 | msgid "is available" | 1425 | msgid "is available" |
1215 | msgstr "est disponible" | 1426 | msgstr "est disponible" |
1216 | 1427 | ||
1217 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:194 | 1428 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:196 |
1218 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:194 | 1429 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:196 |
1219 | msgid "Error" | 1430 | msgid "Error" |
1220 | msgstr "Erreur" | 1431 | msgstr "Erreur" |
1221 | 1432 | ||
1222 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | 1433 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 |
1223 | msgid "Picture wall unavailable (thumbnails are disabled)." | 1434 | msgid "There is no cached thumbnail." |
1224 | msgstr "" | 1435 | msgstr "Il n'y a aucune miniature dans le cache." |
1225 | "Le mur d'images n'est pas disponible (les miniatures sont désactivées)." | ||
1226 | 1436 | ||
1227 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | 1437 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17 |
1228 | #, fuzzy | 1438 | msgid "Try to synchronize them." |
1229 | #| msgid "" | 1439 | msgstr "Essayer de les synchroniser." |
1230 | #| "You don't have any cached thumbnail. Try to <a href=\"?do=thumbs_update" | ||
1231 | #| "\">synchronize them</a>." | ||
1232 | msgid "" | ||
1233 | "There is no cached thumbnail. Try to <a href=\"?do=thumbs_update" | ||
1234 | "\">synchronize them</a>." | ||
1235 | msgstr "" | ||
1236 | "Il n'y a aucune miniature en cache. Essayer de <a href=\"?do=thumbs_update" | ||
1237 | "\">les synchroniser</a>." | ||
1238 | 1440 | ||
1239 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 1441 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 |
1240 | msgid "Picture Wall" | 1442 | msgid "Picture Wall" |
1241 | msgstr "Mur d'images" | 1443 | msgstr "Mur d'images" |
1242 | 1444 | ||
1243 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | 1445 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 |
1244 | msgid "pics" | 1446 | msgid "pics" |
1245 | msgstr "images" | 1447 | msgstr "images" |
1246 | 1448 | ||
@@ -1249,6 +1451,11 @@ msgid "You need to enable Javascript to change plugin loading order." | |||
1249 | msgstr "" | 1451 | msgstr "" |
1250 | "Vous devez activer Javascript pour pouvoir modifier l'ordre des extensions." | 1452 | "Vous devez activer Javascript pour pouvoir modifier l'ordre des extensions." |
1251 | 1453 | ||
1454 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | ||
1455 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
1456 | msgid "Plugin administration" | ||
1457 | msgstr "Administration des plugins" | ||
1458 | |||
1252 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | 1459 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 |
1253 | msgid "Enabled Plugins" | 1460 | msgid "Enabled Plugins" |
1254 | msgstr "Extensions activées" | 1461 | msgstr "Extensions activées" |
@@ -1314,6 +1521,14 @@ msgstr "tags" | |||
1314 | msgid "List all links with those tags" | 1521 | msgid "List all links with those tags" |
1315 | msgstr "Lister tous les liens avec ces tags" | 1522 | msgstr "Lister tous les liens avec ces tags" |
1316 | 1523 | ||
1524 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1525 | msgid "Tag list" | ||
1526 | msgstr "Liste des tags" | ||
1527 | |||
1528 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68 | ||
1529 | msgid "Rename tag" | ||
1530 | msgstr "Renommer le tag" | ||
1531 | |||
1317 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3 | 1532 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3 |
1318 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3 | 1533 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3 |
1319 | msgid "Sort by:" | 1534 | msgid "Sort by:" |
@@ -1359,32 +1574,24 @@ msgid "Rename or delete a tag in all links" | |||
1359 | msgstr "Renommer ou supprimer un tag dans tous les liens" | 1574 | msgstr "Renommer ou supprimer un tag dans tous les liens" |
1360 | 1575 | ||
1361 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | 1576 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 |
1362 | #, fuzzy | ||
1363 | #| msgid "" | ||
1364 | #| "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, " | ||
1365 | #| "delicious…)" | ||
1366 | msgid "" | 1577 | msgid "" |
1367 | "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, " | 1578 | "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, " |
1368 | "delicious...)" | 1579 | "delicious...)" |
1369 | msgstr "" | 1580 | msgstr "" |
1370 | "Importer des marques pages au format Netscape HTML (comme exportés depuis " | 1581 | "Importer des marques pages au format Netscape HTML (comme exportés depuis " |
1371 | "Firefox, Chrome, Opera, delicious…)" | 1582 | "Firefox, Chrome, Opera, delicious...)" |
1372 | 1583 | ||
1373 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | 1584 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 |
1374 | msgid "Import links" | 1585 | msgid "Import links" |
1375 | msgstr "Importer des liens" | 1586 | msgstr "Importer des liens" |
1376 | 1587 | ||
1377 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 | 1588 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 |
1378 | #, fuzzy | ||
1379 | #| msgid "" | ||
1380 | #| "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, " | ||
1381 | #| "Opera, delicious…)" | ||
1382 | msgid "" | 1589 | msgid "" |
1383 | "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, " | 1590 | "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, " |
1384 | "Opera, delicious...)" | 1591 | "Opera, delicious...)" |
1385 | msgstr "" | 1592 | msgstr "" |
1386 | "Exporter les marques pages au format Netscape HTML (comme exportés depuis " | 1593 | "Exporter les marques pages au format Netscape HTML (comme exportés depuis " |
1387 | "Firefox, Chrome, Opera, delicious…)" | 1594 | "Firefox, Chrome, Opera, delicious...)" |
1388 | 1595 | ||
1389 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | 1596 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 |
1390 | msgid "Export database" | 1597 | msgid "Export database" |
@@ -1457,6 +1664,68 @@ msgstr "" | |||
1457 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " | 1664 | "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " |
1458 | "Ajouter aux favoris »" | 1665 | "Ajouter aux favoris »" |
1459 | 1666 | ||
1667 | #, fuzzy | ||
1668 | #~| msgid "Selection" | ||
1669 | #~ msgid ".ui-selecting" | ||
1670 | #~ msgstr "Choisir" | ||
1671 | |||
1672 | #, fuzzy | ||
1673 | #~| msgid "Documentation" | ||
1674 | #~ msgid "document" | ||
1675 | #~ msgstr "Documentation" | ||
1676 | |||
1677 | #~ msgid "The page you are trying to reach does not exist or has been deleted." | ||
1678 | #~ msgstr "" | ||
1679 | #~ "La page que vous essayez de consulter n'existe pas ou a été supprimée." | ||
1680 | |||
1681 | #~ msgid "404 Not Found" | ||
1682 | #~ msgstr "404 Introuvable" | ||
1683 | |||
1684 | #~ msgid "Updates file path is not set, can't write updates." | ||
1685 | #~ msgstr "" | ||
1686 | #~ "Le chemin vers le fichier de mise à jour n'est pas défini, impossible " | ||
1687 | #~ "d'écrire les mises à jour." | ||
1688 | |||
1689 | #~ msgid "Unable to write updates in " | ||
1690 | #~ msgstr "Impossible d'écrire les mises à jour dans " | ||
1691 | |||
1692 | #~ msgid "I said: NO. You are banned for the moment. Go away." | ||
1693 | #~ msgstr "NON. Vous êtes banni pour le moment. Revenez plus tard." | ||
1694 | |||
1695 | #~ msgid "Click to try again." | ||
1696 | #~ msgstr "Cliquer ici pour réessayer." | ||
1697 | |||
1698 | #~ msgid "" | ||
1699 | #~ "Render shaare description with Markdown syntax.<br><strong>Warning</" | ||
1700 | #~ "strong>:\n" | ||
1701 | #~ "If your shaared descriptions contained HTML tags before enabling the " | ||
1702 | #~ "markdown plugin,\n" | ||
1703 | #~ "enabling it might break your page.\n" | ||
1704 | #~ "See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
1705 | #~ "markdown#html-rendering\">README</a>." | ||
1706 | #~ msgstr "" | ||
1707 | #~ "Utilise la syntaxe Markdown pour la description des liens." | ||
1708 | #~ "<br><strong>Attention</strong> :\n" | ||
1709 | #~ "Si vous aviez des descriptions contenant du HTML avant d'activer cette " | ||
1710 | #~ "extension,\n" | ||
1711 | #~ "l'activer pourrait déformer vos pages.\n" | ||
1712 | #~ "Voir le <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
1713 | #~ "markdown#html-rendering\">README</a>." | ||
1714 | |||
1715 | #~ msgid "Synchonize thumbnails" | ||
1716 | #~ msgstr "Synchroniser les miniatures" | ||
1717 | |||
1718 | #, fuzzy | ||
1719 | #~| msgid "" | ||
1720 | #~| "You don't have any cached thumbnail. Try to <a href=\"?do=thumbs_update" | ||
1721 | #~| "\">synchronize them</a>." | ||
1722 | #~ msgid "" | ||
1723 | #~ "There is no cached thumbnail. Try to <a href=\"?do=thumbs_update" | ||
1724 | #~ "\">synchronize them</a>." | ||
1725 | #~ msgstr "" | ||
1726 | #~ "Il n'y a aucune miniature en cache. Essayer de <a href=\"?do=thumbs_update" | ||
1727 | #~ "\">les synchroniser</a>." | ||
1728 | |||
1460 | #~ msgid "" | 1729 | #~ msgid "" |
1461 | #~ "You need to browse your Shaarli over <strong>HTTPS</strong> to use this " | 1730 | #~ "You need to browse your Shaarli over <strong>HTTPS</strong> to use this " |
1462 | #~ "functionality." | 1731 | #~ "functionality." |
diff --git a/inc/languages/jp/LC_MESSAGES/shaarli.po b/inc/languages/jp/LC_MESSAGES/shaarli.po new file mode 100644 index 00000000..b420bb51 --- /dev/null +++ b/inc/languages/jp/LC_MESSAGES/shaarli.po | |||
@@ -0,0 +1,1293 @@ | |||
1 | msgid "" | ||
2 | msgstr "" | ||
3 | "Project-Id-Version: Shaarli\n" | ||
4 | "Report-Msgid-Bugs-To: \n" | ||
5 | "POT-Creation-Date: 2020-02-11 09:31+0900\n" | ||
6 | "PO-Revision-Date: 2020-02-11 10:54+0900\n" | ||
7 | "Last-Translator: yude <yudesleepy@gmail.com>\n" | ||
8 | "Language-Team: Shaarli\n" | ||
9 | "Language: ja\n" | ||
10 | "MIME-Version: 1.0\n" | ||
11 | "Content-Type: text/plain; charset=UTF-8\n" | ||
12 | "Content-Transfer-Encoding: 8bit\n" | ||
13 | "X-Generator: Poedit 2.3\n" | ||
14 | "X-Poedit-Basepath: ../../../..\n" | ||
15 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" | ||
16 | "X-Poedit-SourceCharset: UTF-8\n" | ||
17 | "X-Poedit-KeywordsList: t:1,2;t\n" | ||
18 | "X-Poedit-SearchPath-0: .\n" | ||
19 | "X-Poedit-SearchPathExcluded-0: node_modules\n" | ||
20 | "X-Poedit-SearchPathExcluded-1: vendor\n" | ||
21 | |||
22 | #: application/ApplicationUtils.php:153 | ||
23 | #, php-format | ||
24 | msgid "" | ||
25 | "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " | ||
26 | "cannot run. Your PHP version has known security vulnerabilities and should " | ||
27 | "be updated as soon as possible." | ||
28 | msgstr "" | ||
29 | "使用ã—ã¦ã„ã‚‹ PHP ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒå¤ã™ãŽã¾ã™! Shaarli ã®å®Ÿè¡Œã«ã¯æœ€ä½Žã§ã‚‚ PHP %s " | ||
30 | "ãŒå¿…è¦ã§ã™ã€‚ ç¾åœ¨ä½¿ç”¨ã—ã¦ã„ã‚‹ PHP ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ã¯è„†å¼±æ€§ãŒã‚りã€ã§ãã‚‹ã ã‘速" | ||
31 | "ã‚„ã‹ã«ã‚¢ãƒƒãƒ—デートã™ã‚‹ã¹ãã§ã™ã€‚" | ||
32 | |||
33 | #: application/ApplicationUtils.php:183 application/ApplicationUtils.php:195 | ||
34 | msgid "directory is not readable" | ||
35 | msgstr "ディレクトリをèªã¿è¾¼ã‚ã¾ã›ã‚“" | ||
36 | |||
37 | #: application/ApplicationUtils.php:198 | ||
38 | msgid "directory is not writable" | ||
39 | msgstr "ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«æ›¸ãè¾¼ã‚ã¾ã›ã‚“" | ||
40 | |||
41 | #: application/ApplicationUtils.php:216 | ||
42 | msgid "file is not readable" | ||
43 | msgstr "ファイルをèªã¿å–る権é™ãŒã‚りã¾ã›ã‚“" | ||
44 | |||
45 | #: application/ApplicationUtils.php:219 | ||
46 | msgid "file is not writable" | ||
47 | msgstr "ファイルを書ã込む権é™ãŒã‚りã¾ã›ã‚“" | ||
48 | |||
49 | #: application/Cache.php:16 | ||
50 | #, php-format | ||
51 | msgid "Cannot purge %s: no directory" | ||
52 | msgstr "%s を削除ã§ãã¾ã›ã‚“: ディレクトリãŒå˜åœ¨ã—ã¾ã›ã‚“" | ||
53 | |||
54 | #: application/FeedBuilder.php:151 | ||
55 | msgid "Direct link" | ||
56 | msgstr "ダイレクトリンク" | ||
57 | |||
58 | #: application/FeedBuilder.php:153 | ||
59 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 | ||
60 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:178 | ||
61 | msgid "Permalink" | ||
62 | msgstr "パーマリンク" | ||
63 | |||
64 | #: application/History.php:174 | ||
65 | msgid "History file isn't readable or writable" | ||
66 | msgstr "å±¥æ´ãƒ•ァイルをèªã¿è¾¼ã‚€ã€ã¾ãŸã¯æ›¸ã込むãŸã‚ã®æ¨©é™ãŒã‚りã¾ã›ã‚“" | ||
67 | |||
68 | #: application/History.php:185 | ||
69 | msgid "Could not parse history file" | ||
70 | msgstr "å±¥æ´ãƒ•ァイルをæ£å¸¸ã«å¾©å…ƒã§ãã¾ã›ã‚“ã§ã—ãŸ" | ||
71 | |||
72 | #: application/Languages.php:177 | ||
73 | msgid "Automatic" | ||
74 | msgstr "自動" | ||
75 | |||
76 | #: application/Languages.php:178 | ||
77 | msgid "English" | ||
78 | msgstr "英語" | ||
79 | |||
80 | #: application/Languages.php:179 | ||
81 | msgid "French" | ||
82 | msgstr "フランス語" | ||
83 | |||
84 | #: application/Languages.php:180 | ||
85 | msgid "German" | ||
86 | msgstr "ドイツ語" | ||
87 | |||
88 | #: application/LinkDB.php:136 | ||
89 | msgid "You are not authorized to add a link." | ||
90 | msgstr "ãƒªãƒ³ã‚¯ã‚’è¿½åŠ ã™ã‚‹ã«ã¯ã€ãƒã‚°ã‚¤ãƒ³ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" | ||
91 | |||
92 | #: application/LinkDB.php:139 | ||
93 | msgid "Internal Error: A link should always have an id and URL." | ||
94 | msgstr "エラー: リンクã«ã¯IDã¨URLを登録ã—ãªã‘れã°ãªã‚Šã¾ã›ã‚“。" | ||
95 | |||
96 | #: application/LinkDB.php:142 | ||
97 | msgid "You must specify an integer as a key." | ||
98 | msgstr "æ£å¸¸ãªã‚ーã®å€¤ã§ã¯ã‚りã¾ã›ã‚“。" | ||
99 | |||
100 | #: application/LinkDB.php:145 | ||
101 | msgid "Array offset and link ID must be equal." | ||
102 | msgstr "Array オフセットã¨ãƒªãƒ³ã‚¯ã®IDã¯åŒã˜ã§ãªã‘れã°ãªã‚Šã¾ã›ã‚“。" | ||
103 | |||
104 | #: application/LinkDB.php:251 | ||
105 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
106 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | ||
107 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14 | ||
108 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48 | ||
109 | msgid "" | ||
110 | "The personal, minimalist, super-fast, database free, bookmarking service" | ||
111 | msgstr "" | ||
112 | "個人å‘ã‘ã®ã€ãƒŸãƒ‹ãƒžãƒ ã§é«˜é€Ÿã§ã‹ã¤ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã®ã„らãªã„ブックマークサービス" | ||
113 | |||
114 | #: application/LinkDB.php:253 | ||
115 | msgid "" | ||
116 | "Welcome to Shaarli! This is your first public bookmark. To edit or delete " | ||
117 | "me, you must first login.\n" | ||
118 | "\n" | ||
119 | "To learn how to use Shaarli, consult the link \"Documentation\" at the " | ||
120 | "bottom of this page.\n" | ||
121 | "\n" | ||
122 | "You use the community supported version of the original Shaarli project, by " | ||
123 | "Sebastien Sauvage." | ||
124 | msgstr "" | ||
125 | "Shaarli ã¸ã‚ˆã†ã“ãï¼ ã“れã¯ã‚ãªãŸã®æœ€åˆã®å…¬é–‹ãƒ–ックマークã§ã™ã€‚ã“れを編集ã—ãŸ" | ||
126 | "り削除ã—ãŸã‚Šã™ã‚‹ã«ã¯ã€ãƒã‚°ã‚¤ãƒ³ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚\n" | ||
127 | "\n" | ||
128 | "Shaarli ã®ä½¿ã„方を知るã«ã¯ã€ã“ã®ãƒšãƒ¼ã‚¸ã®ä¸‹ã«ã‚る「ドã‚ュメントã€ã®ãƒªãƒ³ã‚¯ã‚’é–‹" | ||
129 | "ã„ã¦ãã ã•ã„。\n" | ||
130 | "\n" | ||
131 | "ã‚ãªãŸã¯ Sebastien Sauvage ã«ã‚ˆã‚‹ã€ã‚³ãƒŸãƒ¥ãƒ‹ãƒ†ã‚£ãƒ¼ã‚µãƒãƒ¼ãƒˆã®ã‚ã‚‹ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ã‚ª" | ||
132 | "リジナルã®Shaarli プãƒã‚¸ã‚§ã‚¯ãƒˆã‚’使用ã—ã¦ã„ã¾ã™ã€‚" | ||
133 | |||
134 | #: application/LinkDB.php:267 | ||
135 | msgid "My secret stuff... - Pastebin.com" | ||
136 | msgstr "ã‚ãŸã—ã®ã²ðŸ’—ã¿ðŸ’—ã¤ðŸ’— - Pastebin.com" | ||
137 | |||
138 | #: application/LinkDB.php:269 | ||
139 | msgid "Shhhh! I'm a private link only YOU can see. You can delete me too." | ||
140 | msgstr "" | ||
141 | "ã‚·ãƒ¼ãƒƒï¼ ã“れã¯ã‚ãªãŸã—ã‹è¦‹ã‚‰ã‚Œãªã„プライベートリンクã§ã™ã€‚消ã™ã“ã¨ã‚‚ã§ãã¾" | ||
142 | "ã™ã€‚" | ||
143 | |||
144 | #: application/LinkFilter.php:452 | ||
145 | msgid "The link you are trying to reach does not exist or has been deleted." | ||
146 | msgstr "é–‹ã“ã†ã¨ã—ãŸãƒªãƒ³ã‚¯ã¯å˜åœ¨ã—ãªã„ã‹ã€å‰Šé™¤ã•れã¦ã„ã¾ã™ã€‚" | ||
147 | |||
148 | #: application/NetscapeBookmarkUtils.php:35 | ||
149 | msgid "Invalid export selection:" | ||
150 | msgstr "䏿£ãªã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã®é¸æŠž:" | ||
151 | |||
152 | #: application/NetscapeBookmarkUtils.php:81 | ||
153 | #, php-format | ||
154 | msgid "File %s (%d bytes) " | ||
155 | msgstr "ファイル %s (%d ãƒã‚¤ãƒˆ) " | ||
156 | |||
157 | #: application/NetscapeBookmarkUtils.php:83 | ||
158 | msgid "has an unknown file format. Nothing was imported." | ||
159 | msgstr "ã¯ä¸æ˜Žãªãƒ•ァイル形å¼ã§ã™ã€‚インãƒãƒ¼ãƒˆã¯ä¸æ¢ã•れã¾ã—ãŸã€‚" | ||
160 | |||
161 | #: application/NetscapeBookmarkUtils.php:86 | ||
162 | #, php-format | ||
163 | msgid "" | ||
164 | "was successfully processed in %d seconds: %d links imported, %d links " | ||
165 | "overwritten, %d links skipped." | ||
166 | msgstr "" | ||
167 | "㌠%d ç§’ã§å‡¦ç†ã•れã€%d ä»¶ã®ãƒªãƒ³ã‚¯ãŒã‚¤ãƒ³ãƒãƒ¼ãƒˆã•れã€%d ä»¶ã®ãƒªãƒ³ã‚¯ãŒä¸Šæ›¸ãã•" | ||
168 | "れã€%d ä»¶ã®ãƒªãƒ³ã‚¯ãŒã‚¹ã‚ップã•れã¾ã—ãŸã€‚" | ||
169 | |||
170 | #: application/PageBuilder.php:168 | ||
171 | msgid "The page you are trying to reach does not exist or has been deleted." | ||
172 | msgstr "ã‚ãªãŸãŒé–‹ã“ã†ã¨ã—ãŸãƒšãƒ¼ã‚¸ã¯å˜åœ¨ã—ãªã„ã‹ã€å‰Šé™¤ã•れã¦ã„ã¾ã™ã€‚" | ||
173 | |||
174 | #: application/PageBuilder.php:170 | ||
175 | msgid "404 Not Found" | ||
176 | msgstr "404 ページãŒå˜åœ¨ã—ã¾ã›ã‚“" | ||
177 | |||
178 | #: application/PluginManager.php:243 | ||
179 | #, php-format | ||
180 | msgid "Plugin \"%s\" files not found." | ||
181 | msgstr "プラグイン「%sã€ã®ãƒ•ァイルãŒå˜åœ¨ã—ã¾ã›ã‚“。" | ||
182 | |||
183 | #: application/Updater.php:76 | ||
184 | msgid "Couldn't retrieve Updater class methods." | ||
185 | msgstr "アップデーターã®ã‚¯ãƒ©ã‚¹ãƒ¡ã‚¾ãƒƒãƒˆã‚’å—ä¿¡ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" | ||
186 | |||
187 | #: application/Updater.php:532 | ||
188 | msgid "An error occurred while running the update " | ||
189 | msgstr "æ›´æ–°ä¸ã«å•題ãŒç™ºç”Ÿã—ã¾ã—㟠" | ||
190 | |||
191 | #: application/Updater.php:572 | ||
192 | msgid "Updates file path is not set, can't write updates." | ||
193 | msgstr "æ›´æ–°ã™ã‚‹ãƒ•ァイルã®ãƒ‘ã‚¹ãŒæŒ‡å®šã•れã¦ã„ãªã„ãŸã‚ã€æ›´æ–°ã‚’書ãè¾¼ã‚ã¾ã›ã‚“。" | ||
194 | |||
195 | #: application/Updater.php:577 | ||
196 | msgid "Unable to write updates in " | ||
197 | msgstr "更新を次ã®é …ç›®ã«æ›¸ãè¾¼ã‚ã¾ã›ã‚“ã§ã—ãŸ: " | ||
198 | |||
199 | #: application/Utils.php:376 tests/UtilsTest.php:340 | ||
200 | msgid "Setting not set" | ||
201 | msgstr "未è¨å®š" | ||
202 | |||
203 | #: application/Utils.php:383 tests/UtilsTest.php:338 tests/UtilsTest.php:339 | ||
204 | msgid "Unlimited" | ||
205 | msgstr "無制é™" | ||
206 | |||
207 | #: application/Utils.php:386 tests/UtilsTest.php:335 tests/UtilsTest.php:336 | ||
208 | #: tests/UtilsTest.php:350 | ||
209 | msgid "B" | ||
210 | msgstr "B" | ||
211 | |||
212 | #: application/Utils.php:386 tests/UtilsTest.php:329 tests/UtilsTest.php:330 | ||
213 | #: tests/UtilsTest.php:337 | ||
214 | msgid "kiB" | ||
215 | msgstr "kiB" | ||
216 | |||
217 | #: application/Utils.php:386 tests/UtilsTest.php:331 tests/UtilsTest.php:332 | ||
218 | #: tests/UtilsTest.php:348 tests/UtilsTest.php:349 | ||
219 | msgid "MiB" | ||
220 | msgstr "MiB" | ||
221 | |||
222 | #: application/Utils.php:386 tests/UtilsTest.php:333 tests/UtilsTest.php:334 | ||
223 | msgid "GiB" | ||
224 | msgstr "GiB" | ||
225 | |||
226 | #: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:121 | ||
227 | msgid "" | ||
228 | "Shaarli could not create the config file. Please make sure Shaarli has the " | ||
229 | "right to write in the folder is it installed in." | ||
230 | msgstr "" | ||
231 | "Shaarli ã¯è¨å®šãƒ•ァイルを作æˆã§ãã¾ã›ã‚“ã§ã—ãŸã€‚Shaarli ãŒæ£ã—ã„æ¨©é™ä¸‹ã«ç½®ã‹ã‚Œ" | ||
232 | "ã¦ã„ã¦ã€ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¦ã„ã‚‹ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«æ›¸ãè¾¼ã¿ã§ãã‚‹ã“ã¨ã‚’確èªã—ã¦ãã " | ||
233 | "ã•ã„。" | ||
234 | |||
235 | #: application/config/ConfigManager.php:135 | ||
236 | msgid "Invalid setting key parameter. String expected, got: " | ||
237 | msgstr "" | ||
238 | "䏿£ãªã‚ーã®å€¤ã§ã™ã€‚æ–‡å—åˆ—ãŒæƒ³å®šã•れã¦ã„ã¾ã™ãŒã€æ¬¡ã®ã‚ˆã†ã«å…¥åŠ›ã•れã¾ã—ãŸ: " | ||
239 | |||
240 | #: application/config/exception/MissingFieldConfigException.php:21 | ||
241 | #, php-format | ||
242 | msgid "Configuration value is required for %s" | ||
243 | msgstr "%s ã«ã¯è¨å®šãŒå¿…è¦ã§ã™" | ||
244 | |||
245 | #: application/config/exception/PluginConfigOrderException.php:15 | ||
246 | msgid "An error occurred while trying to save plugins loading order." | ||
247 | msgstr "プラグインã®èªè¾¼é †ã‚’変更ã™ã‚‹éš›ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" | ||
248 | |||
249 | #: application/config/exception/UnauthorizedConfigException.php:16 | ||
250 | msgid "You are not authorized to alter config." | ||
251 | msgstr "è¨å®šã‚’変更ã™ã‚‹æ¨©é™ãŒã‚りã¾ã›ã‚“。" | ||
252 | |||
253 | #: application/exceptions/IOException.php:19 | ||
254 | msgid "Error accessing" | ||
255 | msgstr "èªè¾¼ä¸ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ" | ||
256 | |||
257 | #: index.php:142 | ||
258 | msgid "Shared links on " | ||
259 | msgstr "次ã«ãŠã„ã¦å…±æœ‰ã•れãŸãƒªãƒ³ã‚¯:" | ||
260 | |||
261 | #: index.php:164 | ||
262 | msgid "Insufficient permissions:" | ||
263 | msgstr "権é™ãŒã‚りã¾ã›ã‚“:" | ||
264 | |||
265 | #: index.php:303 | ||
266 | msgid "I said: NO. You are banned for the moment. Go away." | ||
267 | msgstr "ã‚ãªãŸã¯ã“ã®ã‚µãƒ¼ãƒãƒ¼ã‹ã‚‰BANã•れã¦ã„ã¾ã™ã€‚" | ||
268 | |||
269 | #: index.php:368 | ||
270 | msgid "Wrong login/password." | ||
271 | msgstr "䏿£ãªãƒ¦ãƒ¼ã‚¶ãƒ¼åã€ã¾ãŸã¯ãƒ‘スワードã§ã™ã€‚" | ||
272 | |||
273 | #: index.php:576 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | ||
274 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:42 | ||
275 | msgid "Daily" | ||
276 | msgstr "デイリー" | ||
277 | |||
278 | #: index.php:681 tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
279 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | ||
280 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71 | ||
281 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:95 | ||
282 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:71 | ||
283 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:95 | ||
284 | msgid "Login" | ||
285 | msgstr "ãƒã‚°ã‚¤ãƒ³" | ||
286 | |||
287 | #: index.php:722 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39 | ||
288 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:39 | ||
289 | msgid "Picture wall" | ||
290 | msgstr "ピクãƒãƒ£ã‚¦ã‚©ãƒ¼ãƒ«" | ||
291 | |||
292 | #: index.php:770 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
293 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:36 | ||
294 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
295 | msgid "Tag cloud" | ||
296 | msgstr "タグクラウド" | ||
297 | |||
298 | #: index.php:803 tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
299 | msgid "Tag list" | ||
300 | msgstr "タグ一覧" | ||
301 | |||
302 | #: index.php:1028 tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | ||
303 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:31 | ||
304 | msgid "Tools" | ||
305 | msgstr "ツール" | ||
306 | |||
307 | #: index.php:1037 | ||
308 | msgid "You are not supposed to change a password on an Open Shaarli." | ||
309 | msgstr "" | ||
310 | "公開ã•れã¦ã„ã‚‹ Shaarli ã«ãŠã„ã¦ã€ãƒ‘スワードを変更ã™ã‚‹ã“ã¨ã¯æƒ³å®šã•れã¦ã„ã¾ã›" | ||
311 | "ん。" | ||
312 | |||
313 | #: index.php:1042 index.php:1084 index.php:1160 index.php:1191 index.php:1291 | ||
314 | msgid "Wrong token." | ||
315 | msgstr "䏿£ãªãƒˆãƒ¼ã‚¯ãƒ³ã§ã™ã€‚" | ||
316 | |||
317 | #: index.php:1047 | ||
318 | msgid "The old password is not correct." | ||
319 | msgstr "å…ƒã®ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ãŒæ£ã—ãã‚りã¾ã›ã‚“。" | ||
320 | |||
321 | #: index.php:1067 | ||
322 | msgid "Your password has been changed" | ||
323 | msgstr "ã‚ãªãŸã®ãƒ‘スワードã¯å¤‰æ›´ã•れã¾ã—ãŸ" | ||
324 | |||
325 | #: index.php:1072 | ||
326 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
327 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
328 | msgid "Change password" | ||
329 | msgstr "パスワードを変更" | ||
330 | |||
331 | #: index.php:1120 | ||
332 | msgid "Configuration was saved." | ||
333 | msgstr "è¨å®šã¯ä¿å˜ã•れã¾ã—ãŸã€‚" | ||
334 | |||
335 | #: index.php:1143 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | ||
336 | msgid "Configure" | ||
337 | msgstr "è¨å®š" | ||
338 | |||
339 | #: index.php:1154 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
340 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
341 | msgid "Manage tags" | ||
342 | msgstr "ã‚¿ã‚°ã‚’è¨å®š" | ||
343 | |||
344 | #: index.php:1172 | ||
345 | #, php-format | ||
346 | msgid "The tag was removed from %d link." | ||
347 | msgid_plural "The tag was removed from %d links." | ||
348 | msgstr[0] "%d ä»¶ã®ãƒªãƒ³ã‚¯ã‹ã‚‰ã‚¿ã‚°ãŒå‰Šé™¤ã•れã¾ã—ãŸã€‚" | ||
349 | msgstr[1] "The tag was removed from %d links." | ||
350 | |||
351 | #: index.php:1173 | ||
352 | #, php-format | ||
353 | msgid "The tag was renamed in %d link." | ||
354 | msgid_plural "The tag was renamed in %d links." | ||
355 | msgstr[0] "タグ㌠%d ä»¶ã®ãƒªãƒ³ã‚¯ã«ãŠã„ã¦ã€åå‰ãŒå¤‰æ›´ã•れã¾ã—ãŸã€‚" | ||
356 | msgstr[1] "タグ㌠%d ä»¶ã®ãƒªãƒ³ã‚¯ã«ãŠã„ã¦ã€åå‰ãŒå¤‰æ›´ã•れã¾ã—ãŸã€‚" | ||
357 | |||
358 | #: index.php:1181 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 | ||
359 | msgid "Shaare a new link" | ||
360 | msgstr "æ–°ã—ã„ãƒªãƒ³ã‚¯ã‚’è¿½åŠ " | ||
361 | |||
362 | #: index.php:1351 tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
363 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170 | ||
364 | msgid "Edit" | ||
365 | msgstr "共有" | ||
366 | |||
367 | #: index.php:1351 index.php:1421 | ||
368 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
369 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | ||
370 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26 | ||
371 | msgid "Shaare" | ||
372 | msgstr "Shaare" | ||
373 | |||
374 | #: index.php:1390 | ||
375 | msgid "Note: " | ||
376 | msgstr "注: " | ||
377 | |||
378 | #: index.php:1430 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 | ||
379 | msgid "Export" | ||
380 | msgstr "エクスãƒãƒ¼ãƒˆ" | ||
381 | |||
382 | #: index.php:1492 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 | ||
383 | msgid "Import" | ||
384 | msgstr "インãƒãƒ¼ãƒˆ" | ||
385 | |||
386 | #: index.php:1502 | ||
387 | #, php-format | ||
388 | msgid "" | ||
389 | "The file you are trying to upload is probably bigger than what this " | ||
390 | "webserver can accept (%s). Please upload in smaller chunks." | ||
391 | msgstr "" | ||
392 | "ã‚ãªãŸãŒã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã—よã†ã¨ã—ã¦ã„るファイルã¯ã€ã‚µãƒ¼ãƒãƒ¼ãŒè¨±å¯ã—ã¦ã„るファイ" | ||
393 | "ルサイズ (%s) よりも大ãã„ã§ã™ã€‚ã‚‚ã†å°‘ã—å°ã•ã„ã‚‚ã®ã‚’アップãƒãƒ¼ãƒ‰ã—ã¦ãã ã•" | ||
394 | "ã„。" | ||
395 | |||
396 | #: index.php:1541 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 | ||
397 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
398 | msgid "Plugin administration" | ||
399 | msgstr "プラグイン管ç†" | ||
400 | |||
401 | #: index.php:1706 | ||
402 | msgid "Search: " | ||
403 | msgstr "検索: " | ||
404 | |||
405 | #: index.php:1933 | ||
406 | #, php-format | ||
407 | msgid "" | ||
408 | "<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " | ||
409 | "variable \"session.save_path\" is set correctly in your PHP config, and that " | ||
410 | "you have write access to it.<br>It currently points to %s.<br>On some " | ||
411 | "browsers, accessing your server via a hostname like 'localhost' or any " | ||
412 | "custom hostname without a dot causes cookie storage to fail. We recommend " | ||
413 | "accessing your server via it's IP address or Fully Qualified Domain Name.<br>" | ||
414 | msgstr "" | ||
415 | "<pre>ã‚»ãƒƒã‚·ãƒ§ãƒ³ãŒæ£å¸¸ã«ã‚ãªãŸã®ã‚µãƒ¼ãƒãƒ¼ä¸Šã§ç¨¼åƒã—ã¦ã„ãªã„よã†ã§ã™ã€‚<br>PHP ã®" | ||
416 | "è¨å®šãƒ•ァイル内ã«ã¦ã€æ£ã—ã \"session.save_path\" ã®å€¤ãŒè¨å®šã•れã¦ã„ã‚‹ã“ã¨ã¨ã€" | ||
417 | "権é™ãŒé–“é•ã£ã¦ã„ãªã„ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。<br>ç¾åœ¨ %s ã‹ã‚‰PHPã®è¨å®šãƒ•ァイル" | ||
418 | "ã‚’èªã¿è¾¼ã‚“ã§ã„ã¾ã™ã€‚<br>一部ã®ãƒ–ラウザーã«ãŠã„ã¦ã€localhost ã‚„ä»–ã®ãƒ‰ãƒƒãƒˆã‚’å«" | ||
419 | "ã¾ãªã„ホストåã«ã¦ã‚µãƒ¼ãƒãƒ¼ã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹éš›ã«ã€ã‚¯ãƒƒã‚ーをä¿å˜ã§ããªã„ã“ã¨ãŒã‚" | ||
420 | "りã¾ã™ã€‚IP アドレスや完全ãªãƒ‰ãƒ¡ã‚¤ãƒ³åã§ã‚µãƒ¼ãƒãƒ¼ã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã“ã¨ã‚’ãŠã™ã™ã‚ã—" | ||
421 | "ã¾ã™ã€‚<br>" | ||
422 | |||
423 | #: index.php:1943 | ||
424 | msgid "Click to try again." | ||
425 | msgstr "クリックã—ã¦å†åº¦è©¦ã—ã¾ã™ã€‚" | ||
426 | |||
427 | #: plugins/addlink_toolbar/addlink_toolbar.php:29 | ||
428 | msgid "URI" | ||
429 | msgstr "URI" | ||
430 | |||
431 | #: plugins/addlink_toolbar/addlink_toolbar.php:33 | ||
432 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
433 | msgid "Add link" | ||
434 | msgstr "ãƒªãƒ³ã‚¯ã‚’è¿½åŠ " | ||
435 | |||
436 | #: plugins/addlink_toolbar/addlink_toolbar.php:50 | ||
437 | msgid "Adds the addlink input on the linklist page." | ||
438 | msgstr "リンク一覧ã®ãƒšãƒ¼ã‚¸ã«ã€ãƒªãƒ³ã‚¯ã‚’è¿½åŠ ã™ã‚‹ãŸã‚ã®ãƒ•ォームを表示ã™ã‚‹ã€‚" | ||
439 | |||
440 | #: plugins/archiveorg/archiveorg.php:23 | ||
441 | msgid "View on archive.org" | ||
442 | msgstr "archive.org 上ã§è¡¨ç¤ºã™ã‚‹" | ||
443 | |||
444 | #: plugins/archiveorg/archiveorg.php:36 | ||
445 | msgid "For each link, add an Archive.org icon." | ||
446 | msgstr "ãれãžã‚Œã®ãƒªãƒ³ã‚¯ã«ã€Archive.org ã®ã‚¢ã‚¤ã‚³ãƒ³ã‚’è¿½åŠ ã™ã‚‹ã€‚" | ||
447 | |||
448 | #: plugins/demo_plugin/demo_plugin.php:465 | ||
449 | msgid "" | ||
450 | "A demo plugin covering all use cases for template designers and plugin " | ||
451 | "developers." | ||
452 | msgstr "" | ||
453 | "テンプレートã®ãƒ‡ã‚¶ã‚¤ãƒŠãƒ¼ã‚„ã€ãƒ—ラグインã®é–‹ç™ºè€…ã®ãŸã‚ã®ã™ã¹ã¦ã®çжæ³ã«å¯¾å¿œã§ã" | ||
454 | "るデモプラグインã§ã™ã€‚" | ||
455 | |||
456 | #: plugins/isso/isso.php:20 | ||
457 | msgid "" | ||
458 | "Isso plugin error: Please define the \"ISSO_SERVER\" setting in the plugin " | ||
459 | "administration page." | ||
460 | msgstr "" | ||
461 | "Isso プラグインエラー: \"ISSO_SERVER\" ã®å€¤ã‚’プラグイン管ç†ãƒšãƒ¼ã‚¸ã«ã¦æŒ‡å®šã—ã¦" | ||
462 | "ãã ã•ã„。" | ||
463 | |||
464 | #: plugins/isso/isso.php:63 | ||
465 | msgid "Let visitor comment your shaares on permalinks with Isso." | ||
466 | msgstr "" | ||
467 | "Isso を使ã£ã¦ã€ã‚ãªãŸã®ãƒ‘ーマリンク上ã®ãƒªãƒ³ã‚¯ã«ç¬¬ä¸‰è€…ãŒã‚³ãƒ¡ãƒ³ãƒˆã‚’残ã™ã“ã¨ãŒã§" | ||
468 | "ãã¾ã™ã€‚" | ||
469 | |||
470 | #: plugins/isso/isso.php:64 | ||
471 | msgid "Isso server URL (without 'http://')" | ||
472 | msgstr "Isso server URL ('http://' 抜ã)" | ||
473 | |||
474 | #: plugins/markdown/markdown.php:158 | ||
475 | msgid "Description will be rendered with" | ||
476 | msgstr "èª¬æ˜Žã¯æ¬¡ã®æ–¹æ³•ã§æç”»ã•れã¾ã™:" | ||
477 | |||
478 | #: plugins/markdown/markdown.php:159 | ||
479 | msgid "Markdown syntax documentation" | ||
480 | msgstr "マークダウン形å¼ã®ãƒ‰ã‚ュメント" | ||
481 | |||
482 | #: plugins/markdown/markdown.php:160 | ||
483 | msgid "Markdown syntax" | ||
484 | msgstr "マークダウン形å¼" | ||
485 | |||
486 | #: plugins/markdown/markdown.php:339 | ||
487 | msgid "" | ||
488 | "Render shaare description with Markdown syntax.<br><strong>Warning</" | ||
489 | "strong>:\n" | ||
490 | "If your shaared descriptions contained HTML tags before enabling the " | ||
491 | "markdown plugin,\n" | ||
492 | "enabling it might break your page.\n" | ||
493 | "See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
494 | "markdown#html-rendering\">README</a>." | ||
495 | msgstr "" | ||
496 | "リンクã®èª¬æ˜Žã‚’マークダウン形å¼ã§è¡¨ç¤ºã—ã¾ã™ã€‚<br><strong>è¦å‘Š</strong>:\n" | ||
497 | "リンクã®èª¬æ˜Žã«HTMLã‚¿ã‚°ãŒã“ã®ãƒ—ラグインを有効ã«ã™ã‚‹å‰ã«å«ã¾ã‚Œã¦ã„ãŸå ´åˆã€\n" | ||
498 | "æ£å¸¸ã«ãƒšãƒ¼ã‚¸ã‚’表示ã§ããªããªã‚‹ã‹ã‚‚ã—れã¾ã›ã‚“。\n" | ||
499 | "詳ã—ã㯠<a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/" | ||
500 | "markdown#html-rendering\">README</a> ã‚’ã”覧ãã ã•ã„。" | ||
501 | |||
502 | #: plugins/piwik/piwik.php:21 | ||
503 | msgid "" | ||
504 | "Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin " | ||
505 | "administration page." | ||
506 | msgstr "" | ||
507 | "Piwik プラグインエラー: PIWIK_URL 㨠PIWIK_SITEID ã®å€¤ã‚’プラグイン管ç†ãƒšãƒ¼ã‚¸" | ||
508 | "ã§æŒ‡å®šã—ã¦ãã ã•ã„。" | ||
509 | |||
510 | #: plugins/piwik/piwik.php:70 | ||
511 | msgid "A plugin that adds Piwik tracking code to Shaarli pages." | ||
512 | msgstr "Piwik ã®ãƒˆãƒ©ãƒƒã‚ングコードをShaarliã«è¿½åŠ ã™ã‚‹ãƒ—ラグインã§ã™ã€‚" | ||
513 | |||
514 | #: plugins/piwik/piwik.php:71 | ||
515 | msgid "Piwik URL" | ||
516 | msgstr "Piwik URL" | ||
517 | |||
518 | #: plugins/piwik/piwik.php:72 | ||
519 | msgid "Piwik site ID" | ||
520 | msgstr "Piwik サイトID" | ||
521 | |||
522 | #: plugins/playvideos/playvideos.php:22 | ||
523 | msgid "Video player" | ||
524 | msgstr "動画プレイヤー" | ||
525 | |||
526 | #: plugins/playvideos/playvideos.php:25 | ||
527 | msgid "Play Videos" | ||
528 | msgstr "動画をå†ç”Ÿ" | ||
529 | |||
530 | #: plugins/playvideos/playvideos.php:56 | ||
531 | msgid "Add a button in the toolbar allowing to watch all videos." | ||
532 | msgstr "ã™ã¹ã¦ã®å‹•画を閲覧ã™ã‚‹ãƒœã‚¿ãƒ³ã‚’ツールãƒãƒ¼ã«è¿½åŠ ã—ã¾ã™ã€‚" | ||
533 | |||
534 | #: plugins/playvideos/youtube_playlist.js:214 | ||
535 | msgid "plugins/playvideos/jquery-1.11.2.min.js" | ||
536 | msgstr "plugins/playvideos/jquery-1.11.2.min.js" | ||
537 | |||
538 | #: plugins/pubsubhubbub/pubsubhubbub.php:69 | ||
539 | #, php-format | ||
540 | msgid "Could not publish to PubSubHubbub: %s" | ||
541 | msgstr "PubSubHubbub ã«ç™»éŒ²ã§ãã¾ã›ã‚“ã§ã—ãŸ: %s" | ||
542 | |||
543 | #: plugins/pubsubhubbub/pubsubhubbub.php:95 | ||
544 | #, php-format | ||
545 | msgid "Could not post to %s" | ||
546 | msgstr "%s ã«ç™»éŒ²ã§ãã¾ã›ã‚“ã§ã—ãŸ" | ||
547 | |||
548 | #: plugins/pubsubhubbub/pubsubhubbub.php:99 | ||
549 | #, php-format | ||
550 | msgid "Bad response from the hub %s" | ||
551 | msgstr "ãƒãƒ– %s ã‹ã‚‰ã®ä¸æ£ãªãƒ¬ã‚¹ãƒãƒ³ã‚¹" | ||
552 | |||
553 | #: plugins/pubsubhubbub/pubsubhubbub.php:110 | ||
554 | msgid "Enable PubSubHubbub feed publishing." | ||
555 | msgstr "PubSubHubbub ã¸ã®ãƒ•ィードを公開ã™ã‚‹ã€‚" | ||
556 | |||
557 | #: plugins/qrcode/qrcode.php:69 plugins/wallabag/wallabag.php:68 | ||
558 | msgid "For each link, add a QRCode icon." | ||
559 | msgstr "ãれãžã‚Œã®ãƒªãƒ³ã‚¯ã«ã¤ã„ã¦ã€QRコードã®ã‚¢ã‚¤ã‚³ãƒ³ã‚’è¿½åŠ ã™ã‚‹ã€‚" | ||
560 | |||
561 | #: plugins/wallabag/wallabag.php:21 | ||
562 | msgid "" | ||
563 | "Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the " | ||
564 | "plugin administration page." | ||
565 | msgstr "" | ||
566 | "Wallabag プラグインエラー: \"WALLABAG_URL\" ã®å€¤ã‚’プラグイン管ç†ãƒšãƒ¼ã‚¸ã«ãŠã„" | ||
567 | "ã¦æŒ‡å®šã—ã¦ãã ã•ã„。" | ||
568 | |||
569 | #: plugins/wallabag/wallabag.php:47 | ||
570 | msgid "Save to wallabag" | ||
571 | msgstr "Wallabag ã«ä¿å˜" | ||
572 | |||
573 | #: plugins/wallabag/wallabag.php:69 | ||
574 | msgid "Wallabag API URL" | ||
575 | msgstr "Wallabag ã®APIã®URL" | ||
576 | |||
577 | #: plugins/wallabag/wallabag.php:70 | ||
578 | msgid "Wallabag API version (1 or 2)" | ||
579 | msgstr "Wallabag ã®APIã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ (1 ã¾ãŸã¯ 2)" | ||
580 | |||
581 | #: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227 | ||
582 | #: tests/languages/fr/LanguagesFrTest.php:160 | ||
583 | #: tests/languages/fr/LanguagesFrTest.php:173 | ||
584 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81 | ||
585 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:81 | ||
586 | msgid "Search" | ||
587 | msgid_plural "Search" | ||
588 | msgstr[0] "検索" | ||
589 | msgstr[1] "検索" | ||
590 | |||
591 | #: tmp/404.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12 | ||
592 | msgid "Sorry, nothing to see here." | ||
593 | msgstr "ã™ã¿ã¾ã›ã‚“ãŒã€ã“ã“ã«ã¯ä½•ã‚‚ã‚りã¾ã›ã‚“。" | ||
594 | |||
595 | #: tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
596 | msgid "URL or leave empty to post a note" | ||
597 | msgstr "URL を入力ã™ã‚‹ã‹ã€ç©ºæ¬„ã«ã™ã‚‹ã¨ãƒŽãƒ¼ãƒˆã‚’投稿ã—ã¾ã™" | ||
598 | |||
599 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
600 | msgid "Current password" | ||
601 | msgstr "ç¾åœ¨ã®ãƒ‘スワード" | ||
602 | |||
603 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
604 | msgid "New password" | ||
605 | msgstr "æ–°ã—ã„パスワード" | ||
606 | |||
607 | #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | ||
608 | msgid "Change" | ||
609 | msgstr "変更" | ||
610 | |||
611 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
612 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | ||
613 | msgid "Tag" | ||
614 | msgstr "ã‚¿ã‚°" | ||
615 | |||
616 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | ||
617 | msgid "New name" | ||
618 | msgstr "変更先ã®åå‰" | ||
619 | |||
620 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | ||
621 | msgid "Case sensitive" | ||
622 | msgstr "大文å—ã¨å°æ–‡å—を区別" | ||
623 | |||
624 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | ||
625 | msgid "Rename" | ||
626 | msgstr "åå‰ã‚’変更" | ||
627 | |||
628 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | ||
629 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79 | ||
630 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:172 | ||
631 | msgid "Delete" | ||
632 | msgstr "削除" | ||
633 | |||
634 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39 | ||
635 | msgid "You can also edit tags in the" | ||
636 | msgstr "次ã«å«ã¾ã‚Œã‚‹ã‚¿ã‚°ã‚’編集ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™:" | ||
637 | |||
638 | #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:39 | ||
639 | msgid "tag list" | ||
640 | msgstr "タグ一覧" | ||
641 | |||
642 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
643 | msgid "title" | ||
644 | msgstr "タイトル" | ||
645 | |||
646 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | ||
647 | msgid "Home link" | ||
648 | msgstr "ホームã®ãƒªãƒ³ã‚¯å…ˆ" | ||
649 | |||
650 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | ||
651 | msgid "Default value" | ||
652 | msgstr "既定ã®å€¤" | ||
653 | |||
654 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 | ||
655 | msgid "Theme" | ||
656 | msgstr "テーマ" | ||
657 | |||
658 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87 | ||
659 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78 | ||
660 | msgid "Language" | ||
661 | msgstr "言語" | ||
662 | |||
663 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:116 | ||
664 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 | ||
665 | msgid "Timezone" | ||
666 | msgstr "タイムゾーン" | ||
667 | |||
668 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | ||
669 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 | ||
670 | msgid "Continent" | ||
671 | msgstr "大陸" | ||
672 | |||
673 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | ||
674 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 | ||
675 | msgid "City" | ||
676 | msgstr "町" | ||
677 | |||
678 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:164 | ||
679 | msgid "Disable session cookie hijacking protection" | ||
680 | msgstr "䏿£ãƒã‚°ã‚¤ãƒ³é˜²æ¢ã®ãŸã‚ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã‚¯ãƒƒã‚ーを無効化" | ||
681 | |||
682 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:166 | ||
683 | msgid "Check this if you get disconnected or if your IP address changes often" | ||
684 | msgstr "" | ||
685 | "ã‚ãªãŸãŒåˆ‡æ–ã•れãŸã‚Šã€IPアドレスãŒé »ç¹ã«å¤‰ã‚る環境下ã§ã‚ã‚‹ãªã‚‰ãƒã‚§ãƒƒã‚¯ã‚’入れ" | ||
686 | "ã¦ãã ã•ã„" | ||
687 | |||
688 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183 | ||
689 | msgid "Private links by default" | ||
690 | msgstr "既定ã§ãƒ—ライベートリンク" | ||
691 | |||
692 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:184 | ||
693 | msgid "All new links are private by default" | ||
694 | msgstr "ã™ã¹ã¦ã®æ–°è¦ãƒªãƒ³ã‚¯ã‚’プライベートã§ä½œæˆ" | ||
695 | |||
696 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 | ||
697 | msgid "RSS direct links" | ||
698 | msgstr "RSS 直リンク" | ||
699 | |||
700 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:200 | ||
701 | msgid "Check this to use direct URL instead of permalink in feeds" | ||
702 | msgstr "フィードã§ãƒ‘ーマリンクã®ä»£ã‚りã«ç›´ãƒªãƒ³ã‚¯ã‚’使ã†" | ||
703 | |||
704 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:215 | ||
705 | msgid "Hide public links" | ||
706 | msgstr "å…¬é–‹ãƒªãƒ³ã‚¯ã‚’éš ã™" | ||
707 | |||
708 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:216 | ||
709 | msgid "Do not show any links if the user is not logged in" | ||
710 | msgstr "ãƒã‚°ã‚¤ãƒ³ã—ã¦ã„ãªã„ユーザーã«ã¯ä½•ã®ãƒªãƒ³ã‚¯ã‚‚表示ã—ãªã„" | ||
711 | |||
712 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:231 | ||
713 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150 | ||
714 | msgid "Check updates" | ||
715 | msgstr "更新を確èª" | ||
716 | |||
717 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:232 | ||
718 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:152 | ||
719 | msgid "Notify me when a new release is ready" | ||
720 | msgstr "æ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒãƒªãƒªãƒ¼ã‚¹ã•れãŸã¨ãã«é€šçŸ¥" | ||
721 | |||
722 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247 | ||
723 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | ||
724 | msgid "Enable REST API" | ||
725 | msgstr "REST API を有効化" | ||
726 | |||
727 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248 | ||
728 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170 | ||
729 | msgid "Allow third party software to use Shaarli such as mobile application" | ||
730 | msgstr "" | ||
731 | "モãƒã‚¤ãƒ«ã‚¢ãƒ—リã¨ã„ã£ãŸã‚µãƒ¼ãƒ‰ãƒ‘ーティーã®ã‚½ãƒ•トウェアã«Shaarliを使用ã™ã‚‹ã“ã¨ã‚’" | ||
732 | "許å¯" | ||
733 | |||
734 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263 | ||
735 | msgid "API secret" | ||
736 | msgstr "API シークレット" | ||
737 | |||
738 | #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:274 | ||
739 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | ||
740 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 | ||
741 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 | ||
742 | msgid "Save" | ||
743 | msgstr "ä¿å˜" | ||
744 | |||
745 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
746 | msgid "The Daily Shaarli" | ||
747 | msgstr "デイリーSharli" | ||
748 | |||
749 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17 | ||
750 | msgid "1 RSS entry per day" | ||
751 | msgstr "儿—¥1ã¤ãšã¤ã®RSSé …ç›®" | ||
752 | |||
753 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:37 | ||
754 | msgid "Previous day" | ||
755 | msgstr "剿—¥" | ||
756 | |||
757 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | ||
758 | msgid "All links of one day in a single page." | ||
759 | msgstr "1æ—¥ã«ä½œæˆã•れãŸã™ã¹ã¦ã®ãƒªãƒ³ã‚¯ã§ã™ã€‚" | ||
760 | |||
761 | #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51 | ||
762 | msgid "Next day" | ||
763 | msgstr "翌日" | ||
764 | |||
765 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 | ||
766 | msgid "Created:" | ||
767 | msgstr "作æˆ:" | ||
768 | |||
769 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
770 | msgid "URL" | ||
771 | msgstr "URL" | ||
772 | |||
773 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | ||
774 | msgid "Title" | ||
775 | msgstr "タイトル" | ||
776 | |||
777 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40 | ||
778 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | ||
779 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 | ||
780 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 | ||
781 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124 | ||
782 | msgid "Description" | ||
783 | msgstr "説明" | ||
784 | |||
785 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
786 | msgid "Tags" | ||
787 | msgstr "ã‚¿ã‚°" | ||
788 | |||
789 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:59 | ||
790 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
791 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168 | ||
792 | msgid "Private" | ||
793 | msgstr "プライベート" | ||
794 | |||
795 | #: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | ||
796 | msgid "Apply Changes" | ||
797 | msgstr "変更をé©ç”¨" | ||
798 | |||
799 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
800 | msgid "Export Database" | ||
801 | msgstr "データベースをエクスãƒãƒ¼ãƒˆ" | ||
802 | |||
803 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | ||
804 | msgid "Selection" | ||
805 | msgstr "é¸æŠžæ¸ˆã¿" | ||
806 | |||
807 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | ||
808 | msgid "All" | ||
809 | msgstr "ã™ã¹ã¦" | ||
810 | |||
811 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
812 | msgid "Public" | ||
813 | msgstr "公開" | ||
814 | |||
815 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:52 | ||
816 | msgid "Prepend note permalinks with this Shaarli instance's URL" | ||
817 | msgstr "ã“ã® Shaarli ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã®URL ã«ãƒŽãƒ¼ãƒˆã¸ã®ãƒ‘ーマリンクを付ã‘åŠ ãˆã‚‹" | ||
818 | |||
819 | #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53 | ||
820 | msgid "Useful to import bookmarks in a web browser" | ||
821 | msgstr "ウェブブラウザーã®ãƒªãƒ³ã‚¯ã‚’インãƒãƒ¼ãƒˆã™ã‚‹ã®ã«æœ‰åйã§ã™" | ||
822 | |||
823 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
824 | msgid "Import Database" | ||
825 | msgstr "データベースをインãƒãƒ¼ãƒˆ" | ||
826 | |||
827 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 | ||
828 | msgid "Maximum size allowed:" | ||
829 | msgstr "最大サイズ:" | ||
830 | |||
831 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
832 | msgid "Visibility" | ||
833 | msgstr "å¯è¦–性" | ||
834 | |||
835 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
836 | msgid "Use values from the imported file, default to public" | ||
837 | msgstr "インãƒãƒ¼ãƒˆå…ƒã®ãƒ•ァイルã®å€¤ã‚’使用 (既定ã¯å…¬é–‹ãƒªãƒ³ã‚¯ã¨ãªã‚Šã¾ã™)" | ||
838 | |||
839 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
840 | msgid "Import all bookmarks as private" | ||
841 | msgstr "ã™ã¹ã¦ã®ãƒ–ãƒƒã‚¯ãƒžãƒ¼ã‚¯é …ç›®ã‚’ãƒ—ãƒ©ã‚¤ãƒ™ãƒ¼ãƒˆãƒªãƒ³ã‚¯ã¨ã—ã¦ã‚¤ãƒ³ãƒãƒ¼ãƒˆ" | ||
842 | |||
843 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
844 | msgid "Import all bookmarks as public" | ||
845 | msgstr "ã™ã¹ã¦ã®ãƒ–ãƒƒã‚¯ãƒžãƒ¼ã‚¯é …ç›®ã‚’å…¬é–‹ãƒªãƒ³ã‚¯ã¨ã—ã¦ã‚¤ãƒ³ãƒãƒ¼ãƒˆ" | ||
846 | |||
847 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:57 | ||
848 | msgid "Overwrite existing bookmarks" | ||
849 | msgstr "æ—¢ã«å˜åœ¨ã—ã¦ã„るブックマークを上書ã" | ||
850 | |||
851 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 | ||
852 | msgid "Duplicates based on URL" | ||
853 | msgstr "URL ã«ã‚ˆã‚‹é‡è¤‡" | ||
854 | |||
855 | #: tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 | ||
856 | msgid "Add default tags" | ||
857 | msgstr "既定ã®ã‚¿ã‚°ã‚’è¿½åŠ " | ||
858 | |||
859 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 | ||
860 | msgid "Install Shaarli" | ||
861 | msgstr "Shaarli をインストール" | ||
862 | |||
863 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 | ||
864 | msgid "It looks like it's the first time you run Shaarli. Please configure it." | ||
865 | msgstr "ã©ã†ã‚„ら Shaarli ã‚’åˆã‚ã¦èµ·å‹•ã—ã¦ã„るよã†ã§ã™ã€‚è¨å®šã—ã¦ãã ã•ã„。" | ||
866 | |||
867 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:33 | ||
868 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 | ||
869 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 | ||
870 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147 | ||
871 | msgid "Username" | ||
872 | msgstr "ユーザーå" | ||
873 | |||
874 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | ||
875 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | ||
876 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148 | ||
877 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:148 | ||
878 | msgid "Password" | ||
879 | msgstr "パスワード" | ||
880 | |||
881 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:63 | ||
882 | msgid "Shaarli title" | ||
883 | msgstr "Shaarli ã®ã‚¿ã‚¤ãƒˆãƒ«" | ||
884 | |||
885 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69 | ||
886 | msgid "My links" | ||
887 | msgstr "自分ã®ãƒªãƒ³ã‚¯" | ||
888 | |||
889 | #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182 | ||
890 | msgid "Install" | ||
891 | msgstr "インストール" | ||
892 | |||
893 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
894 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80 | ||
895 | msgid "shaare" | ||
896 | msgid_plural "shaares" | ||
897 | msgstr[0] "共有" | ||
898 | msgstr[1] "共有" | ||
899 | |||
900 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 | ||
901 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84 | ||
902 | msgid "private link" | ||
903 | msgid_plural "private links" | ||
904 | msgstr[0] "プライベートリンク" | ||
905 | msgstr[1] "プライベートリンク" | ||
906 | |||
907 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31 | ||
908 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | ||
909 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:117 | ||
910 | msgid "Search text" | ||
911 | msgstr "æ–‡å—åˆ—ã§æ¤œç´¢" | ||
912 | |||
913 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38 | ||
914 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:124 | ||
915 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:124 | ||
916 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
917 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:64 | ||
918 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 | ||
919 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | ||
920 | msgid "Filter by tag" | ||
921 | msgstr "ã‚¿ã‚°ã«ã‚ˆã£ã¦åˆ†é¡ž" | ||
922 | |||
923 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111 | ||
924 | msgid "Nothing found." | ||
925 | msgstr "何も見ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚" | ||
926 | |||
927 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:119 | ||
928 | #, php-format | ||
929 | msgid "%s result" | ||
930 | msgid_plural "%s results" | ||
931 | msgstr[0] "%s ä»¶ã®çµæžœ" | ||
932 | msgstr[1] "%s ä»¶ã®çµæžœ" | ||
933 | |||
934 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123 | ||
935 | msgid "for" | ||
936 | msgstr "for" | ||
937 | |||
938 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:130 | ||
939 | msgid "tagged" | ||
940 | msgstr "タグ付ã‘ã•れãŸ" | ||
941 | |||
942 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 | ||
943 | msgid "Remove tag" | ||
944 | msgstr "タグを削除" | ||
945 | |||
946 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143 | ||
947 | msgid "with status" | ||
948 | msgstr "with status" | ||
949 | |||
950 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154 | ||
951 | msgid "without any tag" | ||
952 | msgstr "ã‚¿ã‚°ãªã—" | ||
953 | |||
954 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:174 | ||
955 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | ||
956 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:42 | ||
957 | msgid "Fold" | ||
958 | msgstr "畳む" | ||
959 | |||
960 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176 | ||
961 | msgid "Edited: " | ||
962 | msgstr "編集済ã¿: " | ||
963 | |||
964 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:180 | ||
965 | msgid "permalink" | ||
966 | msgstr "パーマリンク" | ||
967 | |||
968 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:182 | ||
969 | msgid "Add tag" | ||
970 | msgstr "ã‚¿ã‚°ã‚’è¿½åŠ " | ||
971 | |||
972 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7 | ||
973 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:7 | ||
974 | msgid "Filters" | ||
975 | msgstr "分類" | ||
976 | |||
977 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:12 | ||
978 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:12 | ||
979 | msgid "Only display private links" | ||
980 | msgstr "プライベートリンクã®ã¿ã‚’表示" | ||
981 | |||
982 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
983 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:15 | ||
984 | msgid "Only display public links" | ||
985 | msgstr "公開リンクã®ã¿ã‚’表示" | ||
986 | |||
987 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20 | ||
988 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:20 | ||
989 | msgid "Filter untagged links" | ||
990 | msgstr "タグ付ã‘ã•れã¦ã„ãªã„リンクã§åˆ†é¡ž" | ||
991 | |||
992 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | ||
993 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76 | ||
994 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:24 | ||
995 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:76 | ||
996 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | ||
997 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:43 | ||
998 | msgid "Fold all" | ||
999 | msgstr "ã™ã¹ã¦ç•³ã‚€" | ||
1000 | |||
1001 | #: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69 | ||
1002 | #: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:69 | ||
1003 | msgid "Links per page" | ||
1004 | msgstr "å„ページをリンク" | ||
1005 | |||
1006 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
1007 | msgid "" | ||
1008 | "You have been banned after too many failed login attempts. Try again later." | ||
1009 | msgstr "è¤‡æ•°å›žã«æ¸¡ã‚‹ãƒã‚°ã‚¤ãƒ³ã¸ã®å¤±æ•—を検出ã—ã¾ã—ãŸã€‚後ã§ã¾ãŸè©¦ã—ã¦ãã ã•ã„。" | ||
1010 | |||
1011 | #: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
1012 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:151 | ||
1013 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:151 | ||
1014 | msgid "Remember me" | ||
1015 | msgstr "パスワードをä¿å˜" | ||
1016 | |||
1017 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
1018 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | ||
1019 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:14 | ||
1020 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48 | ||
1021 | msgid "by the Shaarli community" | ||
1022 | msgstr "by Shaarli コミュニティ" | ||
1023 | |||
1024 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
1025 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15 | ||
1026 | msgid "Documentation" | ||
1027 | msgstr "ドã‚ュメント" | ||
1028 | |||
1029 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 | ||
1030 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:44 | ||
1031 | msgid "Expand" | ||
1032 | msgstr "展開ã™ã‚‹" | ||
1033 | |||
1034 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:45 | ||
1035 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:45 | ||
1036 | msgid "Expand all" | ||
1037 | msgstr "ã™ã¹ã¦å±•é–‹ã™ã‚‹" | ||
1038 | |||
1039 | #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46 | ||
1040 | #: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:46 | ||
1041 | msgid "Are you sure you want to delete this link?" | ||
1042 | msgstr "本当ã«ã“ã®ãƒªãƒ³ã‚¯ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ" | ||
1043 | |||
1044 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:61 | ||
1045 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 | ||
1046 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:61 | ||
1047 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:86 | ||
1048 | msgid "RSS Feed" | ||
1049 | msgstr "RSS フィード" | ||
1050 | |||
1051 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66 | ||
1052 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 | ||
1053 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:66 | ||
1054 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:102 | ||
1055 | msgid "Logout" | ||
1056 | msgstr "ãƒã‚°ã‚¢ã‚¦ãƒˆ" | ||
1057 | |||
1058 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 | ||
1059 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:169 | ||
1060 | msgid "is available" | ||
1061 | msgstr "ãŒåˆ©ç”¨å¯èƒ½" | ||
1062 | |||
1063 | #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:176 | ||
1064 | #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:176 | ||
1065 | msgid "Error" | ||
1066 | msgstr "エラー" | ||
1067 | |||
1068 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
1069 | msgid "Picture Wall" | ||
1070 | msgstr "ピクãƒãƒ£ãƒ¼ã‚¦ã‚©ãƒ¼ãƒ«" | ||
1071 | |||
1072 | #: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
1073 | msgid "pics" | ||
1074 | msgstr "ç”»åƒ" | ||
1075 | |||
1076 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 | ||
1077 | msgid "You need to enable Javascript to change plugin loading order." | ||
1078 | msgstr "" | ||
1079 | "プラグインをèªã¿è¾¼ã‚€é †ç•ªã‚’変更ã™ã‚‹ã«ã¯ã€Javascriptを有効ã«ã™ã‚‹å¿…è¦ãŒã‚りã¾" | ||
1080 | "ã™ã€‚" | ||
1081 | |||
1082 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 | ||
1083 | msgid "Enabled Plugins" | ||
1084 | msgstr "有効ãªãƒ—ラグイン" | ||
1085 | |||
1086 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 | ||
1087 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155 | ||
1088 | msgid "No plugin enabled." | ||
1089 | msgstr "有効ãªãƒ—ラグインã¯ã‚りã¾ã›ã‚“。" | ||
1090 | |||
1091 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40 | ||
1092 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:73 | ||
1093 | msgid "Disable" | ||
1094 | msgstr "無効化" | ||
1095 | |||
1096 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
1097 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 | ||
1098 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:98 | ||
1099 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123 | ||
1100 | msgid "Name" | ||
1101 | msgstr "åå‰" | ||
1102 | |||
1103 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:43 | ||
1104 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76 | ||
1105 | msgid "Order" | ||
1106 | msgstr "é †åº" | ||
1107 | |||
1108 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 | ||
1109 | msgid "Disabled Plugins" | ||
1110 | msgstr "無効ãªãƒ—ラグイン" | ||
1111 | |||
1112 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:91 | ||
1113 | msgid "No plugin disabled." | ||
1114 | msgstr "無効ãªãƒ—ラグインã¯ã‚りã¾ã›ã‚“。" | ||
1115 | |||
1116 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:97 | ||
1117 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122 | ||
1118 | msgid "Enable" | ||
1119 | msgstr "有効化" | ||
1120 | |||
1121 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 | ||
1122 | msgid "More plugins available" | ||
1123 | msgstr "ã•らã«åˆ©ç”¨ã§ãるプラグインãŒã‚りã¾ã™" | ||
1124 | |||
1125 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:136 | ||
1126 | msgid "in the documentation" | ||
1127 | msgstr "ドã‚ュメント内" | ||
1128 | |||
1129 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150 | ||
1130 | msgid "Plugin configuration" | ||
1131 | msgstr "プラグインè¨å®š" | ||
1132 | |||
1133 | #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:195 | ||
1134 | msgid "No parameter available." | ||
1135 | msgstr "利用å¯èƒ½ãªè¨å®šé …ç›®ã¯ã‚りã¾ã›ã‚“。" | ||
1136 | |||
1137 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1138 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 | ||
1139 | msgid "tags" | ||
1140 | msgstr "ã‚¿ã‚°" | ||
1141 | |||
1142 | #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | ||
1143 | #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 | ||
1144 | msgid "List all links with those tags" | ||
1145 | msgstr "ã“ã®ã‚¿ã‚°ãŒä»˜ã„ã¦ã„るリンクをリスト化ã™ã‚‹" | ||
1146 | |||
1147 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:3 | ||
1148 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:3 | ||
1149 | msgid "Sort by:" | ||
1150 | msgstr "分類:" | ||
1151 | |||
1152 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5 | ||
1153 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:5 | ||
1154 | msgid "Cloud" | ||
1155 | msgstr "クラウド" | ||
1156 | |||
1157 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:6 | ||
1158 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:6 | ||
1159 | msgid "Most used" | ||
1160 | msgstr "ã‚‚ã£ã¨ã‚‚使ã‚れãŸ" | ||
1161 | |||
1162 | #: tmp/tag.sort.b91ef64efc3688266305ea9b42e5017e.rtpl.php:7 | ||
1163 | #: tmp/tag.sort.cedf684561d925457130839629000a81.rtpl.php:7 | ||
1164 | msgid "Alphabetical" | ||
1165 | msgstr "ã‚¢ãƒ«ãƒ•ã‚¡ãƒ™ãƒƒãƒˆé †" | ||
1166 | |||
1167 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 | ||
1168 | msgid "Settings" | ||
1169 | msgstr "è¨å®š" | ||
1170 | |||
1171 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 | ||
1172 | msgid "Change Shaarli settings: title, timezone, etc." | ||
1173 | msgstr "Shaarli ã®è¨å®šã‚’変更: タイトルã€ã‚¿ã‚¤ãƒ ゾーンãªã©ã€‚" | ||
1174 | |||
1175 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:17 | ||
1176 | msgid "Configure your Shaarli" | ||
1177 | msgstr "ã‚ãªãŸã® Shaarli ã‚’è¨å®š" | ||
1178 | |||
1179 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21 | ||
1180 | msgid "Enable, disable and configure plugins" | ||
1181 | msgstr "プラグインを有効化ã€ç„¡åŠ¹åŒ–ã€è¨å®šã™ã‚‹" | ||
1182 | |||
1183 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 | ||
1184 | msgid "Change your password" | ||
1185 | msgstr "パスワードを変更" | ||
1186 | |||
1187 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 | ||
1188 | msgid "Rename or delete a tag in all links" | ||
1189 | msgstr "ã™ã¹ã¦ã®ãƒªãƒ³ã‚¯ã®ã‚¿ã‚°ã®åå‰ã‚’変更ã™ã‚‹ã€ã¾ãŸã¯å‰Šé™¤ã™ã‚‹" | ||
1190 | |||
1191 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 | ||
1192 | msgid "" | ||
1193 | "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, " | ||
1194 | "delicious...)" | ||
1195 | msgstr "" | ||
1196 | "Netscape HTML å½¢å¼ã®ãƒ–ックマークをインãƒãƒ¼ãƒˆã™ã‚‹ (Firefoxã€Chromeã€Operaã¨" | ||
1197 | "ã„ã£ãŸãƒ–ラウザーãŒå«ã¾ã‚Œã¾ã™)" | ||
1198 | |||
1199 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 | ||
1200 | msgid "Import links" | ||
1201 | msgstr "リンクをインãƒãƒ¼ãƒˆ" | ||
1202 | |||
1203 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 | ||
1204 | msgid "" | ||
1205 | "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, " | ||
1206 | "Opera, delicious...)" | ||
1207 | msgstr "" | ||
1208 | "Netscape HTML å½¢å¼ã®ãƒ–ックマークをエクスãƒãƒ¼ãƒˆã™ã‚‹ (Firefoxã€Chromeã€Operaã¨" | ||
1209 | "ã„ã£ãŸãƒ–ラウザーãŒå«ã¾ã‚Œã¾ã™)" | ||
1210 | |||
1211 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 | ||
1212 | msgid "Export database" | ||
1213 | msgstr "リンクをエクスãƒãƒ¼ãƒˆ" | ||
1214 | |||
1215 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:71 | ||
1216 | msgid "" | ||
1217 | "Drag one of these button to your bookmarks toolbar or right-click it and " | ||
1218 | "\"Bookmark This Link\"" | ||
1219 | msgstr "" | ||
1220 | "ã“れらã®ãƒœã‚¿ãƒ³ã®ã†ã¡1ã¤ã‚’をブックマークãƒãƒ¼ã«ãƒ‰ãƒ©ãƒƒã‚°ã™ã‚‹ã‹ã€å³ã‚¯ãƒªãƒƒã‚¯ã—ã¦" | ||
1221 | "「ã“ã®ãƒªãƒ³ã‚¯ã‚’ブックマークã«è¿½åŠ ã€ã—ã¦ãã ã•ã„" | ||
1222 | |||
1223 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 | ||
1224 | msgid "then click on the bookmarklet in any page you want to share." | ||
1225 | msgstr "共有ã—ãŸã„ページã§ãƒ–ックマークレットをクリックã—ã¦ãã ã•ã„。" | ||
1226 | |||
1227 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:76 | ||
1228 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:100 | ||
1229 | msgid "" | ||
1230 | "Drag this link to your bookmarks toolbar or right-click it and Bookmark This " | ||
1231 | "Link" | ||
1232 | msgstr "" | ||
1233 | "ã“ã®ãƒªãƒ³ã‚¯ã‚’ブックマークãƒãƒ¼ã«ãƒ‰ãƒ©ãƒƒã‚°ã™ã‚‹ã‹ã€å³ã‚¯ãƒªãƒƒã‚¯ã—ã¦ã€Œã“ã®ãƒªãƒ³ã‚¯ã‚’" | ||
1234 | "ブックマークã«è¿½åŠ ã€ã—ã¦ãã ã•ã„" | ||
1235 | |||
1236 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 | ||
1237 | msgid "then click ✚Shaare link button in any page you want to share" | ||
1238 | msgstr "✚リンクを共有 ボタンをクリックã™ã‚‹ã“ã¨ã§ã€ã©ã“ã§ã‚‚リンクを共有ã§ãã¾ã™" | ||
1239 | |||
1240 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 | ||
1241 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:108 | ||
1242 | msgid "The selected text is too long, it will be truncated." | ||
1243 | msgstr "é¸æŠžã•ã‚ŒãŸæ–‡å—列ã¯é•·ã™ãŽã‚‹ã®ã§ã€ä¸€éƒ¨ãŒåˆ‡ã‚Šæ¨ã¦ã‚‰ã‚Œã¾ã™ã€‚" | ||
1244 | |||
1245 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 | ||
1246 | msgid "Shaare link" | ||
1247 | msgstr "共有リンク" | ||
1248 | |||
1249 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101 | ||
1250 | msgid "" | ||
1251 | "Then click ✚Add Note button anytime to start composing a private Note (text " | ||
1252 | "post) to your Shaarli" | ||
1253 | msgstr "" | ||
1254 | "âœšãƒŽãƒ¼ãƒˆã‚’è¿½åŠ ãƒœã‚¿ãƒ³ã‚’ã‚¯ãƒªãƒƒã‚¯ã™ã‚‹ã“ã¨ã§ã€ã„ã¤ã§ã‚‚プライベートノート(テã‚スト" | ||
1255 | "å½¢å¼)ã‚’Shaarli上ã«ä½œæˆã§ãã¾ã™" | ||
1256 | |||
1257 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:117 | ||
1258 | msgid "Add Note" | ||
1259 | msgstr "ãƒŽãƒ¼ãƒˆã‚’è¿½åŠ " | ||
1260 | |||
1261 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:129 | ||
1262 | msgid "" | ||
1263 | "You need to browse your Shaarli over <strong>HTTPS</strong> to use this " | ||
1264 | "functionality." | ||
1265 | msgstr "" | ||
1266 | "ã“ã®æ©Ÿèƒ½ã‚’使用ã™ã‚‹ã«ã¯ã€<strong>HTTPS</strong> 経由ã§Shaarliã«æŽ¥ç¶šã—ã¦ãã ã•" | ||
1267 | "ã„。" | ||
1268 | |||
1269 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134 | ||
1270 | msgid "Add to" | ||
1271 | msgstr "次ã«è¿½åŠ :" | ||
1272 | |||
1273 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:145 | ||
1274 | msgid "3rd party" | ||
1275 | msgstr "サードパーティー" | ||
1276 | |||
1277 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 | ||
1278 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:153 | ||
1279 | msgid "Plugin" | ||
1280 | msgstr "プラグイン" | ||
1281 | |||
1282 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:148 | ||
1283 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:154 | ||
1284 | msgid "plugin" | ||
1285 | msgstr "プラグイン" | ||
1286 | |||
1287 | #: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:175 | ||
1288 | msgid "" | ||
1289 | "Drag this link to your bookmarks toolbar, or right-click it and choose " | ||
1290 | "Bookmark This Link" | ||
1291 | msgstr "" | ||
1292 | "ã“ã®ãƒªãƒ³ã‚¯ã‚’ブックマークãƒãƒ¼ã«ãƒ‰ãƒ©ãƒƒã‚°ã™ã‚‹ã‹ã€å³ã‚¯ãƒªãƒƒã‚¯ã—ã¦ã€Œã“ã®ãƒªãƒ³ã‚¯ã‚’" | ||
1293 | "ブックマークã«è¿½åŠ ã€ã—ã¦ãã ã•ã„" | ||
@@ -10,129 +10,49 @@ | |||
10 | * - https://github.com/sebsauvage/Shaarli | 10 | * - https://github.com/sebsauvage/Shaarli |
11 | * | 11 | * |
12 | * Licence: http://www.opensource.org/licenses/zlib-license.php | 12 | * Licence: http://www.opensource.org/licenses/zlib-license.php |
13 | * | ||
14 | * Requires: PHP 5.5.x | ||
15 | */ | ||
16 | |||
17 | // Set 'UTC' as the default timezone if it is not defined in php.ini | ||
18 | // See http://php.net/manual/en/datetime.configuration.php#ini.date.timezone | ||
19 | if (date_default_timezone_get() == '') { | ||
20 | date_default_timezone_set('UTC'); | ||
21 | } | ||
22 | |||
23 | /* | ||
24 | * PHP configuration | ||
25 | */ | 13 | */ |
26 | 14 | ||
27 | // http://server.com/x/shaarli --> /shaarli/ | ||
28 | define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0))); | ||
29 | |||
30 | // High execution time in case of problematic imports/exports. | ||
31 | ini_set('max_input_time', '60'); | ||
32 | |||
33 | // Try to set max upload file size and read | ||
34 | ini_set('memory_limit', '128M'); | ||
35 | ini_set('post_max_size', '16M'); | ||
36 | ini_set('upload_max_filesize', '16M'); | ||
37 | |||
38 | // See all error except warnings | ||
39 | error_reporting(E_ALL^E_WARNING); | ||
40 | // See all errors (for debugging only) | ||
41 | //error_reporting(-1); | ||
42 | |||
43 | |||
44 | // 3rd-party libraries | ||
45 | if (! file_exists(__DIR__ . '/vendor/autoload.php')) { | ||
46 | header('Content-Type: text/plain; charset=utf-8'); | ||
47 | echo "Error: missing Composer configuration\n\n" | ||
48 | ."If you installed Shaarli through Git or using the development branch,\n" | ||
49 | ."please refer to the installation documentation to install PHP" | ||
50 | ." dependencies using Composer:\n" | ||
51 | ."- https://shaarli.readthedocs.io/en/master/Server-configuration/\n" | ||
52 | ."- https://shaarli.readthedocs.io/en/master/Download-and-Installation/"; | ||
53 | exit; | ||
54 | } | ||
55 | require_once 'inc/rain.tpl.class.php'; | 15 | require_once 'inc/rain.tpl.class.php'; |
56 | require_once __DIR__ . '/vendor/autoload.php'; | 16 | require_once __DIR__ . '/vendor/autoload.php'; |
57 | 17 | ||
58 | // Shaarli library | 18 | // Shaarli library |
59 | require_once 'application/bookmark/LinkUtils.php'; | 19 | require_once 'application/bookmark/LinkUtils.php'; |
60 | require_once 'application/config/ConfigPlugin.php'; | 20 | require_once 'application/config/ConfigPlugin.php'; |
61 | require_once 'application/feed/Cache.php'; | ||
62 | require_once 'application/http/HttpUtils.php'; | 21 | require_once 'application/http/HttpUtils.php'; |
63 | require_once 'application/http/UrlUtils.php'; | 22 | require_once 'application/http/UrlUtils.php'; |
64 | require_once 'application/updater/UpdaterUtils.php'; | ||
65 | require_once 'application/FileUtils.php'; | ||
66 | require_once 'application/TimeZone.php'; | 23 | require_once 'application/TimeZone.php'; |
67 | require_once 'application/Utils.php'; | 24 | require_once 'application/Utils.php'; |
68 | 25 | ||
69 | use \Shaarli\ApplicationUtils; | 26 | require_once __DIR__ . '/init.php'; |
70 | use \Shaarli\Bookmark\Exception\LinkNotFoundException; | ||
71 | use \Shaarli\Bookmark\LinkDB; | ||
72 | use \Shaarli\Config\ConfigManager; | ||
73 | use \Shaarli\Feed\CachedPage; | ||
74 | use \Shaarli\Feed\FeedBuilder; | ||
75 | use \Shaarli\History; | ||
76 | use \Shaarli\Languages; | ||
77 | use \Shaarli\Netscape\NetscapeBookmarkUtils; | ||
78 | use \Shaarli\Plugin\PluginManager; | ||
79 | use \Shaarli\Render\PageBuilder; | ||
80 | use \Shaarli\Render\ThemeUtils; | ||
81 | use \Shaarli\Router; | ||
82 | use \Shaarli\Security\LoginManager; | ||
83 | use \Shaarli\Security\SessionManager; | ||
84 | use \Shaarli\Thumbnailer; | ||
85 | use \Shaarli\Updater\Updater; | ||
86 | 27 | ||
87 | // Ensure the PHP version is supported | 28 | use Shaarli\Config\ConfigManager; |
88 | try { | 29 | use Shaarli\Container\ContainerBuilder; |
89 | ApplicationUtils::checkPHPVersion('5.5', PHP_VERSION); | 30 | use Shaarli\Languages; |
90 | } catch (Exception $exc) { | 31 | use Shaarli\Security\CookieManager; |
91 | header('Content-Type: text/plain; charset=utf-8'); | 32 | use Shaarli\Security\LoginManager; |
92 | echo $exc->getMessage(); | 33 | use Shaarli\Security\SessionManager; |
93 | exit; | 34 | use Slim\App; |
94 | } | ||
95 | 35 | ||
96 | define('SHAARLI_VERSION', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE)); | 36 | $conf = new ConfigManager(); |
97 | 37 | ||
98 | // Force cookie path (but do not change lifetime) | 38 | // Manually override root URL for complex server configurations |
99 | $cookie = session_get_cookie_params(); | 39 | define('SHAARLI_ROOT_URL', $conf->get('general.root_url', null)); |
100 | $cookiedir = ''; | ||
101 | if (dirname($_SERVER['SCRIPT_NAME']) != '/') { | ||
102 | $cookiedir = dirname($_SERVER["SCRIPT_NAME"]).'/'; | ||
103 | } | ||
104 | // Set default cookie expiration and path. | ||
105 | session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['SERVER_NAME']); | ||
106 | // Set session parameters on server side. | ||
107 | // Use cookies to store session. | ||
108 | ini_set('session.use_cookies', 1); | ||
109 | // Force cookies for session (phpsessionID forbidden in URL). | ||
110 | ini_set('session.use_only_cookies', 1); | ||
111 | // Prevent PHP form using sessionID in URL if cookies are disabled. | ||
112 | ini_set('session.use_trans_sid', false); | ||
113 | 40 | ||
114 | session_name('shaarli'); | 41 | // In dev mode, throw exception on any warning |
115 | // Start session if needed (Some server auto-start sessions). | 42 | if ($conf->get('dev.debug', false)) { |
116 | if (session_status() == PHP_SESSION_NONE) { | 43 | // See all errors (for debugging only) |
117 | session_start(); | 44 | error_reporting(-1); |
118 | } | ||
119 | 45 | ||
120 | // Regenerate session ID if invalid or not defined in cookie. | 46 | set_error_handler(function ($errno, $errstr, $errfile, $errline, array $errcontext) { |
121 | if (isset($_COOKIE['shaarli']) && !SessionManager::checkId($_COOKIE['shaarli'])) { | 47 | throw new ErrorException($errstr, 0, $errno, $errfile, $errline); |
122 | session_regenerate_id(true); | 48 | }); |
123 | $_COOKIE['shaarli'] = session_id(); | ||
124 | } | 49 | } |
125 | 50 | ||
126 | $conf = new ConfigManager(); | 51 | $sessionManager = new SessionManager($_SESSION, $conf, session_save_path()); |
127 | $sessionManager = new SessionManager($_SESSION, $conf); | 52 | $sessionManager->initialize(); |
128 | $loginManager = new LoginManager($conf, $sessionManager); | 53 | $cookieManager = new CookieManager($_COOKIE); |
54 | $loginManager = new LoginManager($conf, $sessionManager, $cookieManager); | ||
129 | $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']); | 55 | $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']); |
130 | $clientIpId = client_ip_id($_SERVER); | ||
131 | |||
132 | // LC_MESSAGES isn't defined without php-intl, in this case use LC_COLLATE locale instead. | ||
133 | if (! defined('LC_MESSAGES')) { | ||
134 | define('LC_MESSAGES', LC_COLLATE); | ||
135 | } | ||
136 | 56 | ||
137 | // Sniff browser language and set date format accordingly. | 57 | // Sniff browser language and set date format accordingly. |
138 | if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { | 58 | if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { |
@@ -142,1792 +62,78 @@ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { | |||
142 | new Languages(setlocale(LC_MESSAGES, 0), $conf); | 62 | new Languages(setlocale(LC_MESSAGES, 0), $conf); |
143 | 63 | ||
144 | $conf->setEmpty('general.timezone', date_default_timezone_get()); | 64 | $conf->setEmpty('general.timezone', date_default_timezone_get()); |
145 | $conf->setEmpty('general.title', t('Shared links on '). escape(index_url($_SERVER))); | 65 | $conf->setEmpty('general.title', t('Shared bookmarks on '). escape(index_url($_SERVER))); |
66 | |||
146 | RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory | 67 | RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory |
147 | RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory | 68 | RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory |
148 | 69 | ||
149 | $pluginManager = new PluginManager($conf); | ||
150 | $pluginManager->load($conf->get('general.enabled_plugins')); | ||
151 | |||
152 | date_default_timezone_set($conf->get('general.timezone', 'UTC')); | 70 | date_default_timezone_set($conf->get('general.timezone', 'UTC')); |
153 | 71 | ||
154 | ob_start(); // Output buffering for the page cache. | 72 | $loginManager->checkLoginState(client_ip_id($_SERVER)); |
155 | 73 | ||
156 | // Prevent caching on client side or proxy: (yes, it's ugly) | 74 | $containerBuilder = new ContainerBuilder($conf, $sessionManager, $cookieManager, $loginManager); |
157 | header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); | 75 | $container = $containerBuilder->build(); |
158 | header("Cache-Control: no-store, no-cache, must-revalidate"); | 76 | $app = new App($container); |
159 | header("Cache-Control: post-check=0, pre-check=0", false); | 77 | |
160 | header("Pragma: no-cache"); | 78 | // Main Shaarli routes |
161 | 79 | $app->group('', function () { | |
162 | if (! is_file($conf->getConfigFileExt())) { | 80 | $this->get('/install', '\Shaarli\Front\Controller\Visitor\InstallController:index')->setName('displayInstall'); |
163 | // Ensure Shaarli has proper access to its resources | 81 | $this->get('/install/session-test', '\Shaarli\Front\Controller\Visitor\InstallController:sessionTest'); |
164 | $errors = ApplicationUtils::checkResourcePermissions($conf); | 82 | $this->post('/install', '\Shaarli\Front\Controller\Visitor\InstallController:save')->setName('saveInstall'); |
165 | 83 | ||
166 | if ($errors != array()) { | 84 | /* -- PUBLIC --*/ |
167 | $message = '<p>'. t('Insufficient permissions:') .'</p><ul>'; | 85 | $this->get('/', '\Shaarli\Front\Controller\Visitor\BookmarkListController:index'); |
168 | 86 | $this->get('/shaare/{hash}', '\Shaarli\Front\Controller\Visitor\BookmarkListController:permalink'); | |
169 | foreach ($errors as $error) { | 87 | $this->get('/login', '\Shaarli\Front\Controller\Visitor\LoginController:index')->setName('login'); |
170 | $message .= '<li>'.$error.'</li>'; | 88 | $this->post('/login', '\Shaarli\Front\Controller\Visitor\LoginController:login')->setName('processLogin'); |
171 | } | 89 | $this->get('/picture-wall', '\Shaarli\Front\Controller\Visitor\PictureWallController:index'); |
172 | $message .= '</ul>'; | 90 | $this->get('/tags/cloud', '\Shaarli\Front\Controller\Visitor\TagCloudController:cloud'); |
173 | 91 | $this->get('/tags/list', '\Shaarli\Front\Controller\Visitor\TagCloudController:list'); | |
174 | header('Content-Type: text/html; charset=utf-8'); | 92 | $this->get('/daily', '\Shaarli\Front\Controller\Visitor\DailyController:index'); |
175 | echo $message; | 93 | $this->get('/daily-rss', '\Shaarli\Front\Controller\Visitor\DailyController:rss')->setName('rss'); |
176 | exit; | 94 | $this->get('/feed/atom', '\Shaarli\Front\Controller\Visitor\FeedController:atom')->setName('atom'); |
177 | } | 95 | $this->get('/feed/rss', '\Shaarli\Front\Controller\Visitor\FeedController:rss'); |
178 | 96 | $this->get('/open-search', '\Shaarli\Front\Controller\Visitor\OpenSearchController:index'); | |
179 | // Display the installation form if no existing config is found | 97 | |
180 | install($conf, $sessionManager, $loginManager); | 98 | $this->get('/add-tag/{newTag}', '\Shaarli\Front\Controller\Visitor\TagController:addTag'); |
181 | } | 99 | $this->get('/remove-tag/{tag}', '\Shaarli\Front\Controller\Visitor\TagController:removeTag'); |
182 | 100 | $this->get('/links-per-page', '\Shaarli\Front\Controller\Visitor\PublicSessionFilterController:linksPerPage'); | |
183 | $loginManager->checkLoginState($_COOKIE, $clientIpId); | 101 | $this->get('/untagged-only', '\Shaarli\Front\Controller\Visitor\PublicSessionFilterController:untaggedOnly'); |
184 | 102 | })->add('\Shaarli\Front\ShaarliMiddleware'); | |
185 | /** | 103 | |
186 | * Adapter function to ensure compatibility with third-party templates | 104 | $app->group('/admin', function () { |
187 | * | 105 | $this->get('/logout', '\Shaarli\Front\Controller\Admin\LogoutController:index'); |
188 | * @see https://github.com/shaarli/Shaarli/pull/1086 | 106 | $this->get('/tools', '\Shaarli\Front\Controller\Admin\ToolsController:index'); |
189 | * | 107 | $this->get('/password', '\Shaarli\Front\Controller\Admin\PasswordController:index'); |
190 | * @return bool true when the user is logged in, false otherwise | 108 | $this->post('/password', '\Shaarli\Front\Controller\Admin\PasswordController:change'); |
191 | */ | 109 | $this->get('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:index'); |
192 | function isLoggedIn() | 110 | $this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save'); |
193 | { | 111 | $this->get('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:index'); |
194 | global $loginManager; | 112 | $this->post('/tags', '\Shaarli\Front\Controller\Admin\ManageTagController:save'); |
195 | return $loginManager->isLoggedIn(); | 113 | $this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:addShaare'); |
196 | } | 114 | $this->get('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayCreateForm'); |
197 | 115 | $this->get('/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayEditForm'); | |
198 | 116 | $this->post('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:save'); | |
199 | // ------------------------------------------------------------------------------------------ | 117 | $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark'); |
200 | // Process login form: Check if login/password is correct. | 118 | $this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility'); |
201 | if (isset($_POST['login'])) { | 119 | $this->get('/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ManageShaareController:pinBookmark'); |
202 | if (! $loginManager->canLogin($_SERVER)) { | 120 | $this->patch( |
203 | die(t('I said: NO. You are banned for the moment. Go away.')); | 121 | '/shaare/{id:[0-9]+}/update-thumbnail', |
204 | } | 122 | '\Shaarli\Front\Controller\Admin\ThumbnailsController:ajaxUpdate' |
205 | if (isset($_POST['password']) | ||
206 | && $sessionManager->checkToken($_POST['token']) | ||
207 | && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password']) | ||
208 | ) { | ||
209 | $loginManager->handleSuccessfulLogin($_SERVER); | ||
210 | |||
211 | $cookiedir = ''; | ||
212 | if (dirname($_SERVER['SCRIPT_NAME']) != '/') { | ||
213 | // Note: Never forget the trailing slash on the cookie path! | ||
214 | $cookiedir = dirname($_SERVER["SCRIPT_NAME"]) . '/'; | ||
215 | } | ||
216 | |||
217 | if (!empty($_POST['longlastingsession'])) { | ||
218 | // Keep the session cookie even after the browser closes | ||
219 | $sessionManager->setStaySignedIn(true); | ||
220 | $expirationTime = $sessionManager->extendSession(); | ||
221 | |||
222 | setcookie( | ||
223 | $loginManager::$STAY_SIGNED_IN_COOKIE, | ||
224 | $loginManager->getStaySignedInToken(), | ||
225 | $expirationTime, | ||
226 | WEB_PATH | ||
227 | ); | ||
228 | } else { | ||
229 | // Standard session expiration (=when browser closes) | ||
230 | $expirationTime = 0; | ||
231 | } | ||
232 | |||
233 | // Send cookie with the new expiration date to the browser | ||
234 | session_set_cookie_params($expirationTime, $cookiedir, $_SERVER['SERVER_NAME']); | ||
235 | session_regenerate_id(true); | ||
236 | |||
237 | // Optional redirect after login: | ||
238 | if (isset($_GET['post'])) { | ||
239 | $uri = '?post='. urlencode($_GET['post']); | ||
240 | foreach (array('description', 'source', 'title', 'tags') as $param) { | ||
241 | if (!empty($_GET[$param])) { | ||
242 | $uri .= '&'.$param.'='.urlencode($_GET[$param]); | ||
243 | } | ||
244 | } | ||
245 | header('Location: '. $uri); | ||
246 | exit; | ||
247 | } | ||
248 | |||
249 | if (isset($_GET['edit_link'])) { | ||
250 | header('Location: ?edit_link='. escape($_GET['edit_link'])); | ||
251 | exit; | ||
252 | } | ||
253 | |||
254 | if (isset($_POST['returnurl'])) { | ||
255 | // Prevent loops over login screen. | ||
256 | if (strpos($_POST['returnurl'], 'do=login') === false) { | ||
257 | header('Location: '. generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST'])); | ||
258 | exit; | ||
259 | } | ||
260 | } | ||
261 | header('Location: ?'); | ||
262 | exit; | ||
263 | } else { | ||
264 | $loginManager->handleFailedLogin($_SERVER); | ||
265 | $redir = '&username='. urlencode($_POST['login']); | ||
266 | if (isset($_GET['post'])) { | ||
267 | $redir .= '&post=' . urlencode($_GET['post']); | ||
268 | foreach (array('description', 'source', 'title', 'tags') as $param) { | ||
269 | if (!empty($_GET[$param])) { | ||
270 | $redir .= '&' . $param . '=' . urlencode($_GET[$param]); | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | // Redirect to login screen. | ||
275 | echo '<script>alert("'. t("Wrong login/password.") .'");document.location=\'?do=login'.$redir.'\';</script>'; | ||
276 | exit; | ||
277 | } | ||
278 | } | ||
279 | |||
280 | // ------------------------------------------------------------------------------------------ | ||
281 | // Token management for XSRF protection | ||
282 | // Token should be used in any form which acts on data (create,update,delete,import...). | ||
283 | if (!isset($_SESSION['tokens'])) { | ||
284 | $_SESSION['tokens']=array(); // Token are attached to the session. | ||
285 | } | ||
286 | |||
287 | /** | ||
288 | * Daily RSS feed: 1 RSS entry per day giving all the links on that day. | ||
289 | * Gives the last 7 days (which have links). | ||
290 | * This RSS feed cannot be filtered. | ||
291 | * | ||
292 | * @param ConfigManager $conf Configuration Manager instance | ||
293 | * @param LoginManager $loginManager LoginManager instance | ||
294 | */ | ||
295 | function showDailyRSS($conf, $loginManager) | ||
296 | { | ||
297 | // Cache system | ||
298 | $query = $_SERVER['QUERY_STRING']; | ||
299 | $cache = new CachedPage( | ||
300 | $conf->get('config.PAGE_CACHE'), | ||
301 | page_url($_SERVER), | ||
302 | startsWith($query, 'do=dailyrss') && !$loginManager->isLoggedIn() | ||
303 | ); | ||
304 | $cached = $cache->cachedVersion(); | ||
305 | if (!empty($cached)) { | ||
306 | echo $cached; | ||
307 | exit; | ||
308 | } | ||
309 | |||
310 | // If cached was not found (or not usable), then read the database and build the response: | ||
311 | // Read links from database (and filter private links if used it not logged in). | ||
312 | $LINKSDB = new LinkDB( | ||
313 | $conf->get('resource.datastore'), | ||
314 | $loginManager->isLoggedIn(), | ||
315 | $conf->get('privacy.hide_public_links') | ||
316 | ); | ||
317 | |||
318 | /* Some Shaarlies may have very few links, so we need to look | ||
319 | back in time until we have enough days ($nb_of_days). | ||
320 | */ | ||
321 | $nb_of_days = 7; // We take 7 days. | ||
322 | $today = date('Ymd'); | ||
323 | $days = array(); | ||
324 | |||
325 | foreach ($LINKSDB as $link) { | ||
326 | $day = $link['created']->format('Ymd'); // Extract day (without time) | ||
327 | if (strcmp($day, $today) < 0) { | ||
328 | if (empty($days[$day])) { | ||
329 | $days[$day] = array(); | ||
330 | } | ||
331 | $days[$day][] = $link; | ||
332 | } | ||
333 | |||
334 | if (count($days) > $nb_of_days) { | ||
335 | break; // Have we collected enough days? | ||
336 | } | ||
337 | } | ||
338 | |||
339 | // Build the RSS feed. | ||
340 | header('Content-Type: application/rss+xml; charset=utf-8'); | ||
341 | $pageaddr = escape(index_url($_SERVER)); | ||
342 | echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">'; | ||
343 | echo '<channel>'; | ||
344 | echo '<title>Daily - '. $conf->get('general.title') . '</title>'; | ||
345 | echo '<link>'. $pageaddr .'</link>'; | ||
346 | echo '<description>Daily shared links</description>'; | ||
347 | echo '<language>en-en</language>'; | ||
348 | echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL; | ||
349 | |||
350 | // For each day. | ||
351 | foreach ($days as $day => $links) { | ||
352 | $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); | ||
353 | $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page. | ||
354 | |||
355 | // We pre-format some fields for proper output. | ||
356 | foreach ($links as &$link) { | ||
357 | $link['formatedDescription'] = format_description($link['description']); | ||
358 | $link['timestamp'] = $link['created']->getTimestamp(); | ||
359 | if (is_note($link['url'])) { | ||
360 | $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute | ||
361 | } | ||
362 | } | ||
363 | |||
364 | // Then build the HTML for this day: | ||
365 | $tpl = new RainTPL; | ||
366 | $tpl->assign('title', $conf->get('general.title')); | ||
367 | $tpl->assign('daydate', $dayDate->getTimestamp()); | ||
368 | $tpl->assign('absurl', $absurl); | ||
369 | $tpl->assign('links', $links); | ||
370 | $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS))); | ||
371 | $tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false)); | ||
372 | $tpl->assign('index_url', $pageaddr); | ||
373 | $html = $tpl->draw('dailyrss', true); | ||
374 | |||
375 | echo $html . PHP_EOL; | ||
376 | } | ||
377 | echo '</channel></rss><!-- Cached version of '. escape(page_url($_SERVER)) .' -->'; | ||
378 | |||
379 | $cache->cache(ob_get_contents()); | ||
380 | ob_end_flush(); | ||
381 | exit; | ||
382 | } | ||
383 | |||
384 | /** | ||
385 | * Show the 'Daily' page. | ||
386 | * | ||
387 | * @param PageBuilder $pageBuilder Template engine wrapper. | ||
388 | * @param LinkDB $LINKSDB LinkDB instance. | ||
389 | * @param ConfigManager $conf Configuration Manager instance. | ||
390 | * @param PluginManager $pluginManager Plugin Manager instance. | ||
391 | * @param LoginManager $loginManager Login Manager instance | ||
392 | */ | ||
393 | function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager) | ||
394 | { | ||
395 | if (isset($_GET['day'])) { | ||
396 | $day = $_GET['day']; | ||
397 | if ($day === date('Ymd', strtotime('now'))) { | ||
398 | $pageBuilder->assign('dayDesc', t('Today')); | ||
399 | } elseif ($day === date('Ymd', strtotime('-1 days'))) { | ||
400 | $pageBuilder->assign('dayDesc', t('Yesterday')); | ||
401 | } | ||
402 | } else { | ||
403 | $day = date('Ymd', strtotime('now')); // Today, in format YYYYMMDD. | ||
404 | $pageBuilder->assign('dayDesc', t('Today')); | ||
405 | } | ||
406 | |||
407 | $days = $LINKSDB->days(); | ||
408 | $i = array_search($day, $days); | ||
409 | if ($i === false && count($days)) { | ||
410 | // no links for day, but at least one day with links | ||
411 | $i = count($days) - 1; | ||
412 | $day = $days[$i]; | ||
413 | } | ||
414 | $previousday = ''; | ||
415 | $nextday = ''; | ||
416 | |||
417 | if ($i !== false) { | ||
418 | if ($i >= 1) { | ||
419 | $previousday=$days[$i - 1]; | ||
420 | } | ||
421 | if ($i < count($days) - 1) { | ||
422 | $nextday = $days[$i + 1]; | ||
423 | } | ||
424 | } | ||
425 | try { | ||
426 | $linksToDisplay = $LINKSDB->filterDay($day); | ||
427 | } catch (Exception $exc) { | ||
428 | error_log($exc); | ||
429 | $linksToDisplay = array(); | ||
430 | } | ||
431 | |||
432 | // We pre-format some fields for proper output. | ||
433 | foreach ($linksToDisplay as $key => $link) { | ||
434 | $taglist = explode(' ', $link['tags']); | ||
435 | uasort($taglist, 'strcasecmp'); | ||
436 | $linksToDisplay[$key]['taglist']=$taglist; | ||
437 | $linksToDisplay[$key]['formatedDescription'] = format_description($link['description']); | ||
438 | $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp(); | ||
439 | } | ||
440 | |||
441 | $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); | ||
442 | $data = array( | ||
443 | 'pagetitle' => $conf->get('general.title') .' - '. format_date($dayDate, false), | ||
444 | 'linksToDisplay' => $linksToDisplay, | ||
445 | 'day' => $dayDate->getTimestamp(), | ||
446 | 'dayDate' => $dayDate, | ||
447 | 'previousday' => $previousday, | ||
448 | 'nextday' => $nextday, | ||
449 | ); | ||
450 | |||
451 | /* Hook is called before column construction so that plugins don't have | ||
452 | to deal with columns. */ | ||
453 | $pluginManager->executeHooks('render_daily', $data, array('loggedin' => $loginManager->isLoggedIn())); | ||
454 | |||
455 | /* We need to spread the articles on 3 columns. | ||
456 | I did not want to use a JavaScript lib like http://masonry.desandro.com/ | ||
457 | so I manually spread entries with a simple method: I roughly evaluate the | ||
458 | height of a div according to title and description length. | ||
459 | */ | ||
460 | $columns = array(array(), array(), array()); // Entries to display, for each column. | ||
461 | $fill = array(0, 0, 0); // Rough estimate of columns fill. | ||
462 | foreach ($data['linksToDisplay'] as $key => $link) { | ||
463 | // Roughly estimate length of entry (by counting characters) | ||
464 | // Title: 30 chars = 1 line. 1 line is 30 pixels height. | ||
465 | // Description: 836 characters gives roughly 342 pixel height. | ||
466 | // This is not perfect, but it's usually OK. | ||
467 | $length = strlen($link['title']) + (342 * strlen($link['description'])) / 836; | ||
468 | if ($link['thumbnail']) { | ||
469 | $length += 100; // 1 thumbnails roughly takes 100 pixels height. | ||
470 | } | ||
471 | // Then put in column which is the less filled: | ||
472 | $smallest = min($fill); // find smallest value in array. | ||
473 | $index = array_search($smallest, $fill); // find index of this smallest value. | ||
474 | array_push($columns[$index], $link); // Put entry in this column. | ||
475 | $fill[$index] += $length; | ||
476 | } | ||
477 | |||
478 | $data['cols'] = $columns; | ||
479 | |||
480 | foreach ($data as $key => $value) { | ||
481 | $pageBuilder->assign($key, $value); | ||
482 | } | ||
483 | |||
484 | $pageBuilder->assign('pagetitle', t('Daily') .' - '. $conf->get('general.title', 'Shaarli')); | ||
485 | $pageBuilder->renderPage('daily'); | ||
486 | exit; | ||
487 | } | ||
488 | |||
489 | /** | ||
490 | * Renders the linklist | ||
491 | * | ||
492 | * @param pageBuilder $PAGE pageBuilder instance. | ||
493 | * @param LinkDB $LINKSDB LinkDB instance. | ||
494 | * @param ConfigManager $conf Configuration Manager instance. | ||
495 | * @param PluginManager $pluginManager Plugin Manager instance. | ||
496 | */ | ||
497 | function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager) | ||
498 | { | ||
499 | buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager); | ||
500 | $PAGE->renderPage('linklist'); | ||
501 | } | ||
502 | |||
503 | /** | ||
504 | * Render HTML page (according to URL parameters and user rights) | ||
505 | * | ||
506 | * @param ConfigManager $conf Configuration Manager instance. | ||
507 | * @param PluginManager $pluginManager Plugin Manager instance, | ||
508 | * @param LinkDB $LINKSDB | ||
509 | * @param History $history instance | ||
510 | * @param SessionManager $sessionManager SessionManager instance | ||
511 | * @param LoginManager $loginManager LoginManager instance | ||
512 | */ | ||
513 | function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $loginManager) | ||
514 | { | ||
515 | $updater = new Updater( | ||
516 | read_updates_file($conf->get('resource.updates')), | ||
517 | $LINKSDB, | ||
518 | $conf, | ||
519 | $loginManager->isLoggedIn(), | ||
520 | $_SESSION | ||
521 | ); | ||
522 | try { | ||
523 | $newUpdates = $updater->update(); | ||
524 | if (! empty($newUpdates)) { | ||
525 | write_updates_file( | ||
526 | $conf->get('resource.updates'), | ||
527 | $updater->getDoneUpdates() | ||
528 | ); | ||
529 | } | ||
530 | } catch (Exception $e) { | ||
531 | die($e->getMessage()); | ||
532 | } | ||
533 | |||
534 | $PAGE = new PageBuilder($conf, $_SESSION, $LINKSDB, $sessionManager->generateToken(), $loginManager->isLoggedIn()); | ||
535 | $PAGE->assign('linkcount', count($LINKSDB)); | ||
536 | $PAGE->assign('privateLinkcount', count_private($LINKSDB)); | ||
537 | $PAGE->assign('plugin_errors', $pluginManager->getErrors()); | ||
538 | |||
539 | // Determine which page will be rendered. | ||
540 | $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; | ||
541 | $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn()); | ||
542 | |||
543 | if (// if the user isn't logged in | ||
544 | !$loginManager->isLoggedIn() && | ||
545 | // and Shaarli doesn't have public content... | ||
546 | $conf->get('privacy.hide_public_links') && | ||
547 | // and is configured to enforce the login | ||
548 | $conf->get('privacy.force_login') && | ||
549 | // and the current page isn't already the login page | ||
550 | $targetPage !== Router::$PAGE_LOGIN && | ||
551 | // and the user is not requesting a feed (which would lead to a different content-type as expected) | ||
552 | $targetPage !== Router::$PAGE_FEED_ATOM && | ||
553 | $targetPage !== Router::$PAGE_FEED_RSS | ||
554 | ) { | ||
555 | // force current page to be the login page | ||
556 | $targetPage = Router::$PAGE_LOGIN; | ||
557 | } | ||
558 | |||
559 | // Call plugin hooks for header, footer and includes, specifying which page will be rendered. | ||
560 | // Then assign generated data to RainTPL. | ||
561 | $common_hooks = array( | ||
562 | 'includes', | ||
563 | 'header', | ||
564 | 'footer', | ||
565 | ); | ||
566 | |||
567 | foreach ($common_hooks as $name) { | ||
568 | $plugin_data = array(); | ||
569 | $pluginManager->executeHooks( | ||
570 | 'render_' . $name, | ||
571 | $plugin_data, | ||
572 | array( | ||
573 | 'target' => $targetPage, | ||
574 | 'loggedin' => $loginManager->isLoggedIn() | ||
575 | ) | ||
576 | ); | ||
577 | $PAGE->assign('plugins_' . $name, $plugin_data); | ||
578 | } | ||
579 | |||
580 | // -------- Display login form. | ||
581 | if ($targetPage == Router::$PAGE_LOGIN) { | ||
582 | if ($conf->get('security.open_shaarli')) { | ||
583 | header('Location: ?'); | ||
584 | exit; | ||
585 | } // No need to login for open Shaarli | ||
586 | if (isset($_GET['username'])) { | ||
587 | $PAGE->assign('username', escape($_GET['username'])); | ||
588 | } | ||
589 | $PAGE->assign('returnurl', (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):'')); | ||
590 | // add default state of the 'remember me' checkbox | ||
591 | $PAGE->assign('remember_user_default', $conf->get('privacy.remember_user_default')); | ||
592 | $PAGE->assign('user_can_login', $loginManager->canLogin($_SERVER)); | ||
593 | $PAGE->assign('pagetitle', t('Login') .' - '. $conf->get('general.title', 'Shaarli')); | ||
594 | $PAGE->renderPage('loginform'); | ||
595 | exit; | ||
596 | } | ||
597 | // -------- User wants to logout. | ||
598 | if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) { | ||
599 | invalidateCaches($conf->get('resource.page_cache')); | ||
600 | $sessionManager->logout(); | ||
601 | setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH); | ||
602 | header('Location: ?'); | ||
603 | exit; | ||
604 | } | ||
605 | |||
606 | // -------- Picture wall | ||
607 | if ($targetPage == Router::$PAGE_PICWALL) { | ||
608 | $PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli')); | ||
609 | if (! $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) { | ||
610 | $PAGE->assign('linksToDisplay', []); | ||
611 | $PAGE->renderPage('picwall'); | ||
612 | exit; | ||
613 | } | ||
614 | |||
615 | // Optionally filter the results: | ||
616 | $links = $LINKSDB->filterSearch($_GET); | ||
617 | $linksToDisplay = array(); | ||
618 | |||
619 | // Get only links which have a thumbnail. | ||
620 | // Note: we do not retrieve thumbnails here, the request is too heavy. | ||
621 | foreach ($links as $key => $link) { | ||
622 | if (isset($link['thumbnail']) && $link['thumbnail'] !== false) { | ||
623 | $linksToDisplay[] = $link; // Add to array. | ||
624 | } | ||
625 | } | ||
626 | |||
627 | $data = array( | ||
628 | 'linksToDisplay' => $linksToDisplay, | ||
629 | ); | ||
630 | $pluginManager->executeHooks('render_picwall', $data, array('loggedin' => $loginManager->isLoggedIn())); | ||
631 | |||
632 | foreach ($data as $key => $value) { | ||
633 | $PAGE->assign($key, $value); | ||
634 | } | ||
635 | |||
636 | |||
637 | $PAGE->renderPage('picwall'); | ||
638 | exit; | ||
639 | } | ||
640 | |||
641 | // -------- Tag cloud | ||
642 | if ($targetPage == Router::$PAGE_TAGCLOUD) { | ||
643 | $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; | ||
644 | $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; | ||
645 | $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); | ||
646 | |||
647 | // We sort tags alphabetically, then choose a font size according to count. | ||
648 | // First, find max value. | ||
649 | $maxcount = 0; | ||
650 | foreach ($tags as $value) { | ||
651 | $maxcount = max($maxcount, $value); | ||
652 | } | ||
653 | |||
654 | alphabetical_sort($tags, false, true); | ||
655 | |||
656 | $tagList = array(); | ||
657 | foreach ($tags as $key => $value) { | ||
658 | if (in_array($key, $filteringTags)) { | ||
659 | continue; | ||
660 | } | ||
661 | // Tag font size scaling: | ||
662 | // default 15 and 30 logarithm bases affect scaling, | ||
663 | // 22 and 6 are arbitrary font sizes for max and min sizes. | ||
664 | $size = log($value, 15) / log($maxcount, 30) * 2.2 + 0.8; | ||
665 | $tagList[$key] = array( | ||
666 | 'count' => $value, | ||
667 | 'size' => number_format($size, 2, '.', ''), | ||
668 | ); | ||
669 | } | ||
670 | |||
671 | $searchTags = implode(' ', escape($filteringTags)); | ||
672 | $data = array( | ||
673 | 'search_tags' => $searchTags, | ||
674 | 'tags' => $tagList, | ||
675 | ); | ||
676 | $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => $loginManager->isLoggedIn())); | ||
677 | |||
678 | foreach ($data as $key => $value) { | ||
679 | $PAGE->assign($key, $value); | ||
680 | } | ||
681 | |||
682 | $searchTags = ! empty($searchTags) ? $searchTags .' - ' : ''; | ||
683 | $PAGE->assign('pagetitle', $searchTags. t('Tag cloud') .' - '. $conf->get('general.title', 'Shaarli')); | ||
684 | $PAGE->renderPage('tag.cloud'); | ||
685 | exit; | ||
686 | } | ||
687 | |||
688 | // -------- Tag list | ||
689 | if ($targetPage == Router::$PAGE_TAGLIST) { | ||
690 | $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; | ||
691 | $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; | ||
692 | $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); | ||
693 | foreach ($filteringTags as $tag) { | ||
694 | if (array_key_exists($tag, $tags)) { | ||
695 | unset($tags[$tag]); | ||
696 | } | ||
697 | } | ||
698 | |||
699 | if (! empty($_GET['sort']) && $_GET['sort'] === 'alpha') { | ||
700 | alphabetical_sort($tags, false, true); | ||
701 | } | ||
702 | |||
703 | $searchTags = implode(' ', escape($filteringTags)); | ||
704 | $data = [ | ||
705 | 'search_tags' => $searchTags, | ||
706 | 'tags' => $tags, | ||
707 | ]; | ||
708 | $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => $loginManager->isLoggedIn()]); | ||
709 | |||
710 | foreach ($data as $key => $value) { | ||
711 | $PAGE->assign($key, $value); | ||
712 | } | ||
713 | |||
714 | $searchTags = ! empty($searchTags) ? $searchTags .' - ' : ''; | ||
715 | $PAGE->assign('pagetitle', $searchTags . t('Tag list') .' - '. $conf->get('general.title', 'Shaarli')); | ||
716 | $PAGE->renderPage('tag.list'); | ||
717 | exit; | ||
718 | } | ||
719 | |||
720 | // Daily page. | ||
721 | if ($targetPage == Router::$PAGE_DAILY) { | ||
722 | showDaily($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager); | ||
723 | } | ||
724 | |||
725 | // ATOM and RSS feed. | ||
726 | if ($targetPage == Router::$PAGE_FEED_ATOM || $targetPage == Router::$PAGE_FEED_RSS) { | ||
727 | $feedType = $targetPage == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM; | ||
728 | header('Content-Type: application/'. $feedType .'+xml; charset=utf-8'); | ||
729 | |||
730 | // Cache system | ||
731 | $query = $_SERVER['QUERY_STRING']; | ||
732 | $cache = new CachedPage( | ||
733 | $conf->get('resource.page_cache'), | ||
734 | page_url($_SERVER), | ||
735 | startsWith($query, 'do='. $targetPage) && !$loginManager->isLoggedIn() | ||
736 | ); | ||
737 | $cached = $cache->cachedVersion(); | ||
738 | if (!empty($cached)) { | ||
739 | echo $cached; | ||
740 | exit; | ||
741 | } | ||
742 | |||
743 | // Generate data. | ||
744 | $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, $loginManager->isLoggedIn()); | ||
745 | $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); | ||
746 | $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !$loginManager->isLoggedIn()); | ||
747 | $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks')); | ||
748 | $data = $feedGenerator->buildData(); | ||
749 | |||
750 | // Process plugin hook. | ||
751 | $pluginManager->executeHooks('render_feed', $data, array( | ||
752 | 'loggedin' => $loginManager->isLoggedIn(), | ||
753 | 'target' => $targetPage, | ||
754 | )); | ||
755 | |||
756 | // Render the template. | ||
757 | $PAGE->assignAll($data); | ||
758 | $PAGE->renderPage('feed.'. $feedType); | ||
759 | $cache->cache(ob_get_contents()); | ||
760 | ob_end_flush(); | ||
761 | exit; | ||
762 | } | ||
763 | |||
764 | // Display opensearch plugin (XML) | ||
765 | if ($targetPage == Router::$PAGE_OPENSEARCH) { | ||
766 | header('Content-Type: application/xml; charset=utf-8'); | ||
767 | $PAGE->assign('serverurl', index_url($_SERVER)); | ||
768 | $PAGE->renderPage('opensearch'); | ||
769 | exit; | ||
770 | } | ||
771 | |||
772 | // -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...) | ||
773 | if (isset($_GET['addtag'])) { | ||
774 | // Get previous URL (http_referer) and add the tag to the searchtags parameters in query. | ||
775 | if (empty($_SERVER['HTTP_REFERER'])) { | ||
776 | // In case browser does not send HTTP_REFERER | ||
777 | header('Location: ?searchtags='.urlencode($_GET['addtag'])); | ||
778 | exit; | ||
779 | } | ||
780 | parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $params); | ||
781 | |||
782 | // Prevent redirection loop | ||
783 | if (isset($params['addtag'])) { | ||
784 | unset($params['addtag']); | ||
785 | } | ||
786 | |||
787 | // Check if this tag is already in the search query and ignore it if it is. | ||
788 | // Each tag is always separated by a space | ||
789 | if (isset($params['searchtags'])) { | ||
790 | $current_tags = explode(' ', $params['searchtags']); | ||
791 | } else { | ||
792 | $current_tags = array(); | ||
793 | } | ||
794 | $addtag = true; | ||
795 | foreach ($current_tags as $value) { | ||
796 | if ($value === $_GET['addtag']) { | ||
797 | $addtag = false; | ||
798 | break; | ||
799 | } | ||
800 | } | ||
801 | // Append the tag if necessary | ||
802 | if (empty($params['searchtags'])) { | ||
803 | $params['searchtags'] = trim($_GET['addtag']); | ||
804 | } elseif ($addtag) { | ||
805 | $params['searchtags'] = trim($params['searchtags']).' '.trim($_GET['addtag']); | ||
806 | } | ||
807 | |||
808 | // We also remove page (keeping the same page has no sense, since the | ||
809 | // results are different) | ||
810 | unset($params['page']); | ||
811 | |||
812 | header('Location: ?'.http_build_query($params)); | ||
813 | exit; | ||
814 | } | ||
815 | |||
816 | // -------- User clicks on a tag in result count: Remove the tag from the list of searched tags (searchtags=...) | ||
817 | if (isset($_GET['removetag'])) { | ||
818 | // Get previous URL (http_referer) and remove the tag from the searchtags parameters in query. | ||
819 | if (empty($_SERVER['HTTP_REFERER'])) { | ||
820 | header('Location: ?'); | ||
821 | exit; | ||
822 | } | ||
823 | |||
824 | // In case browser does not send HTTP_REFERER | ||
825 | parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $params); | ||
826 | |||
827 | // Prevent redirection loop | ||
828 | if (isset($params['removetag'])) { | ||
829 | unset($params['removetag']); | ||
830 | } | ||
831 | |||
832 | if (isset($params['searchtags'])) { | ||
833 | $tags = explode(' ', $params['searchtags']); | ||
834 | // Remove value from array $tags. | ||
835 | $tags = array_diff($tags, array($_GET['removetag'])); | ||
836 | $params['searchtags'] = implode(' ', $tags); | ||
837 | |||
838 | if (empty($params['searchtags'])) { | ||
839 | unset($params['searchtags']); | ||
840 | } | ||
841 | |||
842 | // We also remove page (keeping the same page has no sense, since | ||
843 | // the results are different) | ||
844 | unset($params['page']); | ||
845 | } | ||
846 | header('Location: ?'.http_build_query($params)); | ||
847 | exit; | ||
848 | } | ||
849 | |||
850 | // -------- User wants to change the number of links per page (linksperpage=...) | ||
851 | if (isset($_GET['linksperpage'])) { | ||
852 | if (is_numeric($_GET['linksperpage'])) { | ||
853 | $_SESSION['LINKS_PER_PAGE']=abs(intval($_GET['linksperpage'])); | ||
854 | } | ||
855 | |||
856 | if (! empty($_SERVER['HTTP_REFERER'])) { | ||
857 | $location = generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('linksperpage')); | ||
858 | } else { | ||
859 | $location = '?'; | ||
860 | } | ||
861 | header('Location: '. $location); | ||
862 | exit; | ||
863 | } | ||
864 | |||
865 | // -------- User wants to see only private links (toggle) | ||
866 | if (isset($_GET['visibility'])) { | ||
867 | if ($_GET['visibility'] === 'private') { | ||
868 | // Visibility not set or not already private, set private, otherwise reset it | ||
869 | if (empty($_SESSION['visibility']) || $_SESSION['visibility'] !== 'private') { | ||
870 | // See only private links | ||
871 | $_SESSION['visibility'] = 'private'; | ||
872 | } else { | ||
873 | unset($_SESSION['visibility']); | ||
874 | } | ||
875 | } elseif ($_GET['visibility'] === 'public') { | ||
876 | if (empty($_SESSION['visibility']) || $_SESSION['visibility'] !== 'public') { | ||
877 | // See only public links | ||
878 | $_SESSION['visibility'] = 'public'; | ||
879 | } else { | ||
880 | unset($_SESSION['visibility']); | ||
881 | } | ||
882 | } | ||
883 | |||
884 | if (! empty($_SERVER['HTTP_REFERER'])) { | ||
885 | $location = generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('visibility')); | ||
886 | } else { | ||
887 | $location = '?'; | ||
888 | } | ||
889 | header('Location: '. $location); | ||
890 | exit; | ||
891 | } | ||
892 | |||
893 | // -------- User wants to see only untagged links (toggle) | ||
894 | if (isset($_GET['untaggedonly'])) { | ||
895 | $_SESSION['untaggedonly'] = empty($_SESSION['untaggedonly']); | ||
896 | |||
897 | if (! empty($_SERVER['HTTP_REFERER'])) { | ||
898 | $location = generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('untaggedonly')); | ||
899 | } else { | ||
900 | $location = '?'; | ||
901 | } | ||
902 | header('Location: '. $location); | ||
903 | exit; | ||
904 | } | ||
905 | |||
906 | // -------- Handle other actions allowed for non-logged in users: | ||
907 | if (!$loginManager->isLoggedIn()) { | ||
908 | // User tries to post new link but is not logged in: | ||
909 | // Show login screen, then redirect to ?post=... | ||
910 | if (isset($_GET['post'])) { | ||
911 | header( // Redirect to login page, then back to post link. | ||
912 | 'Location: ?do=login&post='.urlencode($_GET['post']). | ||
913 | (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):''). | ||
914 | (!empty($_GET['description'])?'&description='.urlencode($_GET['description']):''). | ||
915 | (!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):''). | ||
916 | (!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'') | ||
917 | ); | ||
918 | exit; | ||
919 | } | ||
920 | |||
921 | showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager); | ||
922 | if (isset($_GET['edit_link'])) { | ||
923 | header('Location: ?do=login&edit_link='. escape($_GET['edit_link'])); | ||
924 | exit; | ||
925 | } | ||
926 | |||
927 | exit; // Never remove this one! All operations below are reserved for logged in user. | ||
928 | } | ||
929 | |||
930 | // -------- All other functions are reserved for the registered user: | ||
931 | |||
932 | // -------- Display the Tools menu if requested (import/export/bookmarklet...) | ||
933 | if ($targetPage == Router::$PAGE_TOOLS) { | ||
934 | $data = [ | ||
935 | 'pageabsaddr' => index_url($_SERVER), | ||
936 | 'sslenabled' => is_https($_SERVER), | ||
937 | ]; | ||
938 | $pluginManager->executeHooks('render_tools', $data); | ||
939 | |||
940 | foreach ($data as $key => $value) { | ||
941 | $PAGE->assign($key, $value); | ||
942 | } | ||
943 | |||
944 | $PAGE->assign('pagetitle', t('Tools') .' - '. $conf->get('general.title', 'Shaarli')); | ||
945 | $PAGE->renderPage('tools'); | ||
946 | exit; | ||
947 | } | ||
948 | |||
949 | // -------- User wants to change his/her password. | ||
950 | if ($targetPage == Router::$PAGE_CHANGEPASSWORD) { | ||
951 | if ($conf->get('security.open_shaarli')) { | ||
952 | die(t('You are not supposed to change a password on an Open Shaarli.')); | ||
953 | } | ||
954 | |||
955 | if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) { | ||
956 | if (!$sessionManager->checkToken($_POST['token'])) { | ||
957 | die(t('Wrong token.')); // Go away! | ||
958 | } | ||
959 | |||
960 | // Make sure old password is correct. | ||
961 | $oldhash = sha1( | ||
962 | $_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt') | ||
963 | ); | ||
964 | if ($oldhash != $conf->get('credentials.hash')) { | ||
965 | echo '<script>alert("' | ||
966 | . t('The old password is not correct.') | ||
967 | .'");document.location=\'?do=changepasswd\';</script>'; | ||
968 | exit; | ||
969 | } | ||
970 | // Save new password | ||
971 | // Salt renders rainbow-tables attacks useless. | ||
972 | $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); | ||
973 | $conf->set( | ||
974 | 'credentials.hash', | ||
975 | sha1( | ||
976 | $_POST['setpassword'] | ||
977 | . $conf->get('credentials.login') | ||
978 | . $conf->get('credentials.salt') | ||
979 | ) | ||
980 | ); | ||
981 | try { | ||
982 | $conf->write($loginManager->isLoggedIn()); | ||
983 | } catch (Exception $e) { | ||
984 | error_log( | ||
985 | 'ERROR while writing config file after changing password.' . PHP_EOL . | ||
986 | $e->getMessage() | ||
987 | ); | ||
988 | |||
989 | // TODO: do not handle exceptions/errors in JS. | ||
990 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>'; | ||
991 | exit; | ||
992 | } | ||
993 | echo '<script>alert("'. t('Your password has been changed') .'");document.location=\'?do=tools\';</script>'; | ||
994 | exit; | ||
995 | } else { | ||
996 | // show the change password form. | ||
997 | $PAGE->assign('pagetitle', t('Change password') .' - '. $conf->get('general.title', 'Shaarli')); | ||
998 | $PAGE->renderPage('changepassword'); | ||
999 | exit; | ||
1000 | } | ||
1001 | } | ||
1002 | |||
1003 | // -------- User wants to change configuration | ||
1004 | if ($targetPage == Router::$PAGE_CONFIGURE) { | ||
1005 | if (!empty($_POST['title'])) { | ||
1006 | if (!$sessionManager->checkToken($_POST['token'])) { | ||
1007 | die(t('Wrong token.')); // Go away! | ||
1008 | } | ||
1009 | $tz = 'UTC'; | ||
1010 | if (!empty($_POST['continent']) && !empty($_POST['city']) | ||
1011 | && isTimeZoneValid($_POST['continent'], $_POST['city']) | ||
1012 | ) { | ||
1013 | $tz = $_POST['continent'] . '/' . $_POST['city']; | ||
1014 | } | ||
1015 | $conf->set('general.timezone', $tz); | ||
1016 | $conf->set('general.title', escape($_POST['title'])); | ||
1017 | $conf->set('general.header_link', escape($_POST['titleLink'])); | ||
1018 | $conf->set('general.retrieve_description', !empty($_POST['retrieveDescription'])); | ||
1019 | $conf->set('resource.theme', escape($_POST['theme'])); | ||
1020 | $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection'])); | ||
1021 | $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault'])); | ||
1022 | $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks'])); | ||
1023 | $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); | ||
1024 | $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); | ||
1025 | $conf->set('api.enabled', !empty($_POST['enableApi'])); | ||
1026 | $conf->set('api.secret', escape($_POST['apiSecret'])); | ||
1027 | $conf->set('translation.language', escape($_POST['language'])); | ||
1028 | |||
1029 | $thumbnailsMode = extension_loaded('gd') ? $_POST['enableThumbnails'] : Thumbnailer::MODE_NONE; | ||
1030 | if ($thumbnailsMode !== Thumbnailer::MODE_NONE | ||
1031 | && $thumbnailsMode !== $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) | ||
1032 | ) { | ||
1033 | $_SESSION['warnings'][] = t( | ||
1034 | 'You have enabled or changed thumbnails mode. ' | ||
1035 | .'<a href="?do=thumbs_update">Please synchronize them</a>.' | ||
1036 | ); | ||
1037 | } | ||
1038 | $conf->set('thumbnails.mode', $thumbnailsMode); | ||
1039 | |||
1040 | try { | ||
1041 | $conf->write($loginManager->isLoggedIn()); | ||
1042 | $history->updateSettings(); | ||
1043 | invalidateCaches($conf->get('resource.page_cache')); | ||
1044 | } catch (Exception $e) { | ||
1045 | error_log( | ||
1046 | 'ERROR while writing config file after configuration update.' . PHP_EOL . | ||
1047 | $e->getMessage() | ||
1048 | ); | ||
1049 | |||
1050 | // TODO: do not handle exceptions/errors in JS. | ||
1051 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=configure\';</script>'; | ||
1052 | exit; | ||
1053 | } | ||
1054 | echo '<script>alert("'. t('Configuration was saved.') .'");document.location=\'?do=configure\';</script>'; | ||
1055 | exit; | ||
1056 | } else { | ||
1057 | // Show the configuration form. | ||
1058 | $PAGE->assign('title', $conf->get('general.title')); | ||
1059 | $PAGE->assign('theme', $conf->get('resource.theme')); | ||
1060 | $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); | ||
1061 | list($continents, $cities) = generateTimeZoneData( | ||
1062 | timezone_identifiers_list(), | ||
1063 | $conf->get('general.timezone') | ||
1064 | ); | ||
1065 | $PAGE->assign('continents', $continents); | ||
1066 | $PAGE->assign('cities', $cities); | ||
1067 | $PAGE->assign('retrieve_description', $conf->get('general.retrieve_description')); | ||
1068 | $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false)); | ||
1069 | $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false)); | ||
1070 | $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); | ||
1071 | $PAGE->assign('enable_update_check', $conf->get('updates.check_updates', true)); | ||
1072 | $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); | ||
1073 | $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); | ||
1074 | $PAGE->assign('api_secret', $conf->get('api.secret')); | ||
1075 | $PAGE->assign('languages', Languages::getAvailableLanguages()); | ||
1076 | $PAGE->assign('gd_enabled', extension_loaded('gd')); | ||
1077 | $PAGE->assign('thumbnails_mode', $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)); | ||
1078 | $PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1079 | $PAGE->renderPage('configure'); | ||
1080 | exit; | ||
1081 | } | ||
1082 | } | ||
1083 | |||
1084 | // -------- User wants to rename a tag or delete it | ||
1085 | if ($targetPage == Router::$PAGE_CHANGETAG) { | ||
1086 | if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { | ||
1087 | $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : ''); | ||
1088 | $PAGE->assign('pagetitle', t('Manage tags') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1089 | $PAGE->renderPage('changetag'); | ||
1090 | exit; | ||
1091 | } | ||
1092 | |||
1093 | if (!$sessionManager->checkToken($_POST['token'])) { | ||
1094 | die(t('Wrong token.')); | ||
1095 | } | ||
1096 | |||
1097 | $toTag = isset($_POST['totag']) ? escape($_POST['totag']) : null; | ||
1098 | $alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), $toTag); | ||
1099 | $LINKSDB->save($conf->get('resource.page_cache')); | ||
1100 | foreach ($alteredLinks as $link) { | ||
1101 | $history->updateLink($link); | ||
1102 | } | ||
1103 | $delete = empty($_POST['totag']); | ||
1104 | $redirect = $delete ? 'do=changetag' : 'searchtags='. urlencode(escape($_POST['totag'])); | ||
1105 | $count = count($alteredLinks); | ||
1106 | $alert = $delete | ||
1107 | ? sprintf(t('The tag was removed from %d link.', 'The tag was removed from %d links.', $count), $count) | ||
1108 | : sprintf(t('The tag was renamed in %d link.', 'The tag was renamed in %d links.', $count), $count); | ||
1109 | echo '<script>alert("'. $alert .'");document.location=\'?'. $redirect .'\';</script>'; | ||
1110 | exit; | ||
1111 | } | ||
1112 | |||
1113 | // -------- User wants to add a link without using the bookmarklet: Show form. | ||
1114 | if ($targetPage == Router::$PAGE_ADDLINK) { | ||
1115 | $PAGE->assign('pagetitle', t('Shaare a new link') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1116 | $PAGE->renderPage('addlink'); | ||
1117 | exit; | ||
1118 | } | ||
1119 | |||
1120 | // -------- User clicked the "Save" button when editing a link: Save link to database. | ||
1121 | if (isset($_POST['save_edit'])) { | ||
1122 | // Go away! | ||
1123 | if (! $sessionManager->checkToken($_POST['token'])) { | ||
1124 | die(t('Wrong token.')); | ||
1125 | } | ||
1126 | |||
1127 | // lf_id should only be present if the link exists. | ||
1128 | $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId(); | ||
1129 | $link['id'] = $id; | ||
1130 | // Linkdate is kept here to: | ||
1131 | // - use the same permalink for notes as they're displayed when creating them | ||
1132 | // - let users hack creation date of their posts | ||
1133 | // See: https://shaarli.readthedocs.io/en/master/guides/various-hacks/#changing-the-timestamp-for-a-shaare | ||
1134 | $linkdate = escape($_POST['lf_linkdate']); | ||
1135 | $link['created'] = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); | ||
1136 | if (isset($LINKSDB[$id])) { | ||
1137 | // Edit | ||
1138 | $link['updated'] = new DateTime(); | ||
1139 | $link['shorturl'] = $LINKSDB[$id]['shorturl']; | ||
1140 | $link['sticky'] = isset($LINKSDB[$id]['sticky']) ? $LINKSDB[$id]['sticky'] : false; | ||
1141 | $new = false; | ||
1142 | } else { | ||
1143 | // New link | ||
1144 | $link['updated'] = null; | ||
1145 | $link['shorturl'] = link_small_hash($link['created'], $id); | ||
1146 | $link['sticky'] = false; | ||
1147 | $new = true; | ||
1148 | } | ||
1149 | |||
1150 | // Remove multiple spaces. | ||
1151 | $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags'])); | ||
1152 | // Remove first '-' char in tags. | ||
1153 | $tags = preg_replace('/(^| )\-/', '$1', $tags); | ||
1154 | // Remove duplicates. | ||
1155 | $tags = implode(' ', array_unique(explode(' ', $tags))); | ||
1156 | |||
1157 | if (empty(trim($_POST['lf_url']))) { | ||
1158 | $_POST['lf_url'] = '?' . smallHash($linkdate . $id); | ||
1159 | } | ||
1160 | $url = whitelist_protocols(trim($_POST['lf_url']), $conf->get('security.allowed_protocols')); | ||
1161 | |||
1162 | $link = array_merge($link, [ | ||
1163 | 'title' => trim($_POST['lf_title']), | ||
1164 | 'url' => $url, | ||
1165 | 'description' => $_POST['lf_description'], | ||
1166 | 'private' => (isset($_POST['lf_private']) ? 1 : 0), | ||
1167 | 'tags' => str_replace(',', ' ', $tags), | ||
1168 | ]); | ||
1169 | |||
1170 | // If title is empty, use the URL as title. | ||
1171 | if ($link['title'] == '') { | ||
1172 | $link['title'] = $link['url']; | ||
1173 | } | ||
1174 | |||
1175 | if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE | ||
1176 | && ! is_note($link['url']) | ||
1177 | ) { | ||
1178 | $thumbnailer = new Thumbnailer($conf); | ||
1179 | $link['thumbnail'] = $thumbnailer->get($url); | ||
1180 | } | ||
1181 | |||
1182 | $link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false; | ||
1183 | |||
1184 | $pluginManager->executeHooks('save_link', $link); | ||
1185 | |||
1186 | $LINKSDB[$id] = $link; | ||
1187 | $LINKSDB->save($conf->get('resource.page_cache')); | ||
1188 | if ($new) { | ||
1189 | $history->addLink($link); | ||
1190 | } else { | ||
1191 | $history->updateLink($link); | ||
1192 | } | ||
1193 | |||
1194 | // If we are called from the bookmarklet, we must close the popup: | ||
1195 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { | ||
1196 | echo '<script>self.close();</script>'; | ||
1197 | exit; | ||
1198 | } | ||
1199 | |||
1200 | $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?'; | ||
1201 | $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); | ||
1202 | // Scroll to the link which has been edited. | ||
1203 | $location .= '#' . $link['shorturl']; | ||
1204 | // After saving the link, redirect to the page the user was on. | ||
1205 | header('Location: '. $location); | ||
1206 | exit; | ||
1207 | } | ||
1208 | |||
1209 | // -------- User clicked the "Cancel" button when editing a link. | ||
1210 | if (isset($_POST['cancel_edit'])) { | ||
1211 | $id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : false; | ||
1212 | if (! isset($LINKSDB[$id])) { | ||
1213 | header('Location: ?'); | ||
1214 | } | ||
1215 | // If we are called from the bookmarklet, we must close the popup: | ||
1216 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { | ||
1217 | echo '<script>self.close();</script>'; | ||
1218 | exit; | ||
1219 | } | ||
1220 | $link = $LINKSDB[$id]; | ||
1221 | $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); | ||
1222 | // Scroll to the link which has been edited. | ||
1223 | $returnurl .= '#'. $link['shorturl']; | ||
1224 | $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); | ||
1225 | header('Location: '.$returnurl); // After canceling, redirect to the page the user was on. | ||
1226 | exit; | ||
1227 | } | ||
1228 | |||
1229 | // -------- User clicked the "Delete" button when editing a link: Delete link from database. | ||
1230 | if ($targetPage == Router::$PAGE_DELETELINK) { | ||
1231 | if (! $sessionManager->checkToken($_GET['token'])) { | ||
1232 | die(t('Wrong token.')); | ||
1233 | } | ||
1234 | |||
1235 | $ids = trim($_GET['lf_linkdate']); | ||
1236 | if (strpos($ids, ' ') !== false) { | ||
1237 | // multiple, space-separated ids provided | ||
1238 | $ids = array_values(array_filter(preg_split('/\s+/', escape($ids)))); | ||
1239 | } else { | ||
1240 | // only a single id provided | ||
1241 | $ids = [$ids]; | ||
1242 | } | ||
1243 | // assert at least one id is given | ||
1244 | if (!count($ids)) { | ||
1245 | die('no id provided'); | ||
1246 | } | ||
1247 | foreach ($ids as $id) { | ||
1248 | $id = (int) escape($id); | ||
1249 | $link = $LINKSDB[$id]; | ||
1250 | $pluginManager->executeHooks('delete_link', $link); | ||
1251 | $history->deleteLink($link); | ||
1252 | unset($LINKSDB[$id]); | ||
1253 | } | ||
1254 | $LINKSDB->save($conf->get('resource.page_cache')); // save to disk | ||
1255 | |||
1256 | // If we are called from the bookmarklet, we must close the popup: | ||
1257 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { | ||
1258 | echo '<script>self.close();</script>'; | ||
1259 | exit; | ||
1260 | } | ||
1261 | |||
1262 | $location = '?'; | ||
1263 | if (isset($_SERVER['HTTP_REFERER'])) { | ||
1264 | // Don't redirect to where we were previously if it was a permalink or an edit_link, because it would 404. | ||
1265 | $location = generateLocation( | ||
1266 | $_SERVER['HTTP_REFERER'], | ||
1267 | $_SERVER['HTTP_HOST'], | ||
1268 | ['delete_link', 'edit_link', $link['shorturl']] | ||
1269 | ); | ||
1270 | } | ||
1271 | |||
1272 | header('Location: ' . $location); // After deleting the link, redirect to appropriate location | ||
1273 | exit; | ||
1274 | } | ||
1275 | |||
1276 | // -------- User clicked either "Set public" or "Set private" bulk operation | ||
1277 | if ($targetPage == Router::$PAGE_CHANGE_VISIBILITY) { | ||
1278 | if (! $sessionManager->checkToken($_GET['token'])) { | ||
1279 | die(t('Wrong token.')); | ||
1280 | } | ||
1281 | |||
1282 | $ids = trim($_GET['ids']); | ||
1283 | if (strpos($ids, ' ') !== false) { | ||
1284 | // multiple, space-separated ids provided | ||
1285 | $ids = array_values(array_filter(preg_split('/\s+/', escape($ids)))); | ||
1286 | } else { | ||
1287 | // only a single id provided | ||
1288 | $ids = [$ids]; | ||
1289 | } | ||
1290 | |||
1291 | // assert at least one id is given | ||
1292 | if (!count($ids)) { | ||
1293 | die('no id provided'); | ||
1294 | } | ||
1295 | // assert that the visibility is valid | ||
1296 | if (!isset($_GET['newVisibility']) || !in_array($_GET['newVisibility'], ['public', 'private'])) { | ||
1297 | die('invalid visibility'); | ||
1298 | } else { | ||
1299 | $private = $_GET['newVisibility'] === 'private'; | ||
1300 | } | ||
1301 | foreach ($ids as $id) { | ||
1302 | $id = (int) escape($id); | ||
1303 | $link = $LINKSDB[$id]; | ||
1304 | $link['private'] = $private; | ||
1305 | $pluginManager->executeHooks('save_link', $link); | ||
1306 | $LINKSDB[$id] = $link; | ||
1307 | } | ||
1308 | $LINKSDB->save($conf->get('resource.page_cache')); // save to disk | ||
1309 | |||
1310 | $location = '?'; | ||
1311 | if (isset($_SERVER['HTTP_REFERER'])) { | ||
1312 | $location = generateLocation( | ||
1313 | $_SERVER['HTTP_REFERER'], | ||
1314 | $_SERVER['HTTP_HOST'] | ||
1315 | ); | ||
1316 | } | ||
1317 | header('Location: ' . $location); // After deleting the link, redirect to appropriate location | ||
1318 | exit; | ||
1319 | } | ||
1320 | |||
1321 | // -------- User clicked the "EDIT" button on a link: Display link edit form. | ||
1322 | if (isset($_GET['edit_link'])) { | ||
1323 | $id = (int) escape($_GET['edit_link']); | ||
1324 | $link = $LINKSDB[$id]; // Read database | ||
1325 | if (!$link) { | ||
1326 | header('Location: ?'); | ||
1327 | exit; | ||
1328 | } // Link not found in database. | ||
1329 | $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT); | ||
1330 | $data = array( | ||
1331 | 'link' => $link, | ||
1332 | 'link_is_new' => false, | ||
1333 | 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), | ||
1334 | 'tags' => $LINKSDB->linksCountPerTag(), | ||
1335 | ); | ||
1336 | $pluginManager->executeHooks('render_editlink', $data); | ||
1337 | |||
1338 | foreach ($data as $key => $value) { | ||
1339 | $PAGE->assign($key, $value); | ||
1340 | } | ||
1341 | |||
1342 | $PAGE->assign('pagetitle', t('Edit') .' '. t('Shaare') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1343 | $PAGE->renderPage('editlink'); | ||
1344 | exit; | ||
1345 | } | ||
1346 | |||
1347 | // -------- User want to post a new link: Display link edit form. | ||
1348 | if (isset($_GET['post'])) { | ||
1349 | $url = cleanup_url($_GET['post']); | ||
1350 | |||
1351 | $link_is_new = false; | ||
1352 | // Check if URL is not already in database (in this case, we will edit the existing link) | ||
1353 | $link = $LINKSDB->getLinkFromUrl($url); | ||
1354 | if (! $link) { | ||
1355 | $link_is_new = true; | ||
1356 | $linkdate = strval(date(LinkDB::LINK_DATE_FORMAT)); | ||
1357 | // Get title if it was provided in URL (by the bookmarklet). | ||
1358 | $title = empty($_GET['title']) ? '' : escape($_GET['title']); | ||
1359 | // Get description if it was provided in URL (by the bookmarklet). [Bronco added that] | ||
1360 | $description = empty($_GET['description']) ? '' : escape($_GET['description']); | ||
1361 | $tags = empty($_GET['tags']) ? '' : escape($_GET['tags']); | ||
1362 | $private = !empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0; | ||
1363 | |||
1364 | // If this is an HTTP(S) link, we try go get the page to extract | ||
1365 | // the title (otherwise we will to straight to the edit form.) | ||
1366 | if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { | ||
1367 | $retrieveDescription = $conf->get('general.retrieve_description'); | ||
1368 | // Short timeout to keep the application responsive | ||
1369 | // The callback will fill $charset and $title with data from the downloaded page. | ||
1370 | get_http_response( | ||
1371 | $url, | ||
1372 | $conf->get('general.download_timeout', 30), | ||
1373 | $conf->get('general.download_max_size', 4194304), | ||
1374 | get_curl_download_callback($charset, $title, $description, $tags, $retrieveDescription) | ||
1375 | ); | ||
1376 | if (! empty($title) && strtolower($charset) != 'utf-8') { | ||
1377 | $title = mb_convert_encoding($title, 'utf-8', $charset); | ||
1378 | } | ||
1379 | } | ||
1380 | |||
1381 | if ($url == '') { | ||
1382 | $url = '?' . smallHash($linkdate . $LINKSDB->getNextId()); | ||
1383 | $title = $conf->get('general.default_note_title', t('Note: ')); | ||
1384 | } | ||
1385 | $url = escape($url); | ||
1386 | $title = escape($title); | ||
1387 | |||
1388 | $link = array( | ||
1389 | 'linkdate' => $linkdate, | ||
1390 | 'title' => $title, | ||
1391 | 'url' => $url, | ||
1392 | 'description' => $description, | ||
1393 | 'tags' => $tags, | ||
1394 | 'private' => $private, | ||
1395 | ); | ||
1396 | } else { | ||
1397 | $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT); | ||
1398 | } | ||
1399 | |||
1400 | $data = array( | ||
1401 | 'link' => $link, | ||
1402 | 'link_is_new' => $link_is_new, | ||
1403 | 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), | ||
1404 | 'source' => (isset($_GET['source']) ? $_GET['source'] : ''), | ||
1405 | 'tags' => $LINKSDB->linksCountPerTag(), | ||
1406 | 'default_private_links' => $conf->get('privacy.default_private_links', false), | ||
1407 | ); | ||
1408 | $pluginManager->executeHooks('render_editlink', $data); | ||
1409 | |||
1410 | foreach ($data as $key => $value) { | ||
1411 | $PAGE->assign($key, $value); | ||
1412 | } | ||
1413 | |||
1414 | $PAGE->assign('pagetitle', t('Shaare') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1415 | $PAGE->renderPage('editlink'); | ||
1416 | exit; | ||
1417 | } | ||
1418 | |||
1419 | if ($targetPage == Router::$PAGE_PINLINK) { | ||
1420 | if (! isset($_GET['id']) || empty($LINKSDB[$_GET['id']])) { | ||
1421 | // FIXME! Use a proper error system. | ||
1422 | $msg = t('Invalid link ID provided'); | ||
1423 | echo '<script>alert("'. $msg .'");document.location=\''. index_url($_SERVER) .'\';</script>'; | ||
1424 | exit; | ||
1425 | } | ||
1426 | if (! $sessionManager->checkToken($_GET['token'])) { | ||
1427 | die('Wrong token.'); | ||
1428 | } | ||
1429 | |||
1430 | $link = $LINKSDB[$_GET['id']]; | ||
1431 | $link['sticky'] = ! $link['sticky']; | ||
1432 | $LINKSDB[(int) $_GET['id']] = $link; | ||
1433 | $LINKSDB->save($conf->get('resource.page_cache')); | ||
1434 | header('Location: '.index_url($_SERVER)); | ||
1435 | exit; | ||
1436 | } | ||
1437 | |||
1438 | if ($targetPage == Router::$PAGE_EXPORT) { | ||
1439 | // Export links as a Netscape Bookmarks file | ||
1440 | |||
1441 | if (empty($_GET['selection'])) { | ||
1442 | $PAGE->assign('pagetitle', t('Export') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1443 | $PAGE->renderPage('export'); | ||
1444 | exit; | ||
1445 | } | ||
1446 | |||
1447 | // export as bookmarks_(all|private|public)_YYYYmmdd_HHMMSS.html | ||
1448 | $selection = $_GET['selection']; | ||
1449 | if (isset($_GET['prepend_note_url'])) { | ||
1450 | $prependNoteUrl = $_GET['prepend_note_url']; | ||
1451 | } else { | ||
1452 | $prependNoteUrl = false; | ||
1453 | } | ||
1454 | |||
1455 | try { | ||
1456 | $PAGE->assign( | ||
1457 | 'links', | ||
1458 | NetscapeBookmarkUtils::filterAndFormat( | ||
1459 | $LINKSDB, | ||
1460 | $selection, | ||
1461 | $prependNoteUrl, | ||
1462 | index_url($_SERVER) | ||
1463 | ) | ||
1464 | ); | ||
1465 | } catch (Exception $exc) { | ||
1466 | header('Content-Type: text/plain; charset=utf-8'); | ||
1467 | echo $exc->getMessage(); | ||
1468 | exit; | ||
1469 | } | ||
1470 | $now = new DateTime(); | ||
1471 | header('Content-Type: text/html; charset=utf-8'); | ||
1472 | header( | ||
1473 | 'Content-disposition: attachment; filename=bookmarks_' | ||
1474 | .$selection.'_'.$now->format(LinkDB::LINK_DATE_FORMAT).'.html' | ||
1475 | ); | ||
1476 | $PAGE->assign('date', $now->format(DateTime::RFC822)); | ||
1477 | $PAGE->assign('eol', PHP_EOL); | ||
1478 | $PAGE->assign('selection', $selection); | ||
1479 | $PAGE->renderPage('export.bookmarks'); | ||
1480 | exit; | ||
1481 | } | ||
1482 | |||
1483 | if ($targetPage == Router::$PAGE_IMPORT) { | ||
1484 | // Upload a Netscape bookmark dump to import its contents | ||
1485 | |||
1486 | if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) { | ||
1487 | // Show import dialog | ||
1488 | $PAGE->assign( | ||
1489 | 'maxfilesize', | ||
1490 | get_max_upload_size( | ||
1491 | ini_get('post_max_size'), | ||
1492 | ini_get('upload_max_filesize'), | ||
1493 | false | ||
1494 | ) | ||
1495 | ); | ||
1496 | $PAGE->assign( | ||
1497 | 'maxfilesizeHuman', | ||
1498 | get_max_upload_size( | ||
1499 | ini_get('post_max_size'), | ||
1500 | ini_get('upload_max_filesize'), | ||
1501 | true | ||
1502 | ) | ||
1503 | ); | ||
1504 | $PAGE->assign('pagetitle', t('Import') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1505 | $PAGE->renderPage('import'); | ||
1506 | exit; | ||
1507 | } | ||
1508 | |||
1509 | // Import bookmarks from an uploaded file | ||
1510 | if (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size'] == 0) { | ||
1511 | // The file is too big or some form field may be missing. | ||
1512 | $msg = sprintf( | ||
1513 | t( | ||
1514 | 'The file you are trying to upload is probably bigger than what this webserver can accept' | ||
1515 | .' (%s). Please upload in smaller chunks.' | ||
1516 | ), | ||
1517 | get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')) | ||
1518 | ); | ||
1519 | echo '<script>alert("'. $msg .'");document.location=\'?do='.Router::$PAGE_IMPORT .'\';</script>'; | ||
1520 | exit; | ||
1521 | } | ||
1522 | if (! $sessionManager->checkToken($_POST['token'])) { | ||
1523 | die('Wrong token.'); | ||
1524 | } | ||
1525 | $status = NetscapeBookmarkUtils::import( | ||
1526 | $_POST, | ||
1527 | $_FILES, | ||
1528 | $LINKSDB, | ||
1529 | $conf, | ||
1530 | $history | ||
1531 | ); | ||
1532 | echo '<script>alert("'.$status.'");document.location=\'?do=' | ||
1533 | .Router::$PAGE_IMPORT .'\';</script>'; | ||
1534 | exit; | ||
1535 | } | ||
1536 | |||
1537 | // Plugin administration page | ||
1538 | if ($targetPage == Router::$PAGE_PLUGINSADMIN) { | ||
1539 | $pluginMeta = $pluginManager->getPluginsMeta(); | ||
1540 | |||
1541 | // Split plugins into 2 arrays: ordered enabled plugins and disabled. | ||
1542 | $enabledPlugins = array_filter($pluginMeta, function ($v) { | ||
1543 | return $v['order'] !== false; | ||
1544 | }); | ||
1545 | // Load parameters. | ||
1546 | $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array())); | ||
1547 | uasort( | ||
1548 | $enabledPlugins, | ||
1549 | function ($a, $b) { | ||
1550 | return $a['order'] - $b['order']; | ||
1551 | } | ||
1552 | ); | ||
1553 | $disabledPlugins = array_filter($pluginMeta, function ($v) { | ||
1554 | return $v['order'] === false; | ||
1555 | }); | ||
1556 | |||
1557 | $PAGE->assign('enabledPlugins', $enabledPlugins); | ||
1558 | $PAGE->assign('disabledPlugins', $disabledPlugins); | ||
1559 | $PAGE->assign('pagetitle', t('Plugin administration') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1560 | $PAGE->renderPage('pluginsadmin'); | ||
1561 | exit; | ||
1562 | } | ||
1563 | |||
1564 | // Plugin administration form action | ||
1565 | if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { | ||
1566 | try { | ||
1567 | if (isset($_POST['parameters_form'])) { | ||
1568 | $pluginManager->executeHooks('save_plugin_parameters', $_POST); | ||
1569 | unset($_POST['parameters_form']); | ||
1570 | foreach ($_POST as $param => $value) { | ||
1571 | $conf->set('plugins.'. $param, escape($value)); | ||
1572 | } | ||
1573 | } else { | ||
1574 | $conf->set('general.enabled_plugins', save_plugin_config($_POST)); | ||
1575 | } | ||
1576 | $conf->write($loginManager->isLoggedIn()); | ||
1577 | $history->updateSettings(); | ||
1578 | } catch (Exception $e) { | ||
1579 | error_log( | ||
1580 | 'ERROR while saving plugin configuration:.' . PHP_EOL . | ||
1581 | $e->getMessage() | ||
1582 | ); | ||
1583 | |||
1584 | // TODO: do not handle exceptions/errors in JS. | ||
1585 | echo '<script>alert("' | ||
1586 | . $e->getMessage() | ||
1587 | .'");document.location=\'?do=' | ||
1588 | . Router::$PAGE_PLUGINSADMIN | ||
1589 | .'\';</script>'; | ||
1590 | exit; | ||
1591 | } | ||
1592 | header('Location: ?do='. Router::$PAGE_PLUGINSADMIN); | ||
1593 | exit; | ||
1594 | } | ||
1595 | |||
1596 | // Get a fresh token | ||
1597 | if ($targetPage == Router::$GET_TOKEN) { | ||
1598 | header('Content-Type:text/plain'); | ||
1599 | echo $sessionManager->generateToken($conf); | ||
1600 | exit; | ||
1601 | } | ||
1602 | |||
1603 | // -------- Thumbnails Update | ||
1604 | if ($targetPage == Router::$PAGE_THUMBS_UPDATE) { | ||
1605 | $ids = []; | ||
1606 | foreach ($LINKSDB as $link) { | ||
1607 | // A note or not HTTP(S) | ||
1608 | if (is_note($link['url']) || ! startsWith(strtolower($link['url']), 'http')) { | ||
1609 | continue; | ||
1610 | } | ||
1611 | $ids[] = $link['id']; | ||
1612 | } | ||
1613 | $PAGE->assign('ids', $ids); | ||
1614 | $PAGE->assign('pagetitle', t('Thumbnails update') .' - '. $conf->get('general.title', 'Shaarli')); | ||
1615 | $PAGE->renderPage('thumbnails'); | ||
1616 | exit; | ||
1617 | } | ||
1618 | |||
1619 | // -------- Single Thumbnail Update | ||
1620 | if ($targetPage == Router::$AJAX_THUMB_UPDATE) { | ||
1621 | if (! isset($_POST['id']) || ! ctype_digit($_POST['id'])) { | ||
1622 | http_response_code(400); | ||
1623 | exit; | ||
1624 | } | ||
1625 | $id = (int) $_POST['id']; | ||
1626 | if (empty($LINKSDB[$id])) { | ||
1627 | http_response_code(404); | ||
1628 | exit; | ||
1629 | } | ||
1630 | $thumbnailer = new Thumbnailer($conf); | ||
1631 | $link = $LINKSDB[$id]; | ||
1632 | $link['thumbnail'] = $thumbnailer->get($link['url']); | ||
1633 | $LINKSDB[$id] = $link; | ||
1634 | $LINKSDB->save($conf->get('resource.page_cache')); | ||
1635 | |||
1636 | echo json_encode($link); | ||
1637 | exit; | ||
1638 | } | ||
1639 | |||
1640 | // -------- Otherwise, simply display search form and links: | ||
1641 | showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager); | ||
1642 | exit; | ||
1643 | } | ||
1644 | |||
1645 | /** | ||
1646 | * Template for the list of links (<div id="linklist">) | ||
1647 | * This function fills all the necessary fields in the $PAGE for the template 'linklist.html' | ||
1648 | * | ||
1649 | * @param pageBuilder $PAGE pageBuilder instance. | ||
1650 | * @param LinkDB $LINKSDB LinkDB instance. | ||
1651 | * @param ConfigManager $conf Configuration Manager instance. | ||
1652 | * @param PluginManager $pluginManager Plugin Manager instance. | ||
1653 | * @param LoginManager $loginManager LoginManager instance | ||
1654 | */ | ||
1655 | function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager) | ||
1656 | { | ||
1657 | // Used in templates | ||
1658 | if (isset($_GET['searchtags'])) { | ||
1659 | if (! empty($_GET['searchtags'])) { | ||
1660 | $searchtags = escape(normalize_spaces($_GET['searchtags'])); | ||
1661 | } else { | ||
1662 | $searchtags = false; | ||
1663 | } | ||
1664 | } else { | ||
1665 | $searchtags = ''; | ||
1666 | } | ||
1667 | $searchterm = !empty($_GET['searchterm']) ? escape(normalize_spaces($_GET['searchterm'])) : ''; | ||
1668 | |||
1669 | // Smallhash filter | ||
1670 | if (! empty($_SERVER['QUERY_STRING']) | ||
1671 | && preg_match('/^[a-zA-Z0-9-_@]{6}($|&|#)/', $_SERVER['QUERY_STRING'])) { | ||
1672 | try { | ||
1673 | $linksToDisplay = $LINKSDB->filterHash($_SERVER['QUERY_STRING']); | ||
1674 | } catch (LinkNotFoundException $e) { | ||
1675 | $PAGE->render404($e->getMessage()); | ||
1676 | exit; | ||
1677 | } | ||
1678 | } else { | ||
1679 | // Filter links according search parameters. | ||
1680 | $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; | ||
1681 | $request = [ | ||
1682 | 'searchtags' => $searchtags, | ||
1683 | 'searchterm' => $searchterm, | ||
1684 | ]; | ||
1685 | $linksToDisplay = $LINKSDB->filterSearch($request, false, $visibility, !empty($_SESSION['untaggedonly'])); | ||
1686 | } | ||
1687 | |||
1688 | // ---- Handle paging. | ||
1689 | $keys = array(); | ||
1690 | foreach ($linksToDisplay as $key => $value) { | ||
1691 | $keys[] = $key; | ||
1692 | } | ||
1693 | |||
1694 | // Select articles according to paging. | ||
1695 | $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']); | ||
1696 | $pagecount = $pagecount == 0 ? 1 : $pagecount; | ||
1697 | $page= empty($_GET['page']) ? 1 : intval($_GET['page']); | ||
1698 | $page = $page < 1 ? 1 : $page; | ||
1699 | $page = $page > $pagecount ? $pagecount : $page; | ||
1700 | // Start index. | ||
1701 | $i = ($page-1) * $_SESSION['LINKS_PER_PAGE']; | ||
1702 | $end = $i + $_SESSION['LINKS_PER_PAGE']; | ||
1703 | |||
1704 | $thumbnailsEnabled = $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE; | ||
1705 | if ($thumbnailsEnabled) { | ||
1706 | $thumbnailer = new Thumbnailer($conf); | ||
1707 | } | ||
1708 | |||
1709 | $linkDisp = array(); | ||
1710 | while ($i<$end && $i<count($keys)) { | ||
1711 | $link = $linksToDisplay[$keys[$i]]; | ||
1712 | $link['description'] = format_description($link['description']); | ||
1713 | $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; | ||
1714 | $link['class'] = $link['private'] == 0 ? $classLi : 'private'; | ||
1715 | $link['timestamp'] = $link['created']->getTimestamp(); | ||
1716 | if (! empty($link['updated'])) { | ||
1717 | $link['updated_timestamp'] = $link['updated']->getTimestamp(); | ||
1718 | } else { | ||
1719 | $link['updated_timestamp'] = ''; | ||
1720 | } | ||
1721 | $taglist = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY); | ||
1722 | uasort($taglist, 'strcasecmp'); | ||
1723 | $link['taglist'] = $taglist; | ||
1724 | |||
1725 | // Logged in, thumbnails enabled, not a note, | ||
1726 | // and (never retrieved yet or no valid cache file) | ||
1727 | if ($loginManager->isLoggedIn() && $thumbnailsEnabled && $link['url'][0] != '?' | ||
1728 | && (! isset($link['thumbnail']) || ($link['thumbnail'] !== false && ! is_file($link['thumbnail']))) | ||
1729 | ) { | ||
1730 | $elem = $LINKSDB[$keys[$i]]; | ||
1731 | $elem['thumbnail'] = $thumbnailer->get($link['url']); | ||
1732 | $LINKSDB[$keys[$i]] = $elem; | ||
1733 | $updateDB = true; | ||
1734 | $link['thumbnail'] = $elem['thumbnail']; | ||
1735 | } | ||
1736 | |||
1737 | // Check for both signs of a note: starting with ? and 7 chars long. | ||
1738 | if ($link['url'][0] === '?' && strlen($link['url']) === 7) { | ||
1739 | $link['url'] = index_url($_SERVER) . $link['url']; | ||
1740 | } | ||
1741 | |||
1742 | $linkDisp[$keys[$i]] = $link; | ||
1743 | $i++; | ||
1744 | } | ||
1745 | |||
1746 | // If we retrieved new thumbnails, we update the database. | ||
1747 | if (!empty($updateDB)) { | ||
1748 | $LINKSDB->save($conf->get('resource.page_cache')); | ||
1749 | } | ||
1750 | |||
1751 | // Compute paging navigation | ||
1752 | $searchtagsUrl = $searchtags === '' ? '' : '&searchtags=' . urlencode($searchtags); | ||
1753 | $searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm); | ||
1754 | $previous_page_url = ''; | ||
1755 | if ($i != count($keys)) { | ||
1756 | $previous_page_url = '?page=' . ($page+1) . $searchtermUrl . $searchtagsUrl; | ||
1757 | } | ||
1758 | $next_page_url=''; | ||
1759 | if ($page>1) { | ||
1760 | $next_page_url = '?page=' . ($page-1) . $searchtermUrl . $searchtagsUrl; | ||
1761 | } | ||
1762 | |||
1763 | // Fill all template fields. | ||
1764 | $data = array( | ||
1765 | 'previous_page_url' => $previous_page_url, | ||
1766 | 'next_page_url' => $next_page_url, | ||
1767 | 'page_current' => $page, | ||
1768 | 'page_max' => $pagecount, | ||
1769 | 'result_count' => count($linksToDisplay), | ||
1770 | 'search_term' => $searchterm, | ||
1771 | 'search_tags' => $searchtags, | ||
1772 | 'visibility' => ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '', | ||
1773 | 'links' => $linkDisp, | ||
1774 | ); | 123 | ); |
124 | $this->get('/export', '\Shaarli\Front\Controller\Admin\ExportController:index'); | ||
125 | $this->post('/export', '\Shaarli\Front\Controller\Admin\ExportController:export'); | ||
126 | $this->get('/import', '\Shaarli\Front\Controller\Admin\ImportController:index'); | ||
127 | $this->post('/import', '\Shaarli\Front\Controller\Admin\ImportController:import'); | ||
128 | $this->get('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:index'); | ||
129 | $this->post('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:save'); | ||
130 | $this->get('/token', '\Shaarli\Front\Controller\Admin\TokenController:getToken'); | ||
131 | $this->get('/thumbnails', '\Shaarli\Front\Controller\Admin\ThumbnailsController:index'); | ||
1775 | 132 | ||
1776 | // If there is only a single link, we change on-the-fly the title of the page. | 133 | $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); |
1777 | if (count($linksToDisplay) == 1) { | 134 | })->add('\Shaarli\Front\ShaarliAdminMiddleware'); |
1778 | $data['pagetitle'] = $linksToDisplay[$keys[0]]['title'] .' - '. $conf->get('general.title'); | ||
1779 | } elseif (! empty($searchterm) || ! empty($searchtags)) { | ||
1780 | $data['pagetitle'] = t('Search: '); | ||
1781 | $data['pagetitle'] .= ! empty($searchterm) ? $searchterm .' ' : ''; | ||
1782 | $bracketWrap = function ($tag) { | ||
1783 | return '['. $tag .']'; | ||
1784 | }; | ||
1785 | $data['pagetitle'] .= ! empty($searchtags) | ||
1786 | ? implode(' ', array_map($bracketWrap, preg_split('/\s+/', $searchtags))).' ' | ||
1787 | : ''; | ||
1788 | $data['pagetitle'] .= '- '. $conf->get('general.title'); | ||
1789 | } | ||
1790 | |||
1791 | $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => $loginManager->isLoggedIn())); | ||
1792 | |||
1793 | foreach ($data as $key => $value) { | ||
1794 | $PAGE->assign($key, $value); | ||
1795 | } | ||
1796 | |||
1797 | return; | ||
1798 | } | ||
1799 | |||
1800 | /** | ||
1801 | * Installation | ||
1802 | * This function should NEVER be called if the file data/config.php exists. | ||
1803 | * | ||
1804 | * @param ConfigManager $conf Configuration Manager instance. | ||
1805 | * @param SessionManager $sessionManager SessionManager instance | ||
1806 | * @param LoginManager $loginManager LoginManager instance | ||
1807 | */ | ||
1808 | function install($conf, $sessionManager, $loginManager) | ||
1809 | { | ||
1810 | // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. | ||
1811 | if (endsWith($_SERVER['HTTP_HOST'], '.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) { | ||
1812 | mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions', 0705); | ||
1813 | } | ||
1814 | 135 | ||
1815 | 136 | ||
1816 | // This part makes sure sessions works correctly. | ||
1817 | // (Because on some hosts, session.save_path may not be set correctly, | ||
1818 | // or we may not have write access to it.) | ||
1819 | if (isset($_GET['test_session']) | ||
1820 | && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working')) { | ||
1821 | // Step 2: Check if data in session is correct. | ||
1822 | $msg = t( | ||
1823 | '<pre>Sessions do not seem to work correctly on your server.<br>'. | ||
1824 | 'Make sure the variable "session.save_path" is set correctly in your PHP config, '. | ||
1825 | 'and that you have write access to it.<br>'. | ||
1826 | 'It currently points to %s.<br>'. | ||
1827 | 'On some browsers, accessing your server via a hostname like \'localhost\' '. | ||
1828 | 'or any custom hostname without a dot causes cookie storage to fail. '. | ||
1829 | 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>' | ||
1830 | ); | ||
1831 | $msg = sprintf($msg, session_save_path()); | ||
1832 | echo $msg; | ||
1833 | echo '<br><a href="?">'. t('Click to try again.') .'</a></pre>'; | ||
1834 | die; | ||
1835 | } | ||
1836 | if (!isset($_SESSION['session_tested'])) { | ||
1837 | // Step 1 : Try to store data in session and reload page. | ||
1838 | $_SESSION['session_tested'] = 'Working'; // Try to set a variable in session. | ||
1839 | header('Location: '.index_url($_SERVER).'?test_session'); // Redirect to check stored data. | ||
1840 | } | ||
1841 | if (isset($_GET['test_session'])) { | ||
1842 | // Step 3: Sessions are OK. Remove test parameter from URL. | ||
1843 | header('Location: '.index_url($_SERVER)); | ||
1844 | } | ||
1845 | |||
1846 | |||
1847 | if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) { | ||
1848 | $tz = 'UTC'; | ||
1849 | if (!empty($_POST['continent']) && !empty($_POST['city']) | ||
1850 | && isTimeZoneValid($_POST['continent'], $_POST['city']) | ||
1851 | ) { | ||
1852 | $tz = $_POST['continent'].'/'.$_POST['city']; | ||
1853 | } | ||
1854 | $conf->set('general.timezone', $tz); | ||
1855 | $login = $_POST['setlogin']; | ||
1856 | $conf->set('credentials.login', $login); | ||
1857 | $salt = sha1(uniqid('', true) .'_'. mt_rand()); | ||
1858 | $conf->set('credentials.salt', $salt); | ||
1859 | $conf->set('credentials.hash', sha1($_POST['setpassword'] . $login . $salt)); | ||
1860 | if (!empty($_POST['title'])) { | ||
1861 | $conf->set('general.title', escape($_POST['title'])); | ||
1862 | } else { | ||
1863 | $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER))); | ||
1864 | } | ||
1865 | $conf->set('translation.language', escape($_POST['language'])); | ||
1866 | $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); | ||
1867 | $conf->set('api.enabled', !empty($_POST['enableApi'])); | ||
1868 | $conf->set( | ||
1869 | 'api.secret', | ||
1870 | generate_api_secret( | ||
1871 | $conf->get('credentials.login'), | ||
1872 | $conf->get('credentials.salt') | ||
1873 | ) | ||
1874 | ); | ||
1875 | try { | ||
1876 | // Everything is ok, let's create config file. | ||
1877 | $conf->write($loginManager->isLoggedIn()); | ||
1878 | } catch (Exception $e) { | ||
1879 | error_log( | ||
1880 | 'ERROR while writing config file after installation.' . PHP_EOL . | ||
1881 | $e->getMessage() | ||
1882 | ); | ||
1883 | |||
1884 | // TODO: do not handle exceptions/errors in JS. | ||
1885 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>'; | ||
1886 | exit; | ||
1887 | } | ||
1888 | echo '<script>alert(' | ||
1889 | .'"Shaarli is now configured. ' | ||
1890 | .'Please enter your login/password and start shaaring your links!"' | ||
1891 | .');document.location=\'?do=login\';</script>'; | ||
1892 | exit; | ||
1893 | } | ||
1894 | |||
1895 | $PAGE = new PageBuilder($conf, $_SESSION, null, $sessionManager->generateToken()); | ||
1896 | list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); | ||
1897 | $PAGE->assign('continents', $continents); | ||
1898 | $PAGE->assign('cities', $cities); | ||
1899 | $PAGE->assign('languages', Languages::getAvailableLanguages()); | ||
1900 | $PAGE->renderPage('install'); | ||
1901 | exit; | ||
1902 | } | ||
1903 | |||
1904 | if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { | ||
1905 | showDailyRSS($conf, $loginManager); | ||
1906 | exit; | ||
1907 | } | ||
1908 | |||
1909 | if (!isset($_SESSION['LINKS_PER_PAGE'])) { | ||
1910 | $_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20); | ||
1911 | } | ||
1912 | |||
1913 | try { | ||
1914 | $history = new History($conf->get('resource.history')); | ||
1915 | } catch (Exception $e) { | ||
1916 | die($e->getMessage()); | ||
1917 | } | ||
1918 | |||
1919 | $linkDb = new LinkDB( | ||
1920 | $conf->get('resource.datastore'), | ||
1921 | $loginManager->isLoggedIn(), | ||
1922 | $conf->get('privacy.hide_public_links') | ||
1923 | ); | ||
1924 | |||
1925 | $container = new \Slim\Container(); | ||
1926 | $container['conf'] = $conf; | ||
1927 | $container['plugins'] = $pluginManager; | ||
1928 | $container['history'] = $history; | ||
1929 | $app = new \Slim\App($container); | ||
1930 | |||
1931 | // REST API routes | 137 | // REST API routes |
1932 | $app->group('/api/v1', function () { | 138 | $app->group('/api/v1', function () { |
1933 | $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo'); | 139 | $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo'); |
@@ -1947,19 +153,4 @@ $app->group('/api/v1', function () { | |||
1947 | 153 | ||
1948 | $response = $app->run(true); | 154 | $response = $app->run(true); |
1949 | 155 | ||
1950 | // Hack to make Slim and Shaarli router work together: | 156 | $app->respond($response); |
1951 | // If a Slim route isn't found and NOT API call, we call renderPage(). | ||
1952 | if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) { | ||
1953 | // We use UTF-8 for proper international characters handling. | ||
1954 | header('Content-Type: text/html; charset=utf-8'); | ||
1955 | renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager); | ||
1956 | } else { | ||
1957 | $response = $response | ||
1958 | ->withHeader('Access-Control-Allow-Origin', '*') | ||
1959 | ->withHeader( | ||
1960 | 'Access-Control-Allow-Headers', | ||
1961 | 'X-Requested-With, Content-Type, Accept, Origin, Authorization' | ||
1962 | ) | ||
1963 | ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); | ||
1964 | $app->respond($response); | ||
1965 | } | ||
diff --git a/init.php b/init.php new file mode 100644 index 00000000..f0b84368 --- /dev/null +++ b/init.php | |||
@@ -0,0 +1,85 @@ | |||
1 | <?php | ||
2 | |||
3 | require_once __DIR__ . '/vendor/autoload.php'; | ||
4 | |||
5 | use Shaarli\ApplicationUtils; | ||
6 | use Shaarli\Security\SessionManager; | ||
7 | |||
8 | // Set 'UTC' as the default timezone if it is not defined in php.ini | ||
9 | // See http://php.net/manual/en/datetime.configuration.php#ini.date.timezone | ||
10 | if (date_default_timezone_get() == '') { | ||
11 | date_default_timezone_set('UTC'); | ||
12 | } | ||
13 | |||
14 | // High execution time in case of problematic imports/exports. | ||
15 | ini_set('max_input_time', '60'); | ||
16 | |||
17 | // Try to set max upload file size and read | ||
18 | ini_set('memory_limit', '128M'); | ||
19 | ini_set('post_max_size', '16M'); | ||
20 | ini_set('upload_max_filesize', '16M'); | ||
21 | |||
22 | // See all error except warnings | ||
23 | error_reporting(E_ALL^E_WARNING); | ||
24 | |||
25 | // 3rd-party libraries | ||
26 | if (! file_exists(__DIR__ . '/vendor/autoload.php')) { | ||
27 | header('Content-Type: text/plain; charset=utf-8'); | ||
28 | echo "Error: missing Composer configuration\n\n" | ||
29 | ."If you installed Shaarli through Git or using the development branch,\n" | ||
30 | ."please refer to the installation documentation to install PHP" | ||
31 | ." dependencies using Composer:\n" | ||
32 | ."- https://shaarli.readthedocs.io/en/master/Server-configuration/\n" | ||
33 | ."- https://shaarli.readthedocs.io/en/master/Download-and-Installation/"; | ||
34 | exit; | ||
35 | } | ||
36 | |||
37 | // Ensure the PHP version is supported | ||
38 | try { | ||
39 | ApplicationUtils::checkPHPVersion('7.1', PHP_VERSION); | ||
40 | } catch (Exception $exc) { | ||
41 | header('Content-Type: text/plain; charset=utf-8'); | ||
42 | echo $exc->getMessage(); | ||
43 | exit; | ||
44 | } | ||
45 | |||
46 | // Force cookie path (but do not change lifetime) | ||
47 | $cookie = session_get_cookie_params(); | ||
48 | $cookiedir = ''; | ||
49 | if (dirname($_SERVER['SCRIPT_NAME']) != '/') { | ||
50 | $cookiedir = dirname($_SERVER["SCRIPT_NAME"]).'/'; | ||
51 | } | ||
52 | // Set default cookie expiration and path. | ||
53 | session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['SERVER_NAME']); | ||
54 | // Set session parameters on server side. | ||
55 | // Use cookies to store session. | ||
56 | ini_set('session.use_cookies', 1); | ||
57 | // Force cookies for session (phpsessionID forbidden in URL). | ||
58 | ini_set('session.use_only_cookies', 1); | ||
59 | // Prevent PHP form using sessionID in URL if cookies are disabled. | ||
60 | ini_set('session.use_trans_sid', false); | ||
61 | |||
62 | define('SHAARLI_VERSION', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE)); | ||
63 | |||
64 | session_name('shaarli'); | ||
65 | // Start session if needed (Some server auto-start sessions). | ||
66 | if (session_status() == PHP_SESSION_NONE) { | ||
67 | session_start(); | ||
68 | } | ||
69 | |||
70 | // Regenerate session ID if invalid or not defined in cookie. | ||
71 | if (isset($_COOKIE['shaarli']) && !SessionManager::checkId($_COOKIE['shaarli'])) { | ||
72 | session_regenerate_id(true); | ||
73 | $_COOKIE['shaarli'] = session_id(); | ||
74 | } | ||
75 | |||
76 | // LC_MESSAGES isn't defined without php-intl, in this case use LC_COLLATE locale instead. | ||
77 | if (! defined('LC_MESSAGES')) { | ||
78 | define('LC_MESSAGES', LC_COLLATE); | ||
79 | } | ||
80 | |||
81 | // Prevent caching on client side or proxy: (yes, it's ugly) | ||
82 | header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); | ||
83 | header("Cache-Control: no-store, no-cache, must-revalidate"); | ||
84 | header("Cache-Control: post-check=0, pre-check=0", false); | ||
85 | header("Pragma: no-cache"); | ||
@@ -15,42 +15,25 @@ site_dir: doc/html | |||
15 | pages: | 15 | pages: |
16 | - Home: index.md | 16 | - Home: index.md |
17 | - Setup: | 17 | - Setup: |
18 | - Download and Installation: Download-and-Installation.md | ||
19 | - Upgrade and migration: Upgrade-and-migration.md | ||
20 | - Server configuration: Server-configuration.md | 18 | - Server configuration: Server-configuration.md |
21 | - Server security: Server-security.md | 19 | - Installation: Installation.md |
20 | - Docker: Docker.md | ||
21 | - Reverse Proxy: Reverse-proxy.md | ||
22 | - Backup and restore: Backup-and-restore.md | ||
22 | - Shaarli configuration: Shaarli-configuration.md | 23 | - Shaarli configuration: Shaarli-configuration.md |
23 | - Plugins: Plugins.md | 24 | - Plugins: Plugins.md |
24 | - Docker: | 25 | - Upgrade and migration: Upgrade-and-migration.md |
25 | - Docker 101: docker/docker-101.md | ||
26 | - Shaarli images: docker/shaarli-images.md | ||
27 | - Reverse proxy configuration: docker/reverse-proxy-configuration.md | ||
28 | - Docker resources: docker/resources.md | ||
29 | - Usage: | 26 | - Usage: |
30 | - Browsing and searching: Browsing-and-searching.md | 27 | - Usage: Usage.md |
31 | - Sharing content: Sharing-content.md | ||
32 | - RSS feeds: RSS-feeds.md | ||
33 | - REST API: REST-API.md | 28 | - REST API: REST-API.md |
34 | - Community & Related software: Community-&-Related-software.md | 29 | - Community and Related software: Community-and-related-software.md |
35 | - Guides: | ||
36 | - Install Shaarli on Debian 9 with Docker: guides/install-shaarli-with-debian9-and-docker.md | ||
37 | - Backup, restore, import and export: guides/backup-restore-import-export.md | ||
38 | - Various hacks: guides/various-hacks.md | ||
39 | - Development: | 30 | - Development: |
40 | - Development guidelines: Development-guidelines.md | 31 | - Development: dev/Development.md |
41 | - Continuous integration tools: Continuous-integration-tools.md | 32 | - Versioning: dev/Versioning.md |
42 | - GnuPG signature: GnuPG-signature.md | 33 | - GnuPG signature: dev/GnuPG-signature.md |
43 | - Directory structure: Directory-structure.md | 34 | - Plugin System: dev/Plugin-system.md |
44 | - Link Structure: Link-structure.md | 35 | - Translations: dev/Translations.md |
45 | - 3rd party libraries: 3rd-party-libraries.md | 36 | - Release Shaarli: dev/Release-Shaarli.md |
46 | - Plugin System: Plugin-System.md | 37 | - Theming: dev/Theming.md |
47 | - Release Shaarli: Release-Shaarli.md | 38 | - Unit tests: dev/Unit-tests.md |
48 | - Versioning and Branches: Versioning-and-Branches.md | ||
49 | - Security: Security.md | ||
50 | - Static analysis: Static-analysis.md | ||
51 | - Translations: Translations.md | ||
52 | - Theming: Theming.md | ||
53 | - Unit tests: Unit-tests.md | ||
54 | - Unit tests inside Docker: Unit-tests-Docker.md | ||
55 | - FAQ: FAQ.md | ||
56 | - Troubleshooting: Troubleshooting.md | 39 | - Troubleshooting: Troubleshooting.md |
diff --git a/package.json b/package.json index f3d9b51e..8a24512a 100644 --- a/package.json +++ b/package.json | |||
@@ -11,22 +11,23 @@ | |||
11 | "purecss": "^1.0.0" | 11 | "purecss": "^1.0.0" |
12 | }, | 12 | }, |
13 | "devDependencies": { | 13 | "devDependencies": { |
14 | "babel-core": "^6.26.0", | 14 | "@babel/core": "^7.11.6", |
15 | "babel-loader": "^7.1.2", | 15 | "@babel/preset-env": "^7.11.5", |
16 | "babel-minify-webpack-plugin": "^0.2.0", | 16 | "babel-loader": "^8.1.0", |
17 | "babel-preset-env": "^1.6.1", | 17 | "css-loader": "^4.3.0", |
18 | "css-loader": "^0.28.9", | 18 | "eslint": "^7.9.0", |
19 | "eslint": "^4.16.0", | 19 | "eslint-config-airbnb-base": "^14.2.0", |
20 | "eslint-config-airbnb-base": "^12.1.0", | 20 | "eslint-plugin-import": "^2.22.0", |
21 | "eslint-plugin-import": "^2.8.0", | ||
22 | "extract-text-webpack-plugin": "^3.0.2", | ||
23 | "file-loader": "^1.1.6", | 21 | "file-loader": "^1.1.6", |
24 | "node-sass": "^4.12.0", | 22 | "mini-css-extract-plugin": "^0.11.2", |
25 | "sass-lint": "^1.12.1", | 23 | "sass": "^1.26.11", |
26 | "sass-loader": "^6.0.6", | 24 | "sass-loader": "^10.0.2", |
27 | "style-loader": "^0.19.1", | 25 | "stylelint": "^13.7.1", |
28 | "url-loader": "^0.6.2", | 26 | "stylelint-config-standard": "^20.0.0", |
29 | "webpack": "^3.10.0" | 27 | "stylelint-scss": "^3.18.0", |
28 | "terser-webpack-plugin": "^4.2.2", | ||
29 | "webpack": "^4.44.2", | ||
30 | "webpack-cli": "^3.3.12" | ||
30 | }, | 31 | }, |
31 | "scripts": { | 32 | "scripts": { |
32 | "build": "webpack", | 33 | "build": "webpack", |
diff --git a/plugins/addlink_toolbar/addlink_toolbar.php b/plugins/addlink_toolbar/addlink_toolbar.php index 8bf4ed46..ab6ed6de 100644 --- a/plugins/addlink_toolbar/addlink_toolbar.php +++ b/plugins/addlink_toolbar/addlink_toolbar.php | |||
@@ -5,7 +5,7 @@ | |||
5 | * Adds the addlink input on the linklist page. | 5 | * Adds the addlink input on the linklist page. |
6 | */ | 6 | */ |
7 | 7 | ||
8 | use Shaarli\Router; | 8 | use Shaarli\Render\TemplatePage; |
9 | 9 | ||
10 | /** | 10 | /** |
11 | * When linklist is displayed, add play videos to header's toolbar. | 11 | * When linklist is displayed, add play videos to header's toolbar. |
@@ -16,11 +16,11 @@ use Shaarli\Router; | |||
16 | */ | 16 | */ |
17 | function hook_addlink_toolbar_render_header($data) | 17 | function hook_addlink_toolbar_render_header($data) |
18 | { | 18 | { |
19 | if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) { | 19 | if ($data['_PAGE_'] == TemplatePage::LINKLIST && $data['_LOGGEDIN_'] === true) { |
20 | $form = array( | 20 | $form = array( |
21 | 'attr' => array( | 21 | 'attr' => array( |
22 | 'method' => 'GET', | 22 | 'method' => 'GET', |
23 | 'action' => '', | 23 | 'action' => $data['_BASE_PATH_'] . '/admin/shaare', |
24 | 'name' => 'addform', | 24 | 'name' => 'addform', |
25 | 'class' => 'addform', | 25 | 'class' => 'addform', |
26 | ), | 26 | ), |
diff --git a/plugins/archiveorg/archiveorg.html b/plugins/archiveorg/archiveorg.html index ad501f47..e37d887e 100644 --- a/plugins/archiveorg/archiveorg.html +++ b/plugins/archiveorg/archiveorg.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <span> | 1 | <span> |
2 | <a href="https://web.archive.org/web/%s"> | 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" /> | 3 | <img class="linklist-plugin-icon" src="%s/archiveorg/internetarchive.png" title="%s" alt="archive.org" /> |
4 | </a> | 4 | </a> |
5 | </span> | 5 | </span> |
diff --git a/plugins/archiveorg/archiveorg.php b/plugins/archiveorg/archiveorg.php index 0ee1c73c..922b5966 100644 --- a/plugins/archiveorg/archiveorg.php +++ b/plugins/archiveorg/archiveorg.php | |||
@@ -17,12 +17,15 @@ use Shaarli\Plugin\PluginManager; | |||
17 | function hook_archiveorg_render_linklist($data) | 17 | function hook_archiveorg_render_linklist($data) |
18 | { | 18 | { |
19 | $archive_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/archiveorg/archiveorg.html'); | 19 | $archive_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/archiveorg/archiveorg.html'); |
20 | $path = ($data['_BASE_PATH_'] ?? '') . '/' . PluginManager::$PLUGINS_PATH; | ||
20 | 21 | ||
21 | foreach ($data['links'] as &$value) { | 22 | foreach ($data['links'] as &$value) { |
22 | if ($value['private'] && preg_match('/^\?[a-zA-Z0-9-_@]{6}($|&|#)/', $value['real_url'])) { | 23 | $isNote = startsWith($value['real_url'], '/shaare/'); |
24 | if ($value['private'] && $isNote) { | ||
23 | continue; | 25 | continue; |
24 | } | 26 | } |
25 | $archive = sprintf($archive_html, $value['url'], t('View on archive.org')); | 27 | $url = $isNote ? rtrim(index_url($_SERVER), '/') . $value['real_url'] : $value['real_url']; |
28 | $archive = sprintf($archive_html, $url, $path, t('View on archive.org')); | ||
26 | $value['link_plugin'][] = $archive; | 29 | $value['link_plugin'][] = $archive; |
27 | } | 30 | } |
28 | 31 | ||
diff --git a/plugins/default_colors/default_colors.php b/plugins/default_colors/default_colors.php index 1928cc9f..e1fd5cfb 100644 --- a/plugins/default_colors/default_colors.php +++ b/plugins/default_colors/default_colors.php | |||
@@ -15,6 +15,8 @@ const DEFAULT_COLORS_PLACEHOLDERS = [ | |||
15 | 'DEFAULT_COLORS_DARK_MAIN', | 15 | 'DEFAULT_COLORS_DARK_MAIN', |
16 | ]; | 16 | ]; |
17 | 17 | ||
18 | const DEFAULT_COLORS_CSS_FILE = '/default_colors/default_colors.css'; | ||
19 | |||
18 | /** | 20 | /** |
19 | * Display an error if the plugin is active a no color is configured. | 21 | * Display an error if the plugin is active a no color is configured. |
20 | * | 22 | * |
@@ -24,58 +26,62 @@ const DEFAULT_COLORS_PLACEHOLDERS = [ | |||
24 | */ | 26 | */ |
25 | function default_colors_init($conf) | 27 | function default_colors_init($conf) |
26 | { | 28 | { |
27 | $params = ''; | 29 | $params = []; |
28 | foreach (DEFAULT_COLORS_PLACEHOLDERS as $placeholder) { | 30 | foreach (DEFAULT_COLORS_PLACEHOLDERS as $placeholder) { |
29 | $params .= trim($conf->get('plugins.'. $placeholder, '')); | 31 | $value = trim($conf->get('plugins.'. $placeholder, '')); |
32 | if (strlen($value) > 0) { | ||
33 | $params[$placeholder] = $value; | ||
34 | } | ||
30 | } | 35 | } |
31 | 36 | ||
32 | if (empty($params)) { | 37 | if (empty($params)) { |
33 | $error = t('Default colors plugin error: '. | 38 | $error = t('Default colors plugin error: '. |
34 | 'This plugin is active and no custom color is configured.'); | 39 | 'This plugin is active and no custom color is configured.'); |
35 | return array($error); | 40 | return [$error]; |
41 | } | ||
42 | |||
43 | // Colors are defined but the custom CSS file does not exist -> generate it | ||
44 | if (!file_exists(PluginManager::$PLUGINS_PATH . DEFAULT_COLORS_CSS_FILE)) { | ||
45 | default_colors_generate_css_file($params); | ||
36 | } | 46 | } |
37 | } | 47 | } |
38 | 48 | ||
39 | /** | 49 | /** |
40 | * When plugin parameters are saved, we regenerate the custom CSS file with provided settings. | 50 | * When linklist is displayed, include default_colors CSS file. |
41 | * | 51 | * |
42 | * @param array $data $_POST array | 52 | * @param array $data - header data. |
43 | * | 53 | * |
44 | * @return array Updated $_POST array | 54 | * @return mixed - header data with default_colors CSS file added. |
45 | */ | 55 | */ |
46 | function hook_default_colors_save_plugin_parameters($data) | 56 | function hook_default_colors_render_includes($data) |
47 | { | 57 | { |
48 | $file = PluginManager::$PLUGINS_PATH . '/default_colors/default_colors.css'; | 58 | $file = PluginManager::$PLUGINS_PATH . '/default_colors/default_colors.css'; |
49 | $template = file_get_contents(PluginManager::$PLUGINS_PATH . '/default_colors/default_colors.css.template'); | 59 | if (file_exists($file )) { |
50 | $content = ''; | 60 | $data['css_files'][] = $file ; |
51 | foreach (DEFAULT_COLORS_PLACEHOLDERS as $rule) { | ||
52 | $content .= ! empty($data[$rule]) | ||
53 | ? default_colors_format_css_rule($data, $rule) .';'. PHP_EOL | ||
54 | : ''; | ||
55 | } | ||
56 | |||
57 | if (! empty($content)) { | ||
58 | file_put_contents($file, sprintf($template, $content)); | ||
59 | } | 61 | } |
60 | 62 | ||
61 | return $data; | 63 | return $data; |
62 | } | 64 | } |
63 | 65 | ||
64 | /** | 66 | /** |
65 | * When linklist is displayed, include default_colors CSS file. | 67 | * Regenerate the custom CSS file with provided settings. |
66 | * | ||
67 | * @param array $data - header data. | ||
68 | * | 68 | * |
69 | * @return mixed - header data with default_colors CSS file added. | 69 | * @param array $params Plugin configuration (CSS rules) |
70 | */ | 70 | */ |
71 | function hook_default_colors_render_includes($data) | 71 | function default_colors_generate_css_file($params): void |
72 | { | 72 | { |
73 | $file = PluginManager::$PLUGINS_PATH . '/default_colors/default_colors.css'; | 73 | $file = PluginManager::$PLUGINS_PATH . '/default_colors/default_colors.css'; |
74 | if (file_exists($file )) { | 74 | $template = file_get_contents(PluginManager::$PLUGINS_PATH . '/default_colors/default_colors.css.template'); |
75 | $data['css_files'][] = $file ; | 75 | $content = ''; |
76 | foreach (DEFAULT_COLORS_PLACEHOLDERS as $rule) { | ||
77 | $content .= !empty($params[$rule]) | ||
78 | ? default_colors_format_css_rule($params, $rule) .';'. PHP_EOL | ||
79 | : ''; | ||
76 | } | 80 | } |
77 | 81 | ||
78 | return $data; | 82 | if (! empty($content)) { |
83 | file_put_contents($file, sprintf($template, $content)); | ||
84 | } | ||
79 | } | 85 | } |
80 | 86 | ||
81 | /** | 87 | /** |
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php index 71ba7495..defb01f7 100644 --- a/plugins/demo_plugin/demo_plugin.php +++ b/plugins/demo_plugin/demo_plugin.php | |||
@@ -2,8 +2,8 @@ | |||
2 | /** | 2 | /** |
3 | * Demo Plugin. | 3 | * Demo Plugin. |
4 | * | 4 | * |
5 | * This plugin try to cover Shaarli's plugin API entirely. | 5 | * This plugin tries to completely cover Shaarli's plugin API. |
6 | * Can be used by plugin developper to make their own. | 6 | * Can be used by plugin developers to make their own plugin. |
7 | */ | 7 | */ |
8 | 8 | ||
9 | /* | 9 | /* |
@@ -16,7 +16,7 @@ | |||
16 | 16 | ||
17 | use Shaarli\Config\ConfigManager; | 17 | use Shaarli\Config\ConfigManager; |
18 | use Shaarli\Plugin\PluginManager; | 18 | use Shaarli\Plugin\PluginManager; |
19 | use Shaarli\Router; | 19 | use Shaarli\Render\TemplatePage; |
20 | 20 | ||
21 | /** | 21 | /** |
22 | * In the footer hook, there is a working example of a translation extension for Shaarli. | 22 | * In the footer hook, there is a working example of a translation extension for Shaarli. |
@@ -61,7 +61,7 @@ function demo_plugin_init($conf) | |||
61 | 61 | ||
62 | /** | 62 | /** |
63 | * Hook render_header. | 63 | * Hook render_header. |
64 | * Executed on every page redering. | 64 | * Executed on every page render. |
65 | * | 65 | * |
66 | * Template placeholders: | 66 | * Template placeholders: |
67 | * - buttons_toolbar | 67 | * - buttons_toolbar |
@@ -74,7 +74,7 @@ function demo_plugin_init($conf) | |||
74 | function hook_demo_plugin_render_header($data) | 74 | function hook_demo_plugin_render_header($data) |
75 | { | 75 | { |
76 | // Only execute when linklist is rendered. | 76 | // Only execute when linklist is rendered. |
77 | if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { | 77 | if ($data['_PAGE_'] == TemplatePage::LINKLIST) { |
78 | // If loggedin | 78 | // If loggedin |
79 | if ($data['_LOGGEDIN_'] === true) { | 79 | if ($data['_LOGGEDIN_'] === true) { |
80 | /* | 80 | /* |
@@ -118,7 +118,7 @@ function hook_demo_plugin_render_header($data) | |||
118 | $form = array( | 118 | $form = array( |
119 | 'attr' => array( | 119 | 'attr' => array( |
120 | 'method' => 'GET', | 120 | 'method' => 'GET', |
121 | 'action' => '?', | 121 | 'action' => $data['_BASE_PATH_'] . '/', |
122 | 'class' => 'addform', | 122 | 'class' => 'addform', |
123 | ), | 123 | ), |
124 | 'inputs' => array( | 124 | 'inputs' => array( |
@@ -145,7 +145,7 @@ function hook_demo_plugin_render_header($data) | |||
145 | 145 | ||
146 | /** | 146 | /** |
147 | * Hook render_includes. | 147 | * Hook render_includes. |
148 | * Executed on every page redering. | 148 | * Executed on every page render. |
149 | * | 149 | * |
150 | * Template placeholders: | 150 | * Template placeholders: |
151 | * - css_files | 151 | * - css_files |
@@ -169,7 +169,7 @@ function hook_demo_plugin_render_includes($data) | |||
169 | 169 | ||
170 | /** | 170 | /** |
171 | * Hook render_footer. | 171 | * Hook render_footer. |
172 | * Executed on every page redering. | 172 | * Executed on every page render. |
173 | * | 173 | * |
174 | * Template placeholders: | 174 | * Template placeholders: |
175 | * - text | 175 | * - text |
@@ -186,7 +186,7 @@ function hook_demo_plugin_render_includes($data) | |||
186 | */ | 186 | */ |
187 | function hook_demo_plugin_render_footer($data) | 187 | function hook_demo_plugin_render_footer($data) |
188 | { | 188 | { |
189 | // footer text | 189 | // Footer text |
190 | $data['text'][] = '<br>'. demo_plugin_t('Shaarli is now enhanced by the awesome demo_plugin.'); | 190 | $data['text'][] = '<br>'. demo_plugin_t('Shaarli is now enhanced by the awesome demo_plugin.'); |
191 | 191 | ||
192 | // Free elements at the end of the page. | 192 | // Free elements at the end of the page. |
@@ -277,7 +277,7 @@ function hook_demo_plugin_render_editlink($data) | |||
277 | // Load HTML into a string | 277 | // Load HTML into a string |
278 | $html = file_get_contents(PluginManager::$PLUGINS_PATH .'/demo_plugin/field.html'); | 278 | $html = file_get_contents(PluginManager::$PLUGINS_PATH .'/demo_plugin/field.html'); |
279 | 279 | ||
280 | // replace value in HTML if it exists in $data | 280 | // Replace value in HTML if it exists in $data |
281 | if (!empty($data['link']['stuff'])) { | 281 | if (!empty($data['link']['stuff'])) { |
282 | $html = sprintf($html, $data['link']['stuff']); | 282 | $html = sprintf($html, $data['link']['stuff']); |
283 | } else { | 283 | } else { |
@@ -324,9 +324,7 @@ function hook_demo_plugin_render_tools($data) | |||
324 | */ | 324 | */ |
325 | function hook_demo_plugin_render_picwall($data) | 325 | function hook_demo_plugin_render_picwall($data) |
326 | { | 326 | { |
327 | // plugin_start_zone | ||
328 | $data['plugin_start_zone'][] = '<center>BEFORE</center>'; | 327 | $data['plugin_start_zone'][] = '<center>BEFORE</center>'; |
329 | // plugin_end_zone | ||
330 | $data['plugin_end_zone'][] = '<center>AFTER</center>'; | 328 | $data['plugin_end_zone'][] = '<center>AFTER</center>'; |
331 | 329 | ||
332 | return $data; | 330 | return $data; |
@@ -348,9 +346,7 @@ function hook_demo_plugin_render_picwall($data) | |||
348 | */ | 346 | */ |
349 | function hook_demo_plugin_render_tagcloud($data) | 347 | function hook_demo_plugin_render_tagcloud($data) |
350 | { | 348 | { |
351 | // plugin_start_zone | ||
352 | $data['plugin_start_zone'][] = '<center>BEFORE</center>'; | 349 | $data['plugin_start_zone'][] = '<center>BEFORE</center>'; |
353 | // plugin_end_zone | ||
354 | $data['plugin_end_zone'][] = '<center>AFTER</center>'; | 350 | $data['plugin_end_zone'][] = '<center>AFTER</center>'; |
355 | 351 | ||
356 | return $data; | 352 | return $data; |
@@ -372,9 +368,7 @@ function hook_demo_plugin_render_tagcloud($data) | |||
372 | */ | 368 | */ |
373 | function hook_demo_plugin_render_daily($data) | 369 | function hook_demo_plugin_render_daily($data) |
374 | { | 370 | { |
375 | // plugin_start_zone | ||
376 | $data['plugin_start_zone'][] = '<center>BEFORE</center>'; | 371 | $data['plugin_start_zone'][] = '<center>BEFORE</center>'; |
377 | // plugin_end_zone | ||
378 | $data['plugin_end_zone'][] = '<center>AFTER</center>'; | 372 | $data['plugin_end_zone'][] = '<center>AFTER</center>'; |
379 | 373 | ||
380 | 374 | ||
@@ -447,9 +441,9 @@ function hook_demo_plugin_delete_link($data) | |||
447 | function hook_demo_plugin_render_feed($data) | 441 | function hook_demo_plugin_render_feed($data) |
448 | { | 442 | { |
449 | foreach ($data['links'] as &$link) { | 443 | foreach ($data['links'] as &$link) { |
450 | if ($data['_PAGE_'] == Router::$PAGE_FEED_ATOM) { | 444 | if ($data['_PAGE_'] == TemplatePage::FEED_ATOM) { |
451 | $link['description'] .= ' - ATOM Feed' ; | 445 | $link['description'] .= ' - ATOM Feed' ; |
452 | } elseif ($data['_PAGE_'] == Router::$PAGE_FEED_RSS) { | 446 | } elseif ($data['_PAGE_'] == TemplatePage::FEED_RSS) { |
453 | $link['description'] .= ' - RSS Feed'; | 447 | $link['description'] .= ' - RSS Feed'; |
454 | } | 448 | } |
455 | } | 449 | } |
@@ -465,7 +459,8 @@ function hook_demo_plugin_render_feed($data) | |||
465 | */ | 459 | */ |
466 | function hook_demo_plugin_save_plugin_parameters($data) | 460 | function hook_demo_plugin_save_plugin_parameters($data) |
467 | { | 461 | { |
468 | // Here we edit the provided value, but we can use this to generate config files, etc. | 462 | // Here we edit the provided value. |
463 | // This hook can also be used to generate config files, etc. | ||
469 | if (! empty($data['DEMO_PLUGIN_PARAMETER']) && ! endsWith($data['DEMO_PLUGIN_PARAMETER'], '_SUFFIX')) { | 464 | if (! empty($data['DEMO_PLUGIN_PARAMETER']) && ! endsWith($data['DEMO_PLUGIN_PARAMETER'], '_SUFFIX')) { |
470 | $data['DEMO_PLUGIN_PARAMETER'] .= '_SUFFIX'; | 465 | $data['DEMO_PLUGIN_PARAMETER'] .= '_SUFFIX'; |
471 | } | 466 | } |
diff --git a/plugins/isso/isso.php b/plugins/isso/isso.php index dab75dd5..79e7380b 100644 --- a/plugins/isso/isso.php +++ b/plugins/isso/isso.php | |||
@@ -6,7 +6,7 @@ | |||
6 | 6 | ||
7 | use Shaarli\Config\ConfigManager; | 7 | use Shaarli\Config\ConfigManager; |
8 | use Shaarli\Plugin\PluginManager; | 8 | use Shaarli\Plugin\PluginManager; |
9 | use Shaarli\Router; | 9 | use Shaarli\Render\TemplatePage; |
10 | 10 | ||
11 | /** | 11 | /** |
12 | * Display an error everywhere if the plugin is enabled without configuration. | 12 | * Display an error everywhere if the plugin is enabled without configuration. |
@@ -49,7 +49,7 @@ function hook_isso_render_linklist($data, $conf) | |||
49 | $isso = sprintf($issoHtml, $issoUrl, $issoUrl, $link['id'], $link['id']); | 49 | $isso = sprintf($issoHtml, $issoUrl, $issoUrl, $link['id'], $link['id']); |
50 | $data['plugin_end_zone'][] = $isso; | 50 | $data['plugin_end_zone'][] = $isso; |
51 | } else { | 51 | } else { |
52 | $button = '<span><a href="?%s#isso-thread">'; | 52 | $button = '<span><a href="'. ($data['_BASE_PATH_'] ?? '') . '/shaare/%s#isso-thread">'; |
53 | // For the default theme we use a FontAwesome icon which is better than an image | 53 | // For the default theme we use a FontAwesome icon which is better than an image |
54 | if ($conf->get('resource.theme') === 'default') { | 54 | if ($conf->get('resource.theme') === 'default') { |
55 | $button .= '<i class="linklist-plugin-icon fa fa-comment"></i>'; | 55 | $button .= '<i class="linklist-plugin-icon fa fa-comment"></i>'; |
@@ -76,7 +76,7 @@ function hook_isso_render_linklist($data, $conf) | |||
76 | */ | 76 | */ |
77 | function hook_isso_render_includes($data) | 77 | function hook_isso_render_includes($data) |
78 | { | 78 | { |
79 | if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { | 79 | if ($data['_PAGE_'] == TemplatePage::LINKLIST) { |
80 | $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/isso/isso.css'; | 80 | $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/isso/isso.css'; |
81 | } | 81 | } |
82 | 82 | ||
diff --git a/plugins/isso/isso_button.html b/plugins/isso/isso_button.html deleted file mode 100644 index 3f828480..00000000 --- a/plugins/isso/isso_button.html +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | <span> | ||
2 | <a href="?%s#isso-thread"> | ||
3 | <img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="%s" alt="archive.org" /> | ||
4 | </a> | ||
5 | </span> | ||
diff --git a/plugins/markdown/README.md b/plugins/markdown/README.md deleted file mode 100644 index bc9427e2..00000000 --- a/plugins/markdown/README.md +++ /dev/null | |||
@@ -1,102 +0,0 @@ | |||
1 | ## Markdown Shaarli plugin | ||
2 | |||
3 | Convert all your shaares description to HTML formatted Markdown. | ||
4 | |||
5 | [Read more about Markdown syntax](http://daringfireball.net/projects/markdown/syntax). | ||
6 | |||
7 | Markdown processing is done with [Parsedown library](https://github.com/erusev/parsedown). | ||
8 | |||
9 | ### Installation | ||
10 | |||
11 | As a default plugin, it should already be in `tpl/plugins/` directory. | ||
12 | If not, download and unpack it there. | ||
13 | |||
14 | The directory structure should look like: | ||
15 | |||
16 | ``` | ||
17 | --- plugins | ||
18 | |--- markdown | ||
19 | |--- help.html | ||
20 | |--- markdown.css | ||
21 | |--- markdown.meta | ||
22 | |--- markdown.php | ||
23 | |--- README.md | ||
24 | ``` | ||
25 | |||
26 | To enable the plugin, just check it in the plugin administration page. | ||
27 | |||
28 | You can also add `markdown` to your list of enabled plugins in `data/config.json.php` | ||
29 | (`general.enabled_plugins` list). | ||
30 | |||
31 | This should look like: | ||
32 | |||
33 | ``` | ||
34 | "general": { | ||
35 | "enabled_plugins": [ | ||
36 | "markdown", | ||
37 | [...] | ||
38 | ], | ||
39 | } | ||
40 | ``` | ||
41 | |||
42 | Parsedown parsing library is imported using Composer. If you installed Shaarli using `git`, | ||
43 | or the `master` branch, run | ||
44 | |||
45 | composer update --no-dev --prefer-dist | ||
46 | |||
47 | ### No Markdown tag | ||
48 | |||
49 | If the tag `nomarkdown` is set for a shaare, it won't be converted to Markdown syntax. | ||
50 | |||
51 | > Note: this is a special tag, so it won't be displayed in link list. | ||
52 | |||
53 | ### HTML escape | ||
54 | |||
55 | By default, HTML tags are escaped. You can enable HTML tags rendering | ||
56 | by setting `security.markdwon_escape` to `false` in `data/config.json.php`: | ||
57 | |||
58 | ```json | ||
59 | { | ||
60 | "security": { | ||
61 | "markdown_escape": false | ||
62 | } | ||
63 | } | ||
64 | ``` | ||
65 | |||
66 | With this setting, Markdown support HTML tags. For example: | ||
67 | |||
68 | > <strong>strong</strong><strike>strike</strike> | ||
69 | |||
70 | Will render as: | ||
71 | |||
72 | > <strong>strong</strong><strike>strike</strike> | ||
73 | |||
74 | |||
75 | **Warning:** | ||
76 | |||
77 | * This setting might present **security risks** (XSS) on shared instances, even though tags | ||
78 | such as script, iframe, etc should be disabled. | ||
79 | * If you want to shaare HTML code, it is necessary to use inline code or code blocks. | ||
80 | * If your shaared descriptions contained HTML tags before enabling the markdown plugin, | ||
81 | enabling it might break your page. | ||
82 | |||
83 | ### Known issue | ||
84 | |||
85 | #### Redirector | ||
86 | |||
87 | If you're using a redirector, you *need* to add a space after a link, | ||
88 | otherwise the rest of the line will be `urlencode`. | ||
89 | |||
90 | ``` | ||
91 | [link](http://domain.tld)-->test | ||
92 | ``` | ||
93 | |||
94 | Will consider `http://domain.tld)-->test` as URL. | ||
95 | |||
96 | Instead, add an additional space. | ||
97 | |||
98 | ``` | ||
99 | [link](http://domain.tld) -->test | ||
100 | ``` | ||
101 | |||
102 | > Won't fix because a `)` is a valid part of an URL. | ||
diff --git a/plugins/markdown/help.html b/plugins/markdown/help.html deleted file mode 100644 index ded3d347..00000000 --- a/plugins/markdown/help.html +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | <div class="md_help"> | ||
2 | %s | ||
3 | <a href="http://daringfireball.net/projects/markdown/syntax" title="%s"> | ||
4 | %s</a>. | ||
5 | </div> | ||
diff --git a/plugins/markdown/markdown.meta b/plugins/markdown/markdown.meta deleted file mode 100644 index 322856ea..00000000 --- a/plugins/markdown/markdown.meta +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | description="Render shaare description with Markdown syntax.<br><strong>Warning</strong>: | ||
2 | If your shaared descriptions contained HTML tags before enabling the markdown plugin, | ||
3 | enabling it might break your page. | ||
4 | See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/markdown#html-rendering\">README</a>." | ||
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php deleted file mode 100644 index 628970d6..00000000 --- a/plugins/markdown/markdown.php +++ /dev/null | |||
@@ -1,365 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Plugin Markdown. | ||
5 | * | ||
6 | * Shaare's descriptions are parsed with Markdown. | ||
7 | */ | ||
8 | |||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\Plugin\PluginManager; | ||
11 | use Shaarli\Router; | ||
12 | |||
13 | /* | ||
14 | * If this tag is used on a shaare, the description won't be processed by Parsedown. | ||
15 | */ | ||
16 | define('NO_MD_TAG', 'nomarkdown'); | ||
17 | |||
18 | /** | ||
19 | * Parse linklist descriptions. | ||
20 | * | ||
21 | * @param array $data linklist data. | ||
22 | * @param ConfigManager $conf instance. | ||
23 | * | ||
24 | * @return mixed linklist data parsed in markdown (and converted to HTML). | ||
25 | */ | ||
26 | function hook_markdown_render_linklist($data, $conf) | ||
27 | { | ||
28 | foreach ($data['links'] as &$value) { | ||
29 | if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { | ||
30 | $value = stripNoMarkdownTag($value); | ||
31 | continue; | ||
32 | } | ||
33 | $value['description_src'] = $value['description']; | ||
34 | $value['description'] = process_markdown( | ||
35 | $value['description'], | ||
36 | $conf->get('security.markdown_escape', true), | ||
37 | $conf->get('security.allowed_protocols') | ||
38 | ); | ||
39 | } | ||
40 | return $data; | ||
41 | } | ||
42 | |||
43 | /** | ||
44 | * Parse feed linklist descriptions. | ||
45 | * | ||
46 | * @param array $data linklist data. | ||
47 | * @param ConfigManager $conf instance. | ||
48 | * | ||
49 | * @return mixed linklist data parsed in markdown (and converted to HTML). | ||
50 | */ | ||
51 | function hook_markdown_render_feed($data, $conf) | ||
52 | { | ||
53 | foreach ($data['links'] as &$value) { | ||
54 | if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { | ||
55 | $value = stripNoMarkdownTag($value); | ||
56 | continue; | ||
57 | } | ||
58 | $value['description'] = reverse_feed_permalink($value['description']); | ||
59 | $value['description'] = process_markdown( | ||
60 | $value['description'], | ||
61 | $conf->get('security.markdown_escape', true), | ||
62 | $conf->get('security.allowed_protocols') | ||
63 | ); | ||
64 | } | ||
65 | |||
66 | return $data; | ||
67 | } | ||
68 | |||
69 | /** | ||
70 | * Parse daily descriptions. | ||
71 | * | ||
72 | * @param array $data daily data. | ||
73 | * @param ConfigManager $conf instance. | ||
74 | * | ||
75 | * @return mixed daily data parsed in markdown (and converted to HTML). | ||
76 | */ | ||
77 | function hook_markdown_render_daily($data, $conf) | ||
78 | { | ||
79 | //var_dump($data);die; | ||
80 | // Manipulate columns data | ||
81 | foreach ($data['linksToDisplay'] as &$value) { | ||
82 | if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { | ||
83 | $value = stripNoMarkdownTag($value); | ||
84 | continue; | ||
85 | } | ||
86 | $value['formatedDescription'] = process_markdown( | ||
87 | $value['formatedDescription'], | ||
88 | $conf->get('security.markdown_escape', true), | ||
89 | $conf->get('security.allowed_protocols') | ||
90 | ); | ||
91 | } | ||
92 | |||
93 | return $data; | ||
94 | } | ||
95 | |||
96 | /** | ||
97 | * Check if noMarkdown is set in tags. | ||
98 | * | ||
99 | * @param string $tags tag list | ||
100 | * | ||
101 | * @return bool true if markdown should be disabled on this link. | ||
102 | */ | ||
103 | function noMarkdownTag($tags) | ||
104 | { | ||
105 | return preg_match('/(^|\s)'. NO_MD_TAG .'(\s|$)/', $tags); | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * Remove the no-markdown meta tag so it won't be displayed. | ||
110 | * | ||
111 | * @param array $link Link data. | ||
112 | * | ||
113 | * @return array Updated link without no markdown tag. | ||
114 | */ | ||
115 | function stripNoMarkdownTag($link) | ||
116 | { | ||
117 | if (! empty($link['taglist'])) { | ||
118 | $offset = array_search(NO_MD_TAG, $link['taglist']); | ||
119 | if ($offset !== false) { | ||
120 | unset($link['taglist'][$offset]); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | if (!empty($link['tags'])) { | ||
125 | str_replace(NO_MD_TAG, '', $link['tags']); | ||
126 | } | ||
127 | |||
128 | return $link; | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * When link list is displayed, include markdown CSS. | ||
133 | * | ||
134 | * @param array $data includes data. | ||
135 | * | ||
136 | * @return mixed - includes data with markdown CSS file added. | ||
137 | */ | ||
138 | function hook_markdown_render_includes($data) | ||
139 | { | ||
140 | if ($data['_PAGE_'] == Router::$PAGE_LINKLIST | ||
141 | || $data['_PAGE_'] == Router::$PAGE_DAILY | ||
142 | || $data['_PAGE_'] == Router::$PAGE_EDITLINK | ||
143 | ) { | ||
144 | $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/markdown/markdown.css'; | ||
145 | } | ||
146 | |||
147 | return $data; | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Hook render_editlink. | ||
152 | * Adds an help link to markdown syntax. | ||
153 | * | ||
154 | * @param array $data data passed to plugin | ||
155 | * | ||
156 | * @return array altered $data. | ||
157 | */ | ||
158 | function hook_markdown_render_editlink($data) | ||
159 | { | ||
160 | // Load help HTML into a string | ||
161 | $txt = file_get_contents(PluginManager::$PLUGINS_PATH .'/markdown/help.html'); | ||
162 | $translations = [ | ||
163 | t('Description will be rendered with'), | ||
164 | t('Markdown syntax documentation'), | ||
165 | t('Markdown syntax'), | ||
166 | ]; | ||
167 | $data['edit_link_plugin'][] = vsprintf($txt, $translations); | ||
168 | // Add no markdown 'meta-tag' in tag list if it was never used, for autocompletion. | ||
169 | if (! in_array(NO_MD_TAG, $data['tags'])) { | ||
170 | $data['tags'][NO_MD_TAG] = 0; | ||
171 | } | ||
172 | |||
173 | return $data; | ||
174 | } | ||
175 | |||
176 | |||
177 | /** | ||
178 | * Remove HTML links auto generated by Shaarli core system. | ||
179 | * Keeps HREF attributes. | ||
180 | * | ||
181 | * @param string $description input description text. | ||
182 | * | ||
183 | * @return string $description without HTML links. | ||
184 | */ | ||
185 | function reverse_text2clickable($description) | ||
186 | { | ||
187 | $descriptionLines = explode(PHP_EOL, $description); | ||
188 | $descriptionOut = ''; | ||
189 | $codeBlockOn = false; | ||
190 | $lineCount = 0; | ||
191 | |||
192 | foreach ($descriptionLines as $descriptionLine) { | ||
193 | // Detect line of code: starting with 4 spaces, | ||
194 | // except lists which can start with +/*/- or `2.` after spaces. | ||
195 | $codeLineOn = preg_match('/^ +(?=[^\+\*\-])(?=(?!\d\.).)/', $descriptionLine) > 0; | ||
196 | // Detect and toggle block of code | ||
197 | if (!$codeBlockOn) { | ||
198 | $codeBlockOn = preg_match('/^```/', $descriptionLine) > 0; | ||
199 | } elseif (preg_match('/^```/', $descriptionLine) > 0) { | ||
200 | $codeBlockOn = false; | ||
201 | } | ||
202 | |||
203 | $hashtagTitle = ' title="Hashtag [^"]+"'; | ||
204 | // Reverse `inline code` hashtags. | ||
205 | $descriptionLine = preg_replace( | ||
206 | '!(`[^`\n]*)<a href="[^ ]*"'. $hashtagTitle .'>([^<]+)</a>([^`\n]*`)!m', | ||
207 | '$1$2$3', | ||
208 | $descriptionLine | ||
209 | ); | ||
210 | |||
211 | // Reverse all links in code blocks, only non hashtag elsewhere. | ||
212 | $hashtagFilter = (!$codeBlockOn && !$codeLineOn) ? '(?!'. $hashtagTitle .')': '(?:'. $hashtagTitle .')?'; | ||
213 | $descriptionLine = preg_replace( | ||
214 | '#<a href="[^ ]*"'. $hashtagFilter .'>([^<]+)</a>#m', | ||
215 | '$1', | ||
216 | $descriptionLine | ||
217 | ); | ||
218 | |||
219 | // Make hashtag links markdown ready, otherwise the links will be ignored with escape set to true | ||
220 | if (!$codeBlockOn && !$codeLineOn) { | ||
221 | $descriptionLine = preg_replace( | ||
222 | '#<a href="([^ ]*)"'. $hashtagTitle .'>([^<]+)</a>#m', | ||
223 | '[$2]($1)', | ||
224 | $descriptionLine | ||
225 | ); | ||
226 | } | ||
227 | |||
228 | $descriptionOut .= $descriptionLine; | ||
229 | if ($lineCount++ < count($descriptionLines) - 1) { | ||
230 | $descriptionOut .= PHP_EOL; | ||
231 | } | ||
232 | } | ||
233 | return $descriptionOut; | ||
234 | } | ||
235 | |||
236 | /** | ||
237 | * Remove <br> tag to let markdown handle it. | ||
238 | * | ||
239 | * @param string $description input description text. | ||
240 | * | ||
241 | * @return string $description without <br> tags. | ||
242 | */ | ||
243 | function reverse_nl2br($description) | ||
244 | { | ||
245 | return preg_replace('!<br */?>!im', '', $description); | ||
246 | } | ||
247 | |||
248 | /** | ||
249 | * Remove HTML spaces ' ' auto generated by Shaarli core system. | ||
250 | * | ||
251 | * @param string $description input description text. | ||
252 | * | ||
253 | * @return string $description without HTML links. | ||
254 | */ | ||
255 | function reverse_space2nbsp($description) | ||
256 | { | ||
257 | return preg_replace('/(^| ) /m', '$1 ', $description); | ||
258 | } | ||
259 | |||
260 | function reverse_feed_permalink($description) | ||
261 | { | ||
262 | return preg_replace('@— <a href="([^"]+)" title="[^"]+">(\w+)</a>$@im', '— [$2]($1)', $description); | ||
263 | } | ||
264 | |||
265 | /** | ||
266 | * Replace not whitelisted protocols with http:// in given description. | ||
267 | * | ||
268 | * @param string $description input description text. | ||
269 | * @param array $allowedProtocols list of allowed protocols. | ||
270 | * | ||
271 | * @return string $description without malicious link. | ||
272 | */ | ||
273 | function filter_protocols($description, $allowedProtocols) | ||
274 | { | ||
275 | return preg_replace_callback( | ||
276 | '#]\((.*?)\)#is', | ||
277 | function ($match) use ($allowedProtocols) { | ||
278 | return ']('. whitelist_protocols($match[1], $allowedProtocols) .')'; | ||
279 | }, | ||
280 | $description | ||
281 | ); | ||
282 | } | ||
283 | |||
284 | /** | ||
285 | * Remove dangerous HTML tags (tags, iframe, etc.). | ||
286 | * Doesn't affect <code> content (already escaped by Parsedown). | ||
287 | * | ||
288 | * @param string $description input description text. | ||
289 | * | ||
290 | * @return string given string escaped. | ||
291 | */ | ||
292 | function sanitize_html($description) | ||
293 | { | ||
294 | $escapeTags = array( | ||
295 | 'script', | ||
296 | 'style', | ||
297 | 'link', | ||
298 | 'iframe', | ||
299 | 'frameset', | ||
300 | 'frame', | ||
301 | ); | ||
302 | foreach ($escapeTags as $tag) { | ||
303 | $description = preg_replace_callback( | ||
304 | '#<\s*'. $tag .'[^>]*>(.*</\s*'. $tag .'[^>]*>)?#is', | ||
305 | function ($match) { | ||
306 | return escape($match[0]); | ||
307 | }, | ||
308 | $description | ||
309 | ); | ||
310 | } | ||
311 | $description = preg_replace( | ||
312 | '#(<[^>]+\s)on[a-z]*="?[^ "]*"?#is', | ||
313 | '$1', | ||
314 | $description | ||
315 | ); | ||
316 | return $description; | ||
317 | } | ||
318 | |||
319 | /** | ||
320 | * Render shaare contents through Markdown parser. | ||
321 | * 1. Remove HTML generated by Shaarli core. | ||
322 | * 2. Reverse the escape function. | ||
323 | * 3. Generate markdown descriptions. | ||
324 | * 4. Sanitize sensible HTML tags for security. | ||
325 | * 5. Wrap description in 'markdown' CSS class. | ||
326 | * | ||
327 | * @param string $description input description text. | ||
328 | * @param bool $escape escape HTML entities | ||
329 | * | ||
330 | * @return string HTML processed $description. | ||
331 | */ | ||
332 | function process_markdown($description, $escape = true, $allowedProtocols = []) | ||
333 | { | ||
334 | $parsedown = new Parsedown(); | ||
335 | |||
336 | $processedDescription = $description; | ||
337 | $processedDescription = reverse_nl2br($processedDescription); | ||
338 | $processedDescription = reverse_space2nbsp($processedDescription); | ||
339 | $processedDescription = reverse_text2clickable($processedDescription); | ||
340 | $processedDescription = filter_protocols($processedDescription, $allowedProtocols); | ||
341 | $processedDescription = unescape($processedDescription); | ||
342 | $processedDescription = $parsedown | ||
343 | ->setMarkupEscaped($escape) | ||
344 | ->setBreaksEnabled(true) | ||
345 | ->text($processedDescription); | ||
346 | $processedDescription = sanitize_html($processedDescription); | ||
347 | |||
348 | if (!empty($processedDescription)) { | ||
349 | $processedDescription = '<div class="markdown">'. $processedDescription . '</div>'; | ||
350 | } | ||
351 | |||
352 | return $processedDescription; | ||
353 | } | ||
354 | |||
355 | /** | ||
356 | * This function is never called, but contains translation calls for GNU gettext extraction. | ||
357 | */ | ||
358 | function markdown_dummy_translation() | ||
359 | { | ||
360 | // meta | ||
361 | t('Render shaare description with Markdown syntax.<br><strong>Warning</strong>: | ||
362 | If your shaared descriptions contained HTML tags before enabling the markdown plugin, | ||
363 | enabling it might break your page. | ||
364 | See the <a href="https://github.com/shaarli/Shaarli/tree/master/plugins/markdown#html-rendering">README</a>.'); | ||
365 | } | ||
diff --git a/plugins/playvideos/README.md b/plugins/playvideos/README.md index ab4be22a..32a94e88 100644 --- a/plugins/playvideos/README.md +++ b/plugins/playvideos/README.md | |||
@@ -8,22 +8,21 @@ This uses code from https://zaius.github.io/youtube_playlist/ and is currently o | |||
8 | 8 | ||
9 | #### Installation and setup | 9 | #### Installation and setup |
10 | 10 | ||
11 | This is a default Shaarli plugin, you just have to enable it. See https://shaarli.readthedocs.io/en/master/Shaarli-configuration/ | 11 | This is a default Shaarli plugin, you just have to enable it. See [Shaarli configuration](../../doc/md/Shaarli-configuration.md). |
12 | 12 | ||
13 | 13 | ||
14 | #### Troubleshooting | 14 | #### Troubleshooting |
15 | 15 | ||
16 | If your server has [Content Security Policy](http://content-security-policy.com/) headers enabled, this may prevent the script from loading fully. You should relax the CSP in your server settings. Example CSP rule for apache2: | 16 | If your server has [Content Security Policy](http://content-security-policy.com/) headers enabled, this may prevent the script from loading fully. You should relax the CSP in your server settings. Example CSP rule for apache2: |
17 | |||
18 | In `/etc/apache2/conf-available/shaarli-csp.conf`: | ||
19 | 17 | ||
20 | ```apache | 18 | ```apache |
21 | <Directory /path/to/shaarli> | 19 | <Directory /path/to/shaarli> |
20 | # Required for playvideos plugin | ||
22 | Header set Content-Security-Policy "script-src 'self' 'unsafe-inline' https://www.youtube.com https://s.ytimg.com 'unsafe-eval'" | 21 | Header set Content-Security-Policy "script-src 'self' 'unsafe-inline' https://www.youtube.com https://s.ytimg.com 'unsafe-eval'" |
23 | </Directory> | 22 | </Directory> |
24 | ``` | 23 | ``` |
25 | 24 | ||
26 | Then run `a2enconf shaarli-csp; service apache2 reload` | 25 | You may place the `Header` directive in the `<Directory...` section of your [webserver configuration](../../doc/md/Server-configuration.md)/virtualhost file, or write the above snippet to `/etc/apache2/conf-available/shaarli-csp.conf`; then run `a2enconf shaarli-csp; service apache2 reload`. |
27 | 26 | ||
28 | ### License | 27 | ### License |
29 | ``` | 28 | ``` |
diff --git a/plugins/playvideos/playvideos.php b/plugins/playvideos/playvideos.php index 0341ed59..91a9c1e5 100644 --- a/plugins/playvideos/playvideos.php +++ b/plugins/playvideos/playvideos.php | |||
@@ -7,7 +7,7 @@ | |||
7 | */ | 7 | */ |
8 | 8 | ||
9 | use Shaarli\Plugin\PluginManager; | 9 | use Shaarli\Plugin\PluginManager; |
10 | use Shaarli\Router; | 10 | use Shaarli\Render\TemplatePage; |
11 | 11 | ||
12 | /** | 12 | /** |
13 | * When linklist is displayed, add play videos to header's toolbar. | 13 | * When linklist is displayed, add play videos to header's toolbar. |
@@ -18,7 +18,7 @@ use Shaarli\Router; | |||
18 | */ | 18 | */ |
19 | function hook_playvideos_render_header($data) | 19 | function hook_playvideos_render_header($data) |
20 | { | 20 | { |
21 | if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { | 21 | if ($data['_PAGE_'] == TemplatePage::LINKLIST) { |
22 | $playvideo = array( | 22 | $playvideo = array( |
23 | 'attr' => array( | 23 | 'attr' => array( |
24 | 'href' => '#', | 24 | 'href' => '#', |
@@ -42,7 +42,7 @@ function hook_playvideos_render_header($data) | |||
42 | */ | 42 | */ |
43 | function hook_playvideos_render_footer($data) | 43 | function hook_playvideos_render_footer($data) |
44 | { | 44 | { |
45 | if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { | 45 | if ($data['_PAGE_'] == TemplatePage::LINKLIST) { |
46 | $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/jquery-1.11.2.min.js'; | 46 | $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/jquery-1.11.2.min.js'; |
47 | $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/youtube_playlist.js'; | 47 | $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/youtube_playlist.js'; |
48 | } | 48 | } |
diff --git a/plugins/pubsubhubbub/pubsubhubbub.php b/plugins/pubsubhubbub/pubsubhubbub.php index 2878c050..8fe6799c 100644 --- a/plugins/pubsubhubbub/pubsubhubbub.php +++ b/plugins/pubsubhubbub/pubsubhubbub.php | |||
@@ -13,7 +13,7 @@ use pubsubhubbub\publisher\Publisher; | |||
13 | use Shaarli\Config\ConfigManager; | 13 | use Shaarli\Config\ConfigManager; |
14 | use Shaarli\Feed\FeedBuilder; | 14 | use Shaarli\Feed\FeedBuilder; |
15 | use Shaarli\Plugin\PluginManager; | 15 | use Shaarli\Plugin\PluginManager; |
16 | use Shaarli\Router; | 16 | use Shaarli\Render\TemplatePage; |
17 | 17 | ||
18 | /** | 18 | /** |
19 | * Plugin init function - set the hub to the default appspot one. | 19 | * Plugin init function - set the hub to the default appspot one. |
@@ -41,7 +41,7 @@ function pubsubhubbub_init($conf) | |||
41 | */ | 41 | */ |
42 | function hook_pubsubhubbub_render_feed($data, $conf) | 42 | function hook_pubsubhubbub_render_feed($data, $conf) |
43 | { | 43 | { |
44 | $feedType = $data['_PAGE_'] == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM; | 44 | $feedType = $data['_PAGE_'] == TemplatePage::FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM; |
45 | $template = file_get_contents(PluginManager::$PLUGINS_PATH . '/pubsubhubbub/hub.'. $feedType .'.xml'); | 45 | $template = file_get_contents(PluginManager::$PLUGINS_PATH . '/pubsubhubbub/hub.'. $feedType .'.xml'); |
46 | $data['feed_plugins_header'][] = sprintf($template, $conf->get('plugins.PUBSUBHUB_URL')); | 46 | $data['feed_plugins_header'][] = sprintf($template, $conf->get('plugins.PUBSUBHUB_URL')); |
47 | 47 | ||
@@ -60,8 +60,8 @@ function hook_pubsubhubbub_render_feed($data, $conf) | |||
60 | function hook_pubsubhubbub_save_link($data, $conf) | 60 | function hook_pubsubhubbub_save_link($data, $conf) |
61 | { | 61 | { |
62 | $feeds = array( | 62 | $feeds = array( |
63 | index_url($_SERVER) .'?do=atom', | 63 | index_url($_SERVER) .'feed/atom', |
64 | index_url($_SERVER) .'?do=rss', | 64 | index_url($_SERVER) .'feed/rss', |
65 | ); | 65 | ); |
66 | 66 | ||
67 | $httpPost = function_exists('curl_version') ? false : 'nocurl_http_post'; | 67 | $httpPost = function_exists('curl_version') ? false : 'nocurl_http_post'; |
diff --git a/plugins/qrcode/qrcode.php b/plugins/qrcode/qrcode.php index c1d237d5..95499e39 100644 --- a/plugins/qrcode/qrcode.php +++ b/plugins/qrcode/qrcode.php | |||
@@ -6,7 +6,7 @@ | |||
6 | */ | 6 | */ |
7 | 7 | ||
8 | use Shaarli\Plugin\PluginManager; | 8 | use Shaarli\Plugin\PluginManager; |
9 | use Shaarli\Router; | 9 | use Shaarli\Render\TemplatePage; |
10 | 10 | ||
11 | /** | 11 | /** |
12 | * Add qrcode icon to link_plugin when rendering linklist. | 12 | * Add qrcode icon to link_plugin when rendering linklist. |
@@ -19,11 +19,12 @@ function hook_qrcode_render_linklist($data) | |||
19 | { | 19 | { |
20 | $qrcode_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.html'); | 20 | $qrcode_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.html'); |
21 | 21 | ||
22 | $path = ($data['_BASE_PATH_'] ?? '') . '/' . PluginManager::$PLUGINS_PATH; | ||
22 | foreach ($data['links'] as &$value) { | 23 | foreach ($data['links'] as &$value) { |
23 | $qrcode = sprintf( | 24 | $qrcode = sprintf( |
24 | $qrcode_html, | 25 | $qrcode_html, |
25 | $value['url'], | 26 | $value['url'], |
26 | PluginManager::$PLUGINS_PATH | 27 | $path |
27 | ); | 28 | ); |
28 | $value['link_plugin'][] = $qrcode; | 29 | $value['link_plugin'][] = $qrcode; |
29 | } | 30 | } |
@@ -40,7 +41,7 @@ function hook_qrcode_render_linklist($data) | |||
40 | */ | 41 | */ |
41 | function hook_qrcode_render_footer($data) | 42 | function hook_qrcode_render_footer($data) |
42 | { | 43 | { |
43 | if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { | 44 | if ($data['_PAGE_'] == TemplatePage::LINKLIST) { |
44 | $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/shaarli-qrcode.js'; | 45 | $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/shaarli-qrcode.js'; |
45 | } | 46 | } |
46 | 47 | ||
@@ -56,7 +57,7 @@ function hook_qrcode_render_footer($data) | |||
56 | */ | 57 | */ |
57 | function hook_qrcode_render_includes($data) | 58 | function hook_qrcode_render_includes($data) |
58 | { | 59 | { |
59 | if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { | 60 | if ($data['_PAGE_'] == TemplatePage::LINKLIST) { |
60 | $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.css'; | 61 | $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.css'; |
61 | } | 62 | } |
62 | 63 | ||
diff --git a/plugins/qrcode/shaarli-qrcode.js b/plugins/qrcode/shaarli-qrcode.js index fe77c4cd..3316d6f6 100644 --- a/plugins/qrcode/shaarli-qrcode.js +++ b/plugins/qrcode/shaarli-qrcode.js | |||
@@ -28,14 +28,15 @@ | |||
28 | 28 | ||
29 | // Show the QR-Code of a permalink (when the QR-Code icon is clicked). | 29 | // Show the QR-Code of a permalink (when the QR-Code icon is clicked). |
30 | function showQrCode(caller,loading) | 30 | function showQrCode(caller,loading) |
31 | { | 31 | { |
32 | // Dynamic javascript lib loading: We only load qr.js if the QR code icon is clicked: | 32 | // Dynamic javascript lib loading: We only load qr.js if the QR code icon is clicked: |
33 | if (typeof(qr) == 'undefined') // Load qr.js only if not present. | 33 | if (typeof(qr) == 'undefined') // Load qr.js only if not present. |
34 | { | 34 | { |
35 | if (!loading) // If javascript lib is still loading, do not append script to body. | 35 | if (!loading) // If javascript lib is still loading, do not append script to body. |
36 | { | 36 | { |
37 | var element = document.createElement("script"); | 37 | var basePath = document.querySelector('input[name="js_base_path"]').value; |
38 | element.src = "plugins/qrcode/qr-1.1.3.min.js"; | 38 | var element = document.createElement("script"); |
39 | element.src = basePath + "/plugins/qrcode/qr-1.1.3.min.js"; | ||
39 | document.body.appendChild(element); | 40 | document.body.appendChild(element); |
40 | } | 41 | } |
41 | setTimeout(function() { showQrCode(caller,true);}, 200); // Retry in 200 milliseconds. | 42 | setTimeout(function() { showQrCode(caller,true);}, 200); // Retry in 200 milliseconds. |
@@ -44,7 +45,7 @@ function showQrCode(caller,loading) | |||
44 | 45 | ||
45 | // Remove previous qrcode if present. | 46 | // Remove previous qrcode if present. |
46 | removeQrcode(); | 47 | removeQrcode(); |
47 | 48 | ||
48 | // Build the div which contains the QR-Code: | 49 | // Build the div which contains the QR-Code: |
49 | var element = document.createElement('div'); | 50 | var element = document.createElement('div'); |
50 | element.id = 'permalinkQrcode'; | 51 | element.id = 'permalinkQrcode'; |
@@ -57,11 +58,11 @@ function showQrCode(caller,loading) | |||
57 | // Damn IE | 58 | // Damn IE |
58 | element.setAttribute('onclick', 'this.parentNode.removeChild(this);' ); | 59 | element.setAttribute('onclick', 'this.parentNode.removeChild(this);' ); |
59 | } | 60 | } |
60 | 61 | ||
61 | // Build the QR-Code: | 62 | // Build the QR-Code: |
62 | var image = qr.image({size: 8,value: caller.dataset.permalink}); | 63 | var image = qr.image({size: 8,value: caller.dataset.permalink}); |
63 | if (image) | 64 | if (image) |
64 | { | 65 | { |
65 | element.appendChild(image); | 66 | element.appendChild(image); |
66 | element.innerHTML += "<br>Click to close"; | 67 | element.innerHTML += "<br>Click to close"; |
67 | caller.parentNode.appendChild(element); | 68 | caller.parentNode.appendChild(element); |
@@ -87,4 +88,4 @@ function removeQrcode() | |||
87 | elem.parentNode.removeChild(elem); | 88 | elem.parentNode.removeChild(elem); |
88 | } | 89 | } |
89 | return false; | 90 | return false; |
90 | } \ No newline at end of file | 91 | } |
diff --git a/plugins/wallabag/README.md b/plugins/wallabag/README.md index ea21a519..c53a04d9 100644 --- a/plugins/wallabag/README.md +++ b/plugins/wallabag/README.md | |||
@@ -21,7 +21,7 @@ The directory structure should look like: | |||
21 | 21 | ||
22 | To enable the plugin, you can either: | 22 | To enable the plugin, you can either: |
23 | 23 | ||
24 | * enable it in the plugins administration page (`?do=pluginadmin`). | 24 | * enable it in the plugins administration page (`/admin/plugins`). |
25 | * add `wallabag` to your list of enabled plugins in `data/config.json.php` (`general.enabled_plugins` section). | 25 | * add `wallabag` to your list of enabled plugins in `data/config.json.php` (`general.enabled_plugins` section). |
26 | 26 | ||
27 | ### Configuration | 27 | ### Configuration |
diff --git a/plugins/wallabag/wallabag.php b/plugins/wallabag/wallabag.php index bc35df08..805c1ad9 100644 --- a/plugins/wallabag/wallabag.php +++ b/plugins/wallabag/wallabag.php | |||
@@ -45,12 +45,14 @@ function hook_wallabag_render_linklist($data, $conf) | |||
45 | $wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html'); | 45 | $wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html'); |
46 | 46 | ||
47 | $linkTitle = t('Save to wallabag'); | 47 | $linkTitle = t('Save to wallabag'); |
48 | $path = ($data['_BASE_PATH_'] ?? '') . '/' . PluginManager::$PLUGINS_PATH; | ||
49 | |||
48 | foreach ($data['links'] as &$value) { | 50 | foreach ($data['links'] as &$value) { |
49 | $wallabag = sprintf( | 51 | $wallabag = sprintf( |
50 | $wallabagHtml, | 52 | $wallabagHtml, |
51 | $wallabagInstance->getWallabagUrl(), | 53 | $wallabagInstance->getWallabagUrl(), |
52 | urlencode($value['url']), | 54 | urlencode($value['url']), |
53 | PluginManager::$PLUGINS_PATH, | 55 | $path, |
54 | $linkTitle | 56 | $linkTitle |
55 | ); | 57 | ); |
56 | $value['link_plugin'][] = $wallabag; | 58 | $value['link_plugin'][] = $wallabag; |
diff --git a/shaarli_version.php b/shaarli_version.php index 1941d6c3..8d94352f 100644 --- a/shaarli_version.php +++ b/shaarli_version.php | |||
@@ -1 +1 @@ | |||
<?php /* 0.11.1 */ ?> | <?php /* 0.12.0 */ ?> | ||
diff --git a/tests/ApplicationUtilsTest.php b/tests/ApplicationUtilsTest.php index 82f8804d..a232b351 100644 --- a/tests/ApplicationUtilsTest.php +++ b/tests/ApplicationUtilsTest.php | |||
@@ -8,7 +8,7 @@ require_once 'tests/utils/FakeApplicationUtils.php'; | |||
8 | /** | 8 | /** |
9 | * Unitary tests for Shaarli utilities | 9 | * Unitary tests for Shaarli utilities |
10 | */ | 10 | */ |
11 | class ApplicationUtilsTest extends \PHPUnit\Framework\TestCase | 11 | class ApplicationUtilsTest extends \Shaarli\TestCase |
12 | { | 12 | { |
13 | protected static $testUpdateFile = 'sandbox/update.txt'; | 13 | protected static $testUpdateFile = 'sandbox/update.txt'; |
14 | protected static $testVersion = '0.5.0'; | 14 | protected static $testVersion = '0.5.0'; |
@@ -17,7 +17,7 @@ class ApplicationUtilsTest extends \PHPUnit\Framework\TestCase | |||
17 | /** | 17 | /** |
18 | * Reset test data for each test | 18 | * Reset test data for each test |
19 | */ | 19 | */ |
20 | public function setUp() | 20 | protected function setUp(): void |
21 | { | 21 | { |
22 | FakeApplicationUtils::$VERSION_CODE = ''; | 22 | FakeApplicationUtils::$VERSION_CODE = ''; |
23 | if (file_exists(self::$testUpdateFile)) { | 23 | if (file_exists(self::$testUpdateFile)) { |
@@ -28,7 +28,7 @@ class ApplicationUtilsTest extends \PHPUnit\Framework\TestCase | |||
28 | /** | 28 | /** |
29 | * Remove test version file if it exists | 29 | * Remove test version file if it exists |
30 | */ | 30 | */ |
31 | public function tearDown() | 31 | protected function tearDown(): void |
32 | { | 32 | { |
33 | if (is_file('sandbox/version.php')) { | 33 | if (is_file('sandbox/version.php')) { |
34 | unlink('sandbox/version.php'); | 34 | unlink('sandbox/version.php'); |
@@ -144,11 +144,12 @@ class ApplicationUtilsTest extends \PHPUnit\Framework\TestCase | |||
144 | 144 | ||
145 | /** | 145 | /** |
146 | * Test update checks - invalid Git branch | 146 | * Test update checks - invalid Git branch |
147 | * @expectedException Exception | ||
148 | * @expectedExceptionMessageRegExp /Invalid branch selected for updates/ | ||
149 | */ | 147 | */ |
150 | public function testCheckUpdateInvalidGitBranch() | 148 | public function testCheckUpdateInvalidGitBranch() |
151 | { | 149 | { |
150 | $this->expectException(\Exception::class); | ||
151 | $this->expectExceptionMessageRegExp('/Invalid branch selected for updates/'); | ||
152 | |||
152 | ApplicationUtils::checkUpdate('', 'null', 0, true, true, 'unstable'); | 153 | ApplicationUtils::checkUpdate('', 'null', 0, true, true, 'unstable'); |
153 | } | 154 | } |
154 | 155 | ||
@@ -253,29 +254,31 @@ class ApplicationUtilsTest extends \PHPUnit\Framework\TestCase | |||
253 | public function testCheckSupportedPHPVersion() | 254 | public function testCheckSupportedPHPVersion() |
254 | { | 255 | { |
255 | $minVersion = '5.3'; | 256 | $minVersion = '5.3'; |
256 | ApplicationUtils::checkPHPVersion($minVersion, '5.4.32'); | 257 | $this->assertTrue(ApplicationUtils::checkPHPVersion($minVersion, '5.4.32')); |
257 | ApplicationUtils::checkPHPVersion($minVersion, '5.5'); | 258 | $this->assertTrue(ApplicationUtils::checkPHPVersion($minVersion, '5.5')); |
258 | ApplicationUtils::checkPHPVersion($minVersion, '5.6.10'); | 259 | $this->assertTrue(ApplicationUtils::checkPHPVersion($minVersion, '5.6.10')); |
259 | } | 260 | } |
260 | 261 | ||
261 | /** | 262 | /** |
262 | * Check a unsupported PHP version | 263 | * Check a unsupported PHP version |
263 | * @expectedException Exception | ||
264 | * @expectedExceptionMessageRegExp /Your PHP version is obsolete/ | ||
265 | */ | 264 | */ |
266 | public function testCheckSupportedPHPVersion51() | 265 | public function testCheckSupportedPHPVersion51() |
267 | { | 266 | { |
268 | ApplicationUtils::checkPHPVersion('5.3', '5.1.0'); | 267 | $this->expectException(\Exception::class); |
268 | $this->expectExceptionMessageRegExp('/Your PHP version is obsolete/'); | ||
269 | |||
270 | $this->assertTrue(ApplicationUtils::checkPHPVersion('5.3', '5.1.0')); | ||
269 | } | 271 | } |
270 | 272 | ||
271 | /** | 273 | /** |
272 | * Check another unsupported PHP version | 274 | * Check another unsupported PHP version |
273 | * @expectedException Exception | ||
274 | * @expectedExceptionMessageRegExp /Your PHP version is obsolete/ | ||
275 | */ | 275 | */ |
276 | public function testCheckSupportedPHPVersion52() | 276 | public function testCheckSupportedPHPVersion52() |
277 | { | 277 | { |
278 | ApplicationUtils::checkPHPVersion('5.3', '5.2'); | 278 | $this->expectException(\Exception::class); |
279 | $this->expectExceptionMessageRegExp('/Your PHP version is obsolete/'); | ||
280 | |||
281 | $this->assertTrue(ApplicationUtils::checkPHPVersion('5.3', '5.2')); | ||
279 | } | 282 | } |
280 | 283 | ||
281 | /** | 284 | /** |
diff --git a/tests/FileUtilsTest.php b/tests/FileUtilsTest.php index 57719175..9163bdf1 100644 --- a/tests/FileUtilsTest.php +++ b/tests/FileUtilsTest.php | |||
@@ -9,7 +9,7 @@ use Exception; | |||
9 | * | 9 | * |
10 | * Test file utility class. | 10 | * Test file utility class. |
11 | */ | 11 | */ |
12 | class FileUtilsTest extends \PHPUnit\Framework\TestCase | 12 | class FileUtilsTest extends \Shaarli\TestCase |
13 | { | 13 | { |
14 | /** | 14 | /** |
15 | * @var string Test file path. | 15 | * @var string Test file path. |
@@ -19,7 +19,7 @@ class FileUtilsTest extends \PHPUnit\Framework\TestCase | |||
19 | /** | 19 | /** |
20 | * Delete test file after every test. | 20 | * Delete test file after every test. |
21 | */ | 21 | */ |
22 | public function tearDown() | 22 | protected function tearDown(): void |
23 | { | 23 | { |
24 | @unlink(self::$file); | 24 | @unlink(self::$file); |
25 | } | 25 | } |
@@ -49,12 +49,12 @@ class FileUtilsTest extends \PHPUnit\Framework\TestCase | |||
49 | 49 | ||
50 | /** | 50 | /** |
51 | * File not writable: raise an exception. | 51 | * File not writable: raise an exception. |
52 | * | ||
53 | * @expectedException Shaarli\Exceptions\IOException | ||
54 | * @expectedExceptionMessage Error accessing "sandbox/flat.db" | ||
55 | */ | 52 | */ |
56 | public function testWriteWithoutPermission() | 53 | public function testWriteWithoutPermission() |
57 | { | 54 | { |
55 | $this->expectException(\Shaarli\Exceptions\IOException::class); | ||
56 | $this->expectExceptionMessage('Error accessing "sandbox/flat.db"'); | ||
57 | |||
58 | touch(self::$file); | 58 | touch(self::$file); |
59 | chmod(self::$file, 0440); | 59 | chmod(self::$file, 0440); |
60 | FileUtils::writeFlatDB(self::$file, null); | 60 | FileUtils::writeFlatDB(self::$file, null); |
@@ -62,23 +62,23 @@ class FileUtilsTest extends \PHPUnit\Framework\TestCase | |||
62 | 62 | ||
63 | /** | 63 | /** |
64 | * Folder non existent: raise an exception. | 64 | * Folder non existent: raise an exception. |
65 | * | ||
66 | * @expectedException Shaarli\Exceptions\IOException | ||
67 | * @expectedExceptionMessage Error accessing "nopefolder" | ||
68 | */ | 65 | */ |
69 | public function testWriteFolderDoesNotExist() | 66 | public function testWriteFolderDoesNotExist() |
70 | { | 67 | { |
68 | $this->expectException(\Shaarli\Exceptions\IOException::class); | ||
69 | $this->expectExceptionMessage('Error accessing "nopefolder"'); | ||
70 | |||
71 | FileUtils::writeFlatDB('nopefolder/file', null); | 71 | FileUtils::writeFlatDB('nopefolder/file', null); |
72 | } | 72 | } |
73 | 73 | ||
74 | /** | 74 | /** |
75 | * Folder non writable: raise an exception. | 75 | * Folder non writable: raise an exception. |
76 | * | ||
77 | * @expectedException Shaarli\Exceptions\IOException | ||
78 | * @expectedExceptionMessage Error accessing "sandbox" | ||
79 | */ | 76 | */ |
80 | public function testWriteFolderPermission() | 77 | public function testWriteFolderPermission() |
81 | { | 78 | { |
79 | $this->expectException(\Shaarli\Exceptions\IOException::class); | ||
80 | $this->expectExceptionMessage('Error accessing "sandbox"'); | ||
81 | |||
82 | chmod(dirname(self::$file), 0555); | 82 | chmod(dirname(self::$file), 0555); |
83 | try { | 83 | try { |
84 | FileUtils::writeFlatDB(self::$file, null); | 84 | FileUtils::writeFlatDB(self::$file, null); |
diff --git a/tests/HistoryTest.php b/tests/HistoryTest.php index 8303e53a..6dc0e5b7 100644 --- a/tests/HistoryTest.php +++ b/tests/HistoryTest.php | |||
@@ -3,9 +3,9 @@ | |||
3 | namespace Shaarli; | 3 | namespace Shaarli; |
4 | 4 | ||
5 | use DateTime; | 5 | use DateTime; |
6 | use Exception; | 6 | use Shaarli\Bookmark\Bookmark; |
7 | 7 | ||
8 | class HistoryTest extends \PHPUnit\Framework\TestCase | 8 | class HistoryTest extends \Shaarli\TestCase |
9 | { | 9 | { |
10 | /** | 10 | /** |
11 | * @var string History file path | 11 | * @var string History file path |
@@ -15,9 +15,11 @@ class HistoryTest extends \PHPUnit\Framework\TestCase | |||
15 | /** | 15 | /** |
16 | * Delete history file. | 16 | * Delete history file. |
17 | */ | 17 | */ |
18 | public function tearDown() | 18 | protected function setUp(): void |
19 | { | 19 | { |
20 | @unlink(self::$historyFilePath); | 20 | if (file_exists(self::$historyFilePath)) { |
21 | unlink(self::$historyFilePath); | ||
22 | } | ||
21 | } | 23 | } |
22 | 24 | ||
23 | /** | 25 | /** |
@@ -41,12 +43,12 @@ class HistoryTest extends \PHPUnit\Framework\TestCase | |||
41 | 43 | ||
42 | /** | 44 | /** |
43 | * Not writable history file: raise an exception. | 45 | * Not writable history file: raise an exception. |
44 | * | ||
45 | * @expectedException Exception | ||
46 | * @expectedExceptionMessage History file isn't readable or writable | ||
47 | */ | 46 | */ |
48 | public function testConstructNotWritable() | 47 | public function testConstructNotWritable() |
49 | { | 48 | { |
49 | $this->expectException(\Exception::class); | ||
50 | $this->expectExceptionMessage('History file isn\'t readable or writable'); | ||
51 | |||
50 | touch(self::$historyFilePath); | 52 | touch(self::$historyFilePath); |
51 | chmod(self::$historyFilePath, 0440); | 53 | chmod(self::$historyFilePath, 0440); |
52 | $history = new History(self::$historyFilePath); | 54 | $history = new History(self::$historyFilePath); |
@@ -55,12 +57,12 @@ class HistoryTest extends \PHPUnit\Framework\TestCase | |||
55 | 57 | ||
56 | /** | 58 | /** |
57 | * Not parsable history file: raise an exception. | 59 | * Not parsable history file: raise an exception. |
58 | * | ||
59 | * @expectedException Exception | ||
60 | * @expectedExceptionMessageRegExp /Could not parse history file/ | ||
61 | */ | 60 | */ |
62 | public function testConstructNotParsable() | 61 | public function testConstructNotParsable() |
63 | { | 62 | { |
63 | $this->expectException(\Exception::class); | ||
64 | $this->expectExceptionMessageRegExp('/Could not parse history file/'); | ||
65 | |||
64 | file_put_contents(self::$historyFilePath, 'not parsable'); | 66 | file_put_contents(self::$historyFilePath, 'not parsable'); |
65 | $history = new History(self::$historyFilePath); | 67 | $history = new History(self::$historyFilePath); |
66 | // gzinflate generates a warning | 68 | // gzinflate generates a warning |
@@ -73,137 +75,140 @@ class HistoryTest extends \PHPUnit\Framework\TestCase | |||
73 | public function testAddLink() | 75 | public function testAddLink() |
74 | { | 76 | { |
75 | $history = new History(self::$historyFilePath); | 77 | $history = new History(self::$historyFilePath); |
76 | $history->addLink(['id' => 0]); | 78 | $bookmark = (new Bookmark())->setId(0); |
79 | $history->addLink($bookmark); | ||
77 | $actual = $history->getHistory()[0]; | 80 | $actual = $history->getHistory()[0]; |
78 | $this->assertEquals(History::CREATED, $actual['event']); | 81 | $this->assertEquals(History::CREATED, $actual['event']); |
79 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 82 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
80 | $this->assertEquals(0, $actual['id']); | 83 | $this->assertEquals(0, $actual['id']); |
81 | 84 | ||
82 | $history = new History(self::$historyFilePath); | 85 | $history = new History(self::$historyFilePath); |
83 | $history->addLink(['id' => 1]); | 86 | $bookmark = (new Bookmark())->setId(1); |
87 | $history->addLink($bookmark); | ||
84 | $actual = $history->getHistory()[0]; | 88 | $actual = $history->getHistory()[0]; |
85 | $this->assertEquals(History::CREATED, $actual['event']); | 89 | $this->assertEquals(History::CREATED, $actual['event']); |
86 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 90 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
87 | $this->assertEquals(1, $actual['id']); | 91 | $this->assertEquals(1, $actual['id']); |
88 | 92 | ||
89 | $history = new History(self::$historyFilePath); | 93 | $history = new History(self::$historyFilePath); |
90 | $history->addLink(['id' => 'str']); | 94 | $bookmark = (new Bookmark())->setId('str'); |
95 | $history->addLink($bookmark); | ||
91 | $actual = $history->getHistory()[0]; | 96 | $actual = $history->getHistory()[0]; |
92 | $this->assertEquals(History::CREATED, $actual['event']); | 97 | $this->assertEquals(History::CREATED, $actual['event']); |
93 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 98 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
94 | $this->assertEquals('str', $actual['id']); | 99 | $this->assertEquals('str', $actual['id']); |
95 | } | 100 | } |
96 | 101 | ||
97 | /** | 102 | // /** |
98 | * Test updated link event | 103 | // * Test updated link event |
99 | */ | 104 | // */ |
100 | public function testUpdateLink() | 105 | // public function testUpdateLink() |
101 | { | 106 | // { |
102 | $history = new History(self::$historyFilePath); | 107 | // $history = new History(self::$historyFilePath); |
103 | $history->updateLink(['id' => 1]); | 108 | // $history->updateLink(['id' => 1]); |
104 | $actual = $history->getHistory()[0]; | 109 | // $actual = $history->getHistory()[0]; |
105 | $this->assertEquals(History::UPDATED, $actual['event']); | 110 | // $this->assertEquals(History::UPDATED, $actual['event']); |
106 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 111 | // $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
107 | $this->assertEquals(1, $actual['id']); | 112 | // $this->assertEquals(1, $actual['id']); |
108 | } | 113 | // } |
109 | 114 | // | |
110 | /** | 115 | // /** |
111 | * Test delete link event | 116 | // * Test delete link event |
112 | */ | 117 | // */ |
113 | public function testDeleteLink() | 118 | // public function testDeleteLink() |
114 | { | 119 | // { |
115 | $history = new History(self::$historyFilePath); | 120 | // $history = new History(self::$historyFilePath); |
116 | $history->deleteLink(['id' => 1]); | 121 | // $history->deleteLink(['id' => 1]); |
117 | $actual = $history->getHistory()[0]; | 122 | // $actual = $history->getHistory()[0]; |
118 | $this->assertEquals(History::DELETED, $actual['event']); | 123 | // $this->assertEquals(History::DELETED, $actual['event']); |
119 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 124 | // $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
120 | $this->assertEquals(1, $actual['id']); | 125 | // $this->assertEquals(1, $actual['id']); |
121 | } | 126 | // } |
122 | 127 | // | |
123 | /** | 128 | // /** |
124 | * Test updated settings event | 129 | // * Test updated settings event |
125 | */ | 130 | // */ |
126 | public function testUpdateSettings() | 131 | // public function testUpdateSettings() |
127 | { | 132 | // { |
128 | $history = new History(self::$historyFilePath); | 133 | // $history = new History(self::$historyFilePath); |
129 | $history->updateSettings(); | 134 | // $history->updateSettings(); |
130 | $actual = $history->getHistory()[0]; | 135 | // $actual = $history->getHistory()[0]; |
131 | $this->assertEquals(History::SETTINGS, $actual['event']); | 136 | // $this->assertEquals(History::SETTINGS, $actual['event']); |
132 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 137 | // $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
133 | $this->assertEmpty($actual['id']); | 138 | // $this->assertEmpty($actual['id']); |
134 | } | 139 | // } |
135 | 140 | // | |
136 | /** | 141 | // /** |
137 | * Make sure that new items are stored at the beginning | 142 | // * Make sure that new items are stored at the beginning |
138 | */ | 143 | // */ |
139 | public function testHistoryOrder() | 144 | // public function testHistoryOrder() |
140 | { | 145 | // { |
141 | $history = new History(self::$historyFilePath); | 146 | // $history = new History(self::$historyFilePath); |
142 | $history->updateLink(['id' => 1]); | 147 | // $history->updateLink(['id' => 1]); |
143 | $actual = $history->getHistory()[0]; | 148 | // $actual = $history->getHistory()[0]; |
144 | $this->assertEquals(History::UPDATED, $actual['event']); | 149 | // $this->assertEquals(History::UPDATED, $actual['event']); |
145 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 150 | // $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
146 | $this->assertEquals(1, $actual['id']); | 151 | // $this->assertEquals(1, $actual['id']); |
147 | 152 | // | |
148 | $history->addLink(['id' => 1]); | 153 | // $history->addLink(['id' => 1]); |
149 | $actual = $history->getHistory()[0]; | 154 | // $actual = $history->getHistory()[0]; |
150 | $this->assertEquals(History::CREATED, $actual['event']); | 155 | // $this->assertEquals(History::CREATED, $actual['event']); |
151 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 156 | // $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
152 | $this->assertEquals(1, $actual['id']); | 157 | // $this->assertEquals(1, $actual['id']); |
153 | } | 158 | // } |
154 | 159 | // | |
155 | /** | 160 | // /** |
156 | * Re-read history from file after writing an event | 161 | // * Re-read history from file after writing an event |
157 | */ | 162 | // */ |
158 | public function testHistoryRead() | 163 | // public function testHistoryRead() |
159 | { | 164 | // { |
160 | $history = new History(self::$historyFilePath); | 165 | // $history = new History(self::$historyFilePath); |
161 | $history->updateLink(['id' => 1]); | 166 | // $history->updateLink(['id' => 1]); |
162 | $history = new History(self::$historyFilePath); | 167 | // $history = new History(self::$historyFilePath); |
163 | $actual = $history->getHistory()[0]; | 168 | // $actual = $history->getHistory()[0]; |
164 | $this->assertEquals(History::UPDATED, $actual['event']); | 169 | // $this->assertEquals(History::UPDATED, $actual['event']); |
165 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 170 | // $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
166 | $this->assertEquals(1, $actual['id']); | 171 | // $this->assertEquals(1, $actual['id']); |
167 | } | 172 | // } |
168 | 173 | // | |
169 | /** | 174 | // /** |
170 | * Re-read history from file after writing an event and make sure that the order is correct | 175 | // * Re-read history from file after writing an event and make sure that the order is correct |
171 | */ | 176 | // */ |
172 | public function testHistoryOrderRead() | 177 | // public function testHistoryOrderRead() |
173 | { | 178 | // { |
174 | $history = new History(self::$historyFilePath); | 179 | // $history = new History(self::$historyFilePath); |
175 | $history->updateLink(['id' => 1]); | 180 | // $history->updateLink(['id' => 1]); |
176 | $history->addLink(['id' => 1]); | 181 | // $history->addLink(['id' => 1]); |
177 | 182 | // | |
178 | $history = new History(self::$historyFilePath); | 183 | // $history = new History(self::$historyFilePath); |
179 | $actual = $history->getHistory()[0]; | 184 | // $actual = $history->getHistory()[0]; |
180 | $this->assertEquals(History::CREATED, $actual['event']); | 185 | // $this->assertEquals(History::CREATED, $actual['event']); |
181 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 186 | // $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
182 | $this->assertEquals(1, $actual['id']); | 187 | // $this->assertEquals(1, $actual['id']); |
183 | 188 | // | |
184 | $actual = $history->getHistory()[1]; | 189 | // $actual = $history->getHistory()[1]; |
185 | $this->assertEquals(History::UPDATED, $actual['event']); | 190 | // $this->assertEquals(History::UPDATED, $actual['event']); |
186 | $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); | 191 | // $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']); |
187 | $this->assertEquals(1, $actual['id']); | 192 | // $this->assertEquals(1, $actual['id']); |
188 | } | 193 | // } |
189 | 194 | // | |
190 | /** | 195 | // /** |
191 | * Test retention time: delete old entries. | 196 | // * Test retention time: delete old entries. |
192 | */ | 197 | // */ |
193 | public function testHistoryRententionTime() | 198 | // public function testHistoryRententionTime() |
194 | { | 199 | // { |
195 | $history = new History(self::$historyFilePath, 5); | 200 | // $history = new History(self::$historyFilePath, 5); |
196 | $history->updateLink(['id' => 1]); | 201 | // $history->updateLink(['id' => 1]); |
197 | $this->assertEquals(1, count($history->getHistory())); | 202 | // $this->assertEquals(1, count($history->getHistory())); |
198 | $arr = $history->getHistory(); | 203 | // $arr = $history->getHistory(); |
199 | $arr[0]['datetime'] = new DateTime('-1 hour'); | 204 | // $arr[0]['datetime'] = new DateTime('-1 hour'); |
200 | FileUtils::writeFlatDB(self::$historyFilePath, $arr); | 205 | // FileUtils::writeFlatDB(self::$historyFilePath, $arr); |
201 | 206 | // | |
202 | $history = new History(self::$historyFilePath, 60); | 207 | // $history = new History(self::$historyFilePath, 60); |
203 | $this->assertEquals(1, count($history->getHistory())); | 208 | // $this->assertEquals(1, count($history->getHistory())); |
204 | $this->assertEquals(1, $history->getHistory()[0]['id']); | 209 | // $this->assertEquals(1, $history->getHistory()[0]['id']); |
205 | $history->updateLink(['id' => 2]); | 210 | // $history->updateLink(['id' => 2]); |
206 | $this->assertEquals(1, count($history->getHistory())); | 211 | // $this->assertEquals(1, count($history->getHistory())); |
207 | $this->assertEquals(2, $history->getHistory()[0]['id']); | 212 | // $this->assertEquals(2, $history->getHistory()[0]['id']); |
208 | } | 213 | // } |
209 | } | 214 | } |
diff --git a/tests/LanguagesTest.php b/tests/LanguagesTest.php index de83f291..ce24c160 100644 --- a/tests/LanguagesTest.php +++ b/tests/LanguagesTest.php | |||
@@ -7,7 +7,7 @@ use Shaarli\Config\ConfigManager; | |||
7 | /** | 7 | /** |
8 | * Class LanguagesTest. | 8 | * Class LanguagesTest. |
9 | */ | 9 | */ |
10 | class LanguagesTest extends \PHPUnit\Framework\TestCase | 10 | class LanguagesTest extends \Shaarli\TestCase |
11 | { | 11 | { |
12 | /** | 12 | /** |
13 | * @var string Config file path (without extension). | 13 | * @var string Config file path (without extension). |
@@ -22,7 +22,7 @@ class LanguagesTest extends \PHPUnit\Framework\TestCase | |||
22 | /** | 22 | /** |
23 | * | 23 | * |
24 | */ | 24 | */ |
25 | public function setUp() | 25 | protected function setUp(): void |
26 | { | 26 | { |
27 | $this->conf = new ConfigManager(self::$configFile); | 27 | $this->conf = new ConfigManager(self::$configFile); |
28 | } | 28 | } |
diff --git a/tests/PluginManagerTest.php b/tests/PluginManagerTest.php index 71761ac1..efef5e87 100644 --- a/tests/PluginManagerTest.php +++ b/tests/PluginManagerTest.php | |||
@@ -1,4 +1,5 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
2 | namespace Shaarli\Plugin; | 3 | namespace Shaarli\Plugin; |
3 | 4 | ||
4 | use Shaarli\Config\ConfigManager; | 5 | use Shaarli\Config\ConfigManager; |
@@ -6,7 +7,7 @@ use Shaarli\Config\ConfigManager; | |||
6 | /** | 7 | /** |
7 | * Unit tests for Plugins | 8 | * Unit tests for Plugins |
8 | */ | 9 | */ |
9 | class PluginManagerTest extends \PHPUnit\Framework\TestCase | 10 | class PluginManagerTest extends \Shaarli\TestCase |
10 | { | 11 | { |
11 | /** | 12 | /** |
12 | * Path to tests plugin. | 13 | * Path to tests plugin. |
@@ -25,7 +26,7 @@ class PluginManagerTest extends \PHPUnit\Framework\TestCase | |||
25 | */ | 26 | */ |
26 | protected $pluginManager; | 27 | protected $pluginManager; |
27 | 28 | ||
28 | public function setUp() | 29 | public function setUp(): void |
29 | { | 30 | { |
30 | $conf = new ConfigManager(''); | 31 | $conf = new ConfigManager(''); |
31 | $this->pluginManager = new PluginManager($conf); | 32 | $this->pluginManager = new PluginManager($conf); |
@@ -33,58 +34,88 @@ class PluginManagerTest extends \PHPUnit\Framework\TestCase | |||
33 | 34 | ||
34 | /** | 35 | /** |
35 | * Test plugin loading and hook execution. | 36 | * Test plugin loading and hook execution. |
36 | * | ||
37 | * @return void | ||
38 | */ | 37 | */ |
39 | public function testPlugin() | 38 | public function testPlugin(): void |
40 | { | 39 | { |
41 | PluginManager::$PLUGINS_PATH = self::$pluginPath; | 40 | PluginManager::$PLUGINS_PATH = self::$pluginPath; |
42 | $this->pluginManager->load(array(self::$pluginName)); | 41 | $this->pluginManager->load(array(self::$pluginName)); |
43 | 42 | ||
44 | $this->assertTrue(function_exists('hook_test_random')); | 43 | $this->assertTrue(function_exists('hook_test_random')); |
45 | 44 | ||
46 | $data = array(0 => 'woot'); | 45 | $data = [0 => 'woot']; |
47 | $this->pluginManager->executeHooks('random', $data); | 46 | $this->pluginManager->executeHooks('random', $data); |
48 | $this->assertEquals('woot', $data[1]); | ||
49 | 47 | ||
50 | $data = array(0 => 'woot'); | 48 | static::assertCount(2, $data); |
49 | static::assertSame('woot', $data[1]); | ||
50 | |||
51 | $data = [0 => 'woot']; | ||
51 | $this->pluginManager->executeHooks('random', $data, array('target' => 'test')); | 52 | $this->pluginManager->executeHooks('random', $data, array('target' => 'test')); |
52 | $this->assertEquals('page test', $data[1]); | ||
53 | 53 | ||
54 | $data = array(0 => 'woot'); | 54 | static::assertCount(2, $data); |
55 | static::assertSame('page test', $data[1]); | ||
56 | |||
57 | $data = [0 => 'woot']; | ||
55 | $this->pluginManager->executeHooks('random', $data, array('loggedin' => true)); | 58 | $this->pluginManager->executeHooks('random', $data, array('loggedin' => true)); |
56 | $this->assertEquals('loggedin', $data[1]); | 59 | |
60 | static::assertCount(2, $data); | ||
61 | static::assertEquals('loggedin', $data[1]); | ||
62 | |||
63 | $data = [0 => 'woot']; | ||
64 | $this->pluginManager->executeHooks('random', $data, array('loggedin' => null)); | ||
65 | |||
66 | static::assertCount(3, $data); | ||
67 | static::assertEquals('loggedin', $data[1]); | ||
68 | static::assertArrayHasKey(2, $data); | ||
69 | static::assertNull($data[2]); | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * Test plugin loading and hook execution with an error: raise an incompatibility error. | ||
74 | */ | ||
75 | public function testPluginWithPhpError(): void | ||
76 | { | ||
77 | PluginManager::$PLUGINS_PATH = self::$pluginPath; | ||
78 | $this->pluginManager->load(array(self::$pluginName)); | ||
79 | |||
80 | $this->assertTrue(function_exists('hook_test_error')); | ||
81 | |||
82 | $data = []; | ||
83 | $this->pluginManager->executeHooks('error', $data); | ||
84 | |||
85 | $this->assertRegExp( | ||
86 | '/test \[plugin incompatibility\]: Class [\'"]Unknown[\'"] not found/', | ||
87 | $this->pluginManager->getErrors()[0] | ||
88 | ); | ||
57 | } | 89 | } |
58 | 90 | ||
59 | /** | 91 | /** |
60 | * Test missing plugin loading. | 92 | * Test missing plugin loading. |
61 | * | ||
62 | * @return void | ||
63 | */ | 93 | */ |
64 | public function testPluginNotFound() | 94 | public function testPluginNotFound(): void |
65 | { | 95 | { |
66 | $this->pluginManager->load(array()); | 96 | $this->pluginManager->load([]); |
67 | $this->pluginManager->load(array('nope', 'renope')); | 97 | $this->pluginManager->load(['nope', 'renope']); |
98 | $this->addToAssertionCount(1); | ||
68 | } | 99 | } |
69 | 100 | ||
70 | /** | 101 | /** |
71 | * Test plugin metadata loading. | 102 | * Test plugin metadata loading. |
72 | */ | 103 | */ |
73 | public function testGetPluginsMeta() | 104 | public function testGetPluginsMeta(): void |
74 | { | 105 | { |
75 | PluginManager::$PLUGINS_PATH = self::$pluginPath; | 106 | PluginManager::$PLUGINS_PATH = self::$pluginPath; |
76 | $this->pluginManager->load(array(self::$pluginName)); | 107 | $this->pluginManager->load([self::$pluginName]); |
77 | 108 | ||
78 | $expectedParameters = array( | 109 | $expectedParameters = [ |
79 | 'pop' => array( | 110 | 'pop' => [ |
80 | 'value' => '', | 111 | 'value' => '', |
81 | 'desc' => 'pop description', | 112 | 'desc' => 'pop description', |
82 | ), | 113 | ], |
83 | 'hip' => array( | 114 | 'hip' => [ |
84 | 'value' => '', | 115 | 'value' => '', |
85 | 'desc' => '', | 116 | 'desc' => '', |
86 | ), | 117 | ], |
87 | ); | 118 | ]; |
88 | $meta = $this->pluginManager->getPluginsMeta(); | 119 | $meta = $this->pluginManager->getPluginsMeta(); |
89 | $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); | 120 | $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); |
90 | $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); | 121 | $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); |
diff --git a/tests/RouterTest.php b/tests/RouterTest.php deleted file mode 100644 index 0cd49bb8..00000000 --- a/tests/RouterTest.php +++ /dev/null | |||
@@ -1,509 +0,0 @@ | |||
1 | <?php | ||
2 | namespace Shaarli; | ||
3 | |||
4 | /** | ||
5 | * Unit tests for Router | ||
6 | */ | ||
7 | class RouterTest extends \PHPUnit\Framework\TestCase | ||
8 | { | ||
9 | /** | ||
10 | * Test findPage: login page output. | ||
11 | * Valid: page should be return. | ||
12 | * | ||
13 | * @return void | ||
14 | */ | ||
15 | public function testFindPageLoginValid() | ||
16 | { | ||
17 | $this->assertEquals( | ||
18 | Router::$PAGE_LOGIN, | ||
19 | Router::findPage('do=login', array(), false) | ||
20 | ); | ||
21 | |||
22 | $this->assertEquals( | ||
23 | Router::$PAGE_LOGIN, | ||
24 | Router::findPage('do=login', array(), 1) | ||
25 | ); | ||
26 | |||
27 | $this->assertEquals( | ||
28 | Router::$PAGE_LOGIN, | ||
29 | Router::findPage('do=login&stuff', array(), false) | ||
30 | ); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Test findPage: login page output. | ||
35 | * Invalid: page shouldn't be return. | ||
36 | * | ||
37 | * @return void | ||
38 | */ | ||
39 | public function testFindPageLoginInvalid() | ||
40 | { | ||
41 | $this->assertNotEquals( | ||
42 | Router::$PAGE_LOGIN, | ||
43 | Router::findPage('do=login', array(), true) | ||
44 | ); | ||
45 | |||
46 | $this->assertNotEquals( | ||
47 | Router::$PAGE_LOGIN, | ||
48 | Router::findPage('do=other', array(), false) | ||
49 | ); | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Test findPage: picwall page output. | ||
54 | * Valid: page should be return. | ||
55 | * | ||
56 | * @return void | ||
57 | */ | ||
58 | public function testFindPagePicwallValid() | ||
59 | { | ||
60 | $this->assertEquals( | ||
61 | Router::$PAGE_PICWALL, | ||
62 | Router::findPage('do=picwall', array(), false) | ||
63 | ); | ||
64 | |||
65 | $this->assertEquals( | ||
66 | Router::$PAGE_PICWALL, | ||
67 | Router::findPage('do=picwall', array(), true) | ||
68 | ); | ||
69 | } | ||
70 | |||
71 | /** | ||
72 | * Test findPage: picwall page output. | ||
73 | * Invalid: page shouldn't be return. | ||
74 | * | ||
75 | * @return void | ||
76 | */ | ||
77 | public function testFindPagePicwallInvalid() | ||
78 | { | ||
79 | $this->assertEquals( | ||
80 | Router::$PAGE_PICWALL, | ||
81 | Router::findPage('do=picwall&stuff', array(), false) | ||
82 | ); | ||
83 | |||
84 | $this->assertNotEquals( | ||
85 | Router::$PAGE_PICWALL, | ||
86 | Router::findPage('do=other', array(), false) | ||
87 | ); | ||
88 | } | ||
89 | |||
90 | /** | ||
91 | * Test findPage: tagcloud page output. | ||
92 | * Valid: page should be return. | ||
93 | * | ||
94 | * @return void | ||
95 | */ | ||
96 | public function testFindPageTagcloudValid() | ||
97 | { | ||
98 | $this->assertEquals( | ||
99 | Router::$PAGE_TAGCLOUD, | ||
100 | Router::findPage('do=tagcloud', array(), false) | ||
101 | ); | ||
102 | |||
103 | $this->assertEquals( | ||
104 | Router::$PAGE_TAGCLOUD, | ||
105 | Router::findPage('do=tagcloud', array(), true) | ||
106 | ); | ||
107 | |||
108 | $this->assertEquals( | ||
109 | Router::$PAGE_TAGCLOUD, | ||
110 | Router::findPage('do=tagcloud&stuff', array(), false) | ||
111 | ); | ||
112 | } | ||
113 | |||
114 | /** | ||
115 | * Test findPage: tagcloud page output. | ||
116 | * Invalid: page shouldn't be return. | ||
117 | * | ||
118 | * @return void | ||
119 | */ | ||
120 | public function testFindPageTagcloudInvalid() | ||
121 | { | ||
122 | $this->assertNotEquals( | ||
123 | Router::$PAGE_TAGCLOUD, | ||
124 | Router::findPage('do=other', array(), false) | ||
125 | ); | ||
126 | } | ||
127 | |||
128 | /** | ||
129 | * Test findPage: linklist page output. | ||
130 | * Valid: page should be return. | ||
131 | * | ||
132 | * @return void | ||
133 | */ | ||
134 | public function testFindPageLinklistValid() | ||
135 | { | ||
136 | $this->assertEquals( | ||
137 | Router::$PAGE_LINKLIST, | ||
138 | Router::findPage('', array(), true) | ||
139 | ); | ||
140 | |||
141 | $this->assertEquals( | ||
142 | Router::$PAGE_LINKLIST, | ||
143 | Router::findPage('whatever', array(), true) | ||
144 | ); | ||
145 | |||
146 | $this->assertEquals( | ||
147 | Router::$PAGE_LINKLIST, | ||
148 | Router::findPage('whatever', array(), false) | ||
149 | ); | ||
150 | |||
151 | $this->assertEquals( | ||
152 | Router::$PAGE_LINKLIST, | ||
153 | Router::findPage('do=tools', array(), false) | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | /** | ||
158 | * Test findPage: tools page output. | ||
159 | * Valid: page should be return. | ||
160 | * | ||
161 | * @return void | ||
162 | */ | ||
163 | public function testFindPageToolsValid() | ||
164 | { | ||
165 | $this->assertEquals( | ||
166 | Router::$PAGE_TOOLS, | ||
167 | Router::findPage('do=tools', array(), true) | ||
168 | ); | ||
169 | |||
170 | $this->assertEquals( | ||
171 | Router::$PAGE_TOOLS, | ||
172 | Router::findPage('do=tools&stuff', array(), true) | ||
173 | ); | ||
174 | } | ||
175 | |||
176 | /** | ||
177 | * Test findPage: tools page output. | ||
178 | * Invalid: page shouldn't be return. | ||
179 | * | ||
180 | * @return void | ||
181 | */ | ||
182 | public function testFindPageToolsInvalid() | ||
183 | { | ||
184 | $this->assertNotEquals( | ||
185 | Router::$PAGE_TOOLS, | ||
186 | Router::findPage('do=tools', array(), 1) | ||
187 | ); | ||
188 | |||
189 | $this->assertNotEquals( | ||
190 | Router::$PAGE_TOOLS, | ||
191 | Router::findPage('do=tools', array(), false) | ||
192 | ); | ||
193 | |||
194 | $this->assertNotEquals( | ||
195 | Router::$PAGE_TOOLS, | ||
196 | Router::findPage('do=other', array(), true) | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | /** | ||
201 | * Test findPage: changepasswd page output. | ||
202 | * Valid: page should be return. | ||
203 | * | ||
204 | * @return void | ||
205 | */ | ||
206 | public function testFindPageChangepasswdValid() | ||
207 | { | ||
208 | $this->assertEquals( | ||
209 | Router::$PAGE_CHANGEPASSWORD, | ||
210 | Router::findPage('do=changepasswd', array(), true) | ||
211 | ); | ||
212 | $this->assertEquals( | ||
213 | Router::$PAGE_CHANGEPASSWORD, | ||
214 | Router::findPage('do=changepasswd&stuff', array(), true) | ||
215 | ); | ||
216 | } | ||
217 | |||
218 | /** | ||
219 | * Test findPage: changepasswd page output. | ||
220 | * Invalid: page shouldn't be return. | ||
221 | * | ||
222 | * @return void | ||
223 | */ | ||
224 | public function testFindPageChangepasswdInvalid() | ||
225 | { | ||
226 | $this->assertNotEquals( | ||
227 | Router::$PAGE_CHANGEPASSWORD, | ||
228 | Router::findPage('do=changepasswd', array(), 1) | ||
229 | ); | ||
230 | |||
231 | $this->assertNotEquals( | ||
232 | Router::$PAGE_CHANGEPASSWORD, | ||
233 | Router::findPage('do=changepasswd', array(), false) | ||
234 | ); | ||
235 | |||
236 | $this->assertNotEquals( | ||
237 | Router::$PAGE_CHANGEPASSWORD, | ||
238 | Router::findPage('do=other', array(), true) | ||
239 | ); | ||
240 | } | ||
241 | /** | ||
242 | * Test findPage: configure page output. | ||
243 | * Valid: page should be return. | ||
244 | * | ||
245 | * @return void | ||
246 | */ | ||
247 | public function testFindPageConfigureValid() | ||
248 | { | ||
249 | $this->assertEquals( | ||
250 | Router::$PAGE_CONFIGURE, | ||
251 | Router::findPage('do=configure', array(), true) | ||
252 | ); | ||
253 | |||
254 | $this->assertEquals( | ||
255 | Router::$PAGE_CONFIGURE, | ||
256 | Router::findPage('do=configure&stuff', array(), true) | ||
257 | ); | ||
258 | } | ||
259 | |||
260 | /** | ||
261 | * Test findPage: configure page output. | ||
262 | * Invalid: page shouldn't be return. | ||
263 | * | ||
264 | * @return void | ||
265 | */ | ||
266 | public function testFindPageConfigureInvalid() | ||
267 | { | ||
268 | $this->assertNotEquals( | ||
269 | Router::$PAGE_CONFIGURE, | ||
270 | Router::findPage('do=configure', array(), 1) | ||
271 | ); | ||
272 | |||
273 | $this->assertNotEquals( | ||
274 | Router::$PAGE_CONFIGURE, | ||
275 | Router::findPage('do=configure', array(), false) | ||
276 | ); | ||
277 | |||
278 | $this->assertNotEquals( | ||
279 | Router::$PAGE_CONFIGURE, | ||
280 | Router::findPage('do=other', array(), true) | ||
281 | ); | ||
282 | } | ||
283 | |||
284 | /** | ||
285 | * Test findPage: changetag page output. | ||
286 | * Valid: page should be return. | ||
287 | * | ||
288 | * @return void | ||
289 | */ | ||
290 | public function testFindPageChangetagValid() | ||
291 | { | ||
292 | $this->assertEquals( | ||
293 | Router::$PAGE_CHANGETAG, | ||
294 | Router::findPage('do=changetag', array(), true) | ||
295 | ); | ||
296 | |||
297 | $this->assertEquals( | ||
298 | Router::$PAGE_CHANGETAG, | ||
299 | Router::findPage('do=changetag&stuff', array(), true) | ||
300 | ); | ||
301 | } | ||
302 | |||
303 | /** | ||
304 | * Test findPage: changetag page output. | ||
305 | * Invalid: page shouldn't be return. | ||
306 | * | ||
307 | * @return void | ||
308 | */ | ||
309 | public function testFindPageChangetagInvalid() | ||
310 | { | ||
311 | $this->assertNotEquals( | ||
312 | Router::$PAGE_CHANGETAG, | ||
313 | Router::findPage('do=changetag', array(), 1) | ||
314 | ); | ||
315 | |||
316 | $this->assertNotEquals( | ||
317 | Router::$PAGE_CHANGETAG, | ||
318 | Router::findPage('do=changetag', array(), false) | ||
319 | ); | ||
320 | |||
321 | $this->assertNotEquals( | ||
322 | Router::$PAGE_CHANGETAG, | ||
323 | Router::findPage('do=other', array(), true) | ||
324 | ); | ||
325 | } | ||
326 | |||
327 | /** | ||
328 | * Test findPage: addlink page output. | ||
329 | * Valid: page should be return. | ||
330 | * | ||
331 | * @return void | ||
332 | */ | ||
333 | public function testFindPageAddlinkValid() | ||
334 | { | ||
335 | $this->assertEquals( | ||
336 | Router::$PAGE_ADDLINK, | ||
337 | Router::findPage('do=addlink', array(), true) | ||
338 | ); | ||
339 | |||
340 | $this->assertEquals( | ||
341 | Router::$PAGE_ADDLINK, | ||
342 | Router::findPage('do=addlink&stuff', array(), true) | ||
343 | ); | ||
344 | } | ||
345 | |||
346 | /** | ||
347 | * Test findPage: addlink page output. | ||
348 | * Invalid: page shouldn't be return. | ||
349 | * | ||
350 | * @return void | ||
351 | */ | ||
352 | public function testFindPageAddlinkInvalid() | ||
353 | { | ||
354 | $this->assertNotEquals( | ||
355 | Router::$PAGE_ADDLINK, | ||
356 | Router::findPage('do=addlink', array(), 1) | ||
357 | ); | ||
358 | |||
359 | $this->assertNotEquals( | ||
360 | Router::$PAGE_ADDLINK, | ||
361 | Router::findPage('do=addlink', array(), false) | ||
362 | ); | ||
363 | |||
364 | $this->assertNotEquals( | ||
365 | Router::$PAGE_ADDLINK, | ||
366 | Router::findPage('do=other', array(), true) | ||
367 | ); | ||
368 | } | ||
369 | |||
370 | /** | ||
371 | * Test findPage: export page output. | ||
372 | * Valid: page should be return. | ||
373 | * | ||
374 | * @return void | ||
375 | */ | ||
376 | public function testFindPageExportValid() | ||
377 | { | ||
378 | $this->assertEquals( | ||
379 | Router::$PAGE_EXPORT, | ||
380 | Router::findPage('do=export', array(), true) | ||
381 | ); | ||
382 | |||
383 | $this->assertEquals( | ||
384 | Router::$PAGE_EXPORT, | ||
385 | Router::findPage('do=export&stuff', array(), true) | ||
386 | ); | ||
387 | } | ||
388 | |||
389 | /** | ||
390 | * Test findPage: export page output. | ||
391 | * Invalid: page shouldn't be return. | ||
392 | * | ||
393 | * @return void | ||
394 | */ | ||
395 | public function testFindPageExportInvalid() | ||
396 | { | ||
397 | $this->assertNotEquals( | ||
398 | Router::$PAGE_EXPORT, | ||
399 | Router::findPage('do=export', array(), 1) | ||
400 | ); | ||
401 | |||
402 | $this->assertNotEquals( | ||
403 | Router::$PAGE_EXPORT, | ||
404 | Router::findPage('do=export', array(), false) | ||
405 | ); | ||
406 | |||
407 | $this->assertNotEquals( | ||
408 | Router::$PAGE_EXPORT, | ||
409 | Router::findPage('do=other', array(), true) | ||
410 | ); | ||
411 | } | ||
412 | |||
413 | /** | ||
414 | * Test findPage: import page output. | ||
415 | * Valid: page should be return. | ||
416 | * | ||
417 | * @return void | ||
418 | */ | ||
419 | public function testFindPageImportValid() | ||
420 | { | ||
421 | $this->assertEquals( | ||
422 | Router::$PAGE_IMPORT, | ||
423 | Router::findPage('do=import', array(), true) | ||
424 | ); | ||
425 | |||
426 | $this->assertEquals( | ||
427 | Router::$PAGE_IMPORT, | ||
428 | Router::findPage('do=import&stuff', array(), true) | ||
429 | ); | ||
430 | } | ||
431 | |||
432 | /** | ||
433 | * Test findPage: import page output. | ||
434 | * Invalid: page shouldn't be return. | ||
435 | * | ||
436 | * @return void | ||
437 | */ | ||
438 | public function testFindPageImportInvalid() | ||
439 | { | ||
440 | $this->assertNotEquals( | ||
441 | Router::$PAGE_IMPORT, | ||
442 | Router::findPage('do=import', array(), 1) | ||
443 | ); | ||
444 | |||
445 | $this->assertNotEquals( | ||
446 | Router::$PAGE_IMPORT, | ||
447 | Router::findPage('do=import', array(), false) | ||
448 | ); | ||
449 | |||
450 | $this->assertNotEquals( | ||
451 | Router::$PAGE_IMPORT, | ||
452 | Router::findPage('do=other', array(), true) | ||
453 | ); | ||
454 | } | ||
455 | |||
456 | /** | ||
457 | * Test findPage: editlink page output. | ||
458 | * Valid: page should be return. | ||
459 | * | ||
460 | * @return void | ||
461 | */ | ||
462 | public function testFindPageEditlinkValid() | ||
463 | { | ||
464 | $this->assertEquals( | ||
465 | Router::$PAGE_EDITLINK, | ||
466 | Router::findPage('whatever', array('edit_link' => 1), true) | ||
467 | ); | ||
468 | |||
469 | $this->assertEquals( | ||
470 | Router::$PAGE_EDITLINK, | ||
471 | Router::findPage('', array('edit_link' => 1), true) | ||
472 | ); | ||
473 | |||
474 | |||
475 | $this->assertEquals( | ||
476 | Router::$PAGE_EDITLINK, | ||
477 | Router::findPage('whatever', array('post' => 1), true) | ||
478 | ); | ||
479 | |||
480 | $this->assertEquals( | ||
481 | Router::$PAGE_EDITLINK, | ||
482 | Router::findPage('whatever', array('post' => 1, 'edit_link' => 1), true) | ||
483 | ); | ||
484 | } | ||
485 | |||
486 | /** | ||
487 | * Test findPage: editlink page output. | ||
488 | * Invalid: page shouldn't be return. | ||
489 | * | ||
490 | * @return void | ||
491 | */ | ||
492 | public function testFindPageEditlinkInvalid() | ||
493 | { | ||
494 | $this->assertNotEquals( | ||
495 | Router::$PAGE_EDITLINK, | ||
496 | Router::findPage('whatever', array('edit_link' => 1), false) | ||
497 | ); | ||
498 | |||
499 | $this->assertNotEquals( | ||
500 | Router::$PAGE_EDITLINK, | ||
501 | Router::findPage('whatever', array('edit_link' => 1), 1) | ||
502 | ); | ||
503 | |||
504 | $this->assertNotEquals( | ||
505 | Router::$PAGE_EDITLINK, | ||
506 | Router::findPage('whatever', array(), true) | ||
507 | ); | ||
508 | } | ||
509 | } | ||
diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 00000000..781e7aa3 --- /dev/null +++ b/tests/TestCase.php | |||
@@ -0,0 +1,77 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli; | ||
6 | |||
7 | /** | ||
8 | * Helper class extending \PHPUnit\Framework\TestCase. | ||
9 | * Used to make Shaarli UT run on multiple versions of PHPUnit. | ||
10 | */ | ||
11 | class TestCase extends \PHPUnit\Framework\TestCase | ||
12 | { | ||
13 | /** | ||
14 | * expectExceptionMessageRegExp has been removed and replaced by expectExceptionMessageMatches in PHPUnit 9. | ||
15 | */ | ||
16 | public function expectExceptionMessageRegExp(string $regularExpression): void | ||
17 | { | ||
18 | if (method_exists($this, 'expectExceptionMessageMatches')) { | ||
19 | $this->expectExceptionMessageMatches($regularExpression); | ||
20 | } else { | ||
21 | parent::expectExceptionMessageRegExp($regularExpression); | ||
22 | } | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * assertContains is now used for iterable, strings should use assertStringContainsString | ||
27 | */ | ||
28 | public function assertContainsPolyfill($expected, $actual, string $message = ''): void | ||
29 | { | ||
30 | if (is_string($actual) && method_exists($this, 'assertStringContainsString')) { | ||
31 | static::assertStringContainsString($expected, $actual, $message); | ||
32 | } else { | ||
33 | static::assertContains($expected, $actual, $message); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | /** | ||
38 | * assertNotContains is now used for iterable, strings should use assertStringNotContainsString | ||
39 | */ | ||
40 | public function assertNotContainsPolyfill($expected, $actual, string $message = ''): void | ||
41 | { | ||
42 | if (is_string($actual) && method_exists($this, 'assertStringNotContainsString')) { | ||
43 | static::assertStringNotContainsString($expected, $actual, $message); | ||
44 | } else { | ||
45 | static::assertNotContains($expected, $actual, $message); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * assertFileNotExists has been renamed in assertFileDoesNotExist | ||
51 | */ | ||
52 | public static function assertFileNotExists(string $filename, string $message = ''): void | ||
53 | { | ||
54 | if (method_exists(TestCase::class, 'assertFileDoesNotExist')) { | ||
55 | static::assertFileDoesNotExist($filename, $message); | ||
56 | } else { | ||
57 | parent::assertFileNotExists($filename, $message); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | /** | ||
62 | * assertRegExp has been renamed in assertMatchesRegularExpression | ||
63 | */ | ||
64 | public static function assertRegExp(string $pattern, string $string, string $message = ''): void | ||
65 | { | ||
66 | if (method_exists(TestCase::class, 'assertMatchesRegularExpression')) { | ||
67 | static::assertMatchesRegularExpression($pattern, $string, $message); | ||
68 | } else { | ||
69 | parent::assertRegExp($pattern, $string, $message); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | public function isInTestsContext(): bool | ||
74 | { | ||
75 | return true; | ||
76 | } | ||
77 | } | ||
diff --git a/tests/ThumbnailerTest.php b/tests/ThumbnailerTest.php index c01849f7..70519aca 100644 --- a/tests/ThumbnailerTest.php +++ b/tests/ThumbnailerTest.php | |||
@@ -2,7 +2,6 @@ | |||
2 | 2 | ||
3 | namespace Shaarli; | 3 | namespace Shaarli; |
4 | 4 | ||
5 | use PHPUnit\Framework\TestCase; | ||
6 | use Shaarli\Config\ConfigManager; | 5 | use Shaarli\Config\ConfigManager; |
7 | use WebThumbnailer\Application\ConfigManager as WTConfigManager; | 6 | use WebThumbnailer\Application\ConfigManager as WTConfigManager; |
8 | 7 | ||
@@ -30,7 +29,7 @@ class ThumbnailerTest extends TestCase | |||
30 | */ | 29 | */ |
31 | protected $conf; | 30 | protected $conf; |
32 | 31 | ||
33 | public function setUp() | 32 | protected function setUp(): void |
34 | { | 33 | { |
35 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 34 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
36 | $this->conf->set('thumbnails.mode', Thumbnailer::MODE_ALL); | 35 | $this->conf->set('thumbnails.mode', Thumbnailer::MODE_ALL); |
@@ -43,7 +42,7 @@ class ThumbnailerTest extends TestCase | |||
43 | WTConfigManager::addFile('tests/utils/config/wt.json'); | 42 | WTConfigManager::addFile('tests/utils/config/wt.json'); |
44 | } | 43 | } |
45 | 44 | ||
46 | public function tearDown() | 45 | protected function tearDown(): void |
47 | { | 46 | { |
48 | $this->rrmdirContent('sandbox/'); | 47 | $this->rrmdirContent('sandbox/'); |
49 | } | 48 | } |
diff --git a/tests/TimeZoneTest.php b/tests/TimeZoneTest.php index 02bf060f..77862855 100644 --- a/tests/TimeZoneTest.php +++ b/tests/TimeZoneTest.php | |||
@@ -8,14 +8,14 @@ require_once 'application/TimeZone.php'; | |||
8 | /** | 8 | /** |
9 | * Unitary tests for timezone utilities | 9 | * Unitary tests for timezone utilities |
10 | */ | 10 | */ |
11 | class TimeZoneTest extends PHPUnit\Framework\TestCase | 11 | class TimeZoneTest extends \Shaarli\TestCase |
12 | { | 12 | { |
13 | /** | 13 | /** |
14 | * @var array of timezones | 14 | * @var array of timezones |
15 | */ | 15 | */ |
16 | protected $installedTimezones; | 16 | protected $installedTimezones; |
17 | 17 | ||
18 | public function setUp() | 18 | protected function setUp(): void |
19 | { | 19 | { |
20 | $this->installedTimezones = [ | 20 | $this->installedTimezones = [ |
21 | 'Antarctica/Syowa', | 21 | 'Antarctica/Syowa', |
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index 8225d95a..6e787d7f 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php | |||
@@ -10,7 +10,7 @@ require_once 'application/Languages.php'; | |||
10 | /** | 10 | /** |
11 | * Unitary tests for Shaarli utilities | 11 | * Unitary tests for Shaarli utilities |
12 | */ | 12 | */ |
13 | class UtilsTest extends PHPUnit\Framework\TestCase | 13 | class UtilsTest extends \Shaarli\TestCase |
14 | { | 14 | { |
15 | // Log file | 15 | // Log file |
16 | protected static $testLogFile = 'tests.log'; | 16 | protected static $testLogFile = 'tests.log'; |
@@ -26,7 +26,7 @@ class UtilsTest extends PHPUnit\Framework\TestCase | |||
26 | /** | 26 | /** |
27 | * Assign reference data | 27 | * Assign reference data |
28 | */ | 28 | */ |
29 | public static function setUpBeforeClass() | 29 | public static function setUpBeforeClass(): void |
30 | { | 30 | { |
31 | self::$defaultTimeZone = date_default_timezone_get(); | 31 | self::$defaultTimeZone = date_default_timezone_get(); |
32 | // Timezone without DST for test consistency | 32 | // Timezone without DST for test consistency |
@@ -36,7 +36,7 @@ class UtilsTest extends PHPUnit\Framework\TestCase | |||
36 | /** | 36 | /** |
37 | * Reset the timezone | 37 | * Reset the timezone |
38 | */ | 38 | */ |
39 | public static function tearDownAfterClass() | 39 | public static function tearDownAfterClass(): void |
40 | { | 40 | { |
41 | date_default_timezone_set(self::$defaultTimeZone); | 41 | date_default_timezone_set(self::$defaultTimeZone); |
42 | } | 42 | } |
@@ -44,7 +44,7 @@ class UtilsTest extends PHPUnit\Framework\TestCase | |||
44 | /** | 44 | /** |
45 | * Resets test data before each test | 45 | * Resets test data before each test |
46 | */ | 46 | */ |
47 | protected function setUp() | 47 | protected function setUp(): void |
48 | { | 48 | { |
49 | if (file_exists(self::$testLogFile)) { | 49 | if (file_exists(self::$testLogFile)) { |
50 | unlink(self::$testLogFile); | 50 | unlink(self::$testLogFile); |
@@ -203,7 +203,7 @@ class UtilsTest extends PHPUnit\Framework\TestCase | |||
203 | public function testGenerateLocationLoop() | 203 | public function testGenerateLocationLoop() |
204 | { | 204 | { |
205 | $ref = 'http://localhost/?test'; | 205 | $ref = 'http://localhost/?test'; |
206 | $this->assertEquals('?', generateLocation($ref, 'localhost', array('test'))); | 206 | $this->assertEquals('./?', generateLocation($ref, 'localhost', array('test'))); |
207 | } | 207 | } |
208 | 208 | ||
209 | /** | 209 | /** |
@@ -212,7 +212,7 @@ class UtilsTest extends PHPUnit\Framework\TestCase | |||
212 | public function testGenerateLocationOut() | 212 | public function testGenerateLocationOut() |
213 | { | 213 | { |
214 | $ref = 'http://somewebsite.com/?test'; | 214 | $ref = 'http://somewebsite.com/?test'; |
215 | $this->assertEquals('?', generateLocation($ref, 'localhost')); | 215 | $this->assertEquals('./?', generateLocation($ref, 'localhost')); |
216 | } | 216 | } |
217 | 217 | ||
218 | 218 | ||
diff --git a/tests/api/ApiMiddlewareTest.php b/tests/api/ApiMiddlewareTest.php index 0b9b03f2..86700840 100644 --- a/tests/api/ApiMiddlewareTest.php +++ b/tests/api/ApiMiddlewareTest.php | |||
@@ -2,6 +2,7 @@ | |||
2 | namespace Shaarli\Api; | 2 | namespace Shaarli\Api; |
3 | 3 | ||
4 | use Shaarli\Config\ConfigManager; | 4 | use Shaarli\Config\ConfigManager; |
5 | use Shaarli\History; | ||
5 | use Slim\Container; | 6 | use Slim\Container; |
6 | use Slim\Http\Environment; | 7 | use Slim\Http\Environment; |
7 | use Slim\Http\Request; | 8 | use Slim\Http\Request; |
@@ -17,7 +18,7 @@ use Slim\Http\Response; | |||
17 | * | 18 | * |
18 | * @package Api | 19 | * @package Api |
19 | */ | 20 | */ |
20 | class ApiMiddlewareTest extends \PHPUnit\Framework\TestCase | 21 | class ApiMiddlewareTest extends \Shaarli\TestCase |
21 | { | 22 | { |
22 | /** | 23 | /** |
23 | * @var string datastore to test write operations | 24 | * @var string datastore to test write operations |
@@ -25,7 +26,7 @@ class ApiMiddlewareTest extends \PHPUnit\Framework\TestCase | |||
25 | protected static $testDatastore = 'sandbox/datastore.php'; | 26 | protected static $testDatastore = 'sandbox/datastore.php'; |
26 | 27 | ||
27 | /** | 28 | /** |
28 | * @var \ConfigManager instance | 29 | * @var ConfigManager instance |
29 | */ | 30 | */ |
30 | protected $conf; | 31 | protected $conf; |
31 | 32 | ||
@@ -40,29 +41,79 @@ class ApiMiddlewareTest extends \PHPUnit\Framework\TestCase | |||
40 | protected $container; | 41 | protected $container; |
41 | 42 | ||
42 | /** | 43 | /** |
43 | * Before every test, instantiate a new Api with its config, plugins and links. | 44 | * Before every test, instantiate a new Api with its config, plugins and bookmarks. |
44 | */ | 45 | */ |
45 | public function setUp() | 46 | protected function setUp(): void |
46 | { | 47 | { |
47 | $this->conf = new ConfigManager('tests/utils/config/configJson.json.php'); | 48 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
48 | $this->conf->set('api.secret', 'NapoleonWasALizard'); | 49 | $this->conf->set('api.secret', 'NapoleonWasALizard'); |
49 | 50 | ||
50 | $this->refDB = new \ReferenceLinkDB(); | 51 | $this->refDB = new \ReferenceLinkDB(); |
51 | $this->refDB->write(self::$testDatastore); | 52 | $this->refDB->write(self::$testDatastore); |
52 | 53 | ||
54 | $history = new History('sandbox/history.php'); | ||
55 | |||
53 | $this->container = new Container(); | 56 | $this->container = new Container(); |
54 | $this->container['conf'] = $this->conf; | 57 | $this->container['conf'] = $this->conf; |
58 | $this->container['history'] = $history; | ||
55 | } | 59 | } |
56 | 60 | ||
57 | /** | 61 | /** |
58 | * After every test, remove the test datastore. | 62 | * After every test, remove the test datastore. |
59 | */ | 63 | */ |
60 | public function tearDown() | 64 | protected function tearDown(): void |
61 | { | 65 | { |
62 | @unlink(self::$testDatastore); | 66 | @unlink(self::$testDatastore); |
63 | } | 67 | } |
64 | 68 | ||
65 | /** | 69 | /** |
70 | * Invoke the middleware with a valid token | ||
71 | */ | ||
72 | public function testInvokeMiddlewareWithValidToken(): void | ||
73 | { | ||
74 | $next = function (Request $request, Response $response): Response { | ||
75 | return $response; | ||
76 | }; | ||
77 | $mw = new ApiMiddleware($this->container); | ||
78 | $env = Environment::mock([ | ||
79 | 'REQUEST_METHOD' => 'GET', | ||
80 | 'REQUEST_URI' => '/echo', | ||
81 | 'HTTP_AUTHORIZATION'=> 'Bearer ' . ApiUtilsTest::generateValidJwtToken('NapoleonWasALizard'), | ||
82 | ]); | ||
83 | $request = Request::createFromEnvironment($env); | ||
84 | $response = new Response(); | ||
85 | /** @var Response $response */ | ||
86 | $response = $mw($request, $response, $next); | ||
87 | |||
88 | $this->assertEquals(200, $response->getStatusCode()); | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * Invoke the middleware with a valid token | ||
93 | * Using specific Apache CGI redirected authorization. | ||
94 | */ | ||
95 | public function testInvokeMiddlewareWithValidTokenFromRedirectedHeader(): void | ||
96 | { | ||
97 | $next = function (Request $request, Response $response): Response { | ||
98 | return $response; | ||
99 | }; | ||
100 | |||
101 | $token = 'Bearer ' . ApiUtilsTest::generateValidJwtToken('NapoleonWasALizard'); | ||
102 | $this->container->environment['REDIRECT_HTTP_AUTHORIZATION'] = $token; | ||
103 | $mw = new ApiMiddleware($this->container); | ||
104 | $env = Environment::mock([ | ||
105 | 'REQUEST_METHOD' => 'GET', | ||
106 | 'REQUEST_URI' => '/echo', | ||
107 | ]); | ||
108 | $request = Request::createFromEnvironment($env); | ||
109 | $response = new Response(); | ||
110 | /** @var Response $response */ | ||
111 | $response = $mw($request, $response, $next); | ||
112 | |||
113 | $this->assertEquals(200, $response->getStatusCode()); | ||
114 | } | ||
115 | |||
116 | /** | ||
66 | * Invoke the middleware with the API disabled: | 117 | * Invoke the middleware with the API disabled: |
67 | * should return a 401 error Unauthorized. | 118 | * should return a 401 error Unauthorized. |
68 | */ | 119 | */ |
@@ -105,7 +156,7 @@ class ApiMiddlewareTest extends \PHPUnit\Framework\TestCase | |||
105 | $this->assertEquals(401, $response->getStatusCode()); | 156 | $this->assertEquals(401, $response->getStatusCode()); |
106 | $body = json_decode((string) $response->getBody()); | 157 | $body = json_decode((string) $response->getBody()); |
107 | $this->assertEquals('Not authorized: API is disabled', $body->message); | 158 | $this->assertEquals('Not authorized: API is disabled', $body->message); |
108 | $this->assertContains('ApiAuthorizationException', $body->stacktrace); | 159 | $this->assertContainsPolyfill('ApiAuthorizationException', $body->stacktrace); |
109 | } | 160 | } |
110 | 161 | ||
111 | /** | 162 | /** |
@@ -128,7 +179,7 @@ class ApiMiddlewareTest extends \PHPUnit\Framework\TestCase | |||
128 | $this->assertEquals(401, $response->getStatusCode()); | 179 | $this->assertEquals(401, $response->getStatusCode()); |
129 | $body = json_decode((string) $response->getBody()); | 180 | $body = json_decode((string) $response->getBody()); |
130 | $this->assertEquals('Not authorized: JWT token not provided', $body->message); | 181 | $this->assertEquals('Not authorized: JWT token not provided', $body->message); |
131 | $this->assertContains('ApiAuthorizationException', $body->stacktrace); | 182 | $this->assertContainsPolyfill('ApiAuthorizationException', $body->stacktrace); |
132 | } | 183 | } |
133 | 184 | ||
134 | /** | 185 | /** |
@@ -153,7 +204,7 @@ class ApiMiddlewareTest extends \PHPUnit\Framework\TestCase | |||
153 | $this->assertEquals(401, $response->getStatusCode()); | 204 | $this->assertEquals(401, $response->getStatusCode()); |
154 | $body = json_decode((string) $response->getBody()); | 205 | $body = json_decode((string) $response->getBody()); |
155 | $this->assertEquals('Not authorized: Token secret must be set in Shaarli\'s administration', $body->message); | 206 | $this->assertEquals('Not authorized: Token secret must be set in Shaarli\'s administration', $body->message); |
156 | $this->assertContains('ApiAuthorizationException', $body->stacktrace); | 207 | $this->assertContainsPolyfill('ApiAuthorizationException', $body->stacktrace); |
157 | } | 208 | } |
158 | 209 | ||
159 | /** | 210 | /** |
@@ -176,7 +227,7 @@ class ApiMiddlewareTest extends \PHPUnit\Framework\TestCase | |||
176 | $this->assertEquals(401, $response->getStatusCode()); | 227 | $this->assertEquals(401, $response->getStatusCode()); |
177 | $body = json_decode((string) $response->getBody()); | 228 | $body = json_decode((string) $response->getBody()); |
178 | $this->assertEquals('Not authorized: Invalid JWT header', $body->message); | 229 | $this->assertEquals('Not authorized: Invalid JWT header', $body->message); |
179 | $this->assertContains('ApiAuthorizationException', $body->stacktrace); | 230 | $this->assertContainsPolyfill('ApiAuthorizationException', $body->stacktrace); |
180 | } | 231 | } |
181 | 232 | ||
182 | /** | 233 | /** |
@@ -202,6 +253,6 @@ class ApiMiddlewareTest extends \PHPUnit\Framework\TestCase | |||
202 | $this->assertEquals(401, $response->getStatusCode()); | 253 | $this->assertEquals(401, $response->getStatusCode()); |
203 | $body = json_decode((string) $response->getBody()); | 254 | $body = json_decode((string) $response->getBody()); |
204 | $this->assertEquals('Not authorized: Malformed JWT token', $body->message); | 255 | $this->assertEquals('Not authorized: Malformed JWT token', $body->message); |
205 | $this->assertContains('ApiAuthorizationException', $body->stacktrace); | 256 | $this->assertContainsPolyfill('ApiAuthorizationException', $body->stacktrace); |
206 | } | 257 | } |
207 | } | 258 | } |
diff --git a/tests/api/ApiUtilsTest.php b/tests/api/ApiUtilsTest.php index ea0ae500..7a143859 100644 --- a/tests/api/ApiUtilsTest.php +++ b/tests/api/ApiUtilsTest.php | |||
@@ -2,17 +2,18 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Api; | 3 | namespace Shaarli\Api; |
4 | 4 | ||
5 | use Shaarli\Bookmark\Bookmark; | ||
5 | use Shaarli\Http\Base64Url; | 6 | use Shaarli\Http\Base64Url; |
6 | 7 | ||
7 | /** | 8 | /** |
8 | * Class ApiUtilsTest | 9 | * Class ApiUtilsTest |
9 | */ | 10 | */ |
10 | class ApiUtilsTest extends \PHPUnit\Framework\TestCase | 11 | class ApiUtilsTest extends \Shaarli\TestCase |
11 | { | 12 | { |
12 | /** | 13 | /** |
13 | * Force the timezone for ISO datetimes. | 14 | * Force the timezone for ISO datetimes. |
14 | */ | 15 | */ |
15 | public static function setUpBeforeClass() | 16 | public static function setUpBeforeClass(): void |
16 | { | 17 | { |
17 | date_default_timezone_set('UTC'); | 18 | date_default_timezone_set('UTC'); |
18 | } | 19 | } |
@@ -60,148 +61,148 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
60 | public function testValidateJwtTokenValid() | 61 | public function testValidateJwtTokenValid() |
61 | { | 62 | { |
62 | $secret = 'WarIsPeace'; | 63 | $secret = 'WarIsPeace'; |
63 | ApiUtils::validateJwtToken(self::generateValidJwtToken($secret), $secret); | 64 | $this->assertTrue(ApiUtils::validateJwtToken(self::generateValidJwtToken($secret), $secret)); |
64 | } | 65 | } |
65 | 66 | ||
66 | /** | 67 | /** |
67 | * Test validateJwtToken() with a malformed JWT token. | 68 | * Test validateJwtToken() with a malformed JWT token. |
68 | * | ||
69 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
70 | * @expectedExceptionMessage Malformed JWT token | ||
71 | */ | 69 | */ |
72 | public function testValidateJwtTokenMalformed() | 70 | public function testValidateJwtTokenMalformed() |
73 | { | 71 | { |
72 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
73 | $this->expectExceptionMessage('Malformed JWT token'); | ||
74 | |||
74 | $token = 'ABC.DEF'; | 75 | $token = 'ABC.DEF'; |
75 | ApiUtils::validateJwtToken($token, 'foo'); | 76 | ApiUtils::validateJwtToken($token, 'foo'); |
76 | } | 77 | } |
77 | 78 | ||
78 | /** | 79 | /** |
79 | * Test validateJwtToken() with an empty JWT token. | 80 | * Test validateJwtToken() with an empty JWT token. |
80 | * | ||
81 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
82 | * @expectedExceptionMessage Malformed JWT token | ||
83 | */ | 81 | */ |
84 | public function testValidateJwtTokenMalformedEmpty() | 82 | public function testValidateJwtTokenMalformedEmpty() |
85 | { | 83 | { |
84 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
85 | $this->expectExceptionMessage('Malformed JWT token'); | ||
86 | |||
86 | $token = false; | 87 | $token = false; |
87 | ApiUtils::validateJwtToken($token, 'foo'); | 88 | ApiUtils::validateJwtToken($token, 'foo'); |
88 | } | 89 | } |
89 | 90 | ||
90 | /** | 91 | /** |
91 | * Test validateJwtToken() with a JWT token without header. | 92 | * Test validateJwtToken() with a JWT token without header. |
92 | * | ||
93 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
94 | * @expectedExceptionMessage Malformed JWT token | ||
95 | */ | 93 | */ |
96 | public function testValidateJwtTokenMalformedEmptyHeader() | 94 | public function testValidateJwtTokenMalformedEmptyHeader() |
97 | { | 95 | { |
96 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
97 | $this->expectExceptionMessage('Malformed JWT token'); | ||
98 | |||
98 | $token = '.payload.signature'; | 99 | $token = '.payload.signature'; |
99 | ApiUtils::validateJwtToken($token, 'foo'); | 100 | ApiUtils::validateJwtToken($token, 'foo'); |
100 | } | 101 | } |
101 | 102 | ||
102 | /** | 103 | /** |
103 | * Test validateJwtToken() with a JWT token without payload | 104 | * Test validateJwtToken() with a JWT token without payload |
104 | * | ||
105 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
106 | * @expectedExceptionMessage Malformed JWT token | ||
107 | */ | 105 | */ |
108 | public function testValidateJwtTokenMalformedEmptyPayload() | 106 | public function testValidateJwtTokenMalformedEmptyPayload() |
109 | { | 107 | { |
108 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
109 | $this->expectExceptionMessage('Malformed JWT token'); | ||
110 | |||
110 | $token = 'header..signature'; | 111 | $token = 'header..signature'; |
111 | ApiUtils::validateJwtToken($token, 'foo'); | 112 | ApiUtils::validateJwtToken($token, 'foo'); |
112 | } | 113 | } |
113 | 114 | ||
114 | /** | 115 | /** |
115 | * Test validateJwtToken() with a JWT token with an empty signature. | 116 | * Test validateJwtToken() with a JWT token with an empty signature. |
116 | * | ||
117 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
118 | * @expectedExceptionMessage Invalid JWT signature | ||
119 | */ | 117 | */ |
120 | public function testValidateJwtTokenInvalidSignatureEmpty() | 118 | public function testValidateJwtTokenInvalidSignatureEmpty() |
121 | { | 119 | { |
120 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
121 | $this->expectExceptionMessage('Invalid JWT signature'); | ||
122 | |||
122 | $token = 'header.payload.'; | 123 | $token = 'header.payload.'; |
123 | ApiUtils::validateJwtToken($token, 'foo'); | 124 | ApiUtils::validateJwtToken($token, 'foo'); |
124 | } | 125 | } |
125 | 126 | ||
126 | /** | 127 | /** |
127 | * Test validateJwtToken() with a JWT token with an invalid signature. | 128 | * Test validateJwtToken() with a JWT token with an invalid signature. |
128 | * | ||
129 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
130 | * @expectedExceptionMessage Invalid JWT signature | ||
131 | */ | 129 | */ |
132 | public function testValidateJwtTokenInvalidSignature() | 130 | public function testValidateJwtTokenInvalidSignature() |
133 | { | 131 | { |
132 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
133 | $this->expectExceptionMessage('Invalid JWT signature'); | ||
134 | |||
134 | $token = 'header.payload.nope'; | 135 | $token = 'header.payload.nope'; |
135 | ApiUtils::validateJwtToken($token, 'foo'); | 136 | ApiUtils::validateJwtToken($token, 'foo'); |
136 | } | 137 | } |
137 | 138 | ||
138 | /** | 139 | /** |
139 | * Test validateJwtToken() with a JWT token with a signature generated with the wrong API secret. | 140 | * Test validateJwtToken() with a JWT token with a signature generated with the wrong API secret. |
140 | * | ||
141 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
142 | * @expectedExceptionMessage Invalid JWT signature | ||
143 | */ | 141 | */ |
144 | public function testValidateJwtTokenInvalidSignatureSecret() | 142 | public function testValidateJwtTokenInvalidSignatureSecret() |
145 | { | 143 | { |
144 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
145 | $this->expectExceptionMessage('Invalid JWT signature'); | ||
146 | |||
146 | ApiUtils::validateJwtToken(self::generateValidJwtToken('foo'), 'bar'); | 147 | ApiUtils::validateJwtToken(self::generateValidJwtToken('foo'), 'bar'); |
147 | } | 148 | } |
148 | 149 | ||
149 | /** | 150 | /** |
150 | * Test validateJwtToken() with a JWT token with a an invalid header (not JSON). | 151 | * Test validateJwtToken() with a JWT token with a an invalid header (not JSON). |
151 | * | ||
152 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
153 | * @expectedExceptionMessage Invalid JWT header | ||
154 | */ | 152 | */ |
155 | public function testValidateJwtTokenInvalidHeader() | 153 | public function testValidateJwtTokenInvalidHeader() |
156 | { | 154 | { |
155 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
156 | $this->expectExceptionMessage('Invalid JWT header'); | ||
157 | |||
157 | $token = $this->generateCustomJwtToken('notJSON', '{"JSON":1}', 'secret'); | 158 | $token = $this->generateCustomJwtToken('notJSON', '{"JSON":1}', 'secret'); |
158 | ApiUtils::validateJwtToken($token, 'secret'); | 159 | ApiUtils::validateJwtToken($token, 'secret'); |
159 | } | 160 | } |
160 | 161 | ||
161 | /** | 162 | /** |
162 | * Test validateJwtToken() with a JWT token with a an invalid payload (not JSON). | 163 | * Test validateJwtToken() with a JWT token with a an invalid payload (not JSON). |
163 | * | ||
164 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
165 | * @expectedExceptionMessage Invalid JWT payload | ||
166 | */ | 164 | */ |
167 | public function testValidateJwtTokenInvalidPayload() | 165 | public function testValidateJwtTokenInvalidPayload() |
168 | { | 166 | { |
167 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
168 | $this->expectExceptionMessage('Invalid JWT payload'); | ||
169 | |||
169 | $token = $this->generateCustomJwtToken('{"JSON":1}', 'notJSON', 'secret'); | 170 | $token = $this->generateCustomJwtToken('{"JSON":1}', 'notJSON', 'secret'); |
170 | ApiUtils::validateJwtToken($token, 'secret'); | 171 | ApiUtils::validateJwtToken($token, 'secret'); |
171 | } | 172 | } |
172 | 173 | ||
173 | /** | 174 | /** |
174 | * Test validateJwtToken() with a JWT token without issued time. | 175 | * Test validateJwtToken() with a JWT token without issued time. |
175 | * | ||
176 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
177 | * @expectedExceptionMessage Invalid JWT issued time | ||
178 | */ | 176 | */ |
179 | public function testValidateJwtTokenInvalidTimeEmpty() | 177 | public function testValidateJwtTokenInvalidTimeEmpty() |
180 | { | 178 | { |
179 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
180 | $this->expectExceptionMessage('Invalid JWT issued time'); | ||
181 | |||
181 | $token = $this->generateCustomJwtToken('{"JSON":1}', '{"JSON":1}', 'secret'); | 182 | $token = $this->generateCustomJwtToken('{"JSON":1}', '{"JSON":1}', 'secret'); |
182 | ApiUtils::validateJwtToken($token, 'secret'); | 183 | ApiUtils::validateJwtToken($token, 'secret'); |
183 | } | 184 | } |
184 | 185 | ||
185 | /** | 186 | /** |
186 | * Test validateJwtToken() with an expired JWT token. | 187 | * Test validateJwtToken() with an expired JWT token. |
187 | * | ||
188 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
189 | * @expectedExceptionMessage Invalid JWT issued time | ||
190 | */ | 188 | */ |
191 | public function testValidateJwtTokenInvalidTimeExpired() | 189 | public function testValidateJwtTokenInvalidTimeExpired() |
192 | { | 190 | { |
191 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
192 | $this->expectExceptionMessage('Invalid JWT issued time'); | ||
193 | |||
193 | $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() - 600) . '}', 'secret'); | 194 | $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() - 600) . '}', 'secret'); |
194 | ApiUtils::validateJwtToken($token, 'secret'); | 195 | ApiUtils::validateJwtToken($token, 'secret'); |
195 | } | 196 | } |
196 | 197 | ||
197 | /** | 198 | /** |
198 | * Test validateJwtToken() with a JWT token issued in the future. | 199 | * Test validateJwtToken() with a JWT token issued in the future. |
199 | * | ||
200 | * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException | ||
201 | * @expectedExceptionMessage Invalid JWT issued time | ||
202 | */ | 200 | */ |
203 | public function testValidateJwtTokenInvalidTimeFuture() | 201 | public function testValidateJwtTokenInvalidTimeFuture() |
204 | { | 202 | { |
203 | $this->expectException(\Shaarli\Api\Exceptions\ApiAuthorizationException::class); | ||
204 | $this->expectExceptionMessage('Invalid JWT issued time'); | ||
205 | |||
205 | $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() + 60) . '}', 'secret'); | 206 | $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() + 60) . '}', 'secret'); |
206 | ApiUtils::validateJwtToken($token, 'secret'); | 207 | ApiUtils::validateJwtToken($token, 'secret'); |
207 | } | 208 | } |
@@ -212,7 +213,7 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
212 | public function testFormatLinkComplete() | 213 | public function testFormatLinkComplete() |
213 | { | 214 | { |
214 | $indexUrl = 'https://domain.tld/sub/'; | 215 | $indexUrl = 'https://domain.tld/sub/'; |
215 | $link = [ | 216 | $data = [ |
216 | 'id' => 12, | 217 | 'id' => 12, |
217 | 'url' => 'http://lol.lol', | 218 | 'url' => 'http://lol.lol', |
218 | 'shorturl' => 'abc', | 219 | 'shorturl' => 'abc', |
@@ -223,6 +224,8 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
223 | 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'), | 224 | 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'), |
224 | 'updated' => \DateTime::createFromFormat('Ymd_His', '20170107_160612'), | 225 | 'updated' => \DateTime::createFromFormat('Ymd_His', '20170107_160612'), |
225 | ]; | 226 | ]; |
227 | $bookmark = new Bookmark(); | ||
228 | $bookmark->fromArray($data); | ||
226 | 229 | ||
227 | $expected = [ | 230 | $expected = [ |
228 | 'id' => 12, | 231 | 'id' => 12, |
@@ -236,7 +239,7 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
236 | 'updated' => '2017-01-07T16:06:12+00:00', | 239 | 'updated' => '2017-01-07T16:06:12+00:00', |
237 | ]; | 240 | ]; |
238 | 241 | ||
239 | $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl)); | 242 | $this->assertEquals($expected, ApiUtils::formatLink($bookmark, $indexUrl)); |
240 | } | 243 | } |
241 | 244 | ||
242 | /** | 245 | /** |
@@ -245,7 +248,7 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
245 | public function testFormatLinkMinimalNote() | 248 | public function testFormatLinkMinimalNote() |
246 | { | 249 | { |
247 | $indexUrl = 'https://domain.tld/sub/'; | 250 | $indexUrl = 'https://domain.tld/sub/'; |
248 | $link = [ | 251 | $data = [ |
249 | 'id' => 12, | 252 | 'id' => 12, |
250 | 'url' => '?abc', | 253 | 'url' => '?abc', |
251 | 'shorturl' => 'abc', | 254 | 'shorturl' => 'abc', |
@@ -255,6 +258,8 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
255 | 'private' => '', | 258 | 'private' => '', |
256 | 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'), | 259 | 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'), |
257 | ]; | 260 | ]; |
261 | $bookmark = new Bookmark(); | ||
262 | $bookmark->fromArray($data); | ||
258 | 263 | ||
259 | $expected = [ | 264 | $expected = [ |
260 | 'id' => 12, | 265 | 'id' => 12, |
@@ -268,7 +273,7 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
268 | 'updated' => '', | 273 | 'updated' => '', |
269 | ]; | 274 | ]; |
270 | 275 | ||
271 | $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl)); | 276 | $this->assertEquals($expected, ApiUtils::formatLink($bookmark, $indexUrl)); |
272 | } | 277 | } |
273 | 278 | ||
274 | /** | 279 | /** |
@@ -277,7 +282,7 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
277 | public function testUpdateLink() | 282 | public function testUpdateLink() |
278 | { | 283 | { |
279 | $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102'); | 284 | $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102'); |
280 | $old = [ | 285 | $data = [ |
281 | 'id' => 12, | 286 | 'id' => 12, |
282 | 'url' => '?abc', | 287 | 'url' => '?abc', |
283 | 'shorturl' => 'abc', | 288 | 'shorturl' => 'abc', |
@@ -287,8 +292,10 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
287 | 'private' => '', | 292 | 'private' => '', |
288 | 'created' => $created, | 293 | 'created' => $created, |
289 | ]; | 294 | ]; |
295 | $old = new Bookmark(); | ||
296 | $old->fromArray($data); | ||
290 | 297 | ||
291 | $new = [ | 298 | $data = [ |
292 | 'id' => 13, | 299 | 'id' => 13, |
293 | 'shorturl' => 'nope', | 300 | 'shorturl' => 'nope', |
294 | 'url' => 'http://somewhere.else', | 301 | 'url' => 'http://somewhere.else', |
@@ -299,17 +306,18 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
299 | 'created' => 'creation', | 306 | 'created' => 'creation', |
300 | 'updated' => 'updation', | 307 | 'updated' => 'updation', |
301 | ]; | 308 | ]; |
309 | $new = new Bookmark(); | ||
310 | $new->fromArray($data); | ||
302 | 311 | ||
303 | $result = ApiUtils::updateLink($old, $new); | 312 | $result = ApiUtils::updateLink($old, $new); |
304 | $this->assertEquals(12, $result['id']); | 313 | $this->assertEquals(12, $result->getId()); |
305 | $this->assertEquals('http://somewhere.else', $result['url']); | 314 | $this->assertEquals('http://somewhere.else', $result->getUrl()); |
306 | $this->assertEquals('abc', $result['shorturl']); | 315 | $this->assertEquals('abc', $result->getShortUrl()); |
307 | $this->assertEquals('Le Cid', $result['title']); | 316 | $this->assertEquals('Le Cid', $result->getTitle()); |
308 | $this->assertEquals('Percé jusques au fond du cœur [...]', $result['description']); | 317 | $this->assertEquals('Percé jusques au fond du cœur [...]', $result->getDescription()); |
309 | $this->assertEquals('corneille rodrigue', $result['tags']); | 318 | $this->assertEquals('corneille rodrigue', $result->getTagsString()); |
310 | $this->assertEquals(true, $result['private']); | 319 | $this->assertEquals(true, $result->isPrivate()); |
311 | $this->assertEquals($created, $result['created']); | 320 | $this->assertEquals($created, $result->getCreated()); |
312 | $this->assertTrue(new \DateTime('5 seconds ago') < $result['updated']); | ||
313 | } | 321 | } |
314 | 322 | ||
315 | /** | 323 | /** |
@@ -318,7 +326,7 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
318 | public function testUpdateLinkMinimal() | 326 | public function testUpdateLinkMinimal() |
319 | { | 327 | { |
320 | $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102'); | 328 | $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102'); |
321 | $old = [ | 329 | $data = [ |
322 | 'id' => 12, | 330 | 'id' => 12, |
323 | 'url' => '?abc', | 331 | 'url' => '?abc', |
324 | 'shorturl' => 'abc', | 332 | 'shorturl' => 'abc', |
@@ -328,24 +336,19 @@ class ApiUtilsTest extends \PHPUnit\Framework\TestCase | |||
328 | 'private' => true, | 336 | 'private' => true, |
329 | 'created' => $created, | 337 | 'created' => $created, |
330 | ]; | 338 | ]; |
339 | $old = new Bookmark(); | ||
340 | $old->fromArray($data); | ||
331 | 341 | ||
332 | $new = [ | 342 | $new = new Bookmark(); |
333 | 'url' => '', | ||
334 | 'title' => '', | ||
335 | 'description' => '', | ||
336 | 'tags' => '', | ||
337 | 'private' => false, | ||
338 | ]; | ||
339 | 343 | ||
340 | $result = ApiUtils::updateLink($old, $new); | 344 | $result = ApiUtils::updateLink($old, $new); |
341 | $this->assertEquals(12, $result['id']); | 345 | $this->assertEquals(12, $result->getId()); |
342 | $this->assertEquals('?abc', $result['url']); | 346 | $this->assertEquals('', $result->getUrl()); |
343 | $this->assertEquals('abc', $result['shorturl']); | 347 | $this->assertEquals('abc', $result->getShortUrl()); |
344 | $this->assertEquals('?abc', $result['title']); | 348 | $this->assertEquals('', $result->getTitle()); |
345 | $this->assertEquals('', $result['description']); | 349 | $this->assertEquals('', $result->getDescription()); |
346 | $this->assertEquals('', $result['tags']); | 350 | $this->assertEquals('', $result->getTagsString()); |
347 | $this->assertEquals(false, $result['private']); | 351 | $this->assertEquals(false, $result->isPrivate()); |
348 | $this->assertEquals($created, $result['created']); | 352 | $this->assertEquals($created, $result->getCreated()); |
349 | $this->assertTrue(new \DateTime('5 seconds ago') < $result['updated']); | ||
350 | } | 353 | } |
351 | } | 354 | } |
diff --git a/tests/api/controllers/history/HistoryTest.php b/tests/api/controllers/history/HistoryTest.php index e287f239..84f8716e 100644 --- a/tests/api/controllers/history/HistoryTest.php +++ b/tests/api/controllers/history/HistoryTest.php | |||
@@ -11,7 +11,7 @@ use Slim\Http\Response; | |||
11 | 11 | ||
12 | require_once 'tests/utils/ReferenceHistory.php'; | 12 | require_once 'tests/utils/ReferenceHistory.php'; |
13 | 13 | ||
14 | class HistoryTest extends \PHPUnit\Framework\TestCase | 14 | class HistoryTest extends \Shaarli\TestCase |
15 | { | 15 | { |
16 | /** | 16 | /** |
17 | * @var string datastore to test write operations | 17 | * @var string datastore to test write operations |
@@ -39,11 +39,11 @@ class HistoryTest extends \PHPUnit\Framework\TestCase | |||
39 | protected $controller; | 39 | protected $controller; |
40 | 40 | ||
41 | /** | 41 | /** |
42 | * Before every test, instantiate a new Api with its config, plugins and links. | 42 | * Before every test, instantiate a new Api with its config, plugins and bookmarks. |
43 | */ | 43 | */ |
44 | public function setUp() | 44 | protected function setUp(): void |
45 | { | 45 | { |
46 | $this->conf = new ConfigManager('tests/utils/config/configJson.json.php'); | 46 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
47 | $this->refHistory = new \ReferenceHistory(); | 47 | $this->refHistory = new \ReferenceHistory(); |
48 | $this->refHistory->write(self::$testHistory); | 48 | $this->refHistory->write(self::$testHistory); |
49 | $this->container = new Container(); | 49 | $this->container = new Container(); |
@@ -57,7 +57,7 @@ class HistoryTest extends \PHPUnit\Framework\TestCase | |||
57 | /** | 57 | /** |
58 | * After every test, remove the test datastore. | 58 | * After every test, remove the test datastore. |
59 | */ | 59 | */ |
60 | public function tearDown() | 60 | protected function tearDown(): void |
61 | { | 61 | { |
62 | @unlink(self::$testHistory); | 62 | @unlink(self::$testHistory); |
63 | } | 63 | } |
diff --git a/tests/api/controllers/info/InfoTest.php b/tests/api/controllers/info/InfoTest.php index e70d371b..1598e1e8 100644 --- a/tests/api/controllers/info/InfoTest.php +++ b/tests/api/controllers/info/InfoTest.php | |||
@@ -1,7 +1,10 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli\Api\Controllers; | 2 | namespace Shaarli\Api\Controllers; |
3 | 3 | ||
4 | use Shaarli\Bookmark\BookmarkFileService; | ||
4 | use Shaarli\Config\ConfigManager; | 5 | use Shaarli\Config\ConfigManager; |
6 | use Shaarli\History; | ||
7 | use Shaarli\TestCase; | ||
5 | use Slim\Container; | 8 | use Slim\Container; |
6 | use Slim\Http\Environment; | 9 | use Slim\Http\Environment; |
7 | use Slim\Http\Request; | 10 | use Slim\Http\Request; |
@@ -14,7 +17,7 @@ use Slim\Http\Response; | |||
14 | * | 17 | * |
15 | * @package Api\Controllers | 18 | * @package Api\Controllers |
16 | */ | 19 | */ |
17 | class InfoTest extends \PHPUnit\Framework\TestCase | 20 | class InfoTest extends TestCase |
18 | { | 21 | { |
19 | /** | 22 | /** |
20 | * @var string datastore to test write operations | 23 | * @var string datastore to test write operations |
@@ -42,17 +45,20 @@ class InfoTest extends \PHPUnit\Framework\TestCase | |||
42 | protected $controller; | 45 | protected $controller; |
43 | 46 | ||
44 | /** | 47 | /** |
45 | * Before every test, instantiate a new Api with its config, plugins and links. | 48 | * Before every test, instantiate a new Api with its config, plugins and bookmarks. |
46 | */ | 49 | */ |
47 | public function setUp() | 50 | protected function setUp(): void |
48 | { | 51 | { |
49 | $this->conf = new ConfigManager('tests/utils/config/configJson.json.php'); | 52 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
53 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
50 | $this->refDB = new \ReferenceLinkDB(); | 54 | $this->refDB = new \ReferenceLinkDB(); |
51 | $this->refDB->write(self::$testDatastore); | 55 | $this->refDB->write(self::$testDatastore); |
52 | 56 | ||
57 | $history = new History('sandbox/history.php'); | ||
58 | |||
53 | $this->container = new Container(); | 59 | $this->container = new Container(); |
54 | $this->container['conf'] = $this->conf; | 60 | $this->container['conf'] = $this->conf; |
55 | $this->container['db'] = new \Shaarli\Bookmark\LinkDB(self::$testDatastore, true, false); | 61 | $this->container['db'] = new BookmarkFileService($this->conf, $history, true); |
56 | $this->container['history'] = null; | 62 | $this->container['history'] = null; |
57 | 63 | ||
58 | $this->controller = new Info($this->container); | 64 | $this->controller = new Info($this->container); |
@@ -61,7 +67,7 @@ class InfoTest extends \PHPUnit\Framework\TestCase | |||
61 | /** | 67 | /** |
62 | * After every test, remove the test datastore. | 68 | * After every test, remove the test datastore. |
63 | */ | 69 | */ |
64 | public function tearDown() | 70 | protected function tearDown(): void |
65 | { | 71 | { |
66 | @unlink(self::$testDatastore); | 72 | @unlink(self::$testDatastore); |
67 | } | 73 | } |
@@ -84,11 +90,11 @@ class InfoTest extends \PHPUnit\Framework\TestCase | |||
84 | $this->assertEquals(2, $data['private_counter']); | 90 | $this->assertEquals(2, $data['private_counter']); |
85 | $this->assertEquals('Shaarli', $data['settings']['title']); | 91 | $this->assertEquals('Shaarli', $data['settings']['title']); |
86 | $this->assertEquals('?', $data['settings']['header_link']); | 92 | $this->assertEquals('?', $data['settings']['header_link']); |
87 | $this->assertEquals('UTC', $data['settings']['timezone']); | 93 | $this->assertEquals('Europe/Paris', $data['settings']['timezone']); |
88 | $this->assertEquals(ConfigManager::$DEFAULT_PLUGINS, $data['settings']['enabled_plugins']); | 94 | $this->assertEquals(ConfigManager::$DEFAULT_PLUGINS, $data['settings']['enabled_plugins']); |
89 | $this->assertEquals(false, $data['settings']['default_private_links']); | 95 | $this->assertEquals(true, $data['settings']['default_private_links']); |
90 | 96 | ||
91 | $title = 'My links'; | 97 | $title = 'My bookmarks'; |
92 | $headerLink = 'http://shaarli.tld'; | 98 | $headerLink = 'http://shaarli.tld'; |
93 | $timezone = 'Europe/Paris'; | 99 | $timezone = 'Europe/Paris'; |
94 | $enabledPlugins = array('foo', 'bar'); | 100 | $enabledPlugins = array('foo', 'bar'); |
diff --git a/tests/api/controllers/links/DeleteLinkTest.php b/tests/api/controllers/links/DeleteLinkTest.php index 90193e28..cf9464f0 100644 --- a/tests/api/controllers/links/DeleteLinkTest.php +++ b/tests/api/controllers/links/DeleteLinkTest.php | |||
@@ -3,7 +3,7 @@ | |||
3 | 3 | ||
4 | namespace Shaarli\Api\Controllers; | 4 | namespace Shaarli\Api\Controllers; |
5 | 5 | ||
6 | use Shaarli\Bookmark\LinkDB; | 6 | use Shaarli\Bookmark\BookmarkFileService; |
7 | use Shaarli\Config\ConfigManager; | 7 | use Shaarli\Config\ConfigManager; |
8 | use Shaarli\History; | 8 | use Shaarli\History; |
9 | use Slim\Container; | 9 | use Slim\Container; |
@@ -11,7 +11,7 @@ use Slim\Http\Environment; | |||
11 | use Slim\Http\Request; | 11 | use Slim\Http\Request; |
12 | use Slim\Http\Response; | 12 | use Slim\Http\Response; |
13 | 13 | ||
14 | class DeleteLinkTest extends \PHPUnit\Framework\TestCase | 14 | class DeleteLinkTest extends \Shaarli\TestCase |
15 | { | 15 | { |
16 | /** | 16 | /** |
17 | * @var string datastore to test write operations | 17 | * @var string datastore to test write operations |
@@ -34,9 +34,9 @@ class DeleteLinkTest extends \PHPUnit\Framework\TestCase | |||
34 | protected $refDB = null; | 34 | protected $refDB = null; |
35 | 35 | ||
36 | /** | 36 | /** |
37 | * @var LinkDB instance. | 37 | * @var BookmarkFileService instance. |
38 | */ | 38 | */ |
39 | protected $linkDB; | 39 | protected $bookmarkService; |
40 | 40 | ||
41 | /** | 41 | /** |
42 | * @var HistoryController instance. | 42 | * @var HistoryController instance. |
@@ -54,20 +54,22 @@ class DeleteLinkTest extends \PHPUnit\Framework\TestCase | |||
54 | protected $controller; | 54 | protected $controller; |
55 | 55 | ||
56 | /** | 56 | /** |
57 | * Before each test, instantiate a new Api with its config, plugins and links. | 57 | * Before each test, instantiate a new Api with its config, plugins and bookmarks. |
58 | */ | 58 | */ |
59 | public function setUp() | 59 | protected function setUp(): void |
60 | { | 60 | { |
61 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 61 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
62 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
62 | $this->refDB = new \ReferenceLinkDB(); | 63 | $this->refDB = new \ReferenceLinkDB(); |
63 | $this->refDB->write(self::$testDatastore); | 64 | $this->refDB->write(self::$testDatastore); |
64 | $this->linkDB = new LinkDB(self::$testDatastore, true, false); | ||
65 | $refHistory = new \ReferenceHistory(); | 65 | $refHistory = new \ReferenceHistory(); |
66 | $refHistory->write(self::$testHistory); | 66 | $refHistory->write(self::$testHistory); |
67 | $this->history = new History(self::$testHistory); | 67 | $this->history = new History(self::$testHistory); |
68 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
69 | |||
68 | $this->container = new Container(); | 70 | $this->container = new Container(); |
69 | $this->container['conf'] = $this->conf; | 71 | $this->container['conf'] = $this->conf; |
70 | $this->container['db'] = $this->linkDB; | 72 | $this->container['db'] = $this->bookmarkService; |
71 | $this->container['history'] = $this->history; | 73 | $this->container['history'] = $this->history; |
72 | 74 | ||
73 | $this->controller = new Links($this->container); | 75 | $this->controller = new Links($this->container); |
@@ -76,7 +78,7 @@ class DeleteLinkTest extends \PHPUnit\Framework\TestCase | |||
76 | /** | 78 | /** |
77 | * After each test, remove the test datastore. | 79 | * After each test, remove the test datastore. |
78 | */ | 80 | */ |
79 | public function tearDown() | 81 | protected function tearDown(): void |
80 | { | 82 | { |
81 | @unlink(self::$testDatastore); | 83 | @unlink(self::$testDatastore); |
82 | @unlink(self::$testHistory); | 84 | @unlink(self::$testHistory); |
@@ -88,7 +90,7 @@ class DeleteLinkTest extends \PHPUnit\Framework\TestCase | |||
88 | public function testDeleteLinkValid() | 90 | public function testDeleteLinkValid() |
89 | { | 91 | { |
90 | $id = '41'; | 92 | $id = '41'; |
91 | $this->assertTrue(isset($this->linkDB[$id])); | 93 | $this->assertTrue($this->bookmarkService->exists($id)); |
92 | $env = Environment::mock([ | 94 | $env = Environment::mock([ |
93 | 'REQUEST_METHOD' => 'DELETE', | 95 | 'REQUEST_METHOD' => 'DELETE', |
94 | ]); | 96 | ]); |
@@ -98,8 +100,8 @@ class DeleteLinkTest extends \PHPUnit\Framework\TestCase | |||
98 | $this->assertEquals(204, $response->getStatusCode()); | 100 | $this->assertEquals(204, $response->getStatusCode()); |
99 | $this->assertEmpty((string) $response->getBody()); | 101 | $this->assertEmpty((string) $response->getBody()); |
100 | 102 | ||
101 | $this->linkDB = new LinkDB(self::$testDatastore, true, false); | 103 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); |
102 | $this->assertFalse(isset($this->linkDB[$id])); | 104 | $this->assertFalse($this->bookmarkService->exists($id)); |
103 | 105 | ||
104 | $historyEntry = $this->history->getHistory()[0]; | 106 | $historyEntry = $this->history->getHistory()[0]; |
105 | $this->assertEquals(History::DELETED, $historyEntry['event']); | 107 | $this->assertEquals(History::DELETED, $historyEntry['event']); |
@@ -111,13 +113,13 @@ class DeleteLinkTest extends \PHPUnit\Framework\TestCase | |||
111 | 113 | ||
112 | /** | 114 | /** |
113 | * Test DELETE link endpoint: reach not existing ID. | 115 | * Test DELETE link endpoint: reach not existing ID. |
114 | * | ||
115 | * @expectedException \Shaarli\Api\Exceptions\ApiLinkNotFoundException | ||
116 | */ | 116 | */ |
117 | public function testDeleteLink404() | 117 | public function testDeleteLink404() |
118 | { | 118 | { |
119 | $this->expectException(\Shaarli\Api\Exceptions\ApiLinkNotFoundException::class); | ||
120 | |||
119 | $id = -1; | 121 | $id = -1; |
120 | $this->assertFalse(isset($this->linkDB[$id])); | 122 | $this->assertFalse($this->bookmarkService->exists($id)); |
121 | $env = Environment::mock([ | 123 | $env = Environment::mock([ |
122 | 'REQUEST_METHOD' => 'DELETE', | 124 | 'REQUEST_METHOD' => 'DELETE', |
123 | ]); | 125 | ]); |
diff --git a/tests/api/controllers/links/GetLinkIdTest.php b/tests/api/controllers/links/GetLinkIdTest.php index cb9b7f6a..99dc606f 100644 --- a/tests/api/controllers/links/GetLinkIdTest.php +++ b/tests/api/controllers/links/GetLinkIdTest.php | |||
@@ -2,7 +2,10 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Api\Controllers; | 3 | namespace Shaarli\Api\Controllers; |
4 | 4 | ||
5 | use Shaarli\Bookmark\Bookmark; | ||
6 | use Shaarli\Bookmark\BookmarkFileService; | ||
5 | use Shaarli\Config\ConfigManager; | 7 | use Shaarli\Config\ConfigManager; |
8 | use Shaarli\History; | ||
6 | use Slim\Container; | 9 | use Slim\Container; |
7 | use Slim\Http\Environment; | 10 | use Slim\Http\Environment; |
8 | use Slim\Http\Request; | 11 | use Slim\Http\Request; |
@@ -17,7 +20,7 @@ use Slim\Http\Response; | |||
17 | * | 20 | * |
18 | * @package Shaarli\Api\Controllers | 21 | * @package Shaarli\Api\Controllers |
19 | */ | 22 | */ |
20 | class GetLinkIdTest extends \PHPUnit\Framework\TestCase | 23 | class GetLinkIdTest extends \Shaarli\TestCase |
21 | { | 24 | { |
22 | /** | 25 | /** |
23 | * @var string datastore to test write operations | 26 | * @var string datastore to test write operations |
@@ -50,17 +53,19 @@ class GetLinkIdTest extends \PHPUnit\Framework\TestCase | |||
50 | const NB_FIELDS_LINK = 9; | 53 | const NB_FIELDS_LINK = 9; |
51 | 54 | ||
52 | /** | 55 | /** |
53 | * Before each test, instantiate a new Api with its config, plugins and links. | 56 | * Before each test, instantiate a new Api with its config, plugins and bookmarks. |
54 | */ | 57 | */ |
55 | public function setUp() | 58 | protected function setUp(): void |
56 | { | 59 | { |
57 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 60 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
61 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
58 | $this->refDB = new \ReferenceLinkDB(); | 62 | $this->refDB = new \ReferenceLinkDB(); |
59 | $this->refDB->write(self::$testDatastore); | 63 | $this->refDB->write(self::$testDatastore); |
64 | $history = new History('sandbox/history.php'); | ||
60 | 65 | ||
61 | $this->container = new Container(); | 66 | $this->container = new Container(); |
62 | $this->container['conf'] = $this->conf; | 67 | $this->container['conf'] = $this->conf; |
63 | $this->container['db'] = new \Shaarli\Bookmark\LinkDB(self::$testDatastore, true, false); | 68 | $this->container['db'] = new BookmarkFileService($this->conf, $history, true); |
64 | $this->container['history'] = null; | 69 | $this->container['history'] = null; |
65 | 70 | ||
66 | $this->controller = new Links($this->container); | 71 | $this->controller = new Links($this->container); |
@@ -69,7 +74,7 @@ class GetLinkIdTest extends \PHPUnit\Framework\TestCase | |||
69 | /** | 74 | /** |
70 | * After each test, remove the test datastore. | 75 | * After each test, remove the test datastore. |
71 | */ | 76 | */ |
72 | public function tearDown() | 77 | protected function tearDown(): void |
73 | { | 78 | { |
74 | @unlink(self::$testDatastore); | 79 | @unlink(self::$testDatastore); |
75 | } | 80 | } |
@@ -97,7 +102,7 @@ class GetLinkIdTest extends \PHPUnit\Framework\TestCase | |||
97 | $this->assertEquals($id, $data['id']); | 102 | $this->assertEquals($id, $data['id']); |
98 | 103 | ||
99 | // Check link elements | 104 | // Check link elements |
100 | $this->assertEquals('http://domain.tld/?WDWyig', $data['url']); | 105 | $this->assertEquals('http://domain.tld/shaare/WDWyig', $data['url']); |
101 | $this->assertEquals('WDWyig', $data['shorturl']); | 106 | $this->assertEquals('WDWyig', $data['shorturl']); |
102 | $this->assertEquals('Link title: @website', $data['title']); | 107 | $this->assertEquals('Link title: @website', $data['title']); |
103 | $this->assertEquals( | 108 | $this->assertEquals( |
@@ -107,7 +112,7 @@ class GetLinkIdTest extends \PHPUnit\Framework\TestCase | |||
107 | $this->assertEquals('sTuff', $data['tags'][0]); | 112 | $this->assertEquals('sTuff', $data['tags'][0]); |
108 | $this->assertEquals(false, $data['private']); | 113 | $this->assertEquals(false, $data['private']); |
109 | $this->assertEquals( | 114 | $this->assertEquals( |
110 | \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM), | 115 | \DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM), |
111 | $data['created'] | 116 | $data['created'] |
112 | ); | 117 | ); |
113 | $this->assertEmpty($data['updated']); | 118 | $this->assertEmpty($data['updated']); |
@@ -115,12 +120,12 @@ class GetLinkIdTest extends \PHPUnit\Framework\TestCase | |||
115 | 120 | ||
116 | /** | 121 | /** |
117 | * Test basic getLink service: get non existent link => ApiLinkNotFoundException. | 122 | * Test basic getLink service: get non existent link => ApiLinkNotFoundException. |
118 | * | ||
119 | * @expectedException Shaarli\Api\Exceptions\ApiLinkNotFoundException | ||
120 | * @expectedExceptionMessage Link not found | ||
121 | */ | 123 | */ |
122 | public function testGetLink404() | 124 | public function testGetLink404() |
123 | { | 125 | { |
126 | $this->expectException(\Shaarli\Api\Exceptions\ApiLinkNotFoundException::class); | ||
127 | $this->expectExceptionMessage('Link not found'); | ||
128 | |||
124 | $env = Environment::mock([ | 129 | $env = Environment::mock([ |
125 | 'REQUEST_METHOD' => 'GET', | 130 | 'REQUEST_METHOD' => 'GET', |
126 | ]); | 131 | ]); |
diff --git a/tests/api/controllers/links/GetLinksTest.php b/tests/api/controllers/links/GetLinksTest.php index 711a3152..ca1bfc63 100644 --- a/tests/api/controllers/links/GetLinksTest.php +++ b/tests/api/controllers/links/GetLinksTest.php | |||
@@ -1,8 +1,11 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli\Api\Controllers; | 2 | namespace Shaarli\Api\Controllers; |
3 | 3 | ||
4 | use Shaarli\Bookmark\Bookmark; | ||
5 | use Shaarli\Bookmark\BookmarkFileService; | ||
4 | use Shaarli\Bookmark\LinkDB; | 6 | use Shaarli\Bookmark\LinkDB; |
5 | use Shaarli\Config\ConfigManager; | 7 | use Shaarli\Config\ConfigManager; |
8 | use Shaarli\History; | ||
6 | use Slim\Container; | 9 | use Slim\Container; |
7 | use Slim\Http\Environment; | 10 | use Slim\Http\Environment; |
8 | use Slim\Http\Request; | 11 | use Slim\Http\Request; |
@@ -17,7 +20,7 @@ use Slim\Http\Response; | |||
17 | * | 20 | * |
18 | * @package Shaarli\Api\Controllers | 21 | * @package Shaarli\Api\Controllers |
19 | */ | 22 | */ |
20 | class GetLinksTest extends \PHPUnit\Framework\TestCase | 23 | class GetLinksTest extends \Shaarli\TestCase |
21 | { | 24 | { |
22 | /** | 25 | /** |
23 | * @var string datastore to test write operations | 26 | * @var string datastore to test write operations |
@@ -50,17 +53,19 @@ class GetLinksTest extends \PHPUnit\Framework\TestCase | |||
50 | const NB_FIELDS_LINK = 9; | 53 | const NB_FIELDS_LINK = 9; |
51 | 54 | ||
52 | /** | 55 | /** |
53 | * Before every test, instantiate a new Api with its config, plugins and links. | 56 | * Before every test, instantiate a new Api with its config, plugins and bookmarks. |
54 | */ | 57 | */ |
55 | public function setUp() | 58 | protected function setUp(): void |
56 | { | 59 | { |
57 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 60 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
61 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
58 | $this->refDB = new \ReferenceLinkDB(); | 62 | $this->refDB = new \ReferenceLinkDB(); |
59 | $this->refDB->write(self::$testDatastore); | 63 | $this->refDB->write(self::$testDatastore); |
64 | $history = new History('sandbox/history.php'); | ||
60 | 65 | ||
61 | $this->container = new Container(); | 66 | $this->container = new Container(); |
62 | $this->container['conf'] = $this->conf; | 67 | $this->container['conf'] = $this->conf; |
63 | $this->container['db'] = new LinkDB(self::$testDatastore, true, false); | 68 | $this->container['db'] = new BookmarkFileService($this->conf, $history, true); |
64 | $this->container['history'] = null; | 69 | $this->container['history'] = null; |
65 | 70 | ||
66 | $this->controller = new Links($this->container); | 71 | $this->controller = new Links($this->container); |
@@ -69,13 +74,13 @@ class GetLinksTest extends \PHPUnit\Framework\TestCase | |||
69 | /** | 74 | /** |
70 | * After every test, remove the test datastore. | 75 | * After every test, remove the test datastore. |
71 | */ | 76 | */ |
72 | public function tearDown() | 77 | protected function tearDown(): void |
73 | { | 78 | { |
74 | @unlink(self::$testDatastore); | 79 | @unlink(self::$testDatastore); |
75 | } | 80 | } |
76 | 81 | ||
77 | /** | 82 | /** |
78 | * Test basic getLinks service: returns all links. | 83 | * Test basic getLinks service: returns all bookmarks. |
79 | */ | 84 | */ |
80 | public function testGetLinks() | 85 | public function testGetLinks() |
81 | { | 86 | { |
@@ -104,7 +109,7 @@ class GetLinksTest extends \PHPUnit\Framework\TestCase | |||
104 | 109 | ||
105 | // Check first element fields | 110 | // Check first element fields |
106 | $first = $data[2]; | 111 | $first = $data[2]; |
107 | $this->assertEquals('http://domain.tld/?WDWyig', $first['url']); | 112 | $this->assertEquals('http://domain.tld/shaare/WDWyig', $first['url']); |
108 | $this->assertEquals('WDWyig', $first['shorturl']); | 113 | $this->assertEquals('WDWyig', $first['shorturl']); |
109 | $this->assertEquals('Link title: @website', $first['title']); | 114 | $this->assertEquals('Link title: @website', $first['title']); |
110 | $this->assertEquals( | 115 | $this->assertEquals( |
@@ -114,7 +119,7 @@ class GetLinksTest extends \PHPUnit\Framework\TestCase | |||
114 | $this->assertEquals('sTuff', $first['tags'][0]); | 119 | $this->assertEquals('sTuff', $first['tags'][0]); |
115 | $this->assertEquals(false, $first['private']); | 120 | $this->assertEquals(false, $first['private']); |
116 | $this->assertEquals( | 121 | $this->assertEquals( |
117 | \DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM), | 122 | \DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM), |
118 | $first['created'] | 123 | $first['created'] |
119 | ); | 124 | ); |
120 | $this->assertEmpty($first['updated']); | 125 | $this->assertEmpty($first['updated']); |
@@ -125,7 +130,7 @@ class GetLinksTest extends \PHPUnit\Framework\TestCase | |||
125 | 130 | ||
126 | // Update date | 131 | // Update date |
127 | $this->assertEquals( | 132 | $this->assertEquals( |
128 | \DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160803_093033')->format(\DateTime::ATOM), | 133 | \DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160803_093033')->format(\DateTime::ATOM), |
129 | $link['updated'] | 134 | $link['updated'] |
130 | ); | 135 | ); |
131 | } | 136 | } |
diff --git a/tests/api/controllers/links/PostLinkTest.php b/tests/api/controllers/links/PostLinkTest.php index d683a984..fe3de66f 100644 --- a/tests/api/controllers/links/PostLinkTest.php +++ b/tests/api/controllers/links/PostLinkTest.php | |||
@@ -2,9 +2,11 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Api\Controllers; | 3 | namespace Shaarli\Api\Controllers; |
4 | 4 | ||
5 | use PHPUnit\Framework\TestCase; | 5 | use Shaarli\Bookmark\Bookmark; |
6 | use Shaarli\Bookmark\BookmarkFileService; | ||
6 | use Shaarli\Config\ConfigManager; | 7 | use Shaarli\Config\ConfigManager; |
7 | use Shaarli\History; | 8 | use Shaarli\History; |
9 | use Shaarli\TestCase; | ||
8 | use Slim\Container; | 10 | use Slim\Container; |
9 | use Slim\Http\Environment; | 11 | use Slim\Http\Environment; |
10 | use Slim\Http\Request; | 12 | use Slim\Http\Request; |
@@ -41,6 +43,11 @@ class PostLinkTest extends TestCase | |||
41 | protected $refDB = null; | 43 | protected $refDB = null; |
42 | 44 | ||
43 | /** | 45 | /** |
46 | * @var BookmarkFileService instance. | ||
47 | */ | ||
48 | protected $bookmarkService; | ||
49 | |||
50 | /** | ||
44 | * @var HistoryController instance. | 51 | * @var HistoryController instance. |
45 | */ | 52 | */ |
46 | protected $history; | 53 | protected $history; |
@@ -61,29 +68,30 @@ class PostLinkTest extends TestCase | |||
61 | const NB_FIELDS_LINK = 9; | 68 | const NB_FIELDS_LINK = 9; |
62 | 69 | ||
63 | /** | 70 | /** |
64 | * Before every test, instantiate a new Api with its config, plugins and links. | 71 | * Before every test, instantiate a new Api with its config, plugins and bookmarks. |
65 | */ | 72 | */ |
66 | public function setUp() | 73 | protected function setUp(): void |
67 | { | 74 | { |
68 | $this->conf = new ConfigManager('tests/utils/config/configJson.json.php'); | 75 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
76 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
69 | $this->refDB = new \ReferenceLinkDB(); | 77 | $this->refDB = new \ReferenceLinkDB(); |
70 | $this->refDB->write(self::$testDatastore); | 78 | $this->refDB->write(self::$testDatastore); |
71 | |||
72 | $refHistory = new \ReferenceHistory(); | 79 | $refHistory = new \ReferenceHistory(); |
73 | $refHistory->write(self::$testHistory); | 80 | $refHistory->write(self::$testHistory); |
74 | $this->history = new History(self::$testHistory); | 81 | $this->history = new History(self::$testHistory); |
82 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
75 | 83 | ||
76 | $this->container = new Container(); | 84 | $this->container = new Container(); |
77 | $this->container['conf'] = $this->conf; | 85 | $this->container['conf'] = $this->conf; |
78 | $this->container['db'] = new \Shaarli\Bookmark\LinkDB(self::$testDatastore, true, false); | 86 | $this->container['db'] = $this->bookmarkService; |
79 | $this->container['history'] = new History(self::$testHistory); | 87 | $this->container['history'] = $this->history; |
80 | 88 | ||
81 | $this->controller = new Links($this->container); | 89 | $this->controller = new Links($this->container); |
82 | 90 | ||
83 | $mock = $this->createMock(Router::class); | 91 | $mock = $this->createMock(Router::class); |
84 | $mock->expects($this->any()) | 92 | $mock->expects($this->any()) |
85 | ->method('relativePathFor') | 93 | ->method('relativePathFor') |
86 | ->willReturn('api/v1/links/1'); | 94 | ->willReturn('api/v1/bookmarks/1'); |
87 | 95 | ||
88 | // affect @property-read... seems to work | 96 | // affect @property-read... seems to work |
89 | $this->controller->getCi()->router = $mock; | 97 | $this->controller->getCi()->router = $mock; |
@@ -99,7 +107,7 @@ class PostLinkTest extends TestCase | |||
99 | /** | 107 | /** |
100 | * After every test, remove the test datastore. | 108 | * After every test, remove the test datastore. |
101 | */ | 109 | */ |
102 | public function tearDown() | 110 | protected function tearDown(): void |
103 | { | 111 | { |
104 | @unlink(self::$testDatastore); | 112 | @unlink(self::$testDatastore); |
105 | @unlink(self::$testHistory); | 113 | @unlink(self::$testHistory); |
@@ -118,16 +126,16 @@ class PostLinkTest extends TestCase | |||
118 | 126 | ||
119 | $response = $this->controller->postLink($request, new Response()); | 127 | $response = $this->controller->postLink($request, new Response()); |
120 | $this->assertEquals(201, $response->getStatusCode()); | 128 | $this->assertEquals(201, $response->getStatusCode()); |
121 | $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]); | 129 | $this->assertEquals('api/v1/bookmarks/1', $response->getHeader('Location')[0]); |
122 | $data = json_decode((string) $response->getBody(), true); | 130 | $data = json_decode((string) $response->getBody(), true); |
123 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | 131 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); |
124 | $this->assertEquals(43, $data['id']); | 132 | $this->assertEquals(43, $data['id']); |
125 | $this->assertRegExp('/[\w_-]{6}/', $data['shorturl']); | 133 | $this->assertRegExp('/[\w_-]{6}/', $data['shorturl']); |
126 | $this->assertEquals('http://domain.tld/?' . $data['shorturl'], $data['url']); | 134 | $this->assertEquals('http://domain.tld/shaare/' . $data['shorturl'], $data['url']); |
127 | $this->assertEquals('?' . $data['shorturl'], $data['title']); | 135 | $this->assertEquals('/shaare/' . $data['shorturl'], $data['title']); |
128 | $this->assertEquals('', $data['description']); | 136 | $this->assertEquals('', $data['description']); |
129 | $this->assertEquals([], $data['tags']); | 137 | $this->assertEquals([], $data['tags']); |
130 | $this->assertEquals(false, $data['private']); | 138 | $this->assertEquals(true, $data['private']); |
131 | $this->assertTrue( | 139 | $this->assertTrue( |
132 | new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) | 140 | new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) |
133 | ); | 141 | ); |
@@ -163,7 +171,7 @@ class PostLinkTest extends TestCase | |||
163 | $response = $this->controller->postLink($request, new Response()); | 171 | $response = $this->controller->postLink($request, new Response()); |
164 | 172 | ||
165 | $this->assertEquals(201, $response->getStatusCode()); | 173 | $this->assertEquals(201, $response->getStatusCode()); |
166 | $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]); | 174 | $this->assertEquals('api/v1/bookmarks/1', $response->getHeader('Location')[0]); |
167 | $data = json_decode((string) $response->getBody(), true); | 175 | $data = json_decode((string) $response->getBody(), true); |
168 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | 176 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); |
169 | $this->assertEquals(43, $data['id']); | 177 | $this->assertEquals(43, $data['id']); |
@@ -211,11 +219,11 @@ class PostLinkTest extends TestCase | |||
211 | $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']); | 219 | $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']); |
212 | $this->assertEquals(false, $data['private']); | 220 | $this->assertEquals(false, $data['private']); |
213 | $this->assertEquals( | 221 | $this->assertEquals( |
214 | \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20130614_184135'), | 222 | \DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20130614_184135'), |
215 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) | 223 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) |
216 | ); | 224 | ); |
217 | $this->assertEquals( | 225 | $this->assertEquals( |
218 | \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20130615_184230'), | 226 | \DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20130615_184230'), |
219 | \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) | 227 | \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) |
220 | ); | 228 | ); |
221 | } | 229 | } |
diff --git a/tests/api/controllers/links/PutLinkTest.php b/tests/api/controllers/links/PutLinkTest.php index cd815b66..a2e87c59 100644 --- a/tests/api/controllers/links/PutLinkTest.php +++ b/tests/api/controllers/links/PutLinkTest.php | |||
@@ -3,6 +3,8 @@ | |||
3 | 3 | ||
4 | namespace Shaarli\Api\Controllers; | 4 | namespace Shaarli\Api\Controllers; |
5 | 5 | ||
6 | use Shaarli\Bookmark\Bookmark; | ||
7 | use Shaarli\Bookmark\BookmarkFileService; | ||
6 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
7 | use Shaarli\History; | 9 | use Shaarli\History; |
8 | use Slim\Container; | 10 | use Slim\Container; |
@@ -10,7 +12,7 @@ use Slim\Http\Environment; | |||
10 | use Slim\Http\Request; | 12 | use Slim\Http\Request; |
11 | use Slim\Http\Response; | 13 | use Slim\Http\Response; |
12 | 14 | ||
13 | class PutLinkTest extends \PHPUnit\Framework\TestCase | 15 | class PutLinkTest extends \Shaarli\TestCase |
14 | { | 16 | { |
15 | /** | 17 | /** |
16 | * @var string datastore to test write operations | 18 | * @var string datastore to test write operations |
@@ -33,6 +35,11 @@ class PutLinkTest extends \PHPUnit\Framework\TestCase | |||
33 | protected $refDB = null; | 35 | protected $refDB = null; |
34 | 36 | ||
35 | /** | 37 | /** |
38 | * @var BookmarkFileService instance. | ||
39 | */ | ||
40 | protected $bookmarkService; | ||
41 | |||
42 | /** | ||
36 | * @var HistoryController instance. | 43 | * @var HistoryController instance. |
37 | */ | 44 | */ |
38 | protected $history; | 45 | protected $history; |
@@ -53,22 +60,23 @@ class PutLinkTest extends \PHPUnit\Framework\TestCase | |||
53 | const NB_FIELDS_LINK = 9; | 60 | const NB_FIELDS_LINK = 9; |
54 | 61 | ||
55 | /** | 62 | /** |
56 | * Before every test, instantiate a new Api with its config, plugins and links. | 63 | * Before every test, instantiate a new Api with its config, plugins and bookmarks. |
57 | */ | 64 | */ |
58 | public function setUp() | 65 | protected function setUp(): void |
59 | { | 66 | { |
60 | $this->conf = new ConfigManager('tests/utils/config/configJson.json.php'); | 67 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
68 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
61 | $this->refDB = new \ReferenceLinkDB(); | 69 | $this->refDB = new \ReferenceLinkDB(); |
62 | $this->refDB->write(self::$testDatastore); | 70 | $this->refDB->write(self::$testDatastore); |
63 | |||
64 | $refHistory = new \ReferenceHistory(); | 71 | $refHistory = new \ReferenceHistory(); |
65 | $refHistory->write(self::$testHistory); | 72 | $refHistory->write(self::$testHistory); |
66 | $this->history = new History(self::$testHistory); | 73 | $this->history = new History(self::$testHistory); |
74 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
67 | 75 | ||
68 | $this->container = new Container(); | 76 | $this->container = new Container(); |
69 | $this->container['conf'] = $this->conf; | 77 | $this->container['conf'] = $this->conf; |
70 | $this->container['db'] = new \Shaarli\Bookmark\LinkDB(self::$testDatastore, true, false); | 78 | $this->container['db'] = $this->bookmarkService; |
71 | $this->container['history'] = new History(self::$testHistory); | 79 | $this->container['history'] = $this->history; |
72 | 80 | ||
73 | $this->controller = new Links($this->container); | 81 | $this->controller = new Links($this->container); |
74 | 82 | ||
@@ -83,7 +91,7 @@ class PutLinkTest extends \PHPUnit\Framework\TestCase | |||
83 | /** | 91 | /** |
84 | * After every test, remove the test datastore. | 92 | * After every test, remove the test datastore. |
85 | */ | 93 | */ |
86 | public function tearDown() | 94 | protected function tearDown(): void |
87 | { | 95 | { |
88 | @unlink(self::$testDatastore); | 96 | @unlink(self::$testDatastore); |
89 | @unlink(self::$testHistory); | 97 | @unlink(self::$testHistory); |
@@ -106,11 +114,11 @@ class PutLinkTest extends \PHPUnit\Framework\TestCase | |||
106 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); | 114 | $this->assertEquals(self::NB_FIELDS_LINK, count($data)); |
107 | $this->assertEquals($id, $data['id']); | 115 | $this->assertEquals($id, $data['id']); |
108 | $this->assertEquals('WDWyig', $data['shorturl']); | 116 | $this->assertEquals('WDWyig', $data['shorturl']); |
109 | $this->assertEquals('http://domain.tld/?WDWyig', $data['url']); | 117 | $this->assertEquals('http://domain.tld/shaare/WDWyig', $data['url']); |
110 | $this->assertEquals('?WDWyig', $data['title']); | 118 | $this->assertEquals('/shaare/WDWyig', $data['title']); |
111 | $this->assertEquals('', $data['description']); | 119 | $this->assertEquals('', $data['description']); |
112 | $this->assertEquals([], $data['tags']); | 120 | $this->assertEquals([], $data['tags']); |
113 | $this->assertEquals(false, $data['private']); | 121 | $this->assertEquals(true, $data['private']); |
114 | $this->assertEquals( | 122 | $this->assertEquals( |
115 | \DateTime::createFromFormat('Ymd_His', '20150310_114651'), | 123 | \DateTime::createFromFormat('Ymd_His', '20150310_114651'), |
116 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) | 124 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) |
@@ -199,23 +207,23 @@ class PutLinkTest extends \PHPUnit\Framework\TestCase | |||
199 | $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']); | 207 | $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']); |
200 | $this->assertEquals(false, $data['private']); | 208 | $this->assertEquals(false, $data['private']); |
201 | $this->assertEquals( | 209 | $this->assertEquals( |
202 | \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20130614_184135'), | 210 | \DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20130614_184135'), |
203 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) | 211 | \DateTime::createFromFormat(\DateTime::ATOM, $data['created']) |
204 | ); | 212 | ); |
205 | $this->assertEquals( | 213 | $this->assertEquals( |
206 | \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20130615_184230'), | 214 | \DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20130615_184230'), |
207 | \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) | 215 | \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) |
208 | ); | 216 | ); |
209 | } | 217 | } |
210 | 218 | ||
211 | /** | 219 | /** |
212 | * Test link update on non existent link => ApiLinkNotFoundException. | 220 | * Test link update on non existent link => ApiLinkNotFoundException. |
213 | * | ||
214 | * @expectedException Shaarli\Api\Exceptions\ApiLinkNotFoundException | ||
215 | * @expectedExceptionMessage Link not found | ||
216 | */ | 221 | */ |
217 | public function testGetLink404() | 222 | public function testGetLink404() |
218 | { | 223 | { |
224 | $this->expectException(\Shaarli\Api\Exceptions\ApiLinkNotFoundException::class); | ||
225 | $this->expectExceptionMessage('Link not found'); | ||
226 | |||
219 | $env = Environment::mock([ | 227 | $env = Environment::mock([ |
220 | 'REQUEST_METHOD' => 'PUT', | 228 | 'REQUEST_METHOD' => 'PUT', |
221 | ]); | 229 | ]); |
diff --git a/tests/api/controllers/tags/DeleteTagTest.php b/tests/api/controllers/tags/DeleteTagTest.php index 84e1d56e..1326eb47 100644 --- a/tests/api/controllers/tags/DeleteTagTest.php +++ b/tests/api/controllers/tags/DeleteTagTest.php | |||
@@ -3,6 +3,7 @@ | |||
3 | 3 | ||
4 | namespace Shaarli\Api\Controllers; | 4 | namespace Shaarli\Api\Controllers; |
5 | 5 | ||
6 | use Shaarli\Bookmark\BookmarkFileService; | ||
6 | use Shaarli\Bookmark\LinkDB; | 7 | use Shaarli\Bookmark\LinkDB; |
7 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
8 | use Shaarli\History; | 9 | use Shaarli\History; |
@@ -11,7 +12,7 @@ use Slim\Http\Environment; | |||
11 | use Slim\Http\Request; | 12 | use Slim\Http\Request; |
12 | use Slim\Http\Response; | 13 | use Slim\Http\Response; |
13 | 14 | ||
14 | class DeleteTagTest extends \PHPUnit\Framework\TestCase | 15 | class DeleteTagTest extends \Shaarli\TestCase |
15 | { | 16 | { |
16 | /** | 17 | /** |
17 | * @var string datastore to test write operations | 18 | * @var string datastore to test write operations |
@@ -34,9 +35,9 @@ class DeleteTagTest extends \PHPUnit\Framework\TestCase | |||
34 | protected $refDB = null; | 35 | protected $refDB = null; |
35 | 36 | ||
36 | /** | 37 | /** |
37 | * @var LinkDB instance. | 38 | * @var BookmarkFileService instance. |
38 | */ | 39 | */ |
39 | protected $linkDB; | 40 | protected $bookmarkService; |
40 | 41 | ||
41 | /** | 42 | /** |
42 | * @var HistoryController instance. | 43 | * @var HistoryController instance. |
@@ -54,20 +55,22 @@ class DeleteTagTest extends \PHPUnit\Framework\TestCase | |||
54 | protected $controller; | 55 | protected $controller; |
55 | 56 | ||
56 | /** | 57 | /** |
57 | * Before each test, instantiate a new Api with its config, plugins and links. | 58 | * Before each test, instantiate a new Api with its config, plugins and bookmarks. |
58 | */ | 59 | */ |
59 | public function setUp() | 60 | protected function setUp(): void |
60 | { | 61 | { |
61 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 62 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
63 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
62 | $this->refDB = new \ReferenceLinkDB(); | 64 | $this->refDB = new \ReferenceLinkDB(); |
63 | $this->refDB->write(self::$testDatastore); | 65 | $this->refDB->write(self::$testDatastore); |
64 | $this->linkDB = new LinkDB(self::$testDatastore, true, false); | ||
65 | $refHistory = new \ReferenceHistory(); | 66 | $refHistory = new \ReferenceHistory(); |
66 | $refHistory->write(self::$testHistory); | 67 | $refHistory->write(self::$testHistory); |
67 | $this->history = new History(self::$testHistory); | 68 | $this->history = new History(self::$testHistory); |
69 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
70 | |||
68 | $this->container = new Container(); | 71 | $this->container = new Container(); |
69 | $this->container['conf'] = $this->conf; | 72 | $this->container['conf'] = $this->conf; |
70 | $this->container['db'] = $this->linkDB; | 73 | $this->container['db'] = $this->bookmarkService; |
71 | $this->container['history'] = $this->history; | 74 | $this->container['history'] = $this->history; |
72 | 75 | ||
73 | $this->controller = new Tags($this->container); | 76 | $this->controller = new Tags($this->container); |
@@ -76,7 +79,7 @@ class DeleteTagTest extends \PHPUnit\Framework\TestCase | |||
76 | /** | 79 | /** |
77 | * After each test, remove the test datastore. | 80 | * After each test, remove the test datastore. |
78 | */ | 81 | */ |
79 | public function tearDown() | 82 | protected function tearDown(): void |
80 | { | 83 | { |
81 | @unlink(self::$testDatastore); | 84 | @unlink(self::$testDatastore); |
82 | @unlink(self::$testHistory); | 85 | @unlink(self::$testHistory); |
@@ -88,7 +91,7 @@ class DeleteTagTest extends \PHPUnit\Framework\TestCase | |||
88 | public function testDeleteTagValid() | 91 | public function testDeleteTagValid() |
89 | { | 92 | { |
90 | $tagName = 'gnu'; | 93 | $tagName = 'gnu'; |
91 | $tags = $this->linkDB->linksCountPerTag(); | 94 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
92 | $this->assertTrue($tags[$tagName] > 0); | 95 | $this->assertTrue($tags[$tagName] > 0); |
93 | $env = Environment::mock([ | 96 | $env = Environment::mock([ |
94 | 'REQUEST_METHOD' => 'DELETE', | 97 | 'REQUEST_METHOD' => 'DELETE', |
@@ -99,11 +102,11 @@ class DeleteTagTest extends \PHPUnit\Framework\TestCase | |||
99 | $this->assertEquals(204, $response->getStatusCode()); | 102 | $this->assertEquals(204, $response->getStatusCode()); |
100 | $this->assertEmpty((string) $response->getBody()); | 103 | $this->assertEmpty((string) $response->getBody()); |
101 | 104 | ||
102 | $this->linkDB = new LinkDB(self::$testDatastore, true, false); | 105 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); |
103 | $tags = $this->linkDB->linksCountPerTag(); | 106 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
104 | $this->assertFalse(isset($tags[$tagName])); | 107 | $this->assertFalse(isset($tags[$tagName])); |
105 | 108 | ||
106 | // 2 links affected | 109 | // 2 bookmarks affected |
107 | $historyEntry = $this->history->getHistory()[0]; | 110 | $historyEntry = $this->history->getHistory()[0]; |
108 | $this->assertEquals(History::UPDATED, $historyEntry['event']); | 111 | $this->assertEquals(History::UPDATED, $historyEntry['event']); |
109 | $this->assertTrue( | 112 | $this->assertTrue( |
@@ -122,7 +125,7 @@ class DeleteTagTest extends \PHPUnit\Framework\TestCase | |||
122 | public function testDeleteTagCaseSensitivity() | 125 | public function testDeleteTagCaseSensitivity() |
123 | { | 126 | { |
124 | $tagName = 'sTuff'; | 127 | $tagName = 'sTuff'; |
125 | $tags = $this->linkDB->linksCountPerTag(); | 128 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
126 | $this->assertTrue($tags[$tagName] > 0); | 129 | $this->assertTrue($tags[$tagName] > 0); |
127 | $env = Environment::mock([ | 130 | $env = Environment::mock([ |
128 | 'REQUEST_METHOD' => 'DELETE', | 131 | 'REQUEST_METHOD' => 'DELETE', |
@@ -133,8 +136,8 @@ class DeleteTagTest extends \PHPUnit\Framework\TestCase | |||
133 | $this->assertEquals(204, $response->getStatusCode()); | 136 | $this->assertEquals(204, $response->getStatusCode()); |
134 | $this->assertEmpty((string) $response->getBody()); | 137 | $this->assertEmpty((string) $response->getBody()); |
135 | 138 | ||
136 | $this->linkDB = new LinkDB(self::$testDatastore, true, false); | 139 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); |
137 | $tags = $this->linkDB->linksCountPerTag(); | 140 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
138 | $this->assertFalse(isset($tags[$tagName])); | 141 | $this->assertFalse(isset($tags[$tagName])); |
139 | $this->assertTrue($tags[strtolower($tagName)] > 0); | 142 | $this->assertTrue($tags[strtolower($tagName)] > 0); |
140 | 143 | ||
@@ -147,14 +150,14 @@ class DeleteTagTest extends \PHPUnit\Framework\TestCase | |||
147 | 150 | ||
148 | /** | 151 | /** |
149 | * Test DELETE tag endpoint: reach not existing tag. | 152 | * Test DELETE tag endpoint: reach not existing tag. |
150 | * | ||
151 | * @expectedException Shaarli\Api\Exceptions\ApiTagNotFoundException | ||
152 | * @expectedExceptionMessage Tag not found | ||
153 | */ | 153 | */ |
154 | public function testDeleteLink404() | 154 | public function testDeleteLink404() |
155 | { | 155 | { |
156 | $this->expectException(\Shaarli\Api\Exceptions\ApiTagNotFoundException::class); | ||
157 | $this->expectExceptionMessage('Tag not found'); | ||
158 | |||
156 | $tagName = 'nopenope'; | 159 | $tagName = 'nopenope'; |
157 | $tags = $this->linkDB->linksCountPerTag(); | 160 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
158 | $this->assertFalse(isset($tags[$tagName])); | 161 | $this->assertFalse(isset($tags[$tagName])); |
159 | $env = Environment::mock([ | 162 | $env = Environment::mock([ |
160 | 'REQUEST_METHOD' => 'DELETE', | 163 | 'REQUEST_METHOD' => 'DELETE', |
diff --git a/tests/api/controllers/tags/GetTagNameTest.php b/tests/api/controllers/tags/GetTagNameTest.php index a2525c17..9c05954b 100644 --- a/tests/api/controllers/tags/GetTagNameTest.php +++ b/tests/api/controllers/tags/GetTagNameTest.php | |||
@@ -2,8 +2,10 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Api\Controllers; | 3 | namespace Shaarli\Api\Controllers; |
4 | 4 | ||
5 | use Shaarli\Bookmark\BookmarkFileService; | ||
5 | use Shaarli\Bookmark\LinkDB; | 6 | use Shaarli\Bookmark\LinkDB; |
6 | use Shaarli\Config\ConfigManager; | 7 | use Shaarli\Config\ConfigManager; |
8 | use Shaarli\History; | ||
7 | use Slim\Container; | 9 | use Slim\Container; |
8 | use Slim\Http\Environment; | 10 | use Slim\Http\Environment; |
9 | use Slim\Http\Request; | 11 | use Slim\Http\Request; |
@@ -16,7 +18,7 @@ use Slim\Http\Response; | |||
16 | * | 18 | * |
17 | * @package Shaarli\Api\Controllers | 19 | * @package Shaarli\Api\Controllers |
18 | */ | 20 | */ |
19 | class GetTagNameTest extends \PHPUnit\Framework\TestCase | 21 | class GetTagNameTest extends \Shaarli\TestCase |
20 | { | 22 | { |
21 | /** | 23 | /** |
22 | * @var string datastore to test write operations | 24 | * @var string datastore to test write operations |
@@ -49,17 +51,19 @@ class GetTagNameTest extends \PHPUnit\Framework\TestCase | |||
49 | const NB_FIELDS_TAG = 2; | 51 | const NB_FIELDS_TAG = 2; |
50 | 52 | ||
51 | /** | 53 | /** |
52 | * Before each test, instantiate a new Api with its config, plugins and links. | 54 | * Before each test, instantiate a new Api with its config, plugins and bookmarks. |
53 | */ | 55 | */ |
54 | public function setUp() | 56 | protected function setUp(): void |
55 | { | 57 | { |
56 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 58 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
59 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
57 | $this->refDB = new \ReferenceLinkDB(); | 60 | $this->refDB = new \ReferenceLinkDB(); |
58 | $this->refDB->write(self::$testDatastore); | 61 | $this->refDB->write(self::$testDatastore); |
62 | $history = new History('sandbox/history.php'); | ||
59 | 63 | ||
60 | $this->container = new Container(); | 64 | $this->container = new Container(); |
61 | $this->container['conf'] = $this->conf; | 65 | $this->container['conf'] = $this->conf; |
62 | $this->container['db'] = new LinkDB(self::$testDatastore, true, false); | 66 | $this->container['db'] = new BookmarkFileService($this->conf, $history, true); |
63 | $this->container['history'] = null; | 67 | $this->container['history'] = null; |
64 | 68 | ||
65 | $this->controller = new Tags($this->container); | 69 | $this->controller = new Tags($this->container); |
@@ -68,7 +72,7 @@ class GetTagNameTest extends \PHPUnit\Framework\TestCase | |||
68 | /** | 72 | /** |
69 | * After each test, remove the test datastore. | 73 | * After each test, remove the test datastore. |
70 | */ | 74 | */ |
71 | public function tearDown() | 75 | protected function tearDown(): void |
72 | { | 76 | { |
73 | @unlink(self::$testDatastore); | 77 | @unlink(self::$testDatastore); |
74 | } | 78 | } |
@@ -113,12 +117,12 @@ class GetTagNameTest extends \PHPUnit\Framework\TestCase | |||
113 | 117 | ||
114 | /** | 118 | /** |
115 | * Test basic getTag service: get non existent tag => ApiTagNotFoundException. | 119 | * Test basic getTag service: get non existent tag => ApiTagNotFoundException. |
116 | * | ||
117 | * @expectedException Shaarli\Api\Exceptions\ApiTagNotFoundException | ||
118 | * @expectedExceptionMessage Tag not found | ||
119 | */ | 120 | */ |
120 | public function testGetTag404() | 121 | public function testGetTag404() |
121 | { | 122 | { |
123 | $this->expectException(\Shaarli\Api\Exceptions\ApiTagNotFoundException::class); | ||
124 | $this->expectExceptionMessage('Tag not found'); | ||
125 | |||
122 | $env = Environment::mock([ | 126 | $env = Environment::mock([ |
123 | 'REQUEST_METHOD' => 'GET', | 127 | 'REQUEST_METHOD' => 'GET', |
124 | ]); | 128 | ]); |
diff --git a/tests/api/controllers/tags/GetTagsTest.php b/tests/api/controllers/tags/GetTagsTest.php index 98628c98..3459fdfa 100644 --- a/tests/api/controllers/tags/GetTagsTest.php +++ b/tests/api/controllers/tags/GetTagsTest.php | |||
@@ -1,8 +1,10 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli\Api\Controllers; | 2 | namespace Shaarli\Api\Controllers; |
3 | 3 | ||
4 | use Shaarli\Bookmark\BookmarkFileService; | ||
4 | use Shaarli\Bookmark\LinkDB; | 5 | use Shaarli\Bookmark\LinkDB; |
5 | use Shaarli\Config\ConfigManager; | 6 | use Shaarli\Config\ConfigManager; |
7 | use Shaarli\History; | ||
6 | use Slim\Container; | 8 | use Slim\Container; |
7 | use Slim\Http\Environment; | 9 | use Slim\Http\Environment; |
8 | use Slim\Http\Request; | 10 | use Slim\Http\Request; |
@@ -15,7 +17,7 @@ use Slim\Http\Response; | |||
15 | * | 17 | * |
16 | * @package Shaarli\Api\Controllers | 18 | * @package Shaarli\Api\Controllers |
17 | */ | 19 | */ |
18 | class GetTagsTest extends \PHPUnit\Framework\TestCase | 20 | class GetTagsTest extends \Shaarli\TestCase |
19 | { | 21 | { |
20 | /** | 22 | /** |
21 | * @var string datastore to test write operations | 23 | * @var string datastore to test write operations |
@@ -38,9 +40,9 @@ class GetTagsTest extends \PHPUnit\Framework\TestCase | |||
38 | protected $container; | 40 | protected $container; |
39 | 41 | ||
40 | /** | 42 | /** |
41 | * @var LinkDB instance. | 43 | * @var BookmarkFileService instance. |
42 | */ | 44 | */ |
43 | protected $linkDB; | 45 | protected $bookmarkService; |
44 | 46 | ||
45 | /** | 47 | /** |
46 | * @var Tags controller instance. | 48 | * @var Tags controller instance. |
@@ -53,18 +55,21 @@ class GetTagsTest extends \PHPUnit\Framework\TestCase | |||
53 | const NB_FIELDS_TAG = 2; | 55 | const NB_FIELDS_TAG = 2; |
54 | 56 | ||
55 | /** | 57 | /** |
56 | * Before every test, instantiate a new Api with its config, plugins and links. | 58 | * Before every test, instantiate a new Api with its config, plugins and bookmarks. |
57 | */ | 59 | */ |
58 | public function setUp() | 60 | protected function setUp(): void |
59 | { | 61 | { |
60 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 62 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
63 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
61 | $this->refDB = new \ReferenceLinkDB(); | 64 | $this->refDB = new \ReferenceLinkDB(); |
62 | $this->refDB->write(self::$testDatastore); | 65 | $this->refDB->write(self::$testDatastore); |
66 | $history = new History('sandbox/history.php'); | ||
67 | |||
68 | $this->bookmarkService = new BookmarkFileService($this->conf, $history, true); | ||
63 | 69 | ||
64 | $this->container = new Container(); | 70 | $this->container = new Container(); |
65 | $this->container['conf'] = $this->conf; | 71 | $this->container['conf'] = $this->conf; |
66 | $this->linkDB = new LinkDB(self::$testDatastore, true, false); | 72 | $this->container['db'] = $this->bookmarkService; |
67 | $this->container['db'] = $this->linkDB; | ||
68 | $this->container['history'] = null; | 73 | $this->container['history'] = null; |
69 | 74 | ||
70 | $this->controller = new Tags($this->container); | 75 | $this->controller = new Tags($this->container); |
@@ -73,7 +78,7 @@ class GetTagsTest extends \PHPUnit\Framework\TestCase | |||
73 | /** | 78 | /** |
74 | * After every test, remove the test datastore. | 79 | * After every test, remove the test datastore. |
75 | */ | 80 | */ |
76 | public function tearDown() | 81 | protected function tearDown(): void |
77 | { | 82 | { |
78 | @unlink(self::$testDatastore); | 83 | @unlink(self::$testDatastore); |
79 | } | 84 | } |
@@ -83,7 +88,7 @@ class GetTagsTest extends \PHPUnit\Framework\TestCase | |||
83 | */ | 88 | */ |
84 | public function testGetTagsAll() | 89 | public function testGetTagsAll() |
85 | { | 90 | { |
86 | $tags = $this->linkDB->linksCountPerTag(); | 91 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
87 | $env = Environment::mock([ | 92 | $env = Environment::mock([ |
88 | 'REQUEST_METHOD' => 'GET', | 93 | 'REQUEST_METHOD' => 'GET', |
89 | ]); | 94 | ]); |
@@ -136,7 +141,7 @@ class GetTagsTest extends \PHPUnit\Framework\TestCase | |||
136 | */ | 141 | */ |
137 | public function testGetTagsLimitAll() | 142 | public function testGetTagsLimitAll() |
138 | { | 143 | { |
139 | $tags = $this->linkDB->linksCountPerTag(); | 144 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
140 | $env = Environment::mock([ | 145 | $env = Environment::mock([ |
141 | 'REQUEST_METHOD' => 'GET', | 146 | 'REQUEST_METHOD' => 'GET', |
142 | 'QUERY_STRING' => 'limit=all' | 147 | 'QUERY_STRING' => 'limit=all' |
@@ -170,7 +175,7 @@ class GetTagsTest extends \PHPUnit\Framework\TestCase | |||
170 | */ | 175 | */ |
171 | public function testGetTagsVisibilityPrivate() | 176 | public function testGetTagsVisibilityPrivate() |
172 | { | 177 | { |
173 | $tags = $this->linkDB->linksCountPerTag([], 'private'); | 178 | $tags = $this->bookmarkService->bookmarksCountPerTag([], 'private'); |
174 | $env = Environment::mock([ | 179 | $env = Environment::mock([ |
175 | 'REQUEST_METHOD' => 'GET', | 180 | 'REQUEST_METHOD' => 'GET', |
176 | 'QUERY_STRING' => 'visibility=private' | 181 | 'QUERY_STRING' => 'visibility=private' |
@@ -190,7 +195,7 @@ class GetTagsTest extends \PHPUnit\Framework\TestCase | |||
190 | */ | 195 | */ |
191 | public function testGetTagsVisibilityPublic() | 196 | public function testGetTagsVisibilityPublic() |
192 | { | 197 | { |
193 | $tags = $this->linkDB->linksCountPerTag([], 'public'); | 198 | $tags = $this->bookmarkService->bookmarksCountPerTag([], 'public'); |
194 | $env = Environment::mock( | 199 | $env = Environment::mock( |
195 | [ | 200 | [ |
196 | 'REQUEST_METHOD' => 'GET', | 201 | 'REQUEST_METHOD' => 'GET', |
diff --git a/tests/api/controllers/tags/PutTagTest.php b/tests/api/controllers/tags/PutTagTest.php index 86106fc7..74edde78 100644 --- a/tests/api/controllers/tags/PutTagTest.php +++ b/tests/api/controllers/tags/PutTagTest.php | |||
@@ -3,6 +3,7 @@ | |||
3 | namespace Shaarli\Api\Controllers; | 3 | namespace Shaarli\Api\Controllers; |
4 | 4 | ||
5 | use Shaarli\Api\Exceptions\ApiBadParametersException; | 5 | use Shaarli\Api\Exceptions\ApiBadParametersException; |
6 | use Shaarli\Bookmark\BookmarkFileService; | ||
6 | use Shaarli\Bookmark\LinkDB; | 7 | use Shaarli\Bookmark\LinkDB; |
7 | use Shaarli\Config\ConfigManager; | 8 | use Shaarli\Config\ConfigManager; |
8 | use Shaarli\History; | 9 | use Shaarli\History; |
@@ -11,7 +12,7 @@ use Slim\Http\Environment; | |||
11 | use Slim\Http\Request; | 12 | use Slim\Http\Request; |
12 | use Slim\Http\Response; | 13 | use Slim\Http\Response; |
13 | 14 | ||
14 | class PutTagTest extends \PHPUnit\Framework\TestCase | 15 | class PutTagTest extends \Shaarli\TestCase |
15 | { | 16 | { |
16 | /** | 17 | /** |
17 | * @var string datastore to test write operations | 18 | * @var string datastore to test write operations |
@@ -44,9 +45,9 @@ class PutTagTest extends \PHPUnit\Framework\TestCase | |||
44 | protected $container; | 45 | protected $container; |
45 | 46 | ||
46 | /** | 47 | /** |
47 | * @var LinkDB instance. | 48 | * @var BookmarkFileService instance. |
48 | */ | 49 | */ |
49 | protected $linkDB; | 50 | protected $bookmarkService; |
50 | 51 | ||
51 | /** | 52 | /** |
52 | * @var Tags controller instance. | 53 | * @var Tags controller instance. |
@@ -59,22 +60,22 @@ class PutTagTest extends \PHPUnit\Framework\TestCase | |||
59 | const NB_FIELDS_TAG = 2; | 60 | const NB_FIELDS_TAG = 2; |
60 | 61 | ||
61 | /** | 62 | /** |
62 | * Before every test, instantiate a new Api with its config, plugins and links. | 63 | * Before every test, instantiate a new Api with its config, plugins and bookmarks. |
63 | */ | 64 | */ |
64 | public function setUp() | 65 | protected function setUp(): void |
65 | { | 66 | { |
66 | $this->conf = new ConfigManager('tests/utils/config/configJson.json.php'); | 67 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
68 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
67 | $this->refDB = new \ReferenceLinkDB(); | 69 | $this->refDB = new \ReferenceLinkDB(); |
68 | $this->refDB->write(self::$testDatastore); | 70 | $this->refDB->write(self::$testDatastore); |
69 | |||
70 | $refHistory = new \ReferenceHistory(); | 71 | $refHistory = new \ReferenceHistory(); |
71 | $refHistory->write(self::$testHistory); | 72 | $refHistory->write(self::$testHistory); |
72 | $this->history = new History(self::$testHistory); | 73 | $this->history = new History(self::$testHistory); |
74 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
73 | 75 | ||
74 | $this->container = new Container(); | 76 | $this->container = new Container(); |
75 | $this->container['conf'] = $this->conf; | 77 | $this->container['conf'] = $this->conf; |
76 | $this->linkDB = new LinkDB(self::$testDatastore, true, false); | 78 | $this->container['db'] = $this->bookmarkService; |
77 | $this->container['db'] = $this->linkDB; | ||
78 | $this->container['history'] = $this->history; | 79 | $this->container['history'] = $this->history; |
79 | 80 | ||
80 | $this->controller = new Tags($this->container); | 81 | $this->controller = new Tags($this->container); |
@@ -83,7 +84,7 @@ class PutTagTest extends \PHPUnit\Framework\TestCase | |||
83 | /** | 84 | /** |
84 | * After every test, remove the test datastore. | 85 | * After every test, remove the test datastore. |
85 | */ | 86 | */ |
86 | public function tearDown() | 87 | protected function tearDown(): void |
87 | { | 88 | { |
88 | @unlink(self::$testDatastore); | 89 | @unlink(self::$testDatastore); |
89 | @unlink(self::$testHistory); | 90 | @unlink(self::$testHistory); |
@@ -109,7 +110,7 @@ class PutTagTest extends \PHPUnit\Framework\TestCase | |||
109 | $this->assertEquals($newName, $data['name']); | 110 | $this->assertEquals($newName, $data['name']); |
110 | $this->assertEquals(2, $data['occurrences']); | 111 | $this->assertEquals(2, $data['occurrences']); |
111 | 112 | ||
112 | $tags = $this->linkDB->linksCountPerTag(); | 113 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
113 | $this->assertNotTrue(isset($tags[$tagName])); | 114 | $this->assertNotTrue(isset($tags[$tagName])); |
114 | $this->assertEquals(2, $tags[$newName]); | 115 | $this->assertEquals(2, $tags[$newName]); |
115 | 116 | ||
@@ -133,7 +134,7 @@ class PutTagTest extends \PHPUnit\Framework\TestCase | |||
133 | $tagName = 'gnu'; | 134 | $tagName = 'gnu'; |
134 | $newName = 'w3c'; | 135 | $newName = 'w3c'; |
135 | 136 | ||
136 | $tags = $this->linkDB->linksCountPerTag(); | 137 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
137 | $this->assertEquals(1, $tags[$newName]); | 138 | $this->assertEquals(1, $tags[$newName]); |
138 | $this->assertEquals(2, $tags[$tagName]); | 139 | $this->assertEquals(2, $tags[$tagName]); |
139 | 140 | ||
@@ -151,23 +152,23 @@ class PutTagTest extends \PHPUnit\Framework\TestCase | |||
151 | $this->assertEquals($newName, $data['name']); | 152 | $this->assertEquals($newName, $data['name']); |
152 | $this->assertEquals(3, $data['occurrences']); | 153 | $this->assertEquals(3, $data['occurrences']); |
153 | 154 | ||
154 | $tags = $this->linkDB->linksCountPerTag(); | 155 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
155 | $this->assertNotTrue(isset($tags[$tagName])); | 156 | $this->assertNotTrue(isset($tags[$tagName])); |
156 | $this->assertEquals(3, $tags[$newName]); | 157 | $this->assertEquals(3, $tags[$newName]); |
157 | } | 158 | } |
158 | 159 | ||
159 | /** | 160 | /** |
160 | * Test tag update with an empty new tag name => ApiBadParametersException | 161 | * Test tag update with an empty new tag name => ApiBadParametersException |
161 | * | ||
162 | * @expectedException Shaarli\Api\Exceptions\ApiBadParametersException | ||
163 | * @expectedExceptionMessage New tag name is required in the request body | ||
164 | */ | 162 | */ |
165 | public function testPutTagEmpty() | 163 | public function testPutTagEmpty() |
166 | { | 164 | { |
165 | $this->expectException(\Shaarli\Api\Exceptions\ApiBadParametersException::class); | ||
166 | $this->expectExceptionMessage('New tag name is required in the request body'); | ||
167 | |||
167 | $tagName = 'gnu'; | 168 | $tagName = 'gnu'; |
168 | $newName = ''; | 169 | $newName = ''; |
169 | 170 | ||
170 | $tags = $this->linkDB->linksCountPerTag(); | 171 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
171 | $this->assertEquals(2, $tags[$tagName]); | 172 | $this->assertEquals(2, $tags[$tagName]); |
172 | 173 | ||
173 | $env = Environment::mock([ | 174 | $env = Environment::mock([ |
@@ -185,7 +186,7 @@ class PutTagTest extends \PHPUnit\Framework\TestCase | |||
185 | try { | 186 | try { |
186 | $this->controller->putTag($request, new Response(), ['tagName' => $tagName]); | 187 | $this->controller->putTag($request, new Response(), ['tagName' => $tagName]); |
187 | } catch (ApiBadParametersException $e) { | 188 | } catch (ApiBadParametersException $e) { |
188 | $tags = $this->linkDB->linksCountPerTag(); | 189 | $tags = $this->bookmarkService->bookmarksCountPerTag(); |
189 | $this->assertEquals(2, $tags[$tagName]); | 190 | $this->assertEquals(2, $tags[$tagName]); |
190 | throw $e; | 191 | throw $e; |
191 | } | 192 | } |
@@ -193,12 +194,12 @@ class PutTagTest extends \PHPUnit\Framework\TestCase | |||
193 | 194 | ||
194 | /** | 195 | /** |
195 | * Test tag update on non existent tag => ApiTagNotFoundException. | 196 | * Test tag update on non existent tag => ApiTagNotFoundException. |
196 | * | ||
197 | * @expectedException Shaarli\Api\Exceptions\ApiTagNotFoundException | ||
198 | * @expectedExceptionMessage Tag not found | ||
199 | */ | 197 | */ |
200 | public function testPutTag404() | 198 | public function testPutTag404() |
201 | { | 199 | { |
200 | $this->expectException(\Shaarli\Api\Exceptions\ApiTagNotFoundException::class); | ||
201 | $this->expectExceptionMessage('Tag not found'); | ||
202 | |||
202 | $env = Environment::mock([ | 203 | $env = Environment::mock([ |
203 | 'REQUEST_METHOD' => 'PUT', | 204 | 'REQUEST_METHOD' => 'PUT', |
204 | ]); | 205 | ]); |
diff --git a/tests/bookmark/BookmarkArrayTest.php b/tests/bookmark/BookmarkArrayTest.php new file mode 100644 index 00000000..ebed9bfc --- /dev/null +++ b/tests/bookmark/BookmarkArrayTest.php | |||
@@ -0,0 +1,236 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | use Shaarli\TestCase; | ||
6 | |||
7 | /** | ||
8 | * Class BookmarkArrayTest | ||
9 | */ | ||
10 | class BookmarkArrayTest extends TestCase | ||
11 | { | ||
12 | /** | ||
13 | * Test the constructor and make sure that the instance is properly initialized | ||
14 | */ | ||
15 | public function testArrayConstructorEmpty() | ||
16 | { | ||
17 | $array = new BookmarkArray(); | ||
18 | $this->assertTrue(is_iterable($array)); | ||
19 | $this->assertEmpty($array); | ||
20 | } | ||
21 | |||
22 | /** | ||
23 | * Test adding entries to the array, specifying the key offset or not. | ||
24 | */ | ||
25 | public function testArrayAccessAddEntries() | ||
26 | { | ||
27 | $array = new BookmarkArray(); | ||
28 | $bookmark = new Bookmark(); | ||
29 | $bookmark->setId(11)->validate(); | ||
30 | $array[] = $bookmark; | ||
31 | $this->assertCount(1, $array); | ||
32 | $this->assertTrue(isset($array[11])); | ||
33 | $this->assertNull($array[0]); | ||
34 | $this->assertEquals($bookmark, $array[11]); | ||
35 | |||
36 | $bookmark = new Bookmark(); | ||
37 | $bookmark->setId(14)->validate(); | ||
38 | $array[14] = $bookmark; | ||
39 | $this->assertCount(2, $array); | ||
40 | $this->assertTrue(isset($array[14])); | ||
41 | $this->assertNull($array[0]); | ||
42 | $this->assertEquals($bookmark, $array[14]); | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Test adding a bad entry: wrong type | ||
47 | */ | ||
48 | public function testArrayAccessAddBadEntryInstance() | ||
49 | { | ||
50 | $this->expectException(\Shaarli\Bookmark\Exception\InvalidBookmarkException::class); | ||
51 | |||
52 | $array = new BookmarkArray(); | ||
53 | $array[] = 'nope'; | ||
54 | } | ||
55 | |||
56 | /** | ||
57 | * Test adding a bad entry: no id | ||
58 | */ | ||
59 | public function testArrayAccessAddBadEntryNoId() | ||
60 | { | ||
61 | $this->expectException(\Shaarli\Bookmark\Exception\InvalidBookmarkException::class); | ||
62 | |||
63 | $array = new BookmarkArray(); | ||
64 | $bookmark = new Bookmark(); | ||
65 | $array[] = $bookmark; | ||
66 | } | ||
67 | |||
68 | /** | ||
69 | * Test adding a bad entry: no url | ||
70 | */ | ||
71 | public function testArrayAccessAddBadEntryNoUrl() | ||
72 | { | ||
73 | $this->expectException(\Shaarli\Bookmark\Exception\InvalidBookmarkException::class); | ||
74 | |||
75 | $array = new BookmarkArray(); | ||
76 | $bookmark = (new Bookmark())->setId(11); | ||
77 | $array[] = $bookmark; | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * Test adding a bad entry: invalid offset | ||
82 | */ | ||
83 | public function testArrayAccessAddBadEntryOffset() | ||
84 | { | ||
85 | $this->expectException(\Shaarli\Bookmark\Exception\InvalidBookmarkException::class); | ||
86 | |||
87 | $array = new BookmarkArray(); | ||
88 | $bookmark = (new Bookmark())->setId(11); | ||
89 | $bookmark->validate(); | ||
90 | $array['nope'] = $bookmark; | ||
91 | } | ||
92 | |||
93 | /** | ||
94 | * Test adding a bad entry: invalid ID type | ||
95 | */ | ||
96 | public function testArrayAccessAddBadEntryIdType() | ||
97 | { | ||
98 | $this->expectException(\Shaarli\Bookmark\Exception\InvalidBookmarkException::class); | ||
99 | |||
100 | $array = new BookmarkArray(); | ||
101 | $bookmark = (new Bookmark())->setId('nope'); | ||
102 | $bookmark->validate(); | ||
103 | $array[] = $bookmark; | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Test adding a bad entry: ID/offset not consistent | ||
108 | */ | ||
109 | public function testArrayAccessAddBadEntryIdOffset() | ||
110 | { | ||
111 | $this->expectException(\Shaarli\Bookmark\Exception\InvalidBookmarkException::class); | ||
112 | |||
113 | $array = new BookmarkArray(); | ||
114 | $bookmark = (new Bookmark())->setId(11); | ||
115 | $bookmark->validate(); | ||
116 | $array[14] = $bookmark; | ||
117 | } | ||
118 | |||
119 | /** | ||
120 | * Test update entries through array access. | ||
121 | */ | ||
122 | public function testArrayAccessUpdateEntries() | ||
123 | { | ||
124 | $array = new BookmarkArray(); | ||
125 | $bookmark = new Bookmark(); | ||
126 | $bookmark->setId(11)->validate(); | ||
127 | $bookmark->setTitle('old'); | ||
128 | $array[] = $bookmark; | ||
129 | $bookmark = new Bookmark(); | ||
130 | $bookmark->setId(11)->validate(); | ||
131 | $bookmark->setTitle('test'); | ||
132 | $array[] = $bookmark; | ||
133 | $this->assertCount(1, $array); | ||
134 | $this->assertEquals('test', $array[11]->getTitle()); | ||
135 | |||
136 | $bookmark = new Bookmark(); | ||
137 | $bookmark->setId(11)->validate(); | ||
138 | $bookmark->setTitle('test2'); | ||
139 | $array[11] = $bookmark; | ||
140 | $this->assertCount(1, $array); | ||
141 | $this->assertEquals('test2', $array[11]->getTitle()); | ||
142 | } | ||
143 | |||
144 | /** | ||
145 | * Test delete entries through array access. | ||
146 | */ | ||
147 | public function testArrayAccessDeleteEntries() | ||
148 | { | ||
149 | $array = new BookmarkArray(); | ||
150 | $bookmark11 = new Bookmark(); | ||
151 | $bookmark11->setId(11)->validate(); | ||
152 | $array[] = $bookmark11; | ||
153 | $bookmark14 = new Bookmark(); | ||
154 | $bookmark14->setId(14)->validate(); | ||
155 | $array[] = $bookmark14; | ||
156 | $bookmark23 = new Bookmark(); | ||
157 | $bookmark23->setId(23)->validate(); | ||
158 | $array[] = $bookmark23; | ||
159 | $bookmark0 = new Bookmark(); | ||
160 | $bookmark0->setId(0)->validate(); | ||
161 | $array[] = $bookmark0; | ||
162 | $this->assertCount(4, $array); | ||
163 | |||
164 | unset($array[14]); | ||
165 | $this->assertCount(3, $array); | ||
166 | $this->assertEquals($bookmark11, $array[11]); | ||
167 | $this->assertEquals($bookmark23, $array[23]); | ||
168 | $this->assertEquals($bookmark0, $array[0]); | ||
169 | |||
170 | unset($array[23]); | ||
171 | $this->assertCount(2, $array); | ||
172 | $this->assertEquals($bookmark11, $array[11]); | ||
173 | $this->assertEquals($bookmark0, $array[0]); | ||
174 | |||
175 | unset($array[11]); | ||
176 | $this->assertCount(1, $array); | ||
177 | $this->assertEquals($bookmark0, $array[0]); | ||
178 | |||
179 | unset($array[0]); | ||
180 | $this->assertCount(0, $array); | ||
181 | } | ||
182 | |||
183 | /** | ||
184 | * Test iterating through array access. | ||
185 | */ | ||
186 | public function testArrayAccessIterate() | ||
187 | { | ||
188 | $array = new BookmarkArray(); | ||
189 | $bookmark11 = new Bookmark(); | ||
190 | $bookmark11->setId(11)->validate(); | ||
191 | $array[] = $bookmark11; | ||
192 | $bookmark14 = new Bookmark(); | ||
193 | $bookmark14->setId(14)->validate(); | ||
194 | $array[] = $bookmark14; | ||
195 | $bookmark23 = new Bookmark(); | ||
196 | $bookmark23->setId(23)->validate(); | ||
197 | $array[] = $bookmark23; | ||
198 | $this->assertCount(3, $array); | ||
199 | |||
200 | foreach ($array as $id => $bookmark) { | ||
201 | $this->assertEquals(${'bookmark'. $id}, $bookmark); | ||
202 | } | ||
203 | } | ||
204 | |||
205 | /** | ||
206 | * Test reordering the array. | ||
207 | */ | ||
208 | public function testReorder() | ||
209 | { | ||
210 | $refDB = new \ReferenceLinkDB(); | ||
211 | $refDB->write('sandbox/datastore.php'); | ||
212 | |||
213 | |||
214 | $bookmarks = $refDB->getLinks(); | ||
215 | $bookmarks->reorder('ASC'); | ||
216 | $this->assertInstanceOf(BookmarkArray::class, $bookmarks); | ||
217 | |||
218 | $stickyIds = [11, 10]; | ||
219 | $standardIds = [42, 4, 9, 1, 0, 7, 6, 8, 41]; | ||
220 | $linkIds = array_merge($stickyIds, $standardIds); | ||
221 | $cpt = 0; | ||
222 | foreach ($bookmarks as $key => $value) { | ||
223 | $this->assertEquals($linkIds[$cpt++], $key); | ||
224 | } | ||
225 | |||
226 | $bookmarks = $refDB->getLinks(); | ||
227 | $bookmarks->reorder('DESC'); | ||
228 | $this->assertInstanceOf(BookmarkArray::class, $bookmarks); | ||
229 | |||
230 | $linkIds = array_merge(array_reverse($stickyIds), array_reverse($standardIds)); | ||
231 | $cpt = 0; | ||
232 | foreach ($bookmarks as $key => $value) { | ||
233 | $this->assertEquals($linkIds[$cpt++], $key); | ||
234 | } | ||
235 | } | ||
236 | } | ||
diff --git a/tests/bookmark/BookmarkFileServiceTest.php b/tests/bookmark/BookmarkFileServiceTest.php new file mode 100644 index 00000000..c399822b --- /dev/null +++ b/tests/bookmark/BookmarkFileServiceTest.php | |||
@@ -0,0 +1,1111 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Link datastore tests | ||
4 | */ | ||
5 | |||
6 | namespace Shaarli\Bookmark; | ||
7 | |||
8 | use DateTime; | ||
9 | use ReferenceLinkDB; | ||
10 | use ReflectionClass; | ||
11 | use Shaarli; | ||
12 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
13 | use Shaarli\Config\ConfigManager; | ||
14 | use Shaarli\Formatter\BookmarkMarkdownFormatter; | ||
15 | use Shaarli\History; | ||
16 | use Shaarli\TestCase; | ||
17 | |||
18 | /** | ||
19 | * Unitary tests for LegacyLinkDBTest | ||
20 | */ | ||
21 | class BookmarkFileServiceTest extends TestCase | ||
22 | { | ||
23 | // datastore to test write operations | ||
24 | protected static $testDatastore = 'sandbox/datastore.php'; | ||
25 | |||
26 | protected static $testConf = 'sandbox/config'; | ||
27 | |||
28 | protected static $testUpdates = 'sandbox/updates.txt'; | ||
29 | |||
30 | /** | ||
31 | * @var ConfigManager instance. | ||
32 | */ | ||
33 | protected $conf; | ||
34 | |||
35 | /** | ||
36 | * @var History instance. | ||
37 | */ | ||
38 | protected $history; | ||
39 | |||
40 | /** | ||
41 | * @var ReferenceLinkDB instance. | ||
42 | */ | ||
43 | protected $refDB = null; | ||
44 | |||
45 | /** | ||
46 | * @var BookmarkFileService public LinkDB instance. | ||
47 | */ | ||
48 | protected $publicLinkDB = null; | ||
49 | |||
50 | /** | ||
51 | * @var BookmarkFileService private LinkDB instance. | ||
52 | */ | ||
53 | protected $privateLinkDB = null; | ||
54 | |||
55 | /** | ||
56 | * Instantiates public and private LinkDBs with test data | ||
57 | * | ||
58 | * The reference datastore contains public and private bookmarks that | ||
59 | * will be used to test LinkDB's methods: | ||
60 | * - access filtering (public/private), | ||
61 | * - link searches: | ||
62 | * - by day, | ||
63 | * - by tag, | ||
64 | * - by text, | ||
65 | * - etc. | ||
66 | * | ||
67 | * Resets test data for each test | ||
68 | */ | ||
69 | protected function setUp(): void | ||
70 | { | ||
71 | if (file_exists(self::$testDatastore)) { | ||
72 | unlink(self::$testDatastore); | ||
73 | } | ||
74 | |||
75 | if (file_exists(self::$testConf .'.json.php')) { | ||
76 | unlink(self::$testConf .'.json.php'); | ||
77 | } | ||
78 | |||
79 | if (file_exists(self::$testUpdates)) { | ||
80 | unlink(self::$testUpdates); | ||
81 | } | ||
82 | |||
83 | copy('tests/utils/config/configJson.json.php', self::$testConf .'.json.php'); | ||
84 | $this->conf = new ConfigManager(self::$testConf); | ||
85 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
86 | $this->conf->set('resource.updates', self::$testUpdates); | ||
87 | $this->refDB = new \ReferenceLinkDB(); | ||
88 | $this->refDB->write(self::$testDatastore); | ||
89 | $this->history = new History('sandbox/history.php'); | ||
90 | $this->publicLinkDB = new BookmarkFileService($this->conf, $this->history, false); | ||
91 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
92 | } | ||
93 | |||
94 | /** | ||
95 | * Test migrate() method with a legacy datastore. | ||
96 | */ | ||
97 | public function testDatabaseMigration() | ||
98 | { | ||
99 | if (!defined('SHAARLI_VERSION')) { | ||
100 | define('SHAARLI_VERSION', 'dev'); | ||
101 | } | ||
102 | |||
103 | $this->refDB = new \ReferenceLinkDB(true); | ||
104 | $this->refDB->write(self::$testDatastore); | ||
105 | $db = self::getMethod('migrate'); | ||
106 | $db->invokeArgs($this->privateLinkDB, []); | ||
107 | |||
108 | $db = new \FakeBookmarkService($this->conf, $this->history, true); | ||
109 | $this->assertInstanceOf(BookmarkArray::class, $db->getBookmarks()); | ||
110 | $this->assertEquals($this->refDB->countLinks(), $db->count()); | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Test get() method for a defined and saved bookmark | ||
115 | */ | ||
116 | public function testGetDefinedSaved() | ||
117 | { | ||
118 | $bookmark = $this->privateLinkDB->get(42); | ||
119 | $this->assertEquals(42, $bookmark->getId()); | ||
120 | $this->assertEquals('Note: I have a big ID but an old date', $bookmark->getTitle()); | ||
121 | } | ||
122 | |||
123 | /** | ||
124 | * Test get() method for a defined and not saved bookmark | ||
125 | */ | ||
126 | public function testGetDefinedNotSaved() | ||
127 | { | ||
128 | $bookmark = new Bookmark(); | ||
129 | $this->privateLinkDB->add($bookmark); | ||
130 | $createdBookmark = $this->privateLinkDB->get(43); | ||
131 | $this->assertEquals(43, $createdBookmark->getId()); | ||
132 | $this->assertEmpty($createdBookmark->getDescription()); | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * Test get() method for an undefined bookmark | ||
137 | */ | ||
138 | public function testGetUndefined() | ||
139 | { | ||
140 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); | ||
141 | |||
142 | $this->privateLinkDB->get(666); | ||
143 | } | ||
144 | |||
145 | /** | ||
146 | * Test add() method for a bookmark fully built | ||
147 | */ | ||
148 | public function testAddFull() | ||
149 | { | ||
150 | $bookmark = new Bookmark(); | ||
151 | $bookmark->setUrl($url = 'https://domain.tld/index.php'); | ||
152 | $bookmark->setShortUrl('abc'); | ||
153 | $bookmark->setTitle($title = 'This a brand new bookmark'); | ||
154 | $bookmark->setDescription($desc = 'It should be created and written'); | ||
155 | $bookmark->setTags($tags = ['tag1', 'tagssss']); | ||
156 | $bookmark->setThumbnail($thumb = 'http://thumb.tld/dle.png'); | ||
157 | $bookmark->setPrivate(true); | ||
158 | $bookmark->setSticky(true); | ||
159 | $bookmark->setCreated($created = DateTime::createFromFormat('Ymd_His', '20190518_140354')); | ||
160 | $bookmark->setUpdated($updated = DateTime::createFromFormat('Ymd_His', '20190518_150354')); | ||
161 | |||
162 | $this->privateLinkDB->add($bookmark); | ||
163 | $bookmark = $this->privateLinkDB->get(43); | ||
164 | $this->assertEquals(43, $bookmark->getId()); | ||
165 | $this->assertEquals($url, $bookmark->getUrl()); | ||
166 | $this->assertEquals('abc', $bookmark->getShortUrl()); | ||
167 | $this->assertEquals($title, $bookmark->getTitle()); | ||
168 | $this->assertEquals($desc, $bookmark->getDescription()); | ||
169 | $this->assertEquals($tags, $bookmark->getTags()); | ||
170 | $this->assertEquals($thumb, $bookmark->getThumbnail()); | ||
171 | $this->assertTrue($bookmark->isPrivate()); | ||
172 | $this->assertTrue($bookmark->isSticky()); | ||
173 | $this->assertEquals($created, $bookmark->getCreated()); | ||
174 | $this->assertEquals($updated, $bookmark->getUpdated()); | ||
175 | |||
176 | // reload from file | ||
177 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
178 | |||
179 | $bookmark = $this->privateLinkDB->get(43); | ||
180 | $this->assertEquals(43, $bookmark->getId()); | ||
181 | $this->assertEquals($url, $bookmark->getUrl()); | ||
182 | $this->assertEquals('abc', $bookmark->getShortUrl()); | ||
183 | $this->assertEquals($title, $bookmark->getTitle()); | ||
184 | $this->assertEquals($desc, $bookmark->getDescription()); | ||
185 | $this->assertEquals($tags, $bookmark->getTags()); | ||
186 | $this->assertEquals($thumb, $bookmark->getThumbnail()); | ||
187 | $this->assertTrue($bookmark->isPrivate()); | ||
188 | $this->assertTrue($bookmark->isSticky()); | ||
189 | $this->assertEquals($created, $bookmark->getCreated()); | ||
190 | $this->assertEquals($updated, $bookmark->getUpdated()); | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * Test add() method for a bookmark without any field set | ||
195 | */ | ||
196 | public function testAddMinimal() | ||
197 | { | ||
198 | $bookmark = new Bookmark(); | ||
199 | $this->privateLinkDB->add($bookmark); | ||
200 | |||
201 | $bookmark = $this->privateLinkDB->get(43); | ||
202 | $this->assertEquals(43, $bookmark->getId()); | ||
203 | $this->assertRegExp('#/shaare/[\w\-]{6}#', $bookmark->getUrl()); | ||
204 | $this->assertRegExp('/[\w\-]{6}/', $bookmark->getShortUrl()); | ||
205 | $this->assertEquals($bookmark->getUrl(), $bookmark->getTitle()); | ||
206 | $this->assertEmpty($bookmark->getDescription()); | ||
207 | $this->assertEmpty($bookmark->getTags()); | ||
208 | $this->assertEmpty($bookmark->getThumbnail()); | ||
209 | $this->assertFalse($bookmark->isPrivate()); | ||
210 | $this->assertFalse($bookmark->isSticky()); | ||
211 | $this->assertTrue(new \DateTime('5 seconds ago') < $bookmark->getCreated()); | ||
212 | $this->assertNull($bookmark->getUpdated()); | ||
213 | |||
214 | // reload from file | ||
215 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
216 | |||
217 | $bookmark = $this->privateLinkDB->get(43); | ||
218 | $this->assertEquals(43, $bookmark->getId()); | ||
219 | $this->assertRegExp('#/shaare/[\w\-]{6}#', $bookmark->getUrl()); | ||
220 | $this->assertRegExp('/[\w\-]{6}/', $bookmark->getShortUrl()); | ||
221 | $this->assertEquals($bookmark->getUrl(), $bookmark->getTitle()); | ||
222 | $this->assertEmpty($bookmark->getDescription()); | ||
223 | $this->assertEmpty($bookmark->getTags()); | ||
224 | $this->assertEmpty($bookmark->getThumbnail()); | ||
225 | $this->assertFalse($bookmark->isPrivate()); | ||
226 | $this->assertFalse($bookmark->isSticky()); | ||
227 | $this->assertTrue(new \DateTime('5 seconds ago') < $bookmark->getCreated()); | ||
228 | $this->assertNull($bookmark->getUpdated()); | ||
229 | } | ||
230 | |||
231 | /** | ||
232 | * Test add() method for a bookmark without any field set and without writing the data store | ||
233 | */ | ||
234 | public function testAddMinimalNoWrite() | ||
235 | { | ||
236 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); | ||
237 | |||
238 | $bookmark = new Bookmark(); | ||
239 | $this->privateLinkDB->add($bookmark, false); | ||
240 | |||
241 | $bookmark = $this->privateLinkDB->get(43); | ||
242 | $this->assertEquals(43, $bookmark->getId()); | ||
243 | |||
244 | // reload from file | ||
245 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
246 | |||
247 | $this->privateLinkDB->get(43); | ||
248 | } | ||
249 | |||
250 | /** | ||
251 | * Test add() method while logged out | ||
252 | */ | ||
253 | public function testAddLoggedOut() | ||
254 | { | ||
255 | $this->expectException(\Exception::class); | ||
256 | $this->expectExceptionMessage('You\'re not authorized to alter the datastore'); | ||
257 | |||
258 | $this->publicLinkDB->add(new Bookmark()); | ||
259 | } | ||
260 | |||
261 | /** | ||
262 | * Test add() method with an entry which is not a bookmark instance | ||
263 | */ | ||
264 | public function testAddNotABookmark() | ||
265 | { | ||
266 | $this->expectException(\Exception::class); | ||
267 | $this->expectExceptionMessage('Provided data is invalid'); | ||
268 | |||
269 | $this->privateLinkDB->add(['title' => 'hi!']); | ||
270 | } | ||
271 | |||
272 | /** | ||
273 | * Test add() method with a Bookmark already containing an ID | ||
274 | */ | ||
275 | public function testAddWithId() | ||
276 | { | ||
277 | $this->expectException(\Exception::class); | ||
278 | $this->expectExceptionMessage('This bookmarks already exists'); | ||
279 | |||
280 | $bookmark = new Bookmark(); | ||
281 | $bookmark->setId(43); | ||
282 | $this->privateLinkDB->add($bookmark); | ||
283 | } | ||
284 | |||
285 | /** | ||
286 | * Test set() method for a bookmark fully built | ||
287 | */ | ||
288 | public function testSetFull() | ||
289 | { | ||
290 | $bookmark = $this->privateLinkDB->get(42); | ||
291 | $bookmark->setUrl($url = 'https://domain.tld/index.php'); | ||
292 | $bookmark->setShortUrl('abc'); | ||
293 | $bookmark->setTitle($title = 'This a brand new bookmark'); | ||
294 | $bookmark->setDescription($desc = 'It should be created and written'); | ||
295 | $bookmark->setTags($tags = ['tag1', 'tagssss']); | ||
296 | $bookmark->setThumbnail($thumb = 'http://thumb.tld/dle.png'); | ||
297 | $bookmark->setPrivate(true); | ||
298 | $bookmark->setSticky(true); | ||
299 | $bookmark->setCreated($created = DateTime::createFromFormat('Ymd_His', '20190518_140354')); | ||
300 | $bookmark->setUpdated($updated = DateTime::createFromFormat('Ymd_His', '20190518_150354')); | ||
301 | |||
302 | $this->privateLinkDB->set($bookmark); | ||
303 | $bookmark = $this->privateLinkDB->get(42); | ||
304 | $this->assertEquals(42, $bookmark->getId()); | ||
305 | $this->assertEquals($url, $bookmark->getUrl()); | ||
306 | $this->assertEquals('abc', $bookmark->getShortUrl()); | ||
307 | $this->assertEquals($title, $bookmark->getTitle()); | ||
308 | $this->assertEquals($desc, $bookmark->getDescription()); | ||
309 | $this->assertEquals($tags, $bookmark->getTags()); | ||
310 | $this->assertEquals($thumb, $bookmark->getThumbnail()); | ||
311 | $this->assertTrue($bookmark->isPrivate()); | ||
312 | $this->assertTrue($bookmark->isSticky()); | ||
313 | $this->assertEquals($created, $bookmark->getCreated()); | ||
314 | $this->assertTrue(new \DateTime('5 seconds ago') < $bookmark->getUpdated()); | ||
315 | |||
316 | // reload from file | ||
317 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
318 | |||
319 | $bookmark = $this->privateLinkDB->get(42); | ||
320 | $this->assertEquals(42, $bookmark->getId()); | ||
321 | $this->assertEquals($url, $bookmark->getUrl()); | ||
322 | $this->assertEquals('abc', $bookmark->getShortUrl()); | ||
323 | $this->assertEquals($title, $bookmark->getTitle()); | ||
324 | $this->assertEquals($desc, $bookmark->getDescription()); | ||
325 | $this->assertEquals($tags, $bookmark->getTags()); | ||
326 | $this->assertEquals($thumb, $bookmark->getThumbnail()); | ||
327 | $this->assertTrue($bookmark->isPrivate()); | ||
328 | $this->assertTrue($bookmark->isSticky()); | ||
329 | $this->assertEquals($created, $bookmark->getCreated()); | ||
330 | $this->assertTrue(new \DateTime('5 seconds ago') < $bookmark->getUpdated()); | ||
331 | } | ||
332 | |||
333 | /** | ||
334 | * Test set() method for a bookmark without any field set | ||
335 | */ | ||
336 | public function testSetMinimal() | ||
337 | { | ||
338 | $bookmark = $this->privateLinkDB->get(42); | ||
339 | $this->privateLinkDB->set($bookmark); | ||
340 | |||
341 | $bookmark = $this->privateLinkDB->get(42); | ||
342 | $this->assertEquals(42, $bookmark->getId()); | ||
343 | $this->assertEquals('/shaare/WDWyig', $bookmark->getUrl()); | ||
344 | $this->assertEquals('1eYJ1Q', $bookmark->getShortUrl()); | ||
345 | $this->assertEquals('Note: I have a big ID but an old date', $bookmark->getTitle()); | ||
346 | $this->assertEquals('Used to test bookmarks reordering.', $bookmark->getDescription()); | ||
347 | $this->assertEquals(['ut'], $bookmark->getTags()); | ||
348 | $this->assertFalse($bookmark->getThumbnail()); | ||
349 | $this->assertFalse($bookmark->isPrivate()); | ||
350 | $this->assertFalse($bookmark->isSticky()); | ||
351 | $this->assertEquals( | ||
352 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20100310_101010'), | ||
353 | $bookmark->getCreated() | ||
354 | ); | ||
355 | $this->assertTrue(new \DateTime('5 seconds ago') < $bookmark->getUpdated()); | ||
356 | |||
357 | // reload from file | ||
358 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
359 | |||
360 | $bookmark = $this->privateLinkDB->get(42); | ||
361 | $this->assertEquals(42, $bookmark->getId()); | ||
362 | $this->assertEquals('/shaare/WDWyig', $bookmark->getUrl()); | ||
363 | $this->assertEquals('1eYJ1Q', $bookmark->getShortUrl()); | ||
364 | $this->assertEquals('Note: I have a big ID but an old date', $bookmark->getTitle()); | ||
365 | $this->assertEquals('Used to test bookmarks reordering.', $bookmark->getDescription()); | ||
366 | $this->assertEquals(['ut'], $bookmark->getTags()); | ||
367 | $this->assertFalse($bookmark->getThumbnail()); | ||
368 | $this->assertFalse($bookmark->isPrivate()); | ||
369 | $this->assertFalse($bookmark->isSticky()); | ||
370 | $this->assertEquals( | ||
371 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20100310_101010'), | ||
372 | $bookmark->getCreated() | ||
373 | ); | ||
374 | $this->assertTrue(new \DateTime('5 seconds ago') < $bookmark->getUpdated()); | ||
375 | } | ||
376 | |||
377 | /** | ||
378 | * Test set() method for a bookmark without any field set and without writing the data store | ||
379 | */ | ||
380 | public function testSetMinimalNoWrite() | ||
381 | { | ||
382 | $bookmark = $this->privateLinkDB->get(42); | ||
383 | $bookmark->setTitle($title = 'hi!'); | ||
384 | $this->privateLinkDB->set($bookmark, false); | ||
385 | |||
386 | $bookmark = $this->privateLinkDB->get(42); | ||
387 | $this->assertEquals(42, $bookmark->getId()); | ||
388 | $this->assertEquals($title, $bookmark->getTitle()); | ||
389 | |||
390 | // reload from file | ||
391 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
392 | |||
393 | $bookmark = $this->privateLinkDB->get(42); | ||
394 | $this->assertEquals(42, $bookmark->getId()); | ||
395 | $this->assertEquals('Note: I have a big ID but an old date', $bookmark->getTitle()); | ||
396 | } | ||
397 | |||
398 | /** | ||
399 | * Test set() method while logged out | ||
400 | */ | ||
401 | public function testSetLoggedOut() | ||
402 | { | ||
403 | $this->expectException(\Exception::class); | ||
404 | $this->expectExceptionMessage('You\'re not authorized to alter the datastore'); | ||
405 | |||
406 | $this->publicLinkDB->set(new Bookmark()); | ||
407 | } | ||
408 | |||
409 | /** | ||
410 | * Test set() method with an entry which is not a bookmark instance | ||
411 | */ | ||
412 | public function testSetNotABookmark() | ||
413 | { | ||
414 | $this->expectException(\Exception::class); | ||
415 | $this->expectExceptionMessage('Provided data is invalid'); | ||
416 | |||
417 | $this->privateLinkDB->set(['title' => 'hi!']); | ||
418 | } | ||
419 | |||
420 | /** | ||
421 | * Test set() method with a Bookmark without an ID defined. | ||
422 | */ | ||
423 | public function testSetWithoutId() | ||
424 | { | ||
425 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); | ||
426 | |||
427 | $bookmark = new Bookmark(); | ||
428 | $this->privateLinkDB->set($bookmark); | ||
429 | } | ||
430 | |||
431 | /** | ||
432 | * Test set() method with a Bookmark with an unknow ID | ||
433 | */ | ||
434 | public function testSetWithUnknownId() | ||
435 | { | ||
436 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); | ||
437 | |||
438 | $bookmark = new Bookmark(); | ||
439 | $bookmark->setId(666); | ||
440 | $this->privateLinkDB->set($bookmark); | ||
441 | } | ||
442 | |||
443 | /** | ||
444 | * Test addOrSet() method with a new ID | ||
445 | */ | ||
446 | public function testAddOrSetNew() | ||
447 | { | ||
448 | $bookmark = new Bookmark(); | ||
449 | $this->privateLinkDB->addOrSet($bookmark); | ||
450 | |||
451 | $bookmark = $this->privateLinkDB->get(43); | ||
452 | $this->assertEquals(43, $bookmark->getId()); | ||
453 | |||
454 | // reload from file | ||
455 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
456 | |||
457 | $bookmark = $this->privateLinkDB->get(43); | ||
458 | $this->assertEquals(43, $bookmark->getId()); | ||
459 | } | ||
460 | |||
461 | /** | ||
462 | * Test addOrSet() method with an existing ID | ||
463 | */ | ||
464 | public function testAddOrSetExisting() | ||
465 | { | ||
466 | $bookmark = $this->privateLinkDB->get(42); | ||
467 | $bookmark->setTitle($title = 'hi!'); | ||
468 | $this->privateLinkDB->addOrSet($bookmark); | ||
469 | |||
470 | $bookmark = $this->privateLinkDB->get(42); | ||
471 | $this->assertEquals(42, $bookmark->getId()); | ||
472 | $this->assertEquals($title, $bookmark->getTitle()); | ||
473 | |||
474 | // reload from file | ||
475 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
476 | |||
477 | $bookmark = $this->privateLinkDB->get(42); | ||
478 | $this->assertEquals(42, $bookmark->getId()); | ||
479 | $this->assertEquals($title, $bookmark->getTitle()); | ||
480 | } | ||
481 | |||
482 | /** | ||
483 | * Test addOrSet() method while logged out | ||
484 | */ | ||
485 | public function testAddOrSetLoggedOut() | ||
486 | { | ||
487 | $this->expectException(\Exception::class); | ||
488 | $this->expectExceptionMessage('You\'re not authorized to alter the datastore'); | ||
489 | |||
490 | $this->publicLinkDB->addOrSet(new Bookmark()); | ||
491 | } | ||
492 | |||
493 | /** | ||
494 | * Test addOrSet() method with an entry which is not a bookmark instance | ||
495 | */ | ||
496 | public function testAddOrSetNotABookmark() | ||
497 | { | ||
498 | $this->expectException(\Exception::class); | ||
499 | $this->expectExceptionMessage('Provided data is invalid'); | ||
500 | |||
501 | $this->privateLinkDB->addOrSet(['title' => 'hi!']); | ||
502 | } | ||
503 | |||
504 | /** | ||
505 | * Test addOrSet() method for a bookmark without any field set and without writing the data store | ||
506 | */ | ||
507 | public function testAddOrSetMinimalNoWrite() | ||
508 | { | ||
509 | $bookmark = $this->privateLinkDB->get(42); | ||
510 | $bookmark->setTitle($title = 'hi!'); | ||
511 | $this->privateLinkDB->addOrSet($bookmark, false); | ||
512 | |||
513 | $bookmark = $this->privateLinkDB->get(42); | ||
514 | $this->assertEquals(42, $bookmark->getId()); | ||
515 | $this->assertEquals($title, $bookmark->getTitle()); | ||
516 | |||
517 | // reload from file | ||
518 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
519 | |||
520 | $bookmark = $this->privateLinkDB->get(42); | ||
521 | $this->assertEquals(42, $bookmark->getId()); | ||
522 | $this->assertEquals('Note: I have a big ID but an old date', $bookmark->getTitle()); | ||
523 | } | ||
524 | |||
525 | /** | ||
526 | * Test remove() method with an existing Bookmark | ||
527 | */ | ||
528 | public function testRemoveExisting() | ||
529 | { | ||
530 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); | ||
531 | |||
532 | $bookmark = $this->privateLinkDB->get(42); | ||
533 | $this->privateLinkDB->remove($bookmark); | ||
534 | |||
535 | $exception = null; | ||
536 | try { | ||
537 | $this->privateLinkDB->get(42); | ||
538 | } catch (BookmarkNotFoundException $e) { | ||
539 | $exception = $e; | ||
540 | } | ||
541 | $this->assertInstanceOf(BookmarkNotFoundException::class, $exception); | ||
542 | |||
543 | // reload from file | ||
544 | $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, true); | ||
545 | |||
546 | $this->privateLinkDB->get(42); | ||
547 | } | ||
548 | |||
549 | /** | ||
550 | * Test remove() method while logged out | ||
551 | */ | ||
552 | public function testRemoveLoggedOut() | ||
553 | { | ||
554 | $this->expectException(\Exception::class); | ||
555 | $this->expectExceptionMessage('You\'re not authorized to alter the datastore'); | ||
556 | |||
557 | $bookmark = $this->privateLinkDB->get(42); | ||
558 | $this->publicLinkDB->remove($bookmark); | ||
559 | } | ||
560 | |||
561 | /** | ||
562 | * Test remove() method with an entry which is not a bookmark instance | ||
563 | */ | ||
564 | public function testRemoveNotABookmark() | ||
565 | { | ||
566 | $this->expectException(\Exception::class); | ||
567 | $this->expectExceptionMessage('Provided data is invalid'); | ||
568 | |||
569 | $this->privateLinkDB->remove(['title' => 'hi!']); | ||
570 | } | ||
571 | |||
572 | /** | ||
573 | * Test remove() method with a Bookmark with an unknown ID | ||
574 | */ | ||
575 | public function testRemoveWithUnknownId() | ||
576 | { | ||
577 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); | ||
578 | |||
579 | $bookmark = new Bookmark(); | ||
580 | $bookmark->setId(666); | ||
581 | $this->privateLinkDB->remove($bookmark); | ||
582 | } | ||
583 | |||
584 | /** | ||
585 | * Test exists() method | ||
586 | */ | ||
587 | public function testExists() | ||
588 | { | ||
589 | $this->assertTrue($this->privateLinkDB->exists(42)); // public | ||
590 | $this->assertTrue($this->privateLinkDB->exists(6)); // private | ||
591 | |||
592 | $this->assertTrue($this->privateLinkDB->exists(42, BookmarkFilter::$ALL)); | ||
593 | $this->assertTrue($this->privateLinkDB->exists(6, BookmarkFilter::$ALL)); | ||
594 | |||
595 | $this->assertTrue($this->privateLinkDB->exists(42, BookmarkFilter::$PUBLIC)); | ||
596 | $this->assertFalse($this->privateLinkDB->exists(6, BookmarkFilter::$PUBLIC)); | ||
597 | |||
598 | $this->assertFalse($this->privateLinkDB->exists(42, BookmarkFilter::$PRIVATE)); | ||
599 | $this->assertTrue($this->privateLinkDB->exists(6, BookmarkFilter::$PRIVATE)); | ||
600 | |||
601 | $this->assertTrue($this->publicLinkDB->exists(42)); | ||
602 | $this->assertFalse($this->publicLinkDB->exists(6)); | ||
603 | |||
604 | $this->assertTrue($this->publicLinkDB->exists(42, BookmarkFilter::$PUBLIC)); | ||
605 | $this->assertFalse($this->publicLinkDB->exists(6, BookmarkFilter::$PUBLIC)); | ||
606 | |||
607 | $this->assertFalse($this->publicLinkDB->exists(42, BookmarkFilter::$PRIVATE)); | ||
608 | $this->assertTrue($this->publicLinkDB->exists(6, BookmarkFilter::$PRIVATE)); | ||
609 | } | ||
610 | |||
611 | /** | ||
612 | * Test initialize() method | ||
613 | */ | ||
614 | public function testInitialize() | ||
615 | { | ||
616 | $dbSize = $this->privateLinkDB->count(); | ||
617 | $this->privateLinkDB->initialize(); | ||
618 | $this->assertEquals($dbSize + 3, $this->privateLinkDB->count()); | ||
619 | $this->assertStringStartsWith( | ||
620 | 'Shaarli will automatically pick up the thumbnail for links to a variety of websites.', | ||
621 | $this->privateLinkDB->get(43)->getDescription() | ||
622 | ); | ||
623 | $this->assertStringStartsWith( | ||
624 | 'Adding a shaare without entering a URL creates a text-only "note" post such as this one.', | ||
625 | $this->privateLinkDB->get(44)->getDescription() | ||
626 | ); | ||
627 | $this->assertStringStartsWith( | ||
628 | 'Welcome to Shaarli!', | ||
629 | $this->privateLinkDB->get(45)->getDescription() | ||
630 | ); | ||
631 | } | ||
632 | |||
633 | /* | ||
634 | * The following tests have been taken from the legacy LinkDB test and adapted | ||
635 | * to make sure that nothing have been broken in the migration process. | ||
636 | * They mostly cover search/filters. Some of them might be redundant with the previous ones. | ||
637 | */ | ||
638 | /** | ||
639 | * Attempt to instantiate a LinkDB whereas the datastore is not writable | ||
640 | */ | ||
641 | public function testConstructDatastoreNotWriteable() | ||
642 | { | ||
643 | $this->expectException(\Shaarli\Bookmark\Exception\NotWritableDataStoreException::class); | ||
644 | $this->expectExceptionMessageRegExp('#Couldn\'t load data from the data store file "null".*#'); | ||
645 | |||
646 | $conf = new ConfigManager('tests/utils/config/configJson'); | ||
647 | $conf->set('resource.datastore', 'null/store.db'); | ||
648 | new BookmarkFileService($conf, $this->history, true); | ||
649 | } | ||
650 | |||
651 | /** | ||
652 | * The DB doesn't exist, ensure it is created with an empty datastore | ||
653 | */ | ||
654 | public function testCheckDBNewLoggedIn() | ||
655 | { | ||
656 | unlink(self::$testDatastore); | ||
657 | $this->assertFileNotExists(self::$testDatastore); | ||
658 | new BookmarkFileService($this->conf, $this->history, true); | ||
659 | $this->assertFileExists(self::$testDatastore); | ||
660 | |||
661 | // ensure the correct data has been written | ||
662 | $this->assertGreaterThan(0, filesize(self::$testDatastore)); | ||
663 | } | ||
664 | |||
665 | /** | ||
666 | * The DB doesn't exist, but not logged in, ensure it initialized, but the file is not written | ||
667 | */ | ||
668 | public function testCheckDBNewLoggedOut() | ||
669 | { | ||
670 | unlink(self::$testDatastore); | ||
671 | $this->assertFileNotExists(self::$testDatastore); | ||
672 | $db = new \FakeBookmarkService($this->conf, $this->history, false); | ||
673 | $this->assertFileNotExists(self::$testDatastore); | ||
674 | $this->assertInstanceOf(BookmarkArray::class, $db->getBookmarks()); | ||
675 | $this->assertCount(0, $db->getBookmarks()); | ||
676 | } | ||
677 | |||
678 | /** | ||
679 | * Load public bookmarks from the DB | ||
680 | */ | ||
681 | public function testReadPublicDB() | ||
682 | { | ||
683 | $this->assertEquals( | ||
684 | $this->refDB->countPublicLinks(), | ||
685 | $this->publicLinkDB->count() | ||
686 | ); | ||
687 | } | ||
688 | |||
689 | /** | ||
690 | * Load public and private bookmarks from the DB | ||
691 | */ | ||
692 | public function testReadPrivateDB() | ||
693 | { | ||
694 | $this->assertEquals( | ||
695 | $this->refDB->countLinks(), | ||
696 | $this->privateLinkDB->count() | ||
697 | ); | ||
698 | } | ||
699 | |||
700 | /** | ||
701 | * Save the bookmarks to the DB | ||
702 | */ | ||
703 | public function testSave() | ||
704 | { | ||
705 | $testDB = new BookmarkFileService($this->conf, $this->history, true); | ||
706 | $dbSize = $testDB->count(); | ||
707 | |||
708 | $bookmark = new Bookmark(); | ||
709 | $testDB->add($bookmark); | ||
710 | |||
711 | $testDB = new BookmarkFileService($this->conf, $this->history, true); | ||
712 | $this->assertEquals($dbSize + 1, $testDB->count()); | ||
713 | } | ||
714 | |||
715 | /** | ||
716 | * Count existing bookmarks - public bookmarks hidden | ||
717 | */ | ||
718 | public function testCountHiddenPublic() | ||
719 | { | ||
720 | $this->conf->set('privacy.hide_public_links', true); | ||
721 | $linkDB = new BookmarkFileService($this->conf, $this->history, false); | ||
722 | |||
723 | $this->assertEquals(0, $linkDB->count()); | ||
724 | } | ||
725 | |||
726 | /** | ||
727 | * List the days for which bookmarks have been posted | ||
728 | */ | ||
729 | public function testDays() | ||
730 | { | ||
731 | $this->assertEquals( | ||
732 | ['20100309', '20100310', '20121206', '20121207', '20130614', '20150310'], | ||
733 | $this->publicLinkDB->days() | ||
734 | ); | ||
735 | |||
736 | $this->assertEquals( | ||
737 | ['20100309', '20100310', '20121206', '20121207', '20130614', '20141125', '20150310'], | ||
738 | $this->privateLinkDB->days() | ||
739 | ); | ||
740 | } | ||
741 | |||
742 | /** | ||
743 | * The URL corresponds to an existing entry in the DB | ||
744 | */ | ||
745 | public function testGetKnownLinkFromURL() | ||
746 | { | ||
747 | $link = $this->publicLinkDB->findByUrl('http://mediagoblin.org/'); | ||
748 | |||
749 | $this->assertNotEquals(false, $link); | ||
750 | $this->assertContainsPolyfill( | ||
751 | 'A free software media publishing platform', | ||
752 | $link->getDescription() | ||
753 | ); | ||
754 | } | ||
755 | |||
756 | /** | ||
757 | * The URL is not in the DB | ||
758 | */ | ||
759 | public function testGetUnknownLinkFromURL() | ||
760 | { | ||
761 | $this->assertEquals( | ||
762 | false, | ||
763 | $this->publicLinkDB->findByUrl('http://dev.null') | ||
764 | ); | ||
765 | } | ||
766 | |||
767 | /** | ||
768 | * Lists all tags | ||
769 | */ | ||
770 | public function testAllTags() | ||
771 | { | ||
772 | $this->assertEquals( | ||
773 | [ | ||
774 | 'web' => 3, | ||
775 | 'cartoon' => 2, | ||
776 | 'gnu' => 2, | ||
777 | 'dev' => 1, | ||
778 | 'samba' => 1, | ||
779 | 'media' => 1, | ||
780 | 'software' => 1, | ||
781 | 'stallman' => 1, | ||
782 | 'free' => 1, | ||
783 | '-exclude' => 1, | ||
784 | 'hashtag' => 2, | ||
785 | // The DB contains a link with `sTuff` and another one with `stuff` tag. | ||
786 | // They need to be grouped with the first case found - order by date DESC: `sTuff`. | ||
787 | 'sTuff' => 2, | ||
788 | 'ut' => 1, | ||
789 | ], | ||
790 | $this->publicLinkDB->bookmarksCountPerTag() | ||
791 | ); | ||
792 | |||
793 | $this->assertEquals( | ||
794 | [ | ||
795 | 'web' => 4, | ||
796 | 'cartoon' => 3, | ||
797 | 'gnu' => 2, | ||
798 | 'dev' => 2, | ||
799 | 'samba' => 1, | ||
800 | 'media' => 1, | ||
801 | 'software' => 1, | ||
802 | 'stallman' => 1, | ||
803 | 'free' => 1, | ||
804 | 'html' => 1, | ||
805 | 'w3c' => 1, | ||
806 | 'css' => 1, | ||
807 | 'Mercurial' => 1, | ||
808 | 'sTuff' => 2, | ||
809 | '-exclude' => 1, | ||
810 | '.hidden' => 1, | ||
811 | 'hashtag' => 2, | ||
812 | 'tag1' => 1, | ||
813 | 'tag2' => 1, | ||
814 | 'tag3' => 1, | ||
815 | 'tag4' => 1, | ||
816 | 'ut' => 1, | ||
817 | ], | ||
818 | $this->privateLinkDB->bookmarksCountPerTag() | ||
819 | ); | ||
820 | $this->assertEquals( | ||
821 | [ | ||
822 | 'cartoon' => 2, | ||
823 | 'gnu' => 1, | ||
824 | 'dev' => 1, | ||
825 | 'samba' => 1, | ||
826 | 'media' => 1, | ||
827 | 'html' => 1, | ||
828 | 'w3c' => 1, | ||
829 | 'css' => 1, | ||
830 | 'Mercurial' => 1, | ||
831 | '.hidden' => 1, | ||
832 | 'hashtag' => 1, | ||
833 | ], | ||
834 | $this->privateLinkDB->bookmarksCountPerTag(['web']) | ||
835 | ); | ||
836 | $this->assertEquals( | ||
837 | [ | ||
838 | 'html' => 1, | ||
839 | 'w3c' => 1, | ||
840 | 'css' => 1, | ||
841 | 'Mercurial' => 1, | ||
842 | ], | ||
843 | $this->privateLinkDB->bookmarksCountPerTag(['web'], 'private') | ||
844 | ); | ||
845 | } | ||
846 | |||
847 | /** | ||
848 | * Test filter with string. | ||
849 | */ | ||
850 | public function testFilterString() | ||
851 | { | ||
852 | $tags = 'dev cartoon'; | ||
853 | $request = ['searchtags' => $tags]; | ||
854 | $this->assertEquals( | ||
855 | 2, | ||
856 | count($this->privateLinkDB->search($request, null, true)) | ||
857 | ); | ||
858 | } | ||
859 | |||
860 | /** | ||
861 | * Test filter with array. | ||
862 | */ | ||
863 | public function testFilterArray() | ||
864 | { | ||
865 | $tags = ['dev', 'cartoon']; | ||
866 | $request = ['searchtags' => $tags]; | ||
867 | $this->assertEquals( | ||
868 | 2, | ||
869 | count($this->privateLinkDB->search($request, null, true)) | ||
870 | ); | ||
871 | } | ||
872 | |||
873 | /** | ||
874 | * Test hidden tags feature: | ||
875 | * tags starting with a dot '.' are only visible when logged in. | ||
876 | */ | ||
877 | public function testHiddenTags() | ||
878 | { | ||
879 | $tags = '.hidden'; | ||
880 | $request = ['searchtags' => $tags]; | ||
881 | $this->assertEquals( | ||
882 | 1, | ||
883 | count($this->privateLinkDB->search($request, 'all', true)) | ||
884 | ); | ||
885 | |||
886 | $this->assertEquals( | ||
887 | 0, | ||
888 | count($this->publicLinkDB->search($request, 'public', true)) | ||
889 | ); | ||
890 | } | ||
891 | |||
892 | /** | ||
893 | * Test filterHash() with a valid smallhash. | ||
894 | */ | ||
895 | public function testFilterHashValid() | ||
896 | { | ||
897 | $request = smallHash('20150310_114651'); | ||
898 | $this->assertSame( | ||
899 | $request, | ||
900 | $this->publicLinkDB->findByHash($request)->getShortUrl() | ||
901 | ); | ||
902 | $request = smallHash('20150310_114633' . 8); | ||
903 | $this->assertSame( | ||
904 | $request, | ||
905 | $this->publicLinkDB->findByHash($request)->getShortUrl() | ||
906 | ); | ||
907 | } | ||
908 | |||
909 | /** | ||
910 | * Test filterHash() with an invalid smallhash. | ||
911 | */ | ||
912 | public function testFilterHashInValid1() | ||
913 | { | ||
914 | $this->expectException(BookmarkNotFoundException::class); | ||
915 | |||
916 | $request = 'blabla'; | ||
917 | $this->publicLinkDB->findByHash($request); | ||
918 | } | ||
919 | |||
920 | /** | ||
921 | * Test filterHash() with an empty smallhash. | ||
922 | */ | ||
923 | public function testFilterHashInValid() | ||
924 | { | ||
925 | $this->expectException(BookmarkNotFoundException::class); | ||
926 | |||
927 | $this->publicLinkDB->findByHash(''); | ||
928 | } | ||
929 | |||
930 | /** | ||
931 | * Test linksCountPerTag all tags without filter. | ||
932 | * Equal occurrences should be sorted alphabetically. | ||
933 | */ | ||
934 | public function testCountLinkPerTagAllNoFilter() | ||
935 | { | ||
936 | $expected = [ | ||
937 | 'web' => 4, | ||
938 | 'cartoon' => 3, | ||
939 | 'dev' => 2, | ||
940 | 'gnu' => 2, | ||
941 | 'hashtag' => 2, | ||
942 | 'sTuff' => 2, | ||
943 | '-exclude' => 1, | ||
944 | '.hidden' => 1, | ||
945 | 'Mercurial' => 1, | ||
946 | 'css' => 1, | ||
947 | 'free' => 1, | ||
948 | 'html' => 1, | ||
949 | 'media' => 1, | ||
950 | 'samba' => 1, | ||
951 | 'software' => 1, | ||
952 | 'stallman' => 1, | ||
953 | 'tag1' => 1, | ||
954 | 'tag2' => 1, | ||
955 | 'tag3' => 1, | ||
956 | 'tag4' => 1, | ||
957 | 'ut' => 1, | ||
958 | 'w3c' => 1, | ||
959 | ]; | ||
960 | $tags = $this->privateLinkDB->bookmarksCountPerTag(); | ||
961 | |||
962 | $this->assertEquals($expected, $tags, var_export($tags, true)); | ||
963 | } | ||
964 | |||
965 | /** | ||
966 | * Test linksCountPerTag all tags with filter. | ||
967 | * Equal occurrences should be sorted alphabetically. | ||
968 | */ | ||
969 | public function testCountLinkPerTagAllWithFilter() | ||
970 | { | ||
971 | $expected = [ | ||
972 | 'hashtag' => 2, | ||
973 | '-exclude' => 1, | ||
974 | '.hidden' => 1, | ||
975 | 'free' => 1, | ||
976 | 'media' => 1, | ||
977 | 'software' => 1, | ||
978 | 'stallman' => 1, | ||
979 | 'stuff' => 1, | ||
980 | 'web' => 1, | ||
981 | ]; | ||
982 | $tags = $this->privateLinkDB->bookmarksCountPerTag(['gnu']); | ||
983 | |||
984 | $this->assertEquals($expected, $tags, var_export($tags, true)); | ||
985 | } | ||
986 | |||
987 | /** | ||
988 | * Test linksCountPerTag public tags with filter. | ||
989 | * Equal occurrences should be sorted alphabetically. | ||
990 | */ | ||
991 | public function testCountLinkPerTagPublicWithFilter() | ||
992 | { | ||
993 | $expected = [ | ||
994 | 'hashtag' => 2, | ||
995 | '-exclude' => 1, | ||
996 | '.hidden' => 1, | ||
997 | 'free' => 1, | ||
998 | 'media' => 1, | ||
999 | 'software' => 1, | ||
1000 | 'stallman' => 1, | ||
1001 | 'stuff' => 1, | ||
1002 | 'web' => 1, | ||
1003 | ]; | ||
1004 | $tags = $this->privateLinkDB->bookmarksCountPerTag(['gnu'], 'public'); | ||
1005 | |||
1006 | $this->assertEquals($expected, $tags, var_export($tags, true)); | ||
1007 | } | ||
1008 | |||
1009 | /** | ||
1010 | * Test linksCountPerTag public tags with filter. | ||
1011 | * Equal occurrences should be sorted alphabetically. | ||
1012 | */ | ||
1013 | public function testCountLinkPerTagPrivateWithFilter() | ||
1014 | { | ||
1015 | $expected = [ | ||
1016 | 'cartoon' => 1, | ||
1017 | 'tag1' => 1, | ||
1018 | 'tag2' => 1, | ||
1019 | 'tag3' => 1, | ||
1020 | 'tag4' => 1, | ||
1021 | ]; | ||
1022 | $tags = $this->privateLinkDB->bookmarksCountPerTag(['dev'], 'private'); | ||
1023 | |||
1024 | $this->assertEquals($expected, $tags, var_export($tags, true)); | ||
1025 | } | ||
1026 | |||
1027 | /** | ||
1028 | * Test linksCountPerTag public tags with filter. | ||
1029 | * Equal occurrences should be sorted alphabetically. | ||
1030 | */ | ||
1031 | public function testCountTagsNoMarkdown() | ||
1032 | { | ||
1033 | $expected = [ | ||
1034 | 'cartoon' => 3, | ||
1035 | 'dev' => 2, | ||
1036 | 'tag1' => 1, | ||
1037 | 'tag2' => 1, | ||
1038 | 'tag3' => 1, | ||
1039 | 'tag4' => 1, | ||
1040 | 'web' => 4, | ||
1041 | 'gnu' => 2, | ||
1042 | 'hashtag' => 2, | ||
1043 | 'sTuff' => 2, | ||
1044 | '-exclude' => 1, | ||
1045 | '.hidden' => 1, | ||
1046 | 'Mercurial' => 1, | ||
1047 | 'css' => 1, | ||
1048 | 'free' => 1, | ||
1049 | 'html' => 1, | ||
1050 | 'media' => 1, | ||
1051 | 'newTagToCount' => 1, | ||
1052 | 'samba' => 1, | ||
1053 | 'software' => 1, | ||
1054 | 'stallman' => 1, | ||
1055 | 'ut' => 1, | ||
1056 | 'w3c' => 1, | ||
1057 | ]; | ||
1058 | $bookmark = new Bookmark(); | ||
1059 | $bookmark->setTags(['newTagToCount', BookmarkMarkdownFormatter::NO_MD_TAG]); | ||
1060 | $this->privateLinkDB->add($bookmark); | ||
1061 | |||
1062 | $tags = $this->privateLinkDB->bookmarksCountPerTag(); | ||
1063 | |||
1064 | $this->assertEquals($expected, $tags, var_export($tags, true)); | ||
1065 | } | ||
1066 | |||
1067 | /** | ||
1068 | * Test filterDay while logged in | ||
1069 | */ | ||
1070 | public function testFilterDayLoggedIn(): void | ||
1071 | { | ||
1072 | $bookmarks = $this->privateLinkDB->filterDay('20121206'); | ||
1073 | $expectedIds = [4, 9, 1, 0]; | ||
1074 | |||
1075 | static::assertCount(4, $bookmarks); | ||
1076 | foreach ($bookmarks as $bookmark) { | ||
1077 | $i = ($i ?? -1) + 1; | ||
1078 | static::assertSame($expectedIds[$i], $bookmark->getId()); | ||
1079 | } | ||
1080 | } | ||
1081 | |||
1082 | /** | ||
1083 | * Test filterDay while logged out | ||
1084 | */ | ||
1085 | public function testFilterDayLoggedOut(): void | ||
1086 | { | ||
1087 | $bookmarks = $this->publicLinkDB->filterDay('20121206'); | ||
1088 | $expectedIds = [4, 9, 1]; | ||
1089 | |||
1090 | static::assertCount(3, $bookmarks); | ||
1091 | foreach ($bookmarks as $bookmark) { | ||
1092 | $i = ($i ?? -1) + 1; | ||
1093 | static::assertSame($expectedIds[$i], $bookmark->getId()); | ||
1094 | } | ||
1095 | } | ||
1096 | |||
1097 | /** | ||
1098 | * Allows to test LinkDB's private methods | ||
1099 | * | ||
1100 | * @see | ||
1101 | * https://sebastian-bergmann.de/archives/881-Testing-Your-Privates.html | ||
1102 | * http://stackoverflow.com/a/2798203 | ||
1103 | */ | ||
1104 | protected static function getMethod($name) | ||
1105 | { | ||
1106 | $class = new ReflectionClass('Shaarli\Bookmark\BookmarkFileService'); | ||
1107 | $method = $class->getMethod($name); | ||
1108 | $method->setAccessible(true); | ||
1109 | return $method; | ||
1110 | } | ||
1111 | } | ||
diff --git a/tests/bookmark/BookmarkFilterTest.php b/tests/bookmark/BookmarkFilterTest.php new file mode 100644 index 00000000..48c7f824 --- /dev/null +++ b/tests/bookmark/BookmarkFilterTest.php | |||
@@ -0,0 +1,526 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | use Exception; | ||
6 | use ReferenceLinkDB; | ||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\History; | ||
9 | use Shaarli\TestCase; | ||
10 | |||
11 | /** | ||
12 | * Class BookmarkFilterTest. | ||
13 | */ | ||
14 | class BookmarkFilterTest extends TestCase | ||
15 | { | ||
16 | /** | ||
17 | * @var string Test datastore path. | ||
18 | */ | ||
19 | protected static $testDatastore = 'sandbox/datastore.php'; | ||
20 | /** | ||
21 | * @var BookmarkFilter instance. | ||
22 | */ | ||
23 | protected static $linkFilter; | ||
24 | |||
25 | /** | ||
26 | * @var ReferenceLinkDB instance | ||
27 | */ | ||
28 | protected static $refDB; | ||
29 | |||
30 | /** | ||
31 | * @var BookmarkFileService instance | ||
32 | */ | ||
33 | protected static $bookmarkService; | ||
34 | |||
35 | /** | ||
36 | * Instantiate linkFilter with ReferenceLinkDB data. | ||
37 | */ | ||
38 | public static function setUpBeforeClass(): void | ||
39 | { | ||
40 | $conf = new ConfigManager('tests/utils/config/configJson'); | ||
41 | $conf->set('resource.datastore', self::$testDatastore); | ||
42 | self::$refDB = new \ReferenceLinkDB(); | ||
43 | self::$refDB->write(self::$testDatastore); | ||
44 | $history = new History('sandbox/history.php'); | ||
45 | self::$bookmarkService = new \FakeBookmarkService($conf, $history, true); | ||
46 | self::$linkFilter = new BookmarkFilter(self::$bookmarkService->getBookmarks()); | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * Blank filter. | ||
51 | */ | ||
52 | public function testFilter() | ||
53 | { | ||
54 | $this->assertEquals( | ||
55 | self::$refDB->countLinks(), | ||
56 | count(self::$linkFilter->filter('', '')) | ||
57 | ); | ||
58 | |||
59 | $this->assertEquals( | ||
60 | self::$refDB->countLinks(), | ||
61 | count(self::$linkFilter->filter('', '', 'all')) | ||
62 | ); | ||
63 | |||
64 | $this->assertEquals( | ||
65 | self::$refDB->countLinks(), | ||
66 | count(self::$linkFilter->filter('', '', 'randomstr')) | ||
67 | ); | ||
68 | |||
69 | // Private only. | ||
70 | $this->assertEquals( | ||
71 | self::$refDB->countPrivateLinks(), | ||
72 | count(self::$linkFilter->filter('', '', false, 'private')) | ||
73 | ); | ||
74 | |||
75 | // Public only. | ||
76 | $this->assertEquals( | ||
77 | self::$refDB->countPublicLinks(), | ||
78 | count(self::$linkFilter->filter('', '', false, 'public')) | ||
79 | ); | ||
80 | |||
81 | $this->assertEquals( | ||
82 | ReferenceLinkDB::$NB_LINKS_TOTAL, | ||
83 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, '')) | ||
84 | ); | ||
85 | |||
86 | $this->assertEquals( | ||
87 | self::$refDB->countUntaggedLinks(), | ||
88 | count( | ||
89 | self::$linkFilter->filter( | ||
90 | BookmarkFilter::$FILTER_TAG, | ||
91 | /*$request=*/ | ||
92 | '', | ||
93 | /*$casesensitive=*/ | ||
94 | false, | ||
95 | /*$visibility=*/ | ||
96 | 'all', | ||
97 | /*$untaggedonly=*/ | ||
98 | true | ||
99 | ) | ||
100 | ) | ||
101 | ); | ||
102 | |||
103 | $this->assertEquals( | ||
104 | ReferenceLinkDB::$NB_LINKS_TOTAL, | ||
105 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, '')) | ||
106 | ); | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * Filter bookmarks using a tag | ||
111 | */ | ||
112 | public function testFilterOneTag() | ||
113 | { | ||
114 | $this->assertEquals( | ||
115 | 4, | ||
116 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false)) | ||
117 | ); | ||
118 | |||
119 | $this->assertEquals( | ||
120 | 4, | ||
121 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false, 'all')) | ||
122 | ); | ||
123 | |||
124 | $this->assertEquals( | ||
125 | 4, | ||
126 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false, 'default-blabla')) | ||
127 | ); | ||
128 | |||
129 | // Private only. | ||
130 | $this->assertEquals( | ||
131 | 1, | ||
132 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false, 'private')) | ||
133 | ); | ||
134 | |||
135 | // Public only. | ||
136 | $this->assertEquals( | ||
137 | 3, | ||
138 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false, 'public')) | ||
139 | ); | ||
140 | } | ||
141 | |||
142 | /** | ||
143 | * Filter bookmarks using a tag - case-sensitive | ||
144 | */ | ||
145 | public function testFilterCaseSensitiveTag() | ||
146 | { | ||
147 | $this->assertEquals( | ||
148 | 0, | ||
149 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'mercurial', true)) | ||
150 | ); | ||
151 | |||
152 | $this->assertEquals( | ||
153 | 1, | ||
154 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'Mercurial', true)) | ||
155 | ); | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Filter bookmarks using a tag combination | ||
160 | */ | ||
161 | public function testFilterMultipleTags() | ||
162 | { | ||
163 | $this->assertEquals( | ||
164 | 2, | ||
165 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'dev cartoon', false)) | ||
166 | ); | ||
167 | } | ||
168 | |||
169 | /** | ||
170 | * Filter bookmarks using a non-existent tag | ||
171 | */ | ||
172 | public function testFilterUnknownTag() | ||
173 | { | ||
174 | $this->assertEquals( | ||
175 | 0, | ||
176 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'null', false)) | ||
177 | ); | ||
178 | } | ||
179 | |||
180 | /** | ||
181 | * Return bookmarks for a given day | ||
182 | */ | ||
183 | public function testFilterDay() | ||
184 | { | ||
185 | $this->assertEquals( | ||
186 | 4, | ||
187 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, '20121206')) | ||
188 | ); | ||
189 | } | ||
190 | |||
191 | /** | ||
192 | * Return bookmarks for a given day | ||
193 | */ | ||
194 | public function testFilterDayRestrictedVisibility(): void | ||
195 | { | ||
196 | $this->assertEquals( | ||
197 | 3, | ||
198 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, '20121206', false, BookmarkFilter::$PUBLIC)) | ||
199 | ); | ||
200 | } | ||
201 | |||
202 | /** | ||
203 | * 404 - day not found | ||
204 | */ | ||
205 | public function testFilterUnknownDay() | ||
206 | { | ||
207 | $this->assertEquals( | ||
208 | 0, | ||
209 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, '19700101')) | ||
210 | ); | ||
211 | } | ||
212 | |||
213 | /** | ||
214 | * Use an invalid date format | ||
215 | */ | ||
216 | public function testFilterInvalidDayWithChars() | ||
217 | { | ||
218 | $this->expectException(\Exception::class); | ||
219 | $this->expectExceptionMessageRegExp('/Invalid date format/'); | ||
220 | |||
221 | self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, 'Rainy day, dream away'); | ||
222 | } | ||
223 | |||
224 | /** | ||
225 | * Use an invalid date format | ||
226 | */ | ||
227 | public function testFilterInvalidDayDigits() | ||
228 | { | ||
229 | $this->expectException(\Exception::class); | ||
230 | $this->expectExceptionMessageRegExp('/Invalid date format/'); | ||
231 | |||
232 | self::$linkFilter->filter(BookmarkFilter::$FILTER_DAY, '20'); | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * Retrieve a link entry with its hash | ||
237 | */ | ||
238 | public function testFilterSmallHash() | ||
239 | { | ||
240 | $links = self::$linkFilter->filter(BookmarkFilter::$FILTER_HASH, 'IuWvgA'); | ||
241 | |||
242 | $this->assertEquals( | ||
243 | 1, | ||
244 | count($links) | ||
245 | ); | ||
246 | |||
247 | $this->assertEquals( | ||
248 | 'MediaGoblin', | ||
249 | $links[7]->getTitle() | ||
250 | ); | ||
251 | } | ||
252 | |||
253 | /** | ||
254 | * No link for this hash | ||
255 | */ | ||
256 | public function testFilterUnknownSmallHash() | ||
257 | { | ||
258 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); | ||
259 | |||
260 | self::$linkFilter->filter(BookmarkFilter::$FILTER_HASH, 'Iblaah'); | ||
261 | } | ||
262 | |||
263 | /** | ||
264 | * Full-text search - no result found. | ||
265 | */ | ||
266 | public function testFilterFullTextNoResult() | ||
267 | { | ||
268 | $this->assertEquals( | ||
269 | 0, | ||
270 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'azertyuiop')) | ||
271 | ); | ||
272 | } | ||
273 | |||
274 | /** | ||
275 | * Full-text search - result from a link's URL | ||
276 | */ | ||
277 | public function testFilterFullTextURL() | ||
278 | { | ||
279 | $this->assertEquals( | ||
280 | 2, | ||
281 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'ars.userfriendly.org')) | ||
282 | ); | ||
283 | |||
284 | $this->assertEquals( | ||
285 | 2, | ||
286 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'ars org')) | ||
287 | ); | ||
288 | } | ||
289 | |||
290 | /** | ||
291 | * Full-text search - result from a link's title only | ||
292 | */ | ||
293 | public function testFilterFullTextTitle() | ||
294 | { | ||
295 | // use miscellaneous cases | ||
296 | $this->assertEquals( | ||
297 | 2, | ||
298 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'userfriendly -')) | ||
299 | ); | ||
300 | $this->assertEquals( | ||
301 | 2, | ||
302 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'UserFriendly -')) | ||
303 | ); | ||
304 | $this->assertEquals( | ||
305 | 2, | ||
306 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'uSeRFrIendlY -')) | ||
307 | ); | ||
308 | |||
309 | // use miscellaneous case and offset | ||
310 | $this->assertEquals( | ||
311 | 2, | ||
312 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'RFrIendL')) | ||
313 | ); | ||
314 | } | ||
315 | |||
316 | /** | ||
317 | * Full-text search - result from the link's description only | ||
318 | */ | ||
319 | public function testFilterFullTextDescription() | ||
320 | { | ||
321 | $this->assertEquals( | ||
322 | 1, | ||
323 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'publishing media')) | ||
324 | ); | ||
325 | |||
326 | $this->assertEquals( | ||
327 | 1, | ||
328 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'mercurial w3c')) | ||
329 | ); | ||
330 | |||
331 | $this->assertEquals( | ||
332 | 3, | ||
333 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, '"free software"')) | ||
334 | ); | ||
335 | } | ||
336 | |||
337 | /** | ||
338 | * Full-text search - result from the link's tags only | ||
339 | */ | ||
340 | public function testFilterFullTextTags() | ||
341 | { | ||
342 | $this->assertEquals( | ||
343 | 6, | ||
344 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web')) | ||
345 | ); | ||
346 | |||
347 | $this->assertEquals( | ||
348 | 6, | ||
349 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web', 'all')) | ||
350 | ); | ||
351 | |||
352 | $this->assertEquals( | ||
353 | 6, | ||
354 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web', 'bla')) | ||
355 | ); | ||
356 | |||
357 | // Private only. | ||
358 | $this->assertEquals( | ||
359 | 1, | ||
360 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web', false, 'private')) | ||
361 | ); | ||
362 | |||
363 | // Public only. | ||
364 | $this->assertEquals( | ||
365 | 5, | ||
366 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web', false, 'public')) | ||
367 | ); | ||
368 | } | ||
369 | |||
370 | /** | ||
371 | * Full-text search - result set from mixed sources | ||
372 | */ | ||
373 | public function testFilterFullTextMixed() | ||
374 | { | ||
375 | $this->assertEquals( | ||
376 | 3, | ||
377 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'free software')) | ||
378 | ); | ||
379 | } | ||
380 | |||
381 | /** | ||
382 | * Full-text search - test exclusion with '-'. | ||
383 | */ | ||
384 | public function testExcludeSearch() | ||
385 | { | ||
386 | $this->assertEquals( | ||
387 | 1, | ||
388 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'free -gnu')) | ||
389 | ); | ||
390 | |||
391 | $this->assertEquals( | ||
392 | ReferenceLinkDB::$NB_LINKS_TOTAL - 1, | ||
393 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, '-revolution')) | ||
394 | ); | ||
395 | } | ||
396 | |||
397 | /** | ||
398 | * Full-text search - test AND, exact terms and exclusion combined, across fields. | ||
399 | */ | ||
400 | public function testMultiSearch() | ||
401 | { | ||
402 | $this->assertEquals( | ||
403 | 2, | ||
404 | count(self::$linkFilter->filter( | ||
405 | BookmarkFilter::$FILTER_TEXT, | ||
406 | '"Free Software " stallman "read this" @website stuff' | ||
407 | )) | ||
408 | ); | ||
409 | |||
410 | $this->assertEquals( | ||
411 | 1, | ||
412 | count(self::$linkFilter->filter( | ||
413 | BookmarkFilter::$FILTER_TEXT, | ||
414 | '"free software " stallman "read this" -beard @website stuff' | ||
415 | )) | ||
416 | ); | ||
417 | } | ||
418 | |||
419 | /** | ||
420 | * Full-text search - make sure that exact search won't work across fields. | ||
421 | */ | ||
422 | public function testSearchExactTermMultiFieldsKo() | ||
423 | { | ||
424 | $this->assertEquals( | ||
425 | 0, | ||
426 | count(self::$linkFilter->filter( | ||
427 | BookmarkFilter::$FILTER_TEXT, | ||
428 | '"designer naming"' | ||
429 | )) | ||
430 | ); | ||
431 | |||
432 | $this->assertEquals( | ||
433 | 0, | ||
434 | count(self::$linkFilter->filter( | ||
435 | BookmarkFilter::$FILTER_TEXT, | ||
436 | '"designernaming"' | ||
437 | )) | ||
438 | ); | ||
439 | } | ||
440 | |||
441 | /** | ||
442 | * Tag search with exclusion. | ||
443 | */ | ||
444 | public function testTagFilterWithExclusion() | ||
445 | { | ||
446 | $this->assertEquals( | ||
447 | 1, | ||
448 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'gnu -free')) | ||
449 | ); | ||
450 | |||
451 | $this->assertEquals( | ||
452 | ReferenceLinkDB::$NB_LINKS_TOTAL - 1, | ||
453 | count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, '-free')) | ||
454 | ); | ||
455 | } | ||
456 | |||
457 | /** | ||
458 | * Test crossed search (terms + tags). | ||
459 | */ | ||
460 | public function testFilterCrossedSearch() | ||
461 | { | ||
462 | $terms = '"Free Software " stallman "read this" @website stuff'; | ||
463 | $tags = 'free'; | ||
464 | $this->assertEquals( | ||
465 | 1, | ||
466 | count(self::$linkFilter->filter( | ||
467 | BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT, | ||
468 | array($tags, $terms) | ||
469 | )) | ||
470 | ); | ||
471 | $this->assertEquals( | ||
472 | 2, | ||
473 | count(self::$linkFilter->filter( | ||
474 | BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT, | ||
475 | array('', $terms) | ||
476 | )) | ||
477 | ); | ||
478 | $this->assertEquals( | ||
479 | 1, | ||
480 | count(self::$linkFilter->filter( | ||
481 | BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT, | ||
482 | array(false, 'PSR-2') | ||
483 | )) | ||
484 | ); | ||
485 | $this->assertEquals( | ||
486 | 1, | ||
487 | count(self::$linkFilter->filter( | ||
488 | BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT, | ||
489 | array($tags, '') | ||
490 | )) | ||
491 | ); | ||
492 | $this->assertEquals( | ||
493 | ReferenceLinkDB::$NB_LINKS_TOTAL, | ||
494 | count(self::$linkFilter->filter( | ||
495 | BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT, | ||
496 | '' | ||
497 | )) | ||
498 | ); | ||
499 | } | ||
500 | |||
501 | /** | ||
502 | * Filter bookmarks by #hashtag. | ||
503 | */ | ||
504 | public function testFilterByHashtag() | ||
505 | { | ||
506 | $hashtag = 'hashtag'; | ||
507 | $this->assertEquals( | ||
508 | 3, | ||
509 | count(self::$linkFilter->filter( | ||
510 | BookmarkFilter::$FILTER_TAG, | ||
511 | $hashtag | ||
512 | )) | ||
513 | ); | ||
514 | |||
515 | $hashtag = 'private'; | ||
516 | $this->assertEquals( | ||
517 | 1, | ||
518 | count(self::$linkFilter->filter( | ||
519 | BookmarkFilter::$FILTER_TAG, | ||
520 | $hashtag, | ||
521 | false, | ||
522 | 'private' | ||
523 | )) | ||
524 | ); | ||
525 | } | ||
526 | } | ||
diff --git a/tests/bookmark/BookmarkInitializerTest.php b/tests/bookmark/BookmarkInitializerTest.php new file mode 100644 index 00000000..25704004 --- /dev/null +++ b/tests/bookmark/BookmarkInitializerTest.php | |||
@@ -0,0 +1,150 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | use Shaarli\Config\ConfigManager; | ||
6 | use Shaarli\History; | ||
7 | use Shaarli\TestCase; | ||
8 | |||
9 | /** | ||
10 | * Class BookmarkInitializerTest | ||
11 | * @package Shaarli\Bookmark | ||
12 | */ | ||
13 | class BookmarkInitializerTest extends TestCase | ||
14 | { | ||
15 | /** @var string Path of test data store */ | ||
16 | protected static $testDatastore = 'sandbox/datastore.php'; | ||
17 | |||
18 | /** @var string Path of test config file */ | ||
19 | protected static $testConf = 'sandbox/config'; | ||
20 | |||
21 | /** | ||
22 | * @var ConfigManager instance. | ||
23 | */ | ||
24 | protected $conf; | ||
25 | |||
26 | /** | ||
27 | * @var History instance. | ||
28 | */ | ||
29 | protected $history; | ||
30 | |||
31 | /** @var BookmarkServiceInterface instance */ | ||
32 | protected $bookmarkService; | ||
33 | |||
34 | /** @var BookmarkInitializer instance */ | ||
35 | protected $initializer; | ||
36 | |||
37 | /** | ||
38 | * Initialize an empty BookmarkFileService | ||
39 | */ | ||
40 | public function setUp(): void | ||
41 | { | ||
42 | if (file_exists(self::$testDatastore)) { | ||
43 | unlink(self::$testDatastore); | ||
44 | } | ||
45 | |||
46 | copy('tests/utils/config/configJson.json.php', self::$testConf .'.json.php'); | ||
47 | $this->conf = new ConfigManager(self::$testConf); | ||
48 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
49 | $this->history = new History('sandbox/history.php'); | ||
50 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
51 | |||
52 | $this->initializer = new BookmarkInitializer($this->bookmarkService); | ||
53 | } | ||
54 | |||
55 | /** | ||
56 | * Test initialize() with a data store containing bookmarks. | ||
57 | */ | ||
58 | public function testInitializeNotEmptyDataStore(): void | ||
59 | { | ||
60 | $refDB = new \ReferenceLinkDB(); | ||
61 | $refDB->write(self::$testDatastore); | ||
62 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
63 | $this->initializer = new BookmarkInitializer($this->bookmarkService); | ||
64 | |||
65 | $this->initializer->initialize(); | ||
66 | |||
67 | $this->assertEquals($refDB->countLinks() + 3, $this->bookmarkService->count()); | ||
68 | |||
69 | $bookmark = $this->bookmarkService->get(43); | ||
70 | $this->assertStringStartsWith( | ||
71 | 'Shaarli will automatically pick up the thumbnail for links to a variety of websites.', | ||
72 | $bookmark->getDescription() | ||
73 | ); | ||
74 | $this->assertTrue($bookmark->isPrivate()); | ||
75 | |||
76 | $bookmark = $this->bookmarkService->get(44); | ||
77 | $this->assertStringStartsWith( | ||
78 | 'Adding a shaare without entering a URL creates a text-only "note" post such as this one.', | ||
79 | $bookmark->getDescription() | ||
80 | ); | ||
81 | $this->assertTrue($bookmark->isPrivate()); | ||
82 | |||
83 | $bookmark = $this->bookmarkService->get(45); | ||
84 | $this->assertStringStartsWith( | ||
85 | 'Welcome to Shaarli!', | ||
86 | $bookmark->getDescription() | ||
87 | ); | ||
88 | $this->assertFalse($bookmark->isPrivate()); | ||
89 | |||
90 | $this->bookmarkService->save(); | ||
91 | |||
92 | // Reload from file | ||
93 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
94 | $this->assertEquals($refDB->countLinks() + 3, $this->bookmarkService->count()); | ||
95 | |||
96 | $bookmark = $this->bookmarkService->get(43); | ||
97 | $this->assertStringStartsWith( | ||
98 | 'Shaarli will automatically pick up the thumbnail for links to a variety of websites.', | ||
99 | $bookmark->getDescription() | ||
100 | ); | ||
101 | $this->assertTrue($bookmark->isPrivate()); | ||
102 | |||
103 | $bookmark = $this->bookmarkService->get(44); | ||
104 | $this->assertStringStartsWith( | ||
105 | 'Adding a shaare without entering a URL creates a text-only "note" post such as this one.', | ||
106 | $bookmark->getDescription() | ||
107 | ); | ||
108 | $this->assertTrue($bookmark->isPrivate()); | ||
109 | |||
110 | $bookmark = $this->bookmarkService->get(45); | ||
111 | $this->assertStringStartsWith( | ||
112 | 'Welcome to Shaarli!', | ||
113 | $bookmark->getDescription() | ||
114 | ); | ||
115 | $this->assertFalse($bookmark->isPrivate()); | ||
116 | } | ||
117 | |||
118 | /** | ||
119 | * Test initialize() with an a non existent datastore file . | ||
120 | */ | ||
121 | public function testInitializeNonExistentDataStore(): void | ||
122 | { | ||
123 | $this->conf->set('resource.datastore', static::$testDatastore . '_empty'); | ||
124 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
125 | |||
126 | $this->initializer->initialize(); | ||
127 | |||
128 | $this->assertEquals(3, $this->bookmarkService->count()); | ||
129 | $bookmark = $this->bookmarkService->get(0); | ||
130 | $this->assertStringStartsWith( | ||
131 | 'Shaarli will automatically pick up the thumbnail for links to a variety of websites.', | ||
132 | $bookmark->getDescription() | ||
133 | ); | ||
134 | $this->assertTrue($bookmark->isPrivate()); | ||
135 | |||
136 | $bookmark = $this->bookmarkService->get(1); | ||
137 | $this->assertStringStartsWith( | ||
138 | 'Adding a shaare without entering a URL creates a text-only "note" post such as this one.', | ||
139 | $bookmark->getDescription() | ||
140 | ); | ||
141 | $this->assertTrue($bookmark->isPrivate()); | ||
142 | |||
143 | $bookmark = $this->bookmarkService->get(2); | ||
144 | $this->assertStringStartsWith( | ||
145 | 'Welcome to Shaarli!', | ||
146 | $bookmark->getDescription() | ||
147 | ); | ||
148 | $this->assertFalse($bookmark->isPrivate()); | ||
149 | } | ||
150 | } | ||
diff --git a/tests/bookmark/BookmarkTest.php b/tests/bookmark/BookmarkTest.php new file mode 100644 index 00000000..afec2440 --- /dev/null +++ b/tests/bookmark/BookmarkTest.php | |||
@@ -0,0 +1,388 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Bookmark; | ||
4 | |||
5 | use Shaarli\Bookmark\Exception\InvalidBookmarkException; | ||
6 | use Shaarli\TestCase; | ||
7 | |||
8 | /** | ||
9 | * Class BookmarkTest | ||
10 | */ | ||
11 | class BookmarkTest extends TestCase | ||
12 | { | ||
13 | /** | ||
14 | * Test fromArray() with a link with full data | ||
15 | */ | ||
16 | public function testFromArrayFull() | ||
17 | { | ||
18 | $data = [ | ||
19 | 'id' => 1, | ||
20 | 'shorturl' => 'abc', | ||
21 | 'url' => 'https://domain.tld/oof.html?param=value#anchor', | ||
22 | 'title' => 'This is an array link', | ||
23 | 'description' => 'HTML desc<br><p>hi!</p>', | ||
24 | 'thumbnail' => 'https://domain.tld/pic.png', | ||
25 | 'sticky' => true, | ||
26 | 'created' => new \DateTime('-1 minute'), | ||
27 | 'tags' => ['tag1', 'tag2', 'chair'], | ||
28 | 'updated' => new \DateTime(), | ||
29 | 'private' => true, | ||
30 | ]; | ||
31 | |||
32 | $bookmark = (new Bookmark())->fromArray($data); | ||
33 | $this->assertEquals($data['id'], $bookmark->getId()); | ||
34 | $this->assertEquals($data['shorturl'], $bookmark->getShortUrl()); | ||
35 | $this->assertEquals($data['url'], $bookmark->getUrl()); | ||
36 | $this->assertEquals($data['title'], $bookmark->getTitle()); | ||
37 | $this->assertEquals($data['description'], $bookmark->getDescription()); | ||
38 | $this->assertEquals($data['thumbnail'], $bookmark->getThumbnail()); | ||
39 | $this->assertEquals($data['sticky'], $bookmark->isSticky()); | ||
40 | $this->assertEquals($data['created'], $bookmark->getCreated()); | ||
41 | $this->assertEquals($data['tags'], $bookmark->getTags()); | ||
42 | $this->assertEquals('tag1 tag2 chair', $bookmark->getTagsString()); | ||
43 | $this->assertEquals($data['updated'], $bookmark->getUpdated()); | ||
44 | $this->assertEquals($data['private'], $bookmark->isPrivate()); | ||
45 | $this->assertFalse($bookmark->isNote()); | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * Test fromArray() with a link with minimal data. | ||
50 | * Note that I use null values everywhere but this should not happen in the real world. | ||
51 | */ | ||
52 | public function testFromArrayMinimal() | ||
53 | { | ||
54 | $data = [ | ||
55 | 'id' => null, | ||
56 | 'shorturl' => null, | ||
57 | 'url' => null, | ||
58 | 'title' => null, | ||
59 | 'description' => null, | ||
60 | 'created' => null, | ||
61 | 'tags' => null, | ||
62 | 'private' => null, | ||
63 | ]; | ||
64 | |||
65 | $bookmark = (new Bookmark())->fromArray($data); | ||
66 | $this->assertNull($bookmark->getId()); | ||
67 | $this->assertNull($bookmark->getShortUrl()); | ||
68 | $this->assertNull($bookmark->getUrl()); | ||
69 | $this->assertNull($bookmark->getTitle()); | ||
70 | $this->assertEquals('', $bookmark->getDescription()); | ||
71 | $this->assertNull($bookmark->getCreated()); | ||
72 | $this->assertEquals([], $bookmark->getTags()); | ||
73 | $this->assertEquals('', $bookmark->getTagsString()); | ||
74 | $this->assertNull($bookmark->getUpdated()); | ||
75 | $this->assertFalse($bookmark->getThumbnail()); | ||
76 | $this->assertFalse($bookmark->isSticky()); | ||
77 | $this->assertFalse($bookmark->isPrivate()); | ||
78 | $this->assertTrue($bookmark->isNote()); | ||
79 | } | ||
80 | |||
81 | /** | ||
82 | * Test validate() with a valid minimal bookmark | ||
83 | */ | ||
84 | public function testValidateValidFullBookmark() | ||
85 | { | ||
86 | $bookmark = new Bookmark(); | ||
87 | $bookmark->setId(2); | ||
88 | $bookmark->setShortUrl('abc'); | ||
89 | $bookmark->setCreated($date = \DateTime::createFromFormat('Ymd_His', '20190514_200102')); | ||
90 | $bookmark->setUpdated($dateUp = \DateTime::createFromFormat('Ymd_His', '20190514_210203')); | ||
91 | $bookmark->setUrl($url = 'https://domain.tld/oof.html?param=value#anchor'); | ||
92 | $bookmark->setTitle($title = 'This is an array link'); | ||
93 | $bookmark->setDescription($desc = 'HTML desc<br><p>hi!</p>'); | ||
94 | $bookmark->setTags($tags = ['tag1', 'tag2', 'chair']); | ||
95 | $bookmark->setThumbnail($thumb = 'https://domain.tld/pic.png'); | ||
96 | $bookmark->setPrivate(true); | ||
97 | $bookmark->validate(); | ||
98 | |||
99 | $this->assertEquals(2, $bookmark->getId()); | ||
100 | $this->assertEquals('abc', $bookmark->getShortUrl()); | ||
101 | $this->assertEquals($date, $bookmark->getCreated()); | ||
102 | $this->assertEquals($dateUp, $bookmark->getUpdated()); | ||
103 | $this->assertEquals($url, $bookmark->getUrl()); | ||
104 | $this->assertEquals($title, $bookmark->getTitle()); | ||
105 | $this->assertEquals($desc, $bookmark->getDescription()); | ||
106 | $this->assertEquals($tags, $bookmark->getTags()); | ||
107 | $this->assertEquals(implode(' ', $tags), $bookmark->getTagsString()); | ||
108 | $this->assertEquals($thumb, $bookmark->getThumbnail()); | ||
109 | $this->assertTrue($bookmark->isPrivate()); | ||
110 | $this->assertFalse($bookmark->isNote()); | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Test validate() with a valid minimal bookmark | ||
115 | */ | ||
116 | public function testValidateValidMinimalBookmark() | ||
117 | { | ||
118 | $bookmark = new Bookmark(); | ||
119 | $bookmark->setId(1); | ||
120 | $bookmark->setShortUrl('abc'); | ||
121 | $bookmark->setCreated($date = \DateTime::createFromFormat('Ymd_His', '20190514_200102')); | ||
122 | $bookmark->validate(); | ||
123 | |||
124 | $this->assertEquals(1, $bookmark->getId()); | ||
125 | $this->assertEquals('abc', $bookmark->getShortUrl()); | ||
126 | $this->assertEquals($date, $bookmark->getCreated()); | ||
127 | $this->assertEquals('/shaare/abc', $bookmark->getUrl()); | ||
128 | $this->assertEquals('/shaare/abc', $bookmark->getTitle()); | ||
129 | $this->assertEquals('', $bookmark->getDescription()); | ||
130 | $this->assertEquals([], $bookmark->getTags()); | ||
131 | $this->assertEquals('', $bookmark->getTagsString()); | ||
132 | $this->assertFalse($bookmark->getThumbnail()); | ||
133 | $this->assertFalse($bookmark->isPrivate()); | ||
134 | $this->assertTrue($bookmark->isNote()); | ||
135 | $this->assertNull($bookmark->getUpdated()); | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Test validate() with a a bookmark without ID. | ||
140 | */ | ||
141 | public function testValidateNotValidNoId() | ||
142 | { | ||
143 | $bookmark = new Bookmark(); | ||
144 | $bookmark->setShortUrl('abc'); | ||
145 | $bookmark->setCreated(\DateTime::createFromFormat('Ymd_His', '20190514_200102')); | ||
146 | $exception = null; | ||
147 | try { | ||
148 | $bookmark->validate(); | ||
149 | } catch (InvalidBookmarkException $e) { | ||
150 | $exception = $e; | ||
151 | } | ||
152 | $this->assertNotNull($exception); | ||
153 | $this->assertContainsPolyfill('- ID: '. PHP_EOL, $exception->getMessage()); | ||
154 | } | ||
155 | |||
156 | /** | ||
157 | * Test validate() with a a bookmark with a non integer ID. | ||
158 | */ | ||
159 | public function testValidateNotValidStringId() | ||
160 | { | ||
161 | $bookmark = new Bookmark(); | ||
162 | $bookmark->setId('str'); | ||
163 | $bookmark->setShortUrl('abc'); | ||
164 | $bookmark->setCreated(\DateTime::createFromFormat('Ymd_His', '20190514_200102')); | ||
165 | $exception = null; | ||
166 | try { | ||
167 | $bookmark->validate(); | ||
168 | } catch (InvalidBookmarkException $e) { | ||
169 | $exception = $e; | ||
170 | } | ||
171 | $this->assertNotNull($exception); | ||
172 | $this->assertContainsPolyfill('- ID: str'. PHP_EOL, $exception->getMessage()); | ||
173 | } | ||
174 | |||
175 | /** | ||
176 | * Test validate() with a a bookmark without short url. | ||
177 | */ | ||
178 | public function testValidateNotValidNoShortUrl() | ||
179 | { | ||
180 | $bookmark = new Bookmark(); | ||
181 | $bookmark->setId(1); | ||
182 | $bookmark->setCreated(\DateTime::createFromFormat('Ymd_His', '20190514_200102')); | ||
183 | $bookmark->setShortUrl(null); | ||
184 | $exception = null; | ||
185 | try { | ||
186 | $bookmark->validate(); | ||
187 | } catch (InvalidBookmarkException $e) { | ||
188 | $exception = $e; | ||
189 | } | ||
190 | $this->assertNotNull($exception); | ||
191 | $this->assertContainsPolyfill('- ShortUrl: '. PHP_EOL, $exception->getMessage()); | ||
192 | } | ||
193 | |||
194 | /** | ||
195 | * Test validate() with a a bookmark without created datetime. | ||
196 | */ | ||
197 | public function testValidateNotValidNoCreated() | ||
198 | { | ||
199 | $bookmark = new Bookmark(); | ||
200 | $bookmark->setId(1); | ||
201 | $bookmark->setShortUrl('abc'); | ||
202 | $bookmark->setCreated(null); | ||
203 | $exception = null; | ||
204 | try { | ||
205 | $bookmark->validate(); | ||
206 | } catch (InvalidBookmarkException $e) { | ||
207 | $exception = $e; | ||
208 | } | ||
209 | $this->assertNotNull($exception); | ||
210 | $this->assertContainsPolyfill('- Created: '. PHP_EOL, $exception->getMessage()); | ||
211 | } | ||
212 | |||
213 | /** | ||
214 | * Test validate() with a a bookmark with a bad created datetime. | ||
215 | */ | ||
216 | public function testValidateNotValidBadCreated() | ||
217 | { | ||
218 | $bookmark = new Bookmark(); | ||
219 | $bookmark->setId(1); | ||
220 | $bookmark->setShortUrl('abc'); | ||
221 | $bookmark->setCreated('hi!'); | ||
222 | $exception = null; | ||
223 | try { | ||
224 | $bookmark->validate(); | ||
225 | } catch (InvalidBookmarkException $e) { | ||
226 | $exception = $e; | ||
227 | } | ||
228 | $this->assertNotNull($exception); | ||
229 | $this->assertContainsPolyfill('- Created: Not a DateTime object'. PHP_EOL, $exception->getMessage()); | ||
230 | } | ||
231 | |||
232 | /** | ||
233 | * Test setId() and make sure that default fields are generated. | ||
234 | */ | ||
235 | public function testSetIdEmptyGeneratedFields() | ||
236 | { | ||
237 | $bookmark = new Bookmark(); | ||
238 | $bookmark->setId(2); | ||
239 | |||
240 | $this->assertEquals(2, $bookmark->getId()); | ||
241 | $this->assertRegExp('/[\w\-]{6}/', $bookmark->getShortUrl()); | ||
242 | $this->assertTrue(new \DateTime('5 seconds ago') < $bookmark->getCreated()); | ||
243 | } | ||
244 | |||
245 | /** | ||
246 | * Test setId() and with generated fields already set. | ||
247 | */ | ||
248 | public function testSetIdSetGeneratedFields() | ||
249 | { | ||
250 | $bookmark = new Bookmark(); | ||
251 | $bookmark->setShortUrl('abc'); | ||
252 | $bookmark->setCreated($date = \DateTime::createFromFormat('Ymd_His', '20190514_200102')); | ||
253 | $bookmark->setId(2); | ||
254 | |||
255 | $this->assertEquals(2, $bookmark->getId()); | ||
256 | $this->assertEquals('abc', $bookmark->getShortUrl()); | ||
257 | $this->assertEquals($date, $bookmark->getCreated()); | ||
258 | } | ||
259 | |||
260 | /** | ||
261 | * Test setUrl() and make sure it accepts custom protocols | ||
262 | */ | ||
263 | public function testGetUrlWithValidProtocols() | ||
264 | { | ||
265 | $bookmark = new Bookmark(); | ||
266 | $bookmark->setUrl($url = 'myprotocol://helloworld', ['myprotocol']); | ||
267 | $this->assertEquals($url, $bookmark->getUrl()); | ||
268 | |||
269 | $bookmark->setUrl($url = 'https://helloworld.tld', ['myprotocol']); | ||
270 | $this->assertEquals($url, $bookmark->getUrl()); | ||
271 | } | ||
272 | |||
273 | /** | ||
274 | * Test setUrl() and make sure it accepts custom protocols | ||
275 | */ | ||
276 | public function testGetUrlWithNotValidProtocols() | ||
277 | { | ||
278 | $bookmark = new Bookmark(); | ||
279 | $bookmark->setUrl('myprotocol://helloworld', []); | ||
280 | $this->assertEquals('http://helloworld', $bookmark->getUrl()); | ||
281 | |||
282 | $bookmark->setUrl($url = 'https://helloworld.tld', []); | ||
283 | $this->assertEquals($url, $bookmark->getUrl()); | ||
284 | } | ||
285 | |||
286 | /** | ||
287 | * Test setTagsString() with exotic data | ||
288 | */ | ||
289 | public function testSetTagsString() | ||
290 | { | ||
291 | $bookmark = new Bookmark(); | ||
292 | |||
293 | $str = 'tag1 tag2 tag3.tag3-2, tag4 , -tag5 '; | ||
294 | $bookmark->setTagsString($str); | ||
295 | $this->assertEquals( | ||
296 | [ | ||
297 | 'tag1', | ||
298 | 'tag2', | ||
299 | 'tag3.tag3-2', | ||
300 | 'tag4', | ||
301 | 'tag5', | ||
302 | ], | ||
303 | $bookmark->getTags() | ||
304 | ); | ||
305 | } | ||
306 | |||
307 | /** | ||
308 | * Test setTags() with exotic data | ||
309 | */ | ||
310 | public function testSetTags() | ||
311 | { | ||
312 | $bookmark = new Bookmark(); | ||
313 | |||
314 | $array = [ | ||
315 | 'tag1 ', | ||
316 | ' tag2', | ||
317 | 'tag3.tag3-2,', | ||
318 | ', tag4', | ||
319 | ', ', | ||
320 | '-tag5 ', | ||
321 | ]; | ||
322 | $bookmark->setTags($array); | ||
323 | $this->assertEquals( | ||
324 | [ | ||
325 | 'tag1', | ||
326 | 'tag2', | ||
327 | 'tag3.tag3-2', | ||
328 | 'tag4', | ||
329 | 'tag5', | ||
330 | ], | ||
331 | $bookmark->getTags() | ||
332 | ); | ||
333 | } | ||
334 | |||
335 | /** | ||
336 | * Test renameTag() | ||
337 | */ | ||
338 | public function testRenameTag() | ||
339 | { | ||
340 | $bookmark = new Bookmark(); | ||
341 | $bookmark->setTags(['tag1', 'tag2', 'chair']); | ||
342 | $bookmark->renameTag('chair', 'table'); | ||
343 | $this->assertEquals(['tag1', 'tag2', 'table'], $bookmark->getTags()); | ||
344 | $bookmark->renameTag('tag1', 'tag42'); | ||
345 | $this->assertEquals(['tag42', 'tag2', 'table'], $bookmark->getTags()); | ||
346 | $bookmark->renameTag('tag42', 'tag43'); | ||
347 | $this->assertEquals(['tag43', 'tag2', 'table'], $bookmark->getTags()); | ||
348 | $bookmark->renameTag('table', 'desk'); | ||
349 | $this->assertEquals(['tag43', 'tag2', 'desk'], $bookmark->getTags()); | ||
350 | } | ||
351 | |||
352 | /** | ||
353 | * Test renameTag() with a tag that is not present in the bookmark | ||
354 | */ | ||
355 | public function testRenameTagNotExists() | ||
356 | { | ||
357 | $bookmark = new Bookmark(); | ||
358 | $bookmark->setTags(['tag1', 'tag2', 'chair']); | ||
359 | $bookmark->renameTag('nope', 'table'); | ||
360 | $this->assertEquals(['tag1', 'tag2', 'chair'], $bookmark->getTags()); | ||
361 | } | ||
362 | |||
363 | /** | ||
364 | * Test deleteTag() | ||
365 | */ | ||
366 | public function testDeleteTag() | ||
367 | { | ||
368 | $bookmark = new Bookmark(); | ||
369 | $bookmark->setTags(['tag1', 'tag2', 'chair']); | ||
370 | $bookmark->deleteTag('chair'); | ||
371 | $this->assertEquals(['tag1', 'tag2'], $bookmark->getTags()); | ||
372 | $bookmark->deleteTag('tag1'); | ||
373 | $this->assertEquals(['tag2'], $bookmark->getTags()); | ||
374 | $bookmark->deleteTag('tag2'); | ||
375 | $this->assertEquals([], $bookmark->getTags()); | ||
376 | } | ||
377 | |||
378 | /** | ||
379 | * Test deleteTag() with a tag that is not present in the bookmark | ||
380 | */ | ||
381 | public function testDeleteTagNotExists() | ||
382 | { | ||
383 | $bookmark = new Bookmark(); | ||
384 | $bookmark->setTags(['tag1', 'tag2', 'chair']); | ||
385 | $bookmark->deleteTag('nope'); | ||
386 | $this->assertEquals(['tag1', 'tag2', 'chair'], $bookmark->getTags()); | ||
387 | } | ||
388 | } | ||
diff --git a/tests/bookmark/LinkUtilsTest.php b/tests/bookmark/LinkUtilsTest.php index 78cb8f2a..ef00b92f 100644 --- a/tests/bookmark/LinkUtilsTest.php +++ b/tests/bookmark/LinkUtilsTest.php | |||
@@ -2,9 +2,7 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Bookmark; | 3 | namespace Shaarli\Bookmark; |
4 | 4 | ||
5 | use PHPUnit\Framework\TestCase; | 5 | use Shaarli\TestCase; |
6 | use ReferenceLinkDB; | ||
7 | use Shaarli\Config\ConfigManager; | ||
8 | 6 | ||
9 | require_once 'tests/utils/CurlUtils.php'; | 7 | require_once 'tests/utils/CurlUtils.php'; |
10 | 8 | ||
@@ -45,6 +43,19 @@ class LinkUtilsTest extends TestCase | |||
45 | } | 43 | } |
46 | 44 | ||
47 | /** | 45 | /** |
46 | * Test headers_extract_charset() when the charset is found with odd quotes. | ||
47 | */ | ||
48 | public function testHeadersExtractExistentCharsetWithQuotes() | ||
49 | { | ||
50 | $charset = 'x-MacCroatian'; | ||
51 | $headers = 'text/html; charset="' . $charset . '"otherstuff="test"'; | ||
52 | $this->assertEquals(strtolower($charset), header_extract_charset($headers)); | ||
53 | |||
54 | $headers = 'text/html; charset=\'' . $charset . '\'otherstuff="test"'; | ||
55 | $this->assertEquals(strtolower($charset), header_extract_charset($headers)); | ||
56 | } | ||
57 | |||
58 | /** | ||
48 | * Test headers_extract_charset() when the charset is not found. | 59 | * Test headers_extract_charset() when the charset is not found. |
49 | */ | 60 | */ |
50 | public function testHeadersExtractNonExistentCharset() | 61 | public function testHeadersExtractNonExistentCharset() |
@@ -389,15 +400,6 @@ class LinkUtilsTest extends TestCase | |||
389 | } | 400 | } |
390 | 401 | ||
391 | /** | 402 | /** |
392 | * Test count_private. | ||
393 | */ | ||
394 | public function testCountPrivateLinks() | ||
395 | { | ||
396 | $refDB = new ReferenceLinkDB(); | ||
397 | $this->assertEquals($refDB->countPrivateLinks(), count_private($refDB->getLinks())); | ||
398 | } | ||
399 | |||
400 | /** | ||
401 | * Test text2clickable. | 403 | * Test text2clickable. |
402 | */ | 404 | */ |
403 | public function testText2clickable() | 405 | public function testText2clickable() |
@@ -448,13 +450,13 @@ class LinkUtilsTest extends TestCase | |||
448 | カタカナ #カタカナã€ã‚«ã‚¿ã‚«ãƒŠ\n'; | 450 | カタカナ #カタカナã€ã‚«ã‚¿ã‚«ãƒŠ\n'; |
449 | $autolinkedDescription = hashtag_autolink($rawDescription, $index); | 451 | $autolinkedDescription = hashtag_autolink($rawDescription, $index); |
450 | 452 | ||
451 | $this->assertContains($this->getHashtagLink('hashtag', $index), $autolinkedDescription); | 453 | $this->assertContainsPolyfill($this->getHashtagLink('hashtag', $index), $autolinkedDescription); |
452 | $this->assertNotContains(' #hashtag', $autolinkedDescription); | 454 | $this->assertNotContainsPolyfill(' #hashtag', $autolinkedDescription); |
453 | $this->assertNotContains('>#nothashtag', $autolinkedDescription); | 455 | $this->assertNotContainsPolyfill('>#nothashtag', $autolinkedDescription); |
454 | $this->assertContains($this->getHashtagLink('ашок', $index), $autolinkedDescription); | 456 | $this->assertContainsPolyfill($this->getHashtagLink('ашок', $index), $autolinkedDescription); |
455 | $this->assertContains($this->getHashtagLink('カタカナ', $index), $autolinkedDescription); | 457 | $this->assertContainsPolyfill($this->getHashtagLink('カタカナ', $index), $autolinkedDescription); |
456 | $this->assertContains($this->getHashtagLink('hashtag_hashtag', $index), $autolinkedDescription); | 458 | $this->assertContainsPolyfill($this->getHashtagLink('hashtag_hashtag', $index), $autolinkedDescription); |
457 | $this->assertNotContains($this->getHashtagLink('hashtag-nothashtag', $index), $autolinkedDescription); | 459 | $this->assertNotContainsPolyfill($this->getHashtagLink('hashtag-nothashtag', $index), $autolinkedDescription); |
458 | } | 460 | } |
459 | 461 | ||
460 | /** | 462 | /** |
@@ -465,9 +467,9 @@ class LinkUtilsTest extends TestCase | |||
465 | $rawDescription = 'blabla #hashtag x#nothashtag'; | 467 | $rawDescription = 'blabla #hashtag x#nothashtag'; |
466 | $autolinkedDescription = hashtag_autolink($rawDescription); | 468 | $autolinkedDescription = hashtag_autolink($rawDescription); |
467 | 469 | ||
468 | $this->assertContains($this->getHashtagLink('hashtag'), $autolinkedDescription); | 470 | $this->assertContainsPolyfill($this->getHashtagLink('hashtag'), $autolinkedDescription); |
469 | $this->assertNotContains(' #hashtag', $autolinkedDescription); | 471 | $this->assertNotContainsPolyfill(' #hashtag', $autolinkedDescription); |
470 | $this->assertNotContains('>#nothashtag', $autolinkedDescription); | 472 | $this->assertNotContainsPolyfill('>#nothashtag', $autolinkedDescription); |
471 | } | 473 | } |
472 | 474 | ||
473 | /** | 475 | /** |
@@ -500,7 +502,7 @@ class LinkUtilsTest extends TestCase | |||
500 | */ | 502 | */ |
501 | private function getHashtagLink($hashtag, $index = '') | 503 | private function getHashtagLink($hashtag, $index = '') |
502 | { | 504 | { |
503 | $hashtagLink = '<a href="' . $index . '?addtag=$1" title="Hashtag $1">#$1</a>'; | 505 | $hashtagLink = '<a href="' . $index . './add-tag/$1" title="Hashtag $1">#$1</a>'; |
504 | return str_replace('$1', $hashtag, $hashtagLink); | 506 | return str_replace('$1', $hashtag, $hashtagLink); |
505 | } | 507 | } |
506 | } | 508 | } |
diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d36d73cd..2d675c9a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php | |||
@@ -4,3 +4,29 @@ require_once 'vendor/autoload.php'; | |||
4 | 4 | ||
5 | $conf = new \Shaarli\Config\ConfigManager('tests/utils/config/configJson'); | 5 | $conf = new \Shaarli\Config\ConfigManager('tests/utils/config/configJson'); |
6 | new \Shaarli\Languages('en', $conf); | 6 | new \Shaarli\Languages('en', $conf); |
7 | |||
8 | // is_iterable is only compatible with PHP 7.1+ | ||
9 | if (!function_exists('is_iterable')) { | ||
10 | function is_iterable($var) | ||
11 | { | ||
12 | return is_array($var) || $var instanceof \Traversable; | ||
13 | } | ||
14 | } | ||
15 | |||
16 | // TODO: remove this after fixing UT | ||
17 | require_once 'application/bookmark/LinkUtils.php'; | ||
18 | require_once 'application/Utils.php'; | ||
19 | require_once 'application/http/UrlUtils.php'; | ||
20 | require_once 'application/http/HttpUtils.php'; | ||
21 | require_once 'tests/TestCase.php'; | ||
22 | require_once 'tests/container/ShaarliTestContainer.php'; | ||
23 | require_once 'tests/front/controller/visitor/FrontControllerMockHelper.php'; | ||
24 | require_once 'tests/front/controller/admin/FrontAdminControllerMockHelper.php'; | ||
25 | require_once 'tests/updater/DummyUpdater.php'; | ||
26 | require_once 'tests/utils/FakeBookmarkService.php'; | ||
27 | require_once 'tests/utils/FakeConfigManager.php'; | ||
28 | require_once 'tests/utils/ReferenceHistory.php'; | ||
29 | require_once 'tests/utils/ReferenceLinkDB.php'; | ||
30 | require_once 'tests/utils/ReferenceSessionIdHashes.php'; | ||
31 | |||
32 | \ReferenceSessionIdHashes::genAllHashes(); | ||
diff --git a/tests/config/ConfigJsonTest.php b/tests/config/ConfigJsonTest.php index 95ad060b..c0ba5b8f 100644 --- a/tests/config/ConfigJsonTest.php +++ b/tests/config/ConfigJsonTest.php | |||
@@ -4,14 +4,14 @@ namespace Shaarli\Config; | |||
4 | /** | 4 | /** |
5 | * Class ConfigJsonTest | 5 | * Class ConfigJsonTest |
6 | */ | 6 | */ |
7 | class ConfigJsonTest extends \PHPUnit\Framework\TestCase | 7 | class ConfigJsonTest extends \Shaarli\TestCase |
8 | { | 8 | { |
9 | /** | 9 | /** |
10 | * @var ConfigJson | 10 | * @var ConfigJson |
11 | */ | 11 | */ |
12 | protected $configIO; | 12 | protected $configIO; |
13 | 13 | ||
14 | public function setUp() | 14 | protected function setUp(): void |
15 | { | 15 | { |
16 | $this->configIO = new ConfigJson(); | 16 | $this->configIO = new ConfigJson(); |
17 | } | 17 | } |
@@ -24,7 +24,7 @@ class ConfigJsonTest extends \PHPUnit\Framework\TestCase | |||
24 | $conf = $this->configIO->read('tests/utils/config/configJson.json.php'); | 24 | $conf = $this->configIO->read('tests/utils/config/configJson.json.php'); |
25 | $this->assertEquals('root', $conf['credentials']['login']); | 25 | $this->assertEquals('root', $conf['credentials']['login']); |
26 | $this->assertEquals('lala', $conf['redirector']['url']); | 26 | $this->assertEquals('lala', $conf['redirector']['url']); |
27 | $this->assertEquals('tests/utils/config/datastore.php', $conf['resource']['datastore']); | 27 | $this->assertEquals('sandbox/datastore.php', $conf['resource']['datastore']); |
28 | $this->assertEquals('1', $conf['plugins']['WALLABAG_VERSION']); | 28 | $this->assertEquals('1', $conf['plugins']['WALLABAG_VERSION']); |
29 | } | 29 | } |
30 | 30 | ||
@@ -38,12 +38,12 @@ class ConfigJsonTest extends \PHPUnit\Framework\TestCase | |||
38 | 38 | ||
39 | /** | 39 | /** |
40 | * Read a non existent config file -> empty array. | 40 | * Read a non existent config file -> empty array. |
41 | * | ||
42 | * @expectedException \Exception | ||
43 | * @expectedExceptionMessageRegExp /An error occurred while parsing JSON configuration file \([\w\/\.]+\): error code #4/ | ||
44 | */ | 41 | */ |
45 | public function testReadInvalidJson() | 42 | public function testReadInvalidJson() |
46 | { | 43 | { |
44 | $this->expectException(\Exception::class); | ||
45 | $this->expectExceptionMessageRegExp('/An error occurred while parsing JSON configuration file \\([\\w\\/\\.]+\\): error code #4/'); | ||
46 | |||
47 | $this->configIO->read('tests/utils/config/configInvalid.json.php'); | 47 | $this->configIO->read('tests/utils/config/configInvalid.json.php'); |
48 | } | 48 | } |
49 | 49 | ||
@@ -110,22 +110,11 @@ class ConfigJsonTest extends \PHPUnit\Framework\TestCase | |||
110 | 110 | ||
111 | /** | 111 | /** |
112 | * Write to invalid path. | 112 | * Write to invalid path. |
113 | * | ||
114 | * @expectedException \Shaarli\Exceptions\IOException | ||
115 | */ | ||
116 | public function testWriteInvalidArray() | ||
117 | { | ||
118 | $conf = array('conf' => 'value'); | ||
119 | @$this->configIO->write(array(), $conf); | ||
120 | } | ||
121 | |||
122 | /** | ||
123 | * Write to invalid path. | ||
124 | * | ||
125 | * @expectedException \Shaarli\Exceptions\IOException | ||
126 | */ | 113 | */ |
127 | public function testWriteInvalidBlank() | 114 | public function testWriteInvalidBlank() |
128 | { | 115 | { |
116 | $this->expectException(\Shaarli\Exceptions\IOException::class); | ||
117 | |||
129 | $conf = array('conf' => 'value'); | 118 | $conf = array('conf' => 'value'); |
130 | @$this->configIO->write('', $conf); | 119 | @$this->configIO->write('', $conf); |
131 | } | 120 | } |
diff --git a/tests/config/ConfigManagerTest.php b/tests/config/ConfigManagerTest.php index 33830bc9..65d8ba2c 100644 --- a/tests/config/ConfigManagerTest.php +++ b/tests/config/ConfigManagerTest.php | |||
@@ -7,14 +7,14 @@ namespace Shaarli\Config; | |||
7 | * Note: it only test the manager with ConfigJson, | 7 | * Note: it only test the manager with ConfigJson, |
8 | * ConfigPhp is only a workaround to handle the transition to JSON type. | 8 | * ConfigPhp is only a workaround to handle the transition to JSON type. |
9 | */ | 9 | */ |
10 | class ConfigManagerTest extends \PHPUnit\Framework\TestCase | 10 | class ConfigManagerTest extends \Shaarli\TestCase |
11 | { | 11 | { |
12 | /** | 12 | /** |
13 | * @var ConfigManager | 13 | * @var ConfigManager |
14 | */ | 14 | */ |
15 | protected $conf; | 15 | protected $conf; |
16 | 16 | ||
17 | public function setUp() | 17 | protected function setUp(): void |
18 | { | 18 | { |
19 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 19 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
20 | } | 20 | } |
@@ -95,44 +95,44 @@ class ConfigManagerTest extends \PHPUnit\Framework\TestCase | |||
95 | 95 | ||
96 | /** | 96 | /** |
97 | * Set with an empty key. | 97 | * Set with an empty key. |
98 | * | ||
99 | * @expectedException \Exception | ||
100 | * @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*# | ||
101 | */ | 98 | */ |
102 | public function testSetEmptyKey() | 99 | public function testSetEmptyKey() |
103 | { | 100 | { |
101 | $this->expectException(\Exception::class); | ||
102 | $this->expectExceptionMessageRegExp('#^Invalid setting key parameter. String expected, got.*#'); | ||
103 | |||
104 | $this->conf->set('', 'stuff'); | 104 | $this->conf->set('', 'stuff'); |
105 | } | 105 | } |
106 | 106 | ||
107 | /** | 107 | /** |
108 | * Set with an array key. | 108 | * Set with an array key. |
109 | * | ||
110 | * @expectedException \Exception | ||
111 | * @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*# | ||
112 | */ | 109 | */ |
113 | public function testSetArrayKey() | 110 | public function testSetArrayKey() |
114 | { | 111 | { |
112 | $this->expectException(\Exception::class); | ||
113 | $this->expectExceptionMessageRegExp('#^Invalid setting key parameter. String expected, got.*#'); | ||
114 | |||
115 | $this->conf->set(array('foo' => 'bar'), 'stuff'); | 115 | $this->conf->set(array('foo' => 'bar'), 'stuff'); |
116 | } | 116 | } |
117 | 117 | ||
118 | /** | 118 | /** |
119 | * Remove with an empty key. | 119 | * Remove with an empty key. |
120 | * | ||
121 | * @expectedException \Exception | ||
122 | * @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*# | ||
123 | */ | 120 | */ |
124 | public function testRmoveEmptyKey() | 121 | public function testRmoveEmptyKey() |
125 | { | 122 | { |
123 | $this->expectException(\Exception::class); | ||
124 | $this->expectExceptionMessageRegExp('#^Invalid setting key parameter. String expected, got.*#'); | ||
125 | |||
126 | $this->conf->remove(''); | 126 | $this->conf->remove(''); |
127 | } | 127 | } |
128 | 128 | ||
129 | /** | 129 | /** |
130 | * Try to write the config without mandatory parameter (e.g. 'login'). | 130 | * Try to write the config without mandatory parameter (e.g. 'login'). |
131 | * | ||
132 | * @expectedException Shaarli\Config\Exception\MissingFieldConfigException | ||
133 | */ | 131 | */ |
134 | public function testWriteMissingParameter() | 132 | public function testWriteMissingParameter() |
135 | { | 133 | { |
134 | $this->expectException(\Shaarli\Config\Exception\MissingFieldConfigException::class); | ||
135 | |||
136 | $this->conf->setConfigFile('tests/utils/config/configTmp'); | 136 | $this->conf->setConfigFile('tests/utils/config/configTmp'); |
137 | $this->assertFalse(file_exists($this->conf->getConfigFileExt())); | 137 | $this->assertFalse(file_exists($this->conf->getConfigFileExt())); |
138 | $this->conf->reload(); | 138 | $this->conf->reload(); |
diff --git a/tests/config/ConfigPhpTest.php b/tests/config/ConfigPhpTest.php index 67d878ce..7bf9fe64 100644 --- a/tests/config/ConfigPhpTest.php +++ b/tests/config/ConfigPhpTest.php | |||
@@ -3,15 +3,19 @@ namespace Shaarli\Config; | |||
3 | 3 | ||
4 | /** | 4 | /** |
5 | * Class ConfigPhpTest | 5 | * Class ConfigPhpTest |
6 | * | ||
7 | * We run tests in separate processes due to the usage for $GLOBALS | ||
8 | * which are kept between tests. | ||
9 | * @runTestsInSeparateProcesses | ||
6 | */ | 10 | */ |
7 | class ConfigPhpTest extends \PHPUnit\Framework\TestCase | 11 | class ConfigPhpTest extends \Shaarli\TestCase |
8 | { | 12 | { |
9 | /** | 13 | /** |
10 | * @var ConfigPhp | 14 | * @var ConfigPhp |
11 | */ | 15 | */ |
12 | protected $configIO; | 16 | protected $configIO; |
13 | 17 | ||
14 | public function setUp() | 18 | protected function setUp(): void |
15 | { | 19 | { |
16 | $this->configIO = new ConfigPhp(); | 20 | $this->configIO = new ConfigPhp(); |
17 | } | 21 | } |
diff --git a/tests/config/ConfigPluginTest.php b/tests/config/ConfigPluginTest.php index d7a70e68..fa72d8c4 100644 --- a/tests/config/ConfigPluginTest.php +++ b/tests/config/ConfigPluginTest.php | |||
@@ -2,13 +2,14 @@ | |||
2 | namespace Shaarli\Config; | 2 | namespace Shaarli\Config; |
3 | 3 | ||
4 | use Shaarli\Config\Exception\PluginConfigOrderException; | 4 | use Shaarli\Config\Exception\PluginConfigOrderException; |
5 | use Shaarli\Plugin\PluginManager; | ||
5 | 6 | ||
6 | require_once 'application/config/ConfigPlugin.php'; | 7 | require_once 'application/config/ConfigPlugin.php'; |
7 | 8 | ||
8 | /** | 9 | /** |
9 | * Unitary tests for Shaarli config related functions | 10 | * Unitary tests for Shaarli config related functions |
10 | */ | 11 | */ |
11 | class ConfigPluginTest extends \PHPUnit\Framework\TestCase | 12 | class ConfigPluginTest extends \Shaarli\TestCase |
12 | { | 13 | { |
13 | /** | 14 | /** |
14 | * Test save_plugin_config with valid data. | 15 | * Test save_plugin_config with valid data. |
@@ -17,32 +18,39 @@ class ConfigPluginTest extends \PHPUnit\Framework\TestCase | |||
17 | */ | 18 | */ |
18 | public function testSavePluginConfigValid() | 19 | public function testSavePluginConfigValid() |
19 | { | 20 | { |
20 | $data = array( | 21 | $data = [ |
21 | 'order_plugin1' => 2, // no plugin related | 22 | 'order_plugin1' => 2, // no plugin related |
22 | 'plugin2' => 0, // new - at the end | 23 | 'plugin2' => 0, // new - at the end |
23 | 'plugin3' => 0, // 2nd | 24 | 'plugin3' => 0, // 2nd |
24 | 'order_plugin3' => 8, | 25 | 'order_plugin3' => 8, |
25 | 'plugin4' => 0, // 1st | 26 | 'plugin4' => 0, // 1st |
26 | 'order_plugin4' => 5, | 27 | 'order_plugin4' => 5, |
27 | ); | 28 | ]; |
28 | 29 | ||
29 | $expected = array( | 30 | $expected = [ |
30 | 'plugin3', | 31 | 'plugin3', |
31 | 'plugin4', | 32 | 'plugin4', |
32 | 'plugin2', | 33 | 'plugin2', |
33 | ); | 34 | ]; |
35 | |||
36 | mkdir($path = __DIR__ . '/folder'); | ||
37 | PluginManager::$PLUGINS_PATH = $path; | ||
38 | array_map(function (string $plugin) use ($path) { touch($path . '/' . $plugin); }, $expected); | ||
34 | 39 | ||
35 | $out = save_plugin_config($data); | 40 | $out = save_plugin_config($data); |
36 | $this->assertEquals($expected, $out); | 41 | $this->assertEquals($expected, $out); |
42 | |||
43 | array_map(function (string $plugin) use ($path) { unlink($path . '/' . $plugin); }, $expected); | ||
44 | rmdir($path); | ||
37 | } | 45 | } |
38 | 46 | ||
39 | /** | 47 | /** |
40 | * Test save_plugin_config with invalid data. | 48 | * Test save_plugin_config with invalid data. |
41 | * | ||
42 | * @expectedException Shaarli\Config\Exception\PluginConfigOrderException | ||
43 | */ | 49 | */ |
44 | public function testSavePluginConfigInvalid() | 50 | public function testSavePluginConfigInvalid() |
45 | { | 51 | { |
52 | $this->expectException(\Shaarli\Config\Exception\PluginConfigOrderException::class); | ||
53 | |||
46 | $data = array( | 54 | $data = array( |
47 | 'plugin2' => 0, | 55 | 'plugin2' => 0, |
48 | 'plugin3' => 0, | 56 | 'plugin3' => 0, |
diff --git a/tests/container/ContainerBuilderTest.php b/tests/container/ContainerBuilderTest.php new file mode 100644 index 00000000..5d52daef --- /dev/null +++ b/tests/container/ContainerBuilderTest.php | |||
@@ -0,0 +1,88 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Container; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Feed\FeedBuilder; | ||
10 | use Shaarli\Formatter\FormatterFactory; | ||
11 | use Shaarli\Front\Controller\Visitor\ErrorController; | ||
12 | use Shaarli\Front\Controller\Visitor\ErrorNotFoundController; | ||
13 | use Shaarli\History; | ||
14 | use Shaarli\Http\HttpAccess; | ||
15 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
16 | use Shaarli\Plugin\PluginManager; | ||
17 | use Shaarli\Render\PageBuilder; | ||
18 | use Shaarli\Render\PageCacheManager; | ||
19 | use Shaarli\Security\CookieManager; | ||
20 | use Shaarli\Security\LoginManager; | ||
21 | use Shaarli\Security\SessionManager; | ||
22 | use Shaarli\TestCase; | ||
23 | use Shaarli\Thumbnailer; | ||
24 | use Shaarli\Updater\Updater; | ||
25 | use Slim\Http\Environment; | ||
26 | |||
27 | class ContainerBuilderTest extends TestCase | ||
28 | { | ||
29 | /** @var ConfigManager */ | ||
30 | protected $conf; | ||
31 | |||
32 | /** @var SessionManager */ | ||
33 | protected $sessionManager; | ||
34 | |||
35 | /** @var LoginManager */ | ||
36 | protected $loginManager; | ||
37 | |||
38 | /** @var ContainerBuilder */ | ||
39 | protected $containerBuilder; | ||
40 | |||
41 | /** @var CookieManager */ | ||
42 | protected $cookieManager; | ||
43 | |||
44 | public function setUp(): void | ||
45 | { | ||
46 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | ||
47 | $this->sessionManager = $this->createMock(SessionManager::class); | ||
48 | $this->cookieManager = $this->createMock(CookieManager::class); | ||
49 | |||
50 | $this->loginManager = $this->createMock(LoginManager::class); | ||
51 | $this->loginManager->method('isLoggedIn')->willReturn(true); | ||
52 | |||
53 | $this->containerBuilder = new ContainerBuilder( | ||
54 | $this->conf, | ||
55 | $this->sessionManager, | ||
56 | $this->cookieManager, | ||
57 | $this->loginManager | ||
58 | ); | ||
59 | } | ||
60 | |||
61 | public function testBuildContainer(): void | ||
62 | { | ||
63 | $container = $this->containerBuilder->build(); | ||
64 | |||
65 | static::assertInstanceOf(BookmarkServiceInterface::class, $container->bookmarkService); | ||
66 | static::assertInstanceOf(CookieManager::class, $container->cookieManager); | ||
67 | static::assertInstanceOf(ConfigManager::class, $container->conf); | ||
68 | static::assertInstanceOf(ErrorController::class, $container->errorHandler); | ||
69 | static::assertInstanceOf(Environment::class, $container->environment); | ||
70 | static::assertInstanceOf(FeedBuilder::class, $container->feedBuilder); | ||
71 | static::assertInstanceOf(FormatterFactory::class, $container->formatterFactory); | ||
72 | static::assertInstanceOf(History::class, $container->history); | ||
73 | static::assertInstanceOf(HttpAccess::class, $container->httpAccess); | ||
74 | static::assertInstanceOf(LoginManager::class, $container->loginManager); | ||
75 | static::assertInstanceOf(NetscapeBookmarkUtils::class, $container->netscapeBookmarkUtils); | ||
76 | static::assertInstanceOf(PageBuilder::class, $container->pageBuilder); | ||
77 | static::assertInstanceOf(PageCacheManager::class, $container->pageCacheManager); | ||
78 | static::assertInstanceOf(ErrorController::class, $container->phpErrorHandler); | ||
79 | static::assertInstanceOf(ErrorNotFoundController::class, $container->notFoundHandler); | ||
80 | static::assertInstanceOf(PluginManager::class, $container->pluginManager); | ||
81 | static::assertInstanceOf(SessionManager::class, $container->sessionManager); | ||
82 | static::assertInstanceOf(Thumbnailer::class, $container->thumbnailer); | ||
83 | static::assertInstanceOf(Updater::class, $container->updater); | ||
84 | |||
85 | // Set by the middleware | ||
86 | static::assertNull($container->basePath); | ||
87 | } | ||
88 | } | ||
diff --git a/tests/container/ShaarliTestContainer.php b/tests/container/ShaarliTestContainer.php new file mode 100644 index 00000000..7dbe914c --- /dev/null +++ b/tests/container/ShaarliTestContainer.php | |||
@@ -0,0 +1,42 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Container; | ||
6 | |||
7 | use PHPUnit\Framework\MockObject\MockObject; | ||
8 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\Feed\FeedBuilder; | ||
11 | use Shaarli\Formatter\FormatterFactory; | ||
12 | use Shaarli\History; | ||
13 | use Shaarli\Http\HttpAccess; | ||
14 | use Shaarli\Plugin\PluginManager; | ||
15 | use Shaarli\Render\PageBuilder; | ||
16 | use Shaarli\Render\PageCacheManager; | ||
17 | use Shaarli\Security\LoginManager; | ||
18 | use Shaarli\Security\SessionManager; | ||
19 | use Shaarli\Thumbnailer; | ||
20 | |||
21 | /** | ||
22 | * Test helper allowing auto-completion for MockObjects. | ||
23 | * | ||
24 | * @property mixed[] $environment $_SERVER automatically injected by Slim | ||
25 | * @property MockObject|ConfigManager $conf | ||
26 | * @property MockObject|SessionManager $sessionManager | ||
27 | * @property MockObject|LoginManager $loginManager | ||
28 | * @property MockObject|string $webPath | ||
29 | * @property MockObject|History $history | ||
30 | * @property MockObject|BookmarkServiceInterface $bookmarkService | ||
31 | * @property MockObject|PageBuilder $pageBuilder | ||
32 | * @property MockObject|PluginManager $pluginManager | ||
33 | * @property MockObject|FormatterFactory $formatterFactory | ||
34 | * @property MockObject|PageCacheManager $pageCacheManager | ||
35 | * @property MockObject|FeedBuilder $feedBuilder | ||
36 | * @property MockObject|Thumbnailer $thumbnailer | ||
37 | * @property MockObject|HttpAccess $httpAccess | ||
38 | */ | ||
39 | class ShaarliTestContainer extends ShaarliContainer | ||
40 | { | ||
41 | |||
42 | } | ||
diff --git a/tests/feed/CachedPageTest.php b/tests/feed/CachedPageTest.php index 0bcc1442..904db9dc 100644 --- a/tests/feed/CachedPageTest.php +++ b/tests/feed/CachedPageTest.php | |||
@@ -7,17 +7,17 @@ namespace Shaarli\Feed; | |||
7 | /** | 7 | /** |
8 | * Unitary tests for cached pages | 8 | * Unitary tests for cached pages |
9 | */ | 9 | */ |
10 | class CachedPageTest extends \PHPUnit\Framework\TestCase | 10 | class CachedPageTest extends \Shaarli\TestCase |
11 | { | 11 | { |
12 | // test cache directory | 12 | // test cache directory |
13 | protected static $testCacheDir = 'sandbox/pagecache'; | 13 | protected static $testCacheDir = 'sandbox/pagecache'; |
14 | protected static $url = 'http://shaar.li/?do=atom'; | 14 | protected static $url = 'http://shaar.li/feed/atom'; |
15 | protected static $filename; | 15 | protected static $filename; |
16 | 16 | ||
17 | /** | 17 | /** |
18 | * Create the cache directory if needed | 18 | * Create the cache directory if needed |
19 | */ | 19 | */ |
20 | public static function setUpBeforeClass() | 20 | public static function setUpBeforeClass(): void |
21 | { | 21 | { |
22 | if (!is_dir(self::$testCacheDir)) { | 22 | if (!is_dir(self::$testCacheDir)) { |
23 | mkdir(self::$testCacheDir); | 23 | mkdir(self::$testCacheDir); |
@@ -28,7 +28,7 @@ class CachedPageTest extends \PHPUnit\Framework\TestCase | |||
28 | /** | 28 | /** |
29 | * Reset the page cache | 29 | * Reset the page cache |
30 | */ | 30 | */ |
31 | public function setUp() | 31 | protected function setUp(): void |
32 | { | 32 | { |
33 | if (file_exists(self::$filename)) { | 33 | if (file_exists(self::$filename)) { |
34 | unlink(self::$filename); | 34 | unlink(self::$filename); |
@@ -42,8 +42,9 @@ class CachedPageTest extends \PHPUnit\Framework\TestCase | |||
42 | { | 42 | { |
43 | new CachedPage(self::$testCacheDir, '', true); | 43 | new CachedPage(self::$testCacheDir, '', true); |
44 | new CachedPage(self::$testCacheDir, '', false); | 44 | new CachedPage(self::$testCacheDir, '', false); |
45 | new CachedPage(self::$testCacheDir, 'http://shaar.li/?do=rss', true); | 45 | new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/rss', true); |
46 | new CachedPage(self::$testCacheDir, 'http://shaar.li/?do=atom', false); | 46 | new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/atom', false); |
47 | $this->addToAssertionCount(1); | ||
47 | } | 48 | } |
48 | 49 | ||
49 | /** | 50 | /** |
diff --git a/tests/feed/FeedBuilderTest.php b/tests/feed/FeedBuilderTest.php index b496cb4c..c29e8ef3 100644 --- a/tests/feed/FeedBuilderTest.php +++ b/tests/feed/FeedBuilderTest.php | |||
@@ -4,14 +4,20 @@ namespace Shaarli\Feed; | |||
4 | 4 | ||
5 | use DateTime; | 5 | use DateTime; |
6 | use ReferenceLinkDB; | 6 | use ReferenceLinkDB; |
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\BookmarkFileService; | ||
7 | use Shaarli\Bookmark\LinkDB; | 9 | use Shaarli\Bookmark\LinkDB; |
10 | use Shaarli\Config\ConfigManager; | ||
11 | use Shaarli\Formatter\FormatterFactory; | ||
12 | use Shaarli\History; | ||
13 | use Shaarli\TestCase; | ||
8 | 14 | ||
9 | /** | 15 | /** |
10 | * FeedBuilderTest class. | 16 | * FeedBuilderTest class. |
11 | * | 17 | * |
12 | * Unit tests for FeedBuilder. | 18 | * Unit tests for FeedBuilder. |
13 | */ | 19 | */ |
14 | class FeedBuilderTest extends \PHPUnit\Framework\TestCase | 20 | class FeedBuilderTest extends TestCase |
15 | { | 21 | { |
16 | /** | 22 | /** |
17 | * @var string locale Basque (Spain). | 23 | * @var string locale Basque (Spain). |
@@ -30,74 +36,70 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
30 | 36 | ||
31 | protected static $testDatastore = 'sandbox/datastore.php'; | 37 | protected static $testDatastore = 'sandbox/datastore.php'; |
32 | 38 | ||
33 | public static $linkDB; | 39 | public static $bookmarkService; |
40 | |||
41 | public static $formatter; | ||
34 | 42 | ||
35 | public static $serverInfo; | 43 | public static $serverInfo; |
36 | 44 | ||
37 | /** | 45 | /** |
38 | * Called before every test method. | 46 | * Called before every test method. |
39 | */ | 47 | */ |
40 | public static function setUpBeforeClass() | 48 | public static function setUpBeforeClass(): void |
41 | { | 49 | { |
42 | $refLinkDB = new ReferenceLinkDB(); | 50 | $conf = new ConfigManager('tests/utils/config/configJson'); |
51 | $conf->set('resource.datastore', self::$testDatastore); | ||
52 | $refLinkDB = new \ReferenceLinkDB(); | ||
43 | $refLinkDB->write(self::$testDatastore); | 53 | $refLinkDB->write(self::$testDatastore); |
44 | self::$linkDB = new LinkDB(self::$testDatastore, true, false); | 54 | $history = new History('sandbox/history.php'); |
55 | $factory = new FormatterFactory($conf, true); | ||
56 | self::$formatter = $factory->getFormatter(); | ||
57 | self::$bookmarkService = new BookmarkFileService($conf, $history, true); | ||
58 | |||
45 | self::$serverInfo = array( | 59 | self::$serverInfo = array( |
46 | 'HTTPS' => 'Off', | 60 | 'HTTPS' => 'Off', |
47 | 'SERVER_NAME' => 'host.tld', | 61 | 'SERVER_NAME' => 'host.tld', |
48 | 'SERVER_PORT' => '80', | 62 | 'SERVER_PORT' => '80', |
49 | 'SCRIPT_NAME' => '/index.php', | 63 | 'SCRIPT_NAME' => '/index.php', |
50 | 'REQUEST_URI' => '/index.php?do=feed', | 64 | 'REQUEST_URI' => '/feed/atom', |
51 | ); | 65 | ); |
52 | } | 66 | } |
53 | 67 | ||
54 | /** | 68 | /** |
55 | * Test GetTypeLanguage(). | ||
56 | */ | ||
57 | public function testGetTypeLanguage() | ||
58 | { | ||
59 | $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_ATOM, null, null, false); | ||
60 | $feedBuilder->setLocale(self::$LOCALE); | ||
61 | $this->assertEquals(self::$ATOM_LANGUAGUE, $feedBuilder->getTypeLanguage()); | ||
62 | $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_RSS, null, null, false); | ||
63 | $feedBuilder->setLocale(self::$LOCALE); | ||
64 | $this->assertEquals(self::$RSS_LANGUAGE, $feedBuilder->getTypeLanguage()); | ||
65 | $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_ATOM, null, null, false); | ||
66 | $this->assertEquals('en', $feedBuilder->getTypeLanguage()); | ||
67 | $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_RSS, null, null, false); | ||
68 | $this->assertEquals('en-en', $feedBuilder->getTypeLanguage()); | ||
69 | } | ||
70 | |||
71 | /** | ||
72 | * Test buildData with RSS feed. | 69 | * Test buildData with RSS feed. |
73 | */ | 70 | */ |
74 | public function testRSSBuildData() | 71 | public function testRSSBuildData() |
75 | { | 72 | { |
76 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_RSS, self::$serverInfo, null, false); | 73 | $feedBuilder = new FeedBuilder( |
74 | self::$bookmarkService, | ||
75 | self::$formatter, | ||
76 | static::$serverInfo, | ||
77 | false | ||
78 | ); | ||
77 | $feedBuilder->setLocale(self::$LOCALE); | 79 | $feedBuilder->setLocale(self::$LOCALE); |
78 | $data = $feedBuilder->buildData(); | 80 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_RSS, null); |
79 | // Test headers (RSS) | 81 | // Test headers (RSS) |
80 | $this->assertEquals(self::$RSS_LANGUAGE, $data['language']); | 82 | $this->assertEquals(self::$RSS_LANGUAGE, $data['language']); |
81 | $this->assertRegExp('/Wed, 03 Aug 2016 09:30:33 \+\d{4}/', $data['last_update']); | 83 | $this->assertRegExp('/Wed, 03 Aug 2016 09:30:33 \+\d{4}/', $data['last_update']); |
82 | $this->assertEquals(true, $data['show_dates']); | 84 | $this->assertEquals(true, $data['show_dates']); |
83 | $this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']); | 85 | $this->assertEquals('http://host.tld/feed/atom', $data['self_link']); |
84 | $this->assertEquals('http://host.tld/', $data['index_url']); | 86 | $this->assertEquals('http://host.tld/', $data['index_url']); |
85 | $this->assertFalse($data['usepermalinks']); | 87 | $this->assertFalse($data['usepermalinks']); |
86 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | 88 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); |
87 | 89 | ||
88 | // Test first not pinned link (note link) | 90 | // Test first not pinned link (note link) |
89 | $link = $data['links'][array_keys($data['links'])[2]]; | 91 | $link = $data['links'][array_keys($data['links'])[0]]; |
90 | $this->assertEquals(41, $link['id']); | 92 | $this->assertEquals(41, $link['id']); |
91 | $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); | 93 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); |
92 | $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); | 94 | $this->assertEquals('http://host.tld/shaare/WDWyig', $link['guid']); |
93 | $this->assertEquals('http://host.tld/?WDWyig', $link['url']); | 95 | $this->assertEquals('http://host.tld/shaare/WDWyig', $link['url']); |
94 | $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']); | 96 | $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']); |
95 | $pub = DateTime::createFromFormat(DateTime::RSS, $link['pub_iso_date']); | 97 | $pub = DateTime::createFromFormat(DateTime::RSS, $link['pub_iso_date']); |
96 | $up = DateTime::createFromFormat(DateTime::ATOM, $link['up_iso_date']); | 98 | $up = DateTime::createFromFormat(DateTime::ATOM, $link['up_iso_date']); |
97 | $this->assertEquals($pub, $up); | 99 | $this->assertEquals($pub, $up); |
98 | $this->assertContains('Stallman has a beard', $link['description']); | 100 | $this->assertContainsPolyfill('Stallman has a beard', $link['description']); |
99 | $this->assertContains('Permalink', $link['description']); | 101 | $this->assertContainsPolyfill('Permalink', $link['description']); |
100 | $this->assertContains('http://host.tld/?WDWyig', $link['description']); | 102 | $this->assertContainsPolyfill('http://host.tld/shaare/WDWyig', $link['description']); |
101 | $this->assertEquals(1, count($link['taglist'])); | 103 | $this->assertEquals(1, count($link['taglist'])); |
102 | $this->assertEquals('sTuff', $link['taglist'][0]); | 104 | $this->assertEquals('sTuff', $link['taglist'][0]); |
103 | 105 | ||
@@ -117,12 +119,17 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
117 | */ | 119 | */ |
118 | public function testAtomBuildData() | 120 | public function testAtomBuildData() |
119 | { | 121 | { |
120 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false); | 122 | $feedBuilder = new FeedBuilder( |
123 | self::$bookmarkService, | ||
124 | self::$formatter, | ||
125 | static::$serverInfo, | ||
126 | false | ||
127 | ); | ||
121 | $feedBuilder->setLocale(self::$LOCALE); | 128 | $feedBuilder->setLocale(self::$LOCALE); |
122 | $data = $feedBuilder->buildData(); | 129 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
123 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | 130 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); |
124 | $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']); | 131 | $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']); |
125 | $link = $data['links'][array_keys($data['links'])[2]]; | 132 | $link = $data['links'][array_keys($data['links'])[0]]; |
126 | $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']); | 133 | $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']); |
127 | $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']); | 134 | $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']); |
128 | } | 135 | } |
@@ -136,13 +143,18 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
136 | 'searchtags' => 'stuff', | 143 | 'searchtags' => 'stuff', |
137 | 'searchterm' => 'beard', | 144 | 'searchterm' => 'beard', |
138 | ); | 145 | ); |
139 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false); | 146 | $feedBuilder = new FeedBuilder( |
147 | self::$bookmarkService, | ||
148 | self::$formatter, | ||
149 | static::$serverInfo, | ||
150 | false | ||
151 | ); | ||
140 | $feedBuilder->setLocale(self::$LOCALE); | 152 | $feedBuilder->setLocale(self::$LOCALE); |
141 | $data = $feedBuilder->buildData(); | 153 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, $criteria); |
142 | $this->assertEquals(1, count($data['links'])); | 154 | $this->assertEquals(1, count($data['links'])); |
143 | $link = array_shift($data['links']); | 155 | $link = array_shift($data['links']); |
144 | $this->assertEquals(41, $link['id']); | 156 | $this->assertEquals(41, $link['id']); |
145 | $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); | 157 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); |
146 | } | 158 | } |
147 | 159 | ||
148 | /** | 160 | /** |
@@ -153,13 +165,18 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
153 | $criteria = array( | 165 | $criteria = array( |
154 | 'nb' => '3', | 166 | 'nb' => '3', |
155 | ); | 167 | ); |
156 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false); | 168 | $feedBuilder = new FeedBuilder( |
169 | self::$bookmarkService, | ||
170 | self::$formatter, | ||
171 | static::$serverInfo, | ||
172 | false | ||
173 | ); | ||
157 | $feedBuilder->setLocale(self::$LOCALE); | 174 | $feedBuilder->setLocale(self::$LOCALE); |
158 | $data = $feedBuilder->buildData(); | 175 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, $criteria); |
159 | $this->assertEquals(3, count($data['links'])); | 176 | $this->assertEquals(3, count($data['links'])); |
160 | $link = $data['links'][array_keys($data['links'])[2]]; | 177 | $link = $data['links'][array_keys($data['links'])[0]]; |
161 | $this->assertEquals(41, $link['id']); | 178 | $this->assertEquals(41, $link['id']); |
162 | $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); | 179 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); |
163 | } | 180 | } |
164 | 181 | ||
165 | /** | 182 | /** |
@@ -167,28 +184,33 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
167 | */ | 184 | */ |
168 | public function testBuildDataPermalinks() | 185 | public function testBuildDataPermalinks() |
169 | { | 186 | { |
170 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false); | 187 | $feedBuilder = new FeedBuilder( |
188 | self::$bookmarkService, | ||
189 | self::$formatter, | ||
190 | static::$serverInfo, | ||
191 | false | ||
192 | ); | ||
171 | $feedBuilder->setLocale(self::$LOCALE); | 193 | $feedBuilder->setLocale(self::$LOCALE); |
172 | $feedBuilder->setUsePermalinks(true); | 194 | $feedBuilder->setUsePermalinks(true); |
173 | $data = $feedBuilder->buildData(); | 195 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
174 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | 196 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); |
175 | $this->assertTrue($data['usepermalinks']); | 197 | $this->assertTrue($data['usepermalinks']); |
176 | // First link is a permalink | 198 | // First link is a permalink |
177 | $link = $data['links'][array_keys($data['links'])[2]]; | 199 | $link = $data['links'][array_keys($data['links'])[0]]; |
178 | $this->assertEquals(41, $link['id']); | 200 | $this->assertEquals(41, $link['id']); |
179 | $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); | 201 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), $link['created']); |
180 | $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); | 202 | $this->assertEquals('http://host.tld/shaare/WDWyig', $link['guid']); |
181 | $this->assertEquals('http://host.tld/?WDWyig', $link['url']); | 203 | $this->assertEquals('http://host.tld/shaare/WDWyig', $link['url']); |
182 | $this->assertContains('Direct link', $link['description']); | 204 | $this->assertContainsPolyfill('Direct link', $link['description']); |
183 | $this->assertContains('http://host.tld/?WDWyig', $link['description']); | 205 | $this->assertContainsPolyfill('http://host.tld/shaare/WDWyig', $link['description']); |
184 | // Second link is a direct link | 206 | // Second link is a direct link |
185 | $link = $data['links'][array_keys($data['links'])[3]]; | 207 | $link = $data['links'][array_keys($data['links'])[1]]; |
186 | $this->assertEquals(8, $link['id']); | 208 | $this->assertEquals(8, $link['id']); |
187 | $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'), $link['created']); | 209 | $this->assertEquals(DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114633'), $link['created']); |
188 | $this->assertEquals('http://host.tld/?RttfEw', $link['guid']); | 210 | $this->assertEquals('http://host.tld/shaare/RttfEw', $link['guid']); |
189 | $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']); | 211 | $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']); |
190 | $this->assertContains('Direct link', $link['description']); | 212 | $this->assertContainsPolyfill('Direct link', $link['description']); |
191 | $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']); | 213 | $this->assertContainsPolyfill('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']); |
192 | } | 214 | } |
193 | 215 | ||
194 | /** | 216 | /** |
@@ -196,18 +218,28 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
196 | */ | 218 | */ |
197 | public function testBuildDataHideDates() | 219 | public function testBuildDataHideDates() |
198 | { | 220 | { |
199 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false); | 221 | $feedBuilder = new FeedBuilder( |
222 | self::$bookmarkService, | ||
223 | self::$formatter, | ||
224 | static::$serverInfo, | ||
225 | false | ||
226 | ); | ||
200 | $feedBuilder->setLocale(self::$LOCALE); | 227 | $feedBuilder->setLocale(self::$LOCALE); |
201 | $feedBuilder->setHideDates(true); | 228 | $feedBuilder->setHideDates(true); |
202 | $data = $feedBuilder->buildData(); | 229 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
203 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | 230 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); |
204 | $this->assertFalse($data['show_dates']); | 231 | $this->assertFalse($data['show_dates']); |
205 | 232 | ||
206 | // Show dates while logged in | 233 | // Show dates while logged in |
207 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, true); | 234 | $feedBuilder = new FeedBuilder( |
235 | self::$bookmarkService, | ||
236 | self::$formatter, | ||
237 | static::$serverInfo, | ||
238 | true | ||
239 | ); | ||
208 | $feedBuilder->setLocale(self::$LOCALE); | 240 | $feedBuilder->setLocale(self::$LOCALE); |
209 | $feedBuilder->setHideDates(true); | 241 | $feedBuilder->setHideDates(true); |
210 | $data = $feedBuilder->buildData(); | 242 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
211 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | 243 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); |
212 | $this->assertTrue($data['show_dates']); | 244 | $this->assertTrue($data['show_dates']); |
213 | } | 245 | } |
@@ -222,27 +254,26 @@ class FeedBuilderTest extends \PHPUnit\Framework\TestCase | |||
222 | 'SERVER_NAME' => 'host.tld', | 254 | 'SERVER_NAME' => 'host.tld', |
223 | 'SERVER_PORT' => '8080', | 255 | 'SERVER_PORT' => '8080', |
224 | 'SCRIPT_NAME' => '/~user/shaarli/index.php', | 256 | 'SCRIPT_NAME' => '/~user/shaarli/index.php', |
225 | 'REQUEST_URI' => '/~user/shaarli/index.php?do=feed', | 257 | 'REQUEST_URI' => '/~user/shaarli/feed/atom', |
226 | ); | 258 | ); |
227 | $feedBuilder = new FeedBuilder( | 259 | $feedBuilder = new FeedBuilder( |
228 | self::$linkDB, | 260 | self::$bookmarkService, |
229 | FeedBuilder::$FEED_ATOM, | 261 | self::$formatter, |
230 | $serverInfo, | 262 | $serverInfo, |
231 | null, | ||
232 | false | 263 | false |
233 | ); | 264 | ); |
234 | $feedBuilder->setLocale(self::$LOCALE); | 265 | $feedBuilder->setLocale(self::$LOCALE); |
235 | $data = $feedBuilder->buildData(); | 266 | $data = $feedBuilder->buildData(FeedBuilder::$FEED_ATOM, null); |
236 | 267 | ||
237 | $this->assertEquals( | 268 | $this->assertEquals( |
238 | 'http://host.tld:8080/~user/shaarli/index.php?do=feed', | 269 | 'http://host.tld:8080/~user/shaarli/feed/atom', |
239 | $data['self_link'] | 270 | $data['self_link'] |
240 | ); | 271 | ); |
241 | 272 | ||
242 | // Test first link (note link) | 273 | // Test first link (note link) |
243 | $link = $data['links'][array_keys($data['links'])[2]]; | 274 | $link = $data['links'][array_keys($data['links'])[0]]; |
244 | $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['guid']); | 275 | $this->assertEquals('http://host.tld:8080/~user/shaarli/shaare/WDWyig', $link['guid']); |
245 | $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['url']); | 276 | $this->assertEquals('http://host.tld:8080/~user/shaarli/shaare/WDWyig', $link['url']); |
246 | $this->assertContains('http://host.tld:8080/~user/shaarli/?addtag=hashtag', $link['description']); | 277 | $this->assertContainsPolyfill('http://host.tld:8080/~user/shaarli/./add-tag/hashtag', $link['description']); |
247 | } | 278 | } |
248 | } | 279 | } |
diff --git a/tests/formatter/BookmarkDefaultFormatterTest.php b/tests/formatter/BookmarkDefaultFormatterTest.php new file mode 100644 index 00000000..9534436e --- /dev/null +++ b/tests/formatter/BookmarkDefaultFormatterTest.php | |||
@@ -0,0 +1,177 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use DateTime; | ||
6 | use Shaarli\Bookmark\Bookmark; | ||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\TestCase; | ||
9 | |||
10 | /** | ||
11 | * Class BookmarkDefaultFormatterTest | ||
12 | * @package Shaarli\Formatter | ||
13 | */ | ||
14 | class BookmarkDefaultFormatterTest extends TestCase | ||
15 | { | ||
16 | /** @var string Path of test config file */ | ||
17 | protected static $testConf = 'sandbox/config'; | ||
18 | |||
19 | /** @var BookmarkFormatter */ | ||
20 | protected $formatter; | ||
21 | |||
22 | /** @var ConfigManager instance */ | ||
23 | protected $conf; | ||
24 | |||
25 | /** | ||
26 | * Initialize formatter instance. | ||
27 | */ | ||
28 | protected function setUp(): void | ||
29 | { | ||
30 | copy('tests/utils/config/configJson.json.php', self::$testConf .'.json.php'); | ||
31 | $this->conf = new ConfigManager(self::$testConf); | ||
32 | $this->formatter = new BookmarkDefaultFormatter($this->conf, true); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Test formatting a bookmark with all its attribute filled. | ||
37 | */ | ||
38 | public function testFormatFull() | ||
39 | { | ||
40 | $bookmark = new Bookmark(); | ||
41 | $bookmark->setId($id = 11); | ||
42 | $bookmark->setShortUrl($short = 'abcdef'); | ||
43 | $bookmark->setUrl('https://sub.domain.tld?query=here&for=real#hash'); | ||
44 | $bookmark->setTitle($title = 'This is a <strong>bookmark</strong>'); | ||
45 | $bookmark->setDescription($desc = '<h2>Content</h2><p>`Here is some content</p>'); | ||
46 | $bookmark->setTags($tags = ['tag1', 'bookmark', 'other', '<script>alert("xss");</script>']); | ||
47 | $bookmark->setThumbnail('http://domain2.tdl2/?type=img&name=file.png'); | ||
48 | $bookmark->setSticky(true); | ||
49 | $bookmark->setCreated($created = DateTime::createFromFormat('Ymd_His', '20190521_190412')); | ||
50 | $bookmark->setUpdated($updated = DateTime::createFromFormat('Ymd_His', '20190521_191213')); | ||
51 | $bookmark->setPrivate(true); | ||
52 | |||
53 | $link = $this->formatter->format($bookmark); | ||
54 | $this->assertEquals($id, $link['id']); | ||
55 | $this->assertEquals($short, $link['shorturl']); | ||
56 | $this->assertEquals('https://sub.domain.tld?query=here&for=real#hash', $link['url']); | ||
57 | $this->assertEquals( | ||
58 | 'https://sub.domain.tld?query=here&for=real#hash', | ||
59 | $link['real_url'] | ||
60 | ); | ||
61 | $this->assertEquals('This is a <strong>bookmark</strong>', $link['title']); | ||
62 | $this->assertEquals( | ||
63 | '<h2>Content</h2><p>`Here is some content</p>', | ||
64 | $link['description'] | ||
65 | ); | ||
66 | $tags[3] = '<script>alert("xss");</script>'; | ||
67 | $this->assertEquals($tags, $link['taglist']); | ||
68 | $this->assertEquals(implode(' ', $tags), $link['tags']); | ||
69 | $this->assertEquals( | ||
70 | 'http://domain2.tdl2/?type=img&name=file.png', | ||
71 | $link['thumbnail'] | ||
72 | ); | ||
73 | $this->assertEquals($created, $link['created']); | ||
74 | $this->assertEquals($created->getTimestamp(), $link['timestamp']); | ||
75 | $this->assertEquals($updated, $link['updated']); | ||
76 | $this->assertEquals($updated->getTimestamp(), $link['updated_timestamp']); | ||
77 | $this->assertTrue($link['private']); | ||
78 | $this->assertTrue($link['sticky']); | ||
79 | $this->assertEquals('private', $link['class']); | ||
80 | } | ||
81 | |||
82 | /** | ||
83 | * Test formatting a bookmark with all its attribute filled. | ||
84 | */ | ||
85 | public function testFormatMinimal() | ||
86 | { | ||
87 | $bookmark = new Bookmark(); | ||
88 | |||
89 | $link = $this->formatter->format($bookmark); | ||
90 | $this->assertEmpty($link['id']); | ||
91 | $this->assertEmpty($link['shorturl']); | ||
92 | $this->assertEmpty($link['url']); | ||
93 | $this->assertEmpty($link['real_url']); | ||
94 | $this->assertEmpty($link['title']); | ||
95 | $this->assertEmpty($link['description']); | ||
96 | $this->assertEmpty($link['taglist']); | ||
97 | $this->assertEmpty($link['tags']); | ||
98 | $this->assertEmpty($link['thumbnail']); | ||
99 | $this->assertEmpty($link['created']); | ||
100 | $this->assertEmpty($link['timestamp']); | ||
101 | $this->assertEmpty($link['updated']); | ||
102 | $this->assertEmpty($link['updated_timestamp']); | ||
103 | $this->assertFalse($link['private']); | ||
104 | $this->assertFalse($link['sticky']); | ||
105 | $this->assertEmpty($link['class']); | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * Make sure that the description is properly formatted by the default formatter. | ||
110 | */ | ||
111 | public function testFormatDescription() | ||
112 | { | ||
113 | $description = []; | ||
114 | $description[] = 'This a <strong>description</strong>' . PHP_EOL; | ||
115 | $description[] = 'text https://sub.domain.tld?query=here&for=real#hash more text'. PHP_EOL; | ||
116 | $description[] = 'Also, there is an #hashtag added'. PHP_EOL; | ||
117 | $description[] = ' A N D KEEP SPACES ! '. PHP_EOL; | ||
118 | |||
119 | $bookmark = new Bookmark(); | ||
120 | $bookmark->setDescription(implode('', $description)); | ||
121 | $link = $this->formatter->format($bookmark); | ||
122 | |||
123 | $description[0] = 'This a <strong>description</strong><br />'; | ||
124 | $url = 'https://sub.domain.tld?query=here&for=real#hash'; | ||
125 | $description[1] = 'text <a href="'. $url .'">'. $url .'</a> more text<br />'; | ||
126 | $description[2] = 'Also, there is an <a href="./add-tag/hashtag" '. | ||
127 | 'title="Hashtag hashtag">#hashtag</a> added<br />'; | ||
128 | $description[3] = ' A N D KEEP '. | ||
129 | 'SPACES ! <br />'; | ||
130 | |||
131 | $this->assertEquals(implode(PHP_EOL, $description) . PHP_EOL, $link['description']); | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * Test formatting URL with an index_url set | ||
136 | * It should prepend relative links. | ||
137 | */ | ||
138 | public function testFormatNoteWithIndexUrl() | ||
139 | { | ||
140 | $bookmark = new Bookmark(); | ||
141 | $bookmark->setUrl($short = '?abcdef'); | ||
142 | $description = 'Text #hashtag more text'; | ||
143 | $bookmark->setDescription($description); | ||
144 | |||
145 | $this->formatter->addContextData('index_url', $root = 'https://domain.tld/hithere/'); | ||
146 | |||
147 | $link = $this->formatter->format($bookmark); | ||
148 | $this->assertEquals($root . $short, $link['url']); | ||
149 | $this->assertEquals($root . $short, $link['real_url']); | ||
150 | $this->assertEquals( | ||
151 | 'Text <a href="'. $root .'./add-tag/hashtag" title="Hashtag hashtag">'. | ||
152 | '#hashtag</a> more text', | ||
153 | $link['description'] | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | /** | ||
158 | * Make sure that private tags are properly filtered out when the user is logged out. | ||
159 | */ | ||
160 | public function testFormatTagListRemovePrivate(): void | ||
161 | { | ||
162 | $this->formatter = new BookmarkDefaultFormatter($this->conf, false); | ||
163 | |||
164 | $bookmark = new Bookmark(); | ||
165 | $bookmark->setId($id = 11); | ||
166 | $bookmark->setTags($tags = ['bookmark', '.private', 'othertag']); | ||
167 | |||
168 | $link = $this->formatter->format($bookmark); | ||
169 | |||
170 | unset($tags[1]); | ||
171 | $tags = array_values($tags); | ||
172 | |||
173 | $this->assertSame(11, $link['id']); | ||
174 | $this->assertSame($tags, $link['taglist']); | ||
175 | $this->assertSame(implode(' ', $tags), $link['tags']); | ||
176 | } | ||
177 | } | ||
diff --git a/tests/formatter/BookmarkMarkdownFormatterTest.php b/tests/formatter/BookmarkMarkdownFormatterTest.php new file mode 100644 index 00000000..ab6b4080 --- /dev/null +++ b/tests/formatter/BookmarkMarkdownFormatterTest.php | |||
@@ -0,0 +1,160 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use DateTime; | ||
6 | use Shaarli\Bookmark\Bookmark; | ||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\TestCase; | ||
9 | |||
10 | /** | ||
11 | * Class BookmarkMarkdownFormatterTest | ||
12 | * @package Shaarli\Formatter | ||
13 | */ | ||
14 | class BookmarkMarkdownFormatterTest extends TestCase | ||
15 | { | ||
16 | /** @var string Path of test config file */ | ||
17 | protected static $testConf = 'sandbox/config'; | ||
18 | |||
19 | /** @var BookmarkFormatter */ | ||
20 | protected $formatter; | ||
21 | |||
22 | /** @var ConfigManager instance */ | ||
23 | protected $conf; | ||
24 | |||
25 | /** | ||
26 | * Initialize formatter instance. | ||
27 | */ | ||
28 | protected function setUp(): void | ||
29 | { | ||
30 | copy('tests/utils/config/configJson.json.php', self::$testConf .'.json.php'); | ||
31 | $this->conf = new ConfigManager(self::$testConf); | ||
32 | $this->formatter = new BookmarkMarkdownFormatter($this->conf, true); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Test formatting a bookmark with all its attribute filled. | ||
37 | */ | ||
38 | public function testFormatFull() | ||
39 | { | ||
40 | $bookmark = new Bookmark(); | ||
41 | $bookmark->setId($id = 11); | ||
42 | $bookmark->setShortUrl($short = 'abcdef'); | ||
43 | $bookmark->setUrl('https://sub.domain.tld?query=here&for=real#hash'); | ||
44 | $bookmark->setTitle($title = 'This is a <strong>bookmark</strong>'); | ||
45 | $bookmark->setDescription('<h2>Content</h2><p>`Here is some content</p>'); | ||
46 | $bookmark->setTags($tags = ['tag1', 'bookmark', 'other', '<script>alert("xss");</script>']); | ||
47 | $bookmark->setThumbnail('http://domain2.tdl2/?type=img&name=file.png'); | ||
48 | $bookmark->setSticky(true); | ||
49 | $bookmark->setCreated($created = DateTime::createFromFormat('Ymd_His', '20190521_190412')); | ||
50 | $bookmark->setUpdated($updated = DateTime::createFromFormat('Ymd_His', '20190521_191213')); | ||
51 | $bookmark->setPrivate(true); | ||
52 | |||
53 | $link = $this->formatter->format($bookmark); | ||
54 | $this->assertEquals($id, $link['id']); | ||
55 | $this->assertEquals($short, $link['shorturl']); | ||
56 | $this->assertEquals('https://sub.domain.tld?query=here&for=real#hash', $link['url']); | ||
57 | $this->assertEquals( | ||
58 | 'https://sub.domain.tld?query=here&for=real#hash', | ||
59 | $link['real_url'] | ||
60 | ); | ||
61 | $this->assertEquals('This is a <strong>bookmark</strong>', $link['title']); | ||
62 | $this->assertEquals( | ||
63 | '<div class="markdown"><p>'. | ||
64 | '<h2>Content</h2><p>`Here is some content</p>'. | ||
65 | '</p></div>', | ||
66 | $link['description'] | ||
67 | ); | ||
68 | $tags[3] = '<script>alert("xss");</script>'; | ||
69 | $this->assertEquals($tags, $link['taglist']); | ||
70 | $this->assertEquals(implode(' ', $tags), $link['tags']); | ||
71 | $this->assertEquals( | ||
72 | 'http://domain2.tdl2/?type=img&name=file.png', | ||
73 | $link['thumbnail'] | ||
74 | ); | ||
75 | $this->assertEquals($created, $link['created']); | ||
76 | $this->assertEquals($created->getTimestamp(), $link['timestamp']); | ||
77 | $this->assertEquals($updated, $link['updated']); | ||
78 | $this->assertEquals($updated->getTimestamp(), $link['updated_timestamp']); | ||
79 | $this->assertTrue($link['private']); | ||
80 | $this->assertTrue($link['sticky']); | ||
81 | $this->assertEquals('private', $link['class']); | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Test formatting a bookmark with all its attribute filled. | ||
86 | */ | ||
87 | public function testFormatMinimal() | ||
88 | { | ||
89 | $bookmark = new Bookmark(); | ||
90 | |||
91 | $link = $this->formatter->format($bookmark); | ||
92 | $this->assertEmpty($link['id']); | ||
93 | $this->assertEmpty($link['shorturl']); | ||
94 | $this->assertEmpty($link['url']); | ||
95 | $this->assertEmpty($link['real_url']); | ||
96 | $this->assertEmpty($link['title']); | ||
97 | $this->assertEmpty($link['description']); | ||
98 | $this->assertEmpty($link['taglist']); | ||
99 | $this->assertEmpty($link['tags']); | ||
100 | $this->assertEmpty($link['thumbnail']); | ||
101 | $this->assertEmpty($link['created']); | ||
102 | $this->assertEmpty($link['timestamp']); | ||
103 | $this->assertEmpty($link['updated']); | ||
104 | $this->assertEmpty($link['updated_timestamp']); | ||
105 | $this->assertFalse($link['private']); | ||
106 | $this->assertFalse($link['sticky']); | ||
107 | $this->assertEmpty($link['class']); | ||
108 | } | ||
109 | |||
110 | /** | ||
111 | * Make sure that the description is properly formatted by the default formatter. | ||
112 | */ | ||
113 | public function testFormatDescription() | ||
114 | { | ||
115 | $description = 'This a <strong>description</strong>'. PHP_EOL; | ||
116 | $description .= 'text https://sub.domain.tld?query=here&for=real#hash more text'. PHP_EOL; | ||
117 | $description .= 'Also, there is an #hashtag added'. PHP_EOL; | ||
118 | $description .= ' A N D KEEP SPACES ! '. PHP_EOL; | ||
119 | |||
120 | $bookmark = new Bookmark(); | ||
121 | $bookmark->setDescription($description); | ||
122 | $link = $this->formatter->format($bookmark); | ||
123 | |||
124 | $description = '<div class="markdown"><p>'; | ||
125 | $description .= 'This a <strong>description</strong><br />'. PHP_EOL; | ||
126 | $url = 'https://sub.domain.tld?query=here&for=real#hash'; | ||
127 | $description .= 'text <a href="'. $url .'">'. $url .'</a> more text<br />'. PHP_EOL; | ||
128 | $description .= 'Also, there is an <a href="./add-tag/hashtag">#hashtag</a> added<br />'. PHP_EOL; | ||
129 | $description .= 'A N D KEEP SPACES ! '; | ||
130 | $description .= '</p></div>'; | ||
131 | |||
132 | $this->assertEquals($description, $link['description']); | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * Test formatting URL with an index_url set | ||
137 | * It should prepend relative links. | ||
138 | */ | ||
139 | public function testFormatNoteWithIndexUrl() | ||
140 | { | ||
141 | $bookmark = new Bookmark(); | ||
142 | $bookmark->setUrl($short = '?abcdef'); | ||
143 | $description = 'Text #hashtag more text'; | ||
144 | $bookmark->setDescription($description); | ||
145 | |||
146 | $this->formatter->addContextData('index_url', $root = 'https://domain.tld/hithere/'); | ||
147 | |||
148 | $description = '<div class="markdown"><p>'; | ||
149 | $description .= 'Text <a href="'. $root .'./add-tag/hashtag">#hashtag</a> more text'; | ||
150 | $description .= '</p></div>'; | ||
151 | |||
152 | $link = $this->formatter->format($bookmark); | ||
153 | $this->assertEquals($root . $short, $link['url']); | ||
154 | $this->assertEquals($root . $short, $link['real_url']); | ||
155 | $this->assertEquals( | ||
156 | $description, | ||
157 | $link['description'] | ||
158 | ); | ||
159 | } | ||
160 | } | ||
diff --git a/tests/formatter/BookmarkRawFormatterTest.php b/tests/formatter/BookmarkRawFormatterTest.php new file mode 100644 index 00000000..c76bb7b9 --- /dev/null +++ b/tests/formatter/BookmarkRawFormatterTest.php | |||
@@ -0,0 +1,97 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use DateTime; | ||
6 | use Shaarli\Bookmark\Bookmark; | ||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\TestCase; | ||
9 | |||
10 | /** | ||
11 | * Class BookmarkRawFormatterTest | ||
12 | * @package Shaarli\Formatter | ||
13 | */ | ||
14 | class BookmarkRawFormatterTest extends TestCase | ||
15 | { | ||
16 | /** @var string Path of test config file */ | ||
17 | protected static $testConf = 'sandbox/config'; | ||
18 | |||
19 | /** @var BookmarkFormatter */ | ||
20 | protected $formatter; | ||
21 | |||
22 | /** @var ConfigManager instance */ | ||
23 | protected $conf; | ||
24 | |||
25 | /** | ||
26 | * Initialize formatter instance. | ||
27 | */ | ||
28 | protected function setUp(): void | ||
29 | { | ||
30 | copy('tests/utils/config/configJson.json.php', self::$testConf .'.json.php'); | ||
31 | $this->conf = new ConfigManager(self::$testConf); | ||
32 | $this->formatter = new BookmarkRawFormatter($this->conf, true); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Test formatting a bookmark with all its attribute filled. | ||
37 | */ | ||
38 | public function testFormatFull() | ||
39 | { | ||
40 | $bookmark = new Bookmark(); | ||
41 | $bookmark->setId($id = 11); | ||
42 | $bookmark->setShortUrl($short = 'abcdef'); | ||
43 | $bookmark->setUrl($url = 'https://sub.domain.tld?query=here&for=real#hash'); | ||
44 | $bookmark->setTitle($title = 'This is a <strong>bookmark</strong>'); | ||
45 | $bookmark->setDescription($desc = '<h2>Content</h2><p>`Here is some content</p>'); | ||
46 | $bookmark->setTags($tags = ['tag1', 'bookmark', 'other', '<script>alert("xss");</script>']); | ||
47 | $bookmark->setThumbnail($thumb = 'http://domain2.tdl2/file.png'); | ||
48 | $bookmark->setSticky(true); | ||
49 | $bookmark->setCreated($created = DateTime::createFromFormat('Ymd_His', '20190521_190412')); | ||
50 | $bookmark->setUpdated($updated = DateTime::createFromFormat('Ymd_His', '20190521_191213')); | ||
51 | $bookmark->setPrivate(true); | ||
52 | |||
53 | $link = $this->formatter->format($bookmark); | ||
54 | $this->assertEquals($id, $link['id']); | ||
55 | $this->assertEquals($short, $link['shorturl']); | ||
56 | $this->assertEquals($url, $link['url']); | ||
57 | $this->assertEquals($url, $link['real_url']); | ||
58 | $this->assertEquals($title, $link['title']); | ||
59 | $this->assertEquals($desc, $link['description']); | ||
60 | $this->assertEquals($tags, $link['taglist']); | ||
61 | $this->assertEquals(implode(' ', $tags), $link['tags']); | ||
62 | $this->assertEquals($thumb, $link['thumbnail']); | ||
63 | $this->assertEquals($created, $link['created']); | ||
64 | $this->assertEquals($created->getTimestamp(), $link['timestamp']); | ||
65 | $this->assertEquals($updated, $link['updated']); | ||
66 | $this->assertEquals($updated->getTimestamp(), $link['updated_timestamp']); | ||
67 | $this->assertTrue($link['private']); | ||
68 | $this->assertTrue($link['sticky']); | ||
69 | $this->assertEquals('private', $link['class']); | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * Test formatting a bookmark with all its attribute filled. | ||
74 | */ | ||
75 | public function testFormatMinimal() | ||
76 | { | ||
77 | $bookmark = new Bookmark(); | ||
78 | |||
79 | $link = $this->formatter->format($bookmark); | ||
80 | $this->assertEmpty($link['id']); | ||
81 | $this->assertEmpty($link['shorturl']); | ||
82 | $this->assertEmpty($link['url']); | ||
83 | $this->assertEmpty($link['real_url']); | ||
84 | $this->assertEmpty($link['title']); | ||
85 | $this->assertEmpty($link['description']); | ||
86 | $this->assertEmpty($link['taglist']); | ||
87 | $this->assertEmpty($link['tags']); | ||
88 | $this->assertEmpty($link['thumbnail']); | ||
89 | $this->assertEmpty($link['created']); | ||
90 | $this->assertEmpty($link['timestamp']); | ||
91 | $this->assertEmpty($link['updated']); | ||
92 | $this->assertEmpty($link['updated_timestamp']); | ||
93 | $this->assertFalse($link['private']); | ||
94 | $this->assertFalse($link['sticky']); | ||
95 | $this->assertEmpty($link['class']); | ||
96 | } | ||
97 | } | ||
diff --git a/tests/formatter/FormatterFactoryTest.php b/tests/formatter/FormatterFactoryTest.php new file mode 100644 index 00000000..ae476cb5 --- /dev/null +++ b/tests/formatter/FormatterFactoryTest.php | |||
@@ -0,0 +1,101 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Formatter; | ||
4 | |||
5 | use Shaarli\Config\ConfigManager; | ||
6 | use Shaarli\TestCase; | ||
7 | |||
8 | /** | ||
9 | * Class FormatterFactoryTest | ||
10 | * | ||
11 | * @package Shaarli\Formatter | ||
12 | */ | ||
13 | class FormatterFactoryTest extends TestCase | ||
14 | { | ||
15 | /** @var string Path of test config file */ | ||
16 | protected static $testConf = 'sandbox/config'; | ||
17 | |||
18 | /** @var FormatterFactory instance */ | ||
19 | protected $factory; | ||
20 | |||
21 | /** @var ConfigManager instance */ | ||
22 | protected $conf; | ||
23 | |||
24 | /** | ||
25 | * Initialize FormatterFactory instance | ||
26 | */ | ||
27 | protected function setUp(): void | ||
28 | { | ||
29 | copy('tests/utils/config/configJson.json.php', self::$testConf .'.json.php'); | ||
30 | $this->conf = new ConfigManager(self::$testConf); | ||
31 | $this->factory = new FormatterFactory($this->conf, true); | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Test creating an instance of BookmarkFormatter without any setting -> default formatter | ||
36 | */ | ||
37 | public function testCreateInstanceDefault() | ||
38 | { | ||
39 | $this->assertInstanceOf(BookmarkDefaultFormatter::class, $this->factory->getFormatter()); | ||
40 | } | ||
41 | |||
42 | /** | ||
43 | * Test creating an instance of BookmarkDefaultFormatter from settings | ||
44 | */ | ||
45 | public function testCreateInstanceDefaultSetting() | ||
46 | { | ||
47 | $this->conf->set('formatter', 'default'); | ||
48 | $this->assertInstanceOf(BookmarkDefaultFormatter::class, $this->factory->getFormatter()); | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Test creating an instance of BookmarkDefaultFormatter from parameter | ||
53 | */ | ||
54 | public function testCreateInstanceDefaultParameter() | ||
55 | { | ||
56 | $this->assertInstanceOf( | ||
57 | BookmarkDefaultFormatter::class, | ||
58 | $this->factory->getFormatter('default') | ||
59 | ); | ||
60 | } | ||
61 | |||
62 | /** | ||
63 | * Test creating an instance of BookmarkRawFormatter from settings | ||
64 | */ | ||
65 | public function testCreateInstanceRawSetting() | ||
66 | { | ||
67 | $this->conf->set('formatter', 'raw'); | ||
68 | $this->assertInstanceOf(BookmarkRawFormatter::class, $this->factory->getFormatter()); | ||
69 | } | ||
70 | |||
71 | /** | ||
72 | * Test creating an instance of BookmarkRawFormatter from parameter | ||
73 | */ | ||
74 | public function testCreateInstanceRawParameter() | ||
75 | { | ||
76 | $this->assertInstanceOf( | ||
77 | BookmarkRawFormatter::class, | ||
78 | $this->factory->getFormatter('raw') | ||
79 | ); | ||
80 | } | ||
81 | |||
82 | /** | ||
83 | * Test creating an instance of BookmarkMarkdownFormatter from settings | ||
84 | */ | ||
85 | public function testCreateInstanceMarkdownSetting() | ||
86 | { | ||
87 | $this->conf->set('formatter', 'markdown'); | ||
88 | $this->assertInstanceOf(BookmarkMarkdownFormatter::class, $this->factory->getFormatter()); | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * Test creating an instance of BookmarkMarkdownFormatter from parameter | ||
93 | */ | ||
94 | public function testCreateInstanceMarkdownParameter() | ||
95 | { | ||
96 | $this->assertInstanceOf( | ||
97 | BookmarkMarkdownFormatter::class, | ||
98 | $this->factory->getFormatter('markdown') | ||
99 | ); | ||
100 | } | ||
101 | } | ||
diff --git a/tests/front/ShaarliAdminMiddlewareTest.php b/tests/front/ShaarliAdminMiddlewareTest.php new file mode 100644 index 00000000..44025f11 --- /dev/null +++ b/tests/front/ShaarliAdminMiddlewareTest.php | |||
@@ -0,0 +1,100 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Container\ShaarliContainer; | ||
9 | use Shaarli\Security\LoginManager; | ||
10 | use Shaarli\TestCase; | ||
11 | use Shaarli\Updater\Updater; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | use Slim\Http\Uri; | ||
15 | |||
16 | class ShaarliAdminMiddlewareTest extends TestCase | ||
17 | { | ||
18 | protected const TMP_MOCK_FILE = '.tmp'; | ||
19 | |||
20 | /** @var ShaarliContainer */ | ||
21 | protected $container; | ||
22 | |||
23 | /** @var ShaarliMiddleware */ | ||
24 | protected $middleware; | ||
25 | |||
26 | public function setUp(): void | ||
27 | { | ||
28 | $this->container = $this->createMock(ShaarliContainer::class); | ||
29 | |||
30 | touch(static::TMP_MOCK_FILE); | ||
31 | |||
32 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
33 | $this->container->conf->method('getConfigFileExt')->willReturn(static::TMP_MOCK_FILE); | ||
34 | |||
35 | $this->container->loginManager = $this->createMock(LoginManager::class); | ||
36 | $this->container->updater = $this->createMock(Updater::class); | ||
37 | |||
38 | $this->container->environment = ['REQUEST_URI' => 'http://shaarli/subfolder/path']; | ||
39 | |||
40 | $this->middleware = new ShaarliAdminMiddleware($this->container); | ||
41 | } | ||
42 | |||
43 | public function tearDown(): void | ||
44 | { | ||
45 | unlink(static::TMP_MOCK_FILE); | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * Try to access an admin controller while logged out -> redirected to login page. | ||
50 | */ | ||
51 | public function testMiddlewareWhileLoggedOut(): void | ||
52 | { | ||
53 | $this->container->loginManager->expects(static::once())->method('isLoggedIn')->willReturn(false); | ||
54 | |||
55 | $request = $this->createMock(Request::class); | ||
56 | $request->method('getUri')->willReturnCallback(function (): Uri { | ||
57 | $uri = $this->createMock(Uri::class); | ||
58 | $uri->method('getBasePath')->willReturn('/subfolder'); | ||
59 | |||
60 | return $uri; | ||
61 | }); | ||
62 | |||
63 | $response = new Response(); | ||
64 | |||
65 | /** @var Response $result */ | ||
66 | $result = $this->middleware->__invoke($request, $response, function () {}); | ||
67 | |||
68 | static::assertSame(302, $result->getStatusCode()); | ||
69 | static::assertSame( | ||
70 | '/subfolder/login?returnurl=' . urlencode('http://shaarli/subfolder/path'), | ||
71 | $result->getHeader('location')[0] | ||
72 | ); | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Process controller while logged in. | ||
77 | */ | ||
78 | public function testMiddlewareWhileLoggedIn(): void | ||
79 | { | ||
80 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
81 | |||
82 | $request = $this->createMock(Request::class); | ||
83 | $request->method('getUri')->willReturnCallback(function (): Uri { | ||
84 | $uri = $this->createMock(Uri::class); | ||
85 | $uri->method('getBasePath')->willReturn('/subfolder'); | ||
86 | |||
87 | return $uri; | ||
88 | }); | ||
89 | |||
90 | $response = new Response(); | ||
91 | $controller = function (Request $request, Response $response): Response { | ||
92 | return $response->withStatus(418); // I'm a tea pot | ||
93 | }; | ||
94 | |||
95 | /** @var Response $result */ | ||
96 | $result = $this->middleware->__invoke($request, $response, $controller); | ||
97 | |||
98 | static::assertSame(418, $result->getStatusCode()); | ||
99 | } | ||
100 | } | ||
diff --git a/tests/front/ShaarliMiddlewareTest.php b/tests/front/ShaarliMiddlewareTest.php new file mode 100644 index 00000000..655c5bba --- /dev/null +++ b/tests/front/ShaarliMiddlewareTest.php | |||
@@ -0,0 +1,221 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Container\ShaarliContainer; | ||
9 | use Shaarli\Front\Exception\LoginBannedException; | ||
10 | use Shaarli\Front\Exception\UnauthorizedException; | ||
11 | use Shaarli\Render\PageBuilder; | ||
12 | use Shaarli\Render\PageCacheManager; | ||
13 | use Shaarli\Security\LoginManager; | ||
14 | use Shaarli\TestCase; | ||
15 | use Shaarli\Updater\Updater; | ||
16 | use Slim\Http\Request; | ||
17 | use Slim\Http\Response; | ||
18 | use Slim\Http\Uri; | ||
19 | |||
20 | class ShaarliMiddlewareTest extends TestCase | ||
21 | { | ||
22 | protected const TMP_MOCK_FILE = '.tmp'; | ||
23 | |||
24 | /** @var ShaarliContainer */ | ||
25 | protected $container; | ||
26 | |||
27 | /** @var ShaarliMiddleware */ | ||
28 | protected $middleware; | ||
29 | |||
30 | public function setUp(): void | ||
31 | { | ||
32 | $this->container = $this->createMock(ShaarliContainer::class); | ||
33 | |||
34 | touch(static::TMP_MOCK_FILE); | ||
35 | |||
36 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
37 | $this->container->conf->method('getConfigFileExt')->willReturn(static::TMP_MOCK_FILE); | ||
38 | |||
39 | $this->container->loginManager = $this->createMock(LoginManager::class); | ||
40 | |||
41 | $this->container->environment = ['REQUEST_URI' => 'http://shaarli/subfolder/path']; | ||
42 | |||
43 | $this->middleware = new ShaarliMiddleware($this->container); | ||
44 | } | ||
45 | |||
46 | public function tearDown(): void | ||
47 | { | ||
48 | unlink(static::TMP_MOCK_FILE); | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Test middleware execution with valid controller call | ||
53 | */ | ||
54 | public function testMiddlewareExecution(): void | ||
55 | { | ||
56 | $request = $this->createMock(Request::class); | ||
57 | $request->method('getUri')->willReturnCallback(function (): Uri { | ||
58 | $uri = $this->createMock(Uri::class); | ||
59 | $uri->method('getBasePath')->willReturn('/subfolder'); | ||
60 | |||
61 | return $uri; | ||
62 | }); | ||
63 | |||
64 | $response = new Response(); | ||
65 | $controller = function (Request $request, Response $response): Response { | ||
66 | return $response->withStatus(418); // I'm a tea pot | ||
67 | }; | ||
68 | |||
69 | /** @var Response $result */ | ||
70 | $result = $this->middleware->__invoke($request, $response, $controller); | ||
71 | |||
72 | static::assertInstanceOf(Response::class, $result); | ||
73 | static::assertSame(418, $result->getStatusCode()); | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * Test middleware execution with controller throwing a known front exception. | ||
78 | * The exception should be thrown to be later handled by the error handler. | ||
79 | */ | ||
80 | public function testMiddlewareExecutionWithFrontException(): void | ||
81 | { | ||
82 | $request = $this->createMock(Request::class); | ||
83 | $request->method('getUri')->willReturnCallback(function (): Uri { | ||
84 | $uri = $this->createMock(Uri::class); | ||
85 | $uri->method('getBasePath')->willReturn('/subfolder'); | ||
86 | |||
87 | return $uri; | ||
88 | }); | ||
89 | |||
90 | $response = new Response(); | ||
91 | $controller = function (): void { | ||
92 | $exception = new LoginBannedException(); | ||
93 | |||
94 | throw new $exception; | ||
95 | }; | ||
96 | |||
97 | $pageBuilder = $this->createMock(PageBuilder::class); | ||
98 | $pageBuilder->method('render')->willReturnCallback(function (string $message): string { | ||
99 | return $message; | ||
100 | }); | ||
101 | $this->container->pageBuilder = $pageBuilder; | ||
102 | |||
103 | $this->expectException(LoginBannedException::class); | ||
104 | |||
105 | $this->middleware->__invoke($request, $response, $controller); | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * Test middleware execution with controller throwing a not authorized exception | ||
110 | * The middle should send a redirection response to the login page. | ||
111 | */ | ||
112 | public function testMiddlewareExecutionWithUnauthorizedException(): void | ||
113 | { | ||
114 | $request = $this->createMock(Request::class); | ||
115 | $request->method('getUri')->willReturnCallback(function (): Uri { | ||
116 | $uri = $this->createMock(Uri::class); | ||
117 | $uri->method('getBasePath')->willReturn('/subfolder'); | ||
118 | |||
119 | return $uri; | ||
120 | }); | ||
121 | |||
122 | $response = new Response(); | ||
123 | $controller = function (): void { | ||
124 | throw new UnauthorizedException(); | ||
125 | }; | ||
126 | |||
127 | /** @var Response $result */ | ||
128 | $result = $this->middleware->__invoke($request, $response, $controller); | ||
129 | |||
130 | static::assertSame(302, $result->getStatusCode()); | ||
131 | static::assertSame( | ||
132 | '/subfolder/login?returnurl=' . urlencode('http://shaarli/subfolder/path'), | ||
133 | $result->getHeader('location')[0] | ||
134 | ); | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * Test middleware execution with controller throwing a not authorized exception. | ||
139 | * The exception should be thrown to be later handled by the error handler. | ||
140 | */ | ||
141 | public function testMiddlewareExecutionWithServerException(): void | ||
142 | { | ||
143 | $request = $this->createMock(Request::class); | ||
144 | $request->method('getUri')->willReturnCallback(function (): Uri { | ||
145 | $uri = $this->createMock(Uri::class); | ||
146 | $uri->method('getBasePath')->willReturn('/subfolder'); | ||
147 | |||
148 | return $uri; | ||
149 | }); | ||
150 | |||
151 | $dummyException = new class() extends \Exception {}; | ||
152 | |||
153 | $response = new Response(); | ||
154 | $controller = function () use ($dummyException): void { | ||
155 | throw $dummyException; | ||
156 | }; | ||
157 | |||
158 | $parameters = []; | ||
159 | $this->container->pageBuilder = $this->createMock(PageBuilder::class); | ||
160 | $this->container->pageBuilder->method('render')->willReturnCallback(function (string $message): string { | ||
161 | return $message; | ||
162 | }); | ||
163 | $this->container->pageBuilder | ||
164 | ->method('assign') | ||
165 | ->willReturnCallback(function (string $key, string $value) use (&$parameters): void { | ||
166 | $parameters[$key] = $value; | ||
167 | }) | ||
168 | ; | ||
169 | |||
170 | $this->expectException(get_class($dummyException)); | ||
171 | |||
172 | $this->middleware->__invoke($request, $response, $controller); | ||
173 | } | ||
174 | |||
175 | public function testMiddlewareExecutionWithUpdates(): void | ||
176 | { | ||
177 | $request = $this->createMock(Request::class); | ||
178 | $request->method('getUri')->willReturnCallback(function (): Uri { | ||
179 | $uri = $this->createMock(Uri::class); | ||
180 | $uri->method('getBasePath')->willReturn('/subfolder'); | ||
181 | |||
182 | return $uri; | ||
183 | }); | ||
184 | |||
185 | $response = new Response(); | ||
186 | $controller = function (Request $request, Response $response): Response { | ||
187 | return $response->withStatus(418); // I'm a tea pot | ||
188 | }; | ||
189 | |||
190 | $this->container->loginManager = $this->createMock(LoginManager::class); | ||
191 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
192 | |||
193 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
194 | $this->container->conf->method('get')->willReturnCallback(function (string $key): string { | ||
195 | return $key; | ||
196 | }); | ||
197 | $this->container->conf->method('getConfigFileExt')->willReturn(static::TMP_MOCK_FILE); | ||
198 | |||
199 | $this->container->pageCacheManager = $this->createMock(PageCacheManager::class); | ||
200 | $this->container->pageCacheManager->expects(static::once())->method('invalidateCaches'); | ||
201 | |||
202 | $this->container->updater = $this->createMock(Updater::class); | ||
203 | $this->container->updater | ||
204 | ->expects(static::once()) | ||
205 | ->method('update') | ||
206 | ->willReturn(['update123']) | ||
207 | ; | ||
208 | $this->container->updater->method('getDoneUpdates')->willReturn($updates = ['update123', 'other']); | ||
209 | $this->container->updater | ||
210 | ->expects(static::once()) | ||
211 | ->method('writeUpdates') | ||
212 | ->with('resource.updates', $updates) | ||
213 | ; | ||
214 | |||
215 | /** @var Response $result */ | ||
216 | $result = $this->middleware->__invoke($request, $response, $controller); | ||
217 | |||
218 | static::assertInstanceOf(Response::class, $result); | ||
219 | static::assertSame(418, $result->getStatusCode()); | ||
220 | } | ||
221 | } | ||
diff --git a/tests/front/controller/admin/ConfigureControllerTest.php b/tests/front/controller/admin/ConfigureControllerTest.php new file mode 100644 index 00000000..aca6cff3 --- /dev/null +++ b/tests/front/controller/admin/ConfigureControllerTest.php | |||
@@ -0,0 +1,252 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Front\Exception\WrongTokenException; | ||
9 | use Shaarli\Security\SessionManager; | ||
10 | use Shaarli\TestCase; | ||
11 | use Shaarli\Thumbnailer; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | class ConfigureControllerTest extends TestCase | ||
16 | { | ||
17 | use FrontAdminControllerMockHelper; | ||
18 | |||
19 | /** @var ConfigureController */ | ||
20 | protected $controller; | ||
21 | |||
22 | public function setUp(): void | ||
23 | { | ||
24 | $this->createContainer(); | ||
25 | |||
26 | $this->controller = new ConfigureController($this->container); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Test displaying configure page - it should display all config variables | ||
31 | */ | ||
32 | public function testIndex(): void | ||
33 | { | ||
34 | $assignedVariables = []; | ||
35 | $this->assignTemplateVars($assignedVariables); | ||
36 | |||
37 | $request = $this->createMock(Request::class); | ||
38 | $response = new Response(); | ||
39 | |||
40 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
41 | $this->container->conf->method('get')->willReturnCallback(function (string $key) { | ||
42 | return $key; | ||
43 | }); | ||
44 | |||
45 | $result = $this->controller->index($request, $response); | ||
46 | |||
47 | static::assertSame(200, $result->getStatusCode()); | ||
48 | static::assertSame('configure', (string) $result->getBody()); | ||
49 | |||
50 | static::assertSame('Configure - general.title', $assignedVariables['pagetitle']); | ||
51 | static::assertSame('general.title', $assignedVariables['title']); | ||
52 | static::assertSame('resource.theme', $assignedVariables['theme']); | ||
53 | static::assertEmpty($assignedVariables['theme_available']); | ||
54 | static::assertSame(['default', 'markdown'], $assignedVariables['formatter_available']); | ||
55 | static::assertNotEmpty($assignedVariables['continents']); | ||
56 | static::assertNotEmpty($assignedVariables['cities']); | ||
57 | static::assertSame('general.retrieve_description', $assignedVariables['retrieve_description']); | ||
58 | static::assertSame('privacy.default_private_links', $assignedVariables['private_links_default']); | ||
59 | static::assertSame('security.session_protection_disabled', $assignedVariables['session_protection_disabled']); | ||
60 | static::assertSame('feed.rss_permalinks', $assignedVariables['enable_rss_permalinks']); | ||
61 | static::assertSame('updates.check_updates', $assignedVariables['enable_update_check']); | ||
62 | static::assertSame('privacy.hide_public_links', $assignedVariables['hide_public_links']); | ||
63 | static::assertSame('api.enabled', $assignedVariables['api_enabled']); | ||
64 | static::assertSame('api.secret', $assignedVariables['api_secret']); | ||
65 | static::assertCount(5, $assignedVariables['languages']); | ||
66 | static::assertArrayHasKey('gd_enabled', $assignedVariables); | ||
67 | static::assertSame('thumbnails.mode', $assignedVariables['thumbnails_mode']); | ||
68 | } | ||
69 | |||
70 | /** | ||
71 | * Test posting a new config - make sure that everything is saved properly, without errors. | ||
72 | */ | ||
73 | public function testSaveNewConfig(): void | ||
74 | { | ||
75 | $session = []; | ||
76 | $this->assignSessionVars($session); | ||
77 | |||
78 | $parameters = [ | ||
79 | 'token' => 'token', | ||
80 | 'continent' => 'Europe', | ||
81 | 'city' => 'Moscow', | ||
82 | 'title' => 'Shaarli', | ||
83 | 'titleLink' => './', | ||
84 | 'retrieveDescription' => 'on', | ||
85 | 'theme' => 'vintage', | ||
86 | 'disablesessionprotection' => null, | ||
87 | 'privateLinkByDefault' => true, | ||
88 | 'enableRssPermalinks' => true, | ||
89 | 'updateCheck' => false, | ||
90 | 'hidePublicLinks' => 'on', | ||
91 | 'enableApi' => 'on', | ||
92 | 'apiSecret' => 'abcdef', | ||
93 | 'formatter' => 'markdown', | ||
94 | 'language' => 'fr', | ||
95 | 'enableThumbnails' => Thumbnailer::MODE_NONE, | ||
96 | ]; | ||
97 | |||
98 | $parametersConfigMapping = [ | ||
99 | 'general.timezone' => $parameters['continent'] . '/' . $parameters['city'], | ||
100 | 'general.title' => $parameters['title'], | ||
101 | 'general.header_link' => $parameters['titleLink'], | ||
102 | 'general.retrieve_description' => !!$parameters['retrieveDescription'], | ||
103 | 'resource.theme' => $parameters['theme'], | ||
104 | 'security.session_protection_disabled' => !!$parameters['disablesessionprotection'], | ||
105 | 'privacy.default_private_links' => !!$parameters['privateLinkByDefault'], | ||
106 | 'feed.rss_permalinks' => !!$parameters['enableRssPermalinks'], | ||
107 | 'updates.check_updates' => !!$parameters['updateCheck'], | ||
108 | 'privacy.hide_public_links' => !!$parameters['hidePublicLinks'], | ||
109 | 'api.enabled' => !!$parameters['enableApi'], | ||
110 | 'api.secret' => $parameters['apiSecret'], | ||
111 | 'formatter' => $parameters['formatter'], | ||
112 | 'translation.language' => $parameters['language'], | ||
113 | 'thumbnails.mode' => $parameters['enableThumbnails'], | ||
114 | ]; | ||
115 | |||
116 | $request = $this->createMock(Request::class); | ||
117 | $request | ||
118 | ->expects(static::atLeastOnce()) | ||
119 | ->method('getParam')->willReturnCallback(function (string $key) use ($parameters) { | ||
120 | if (false === array_key_exists($key, $parameters)) { | ||
121 | static::fail('unknown key: ' . $key); | ||
122 | } | ||
123 | |||
124 | return $parameters[$key]; | ||
125 | } | ||
126 | ); | ||
127 | |||
128 | $response = new Response(); | ||
129 | |||
130 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
131 | $this->container->conf | ||
132 | ->expects(static::atLeastOnce()) | ||
133 | ->method('set') | ||
134 | ->willReturnCallback(function (string $key, $value) use ($parametersConfigMapping): void { | ||
135 | if (false === array_key_exists($key, $parametersConfigMapping)) { | ||
136 | static::fail('unknown key: ' . $key); | ||
137 | } | ||
138 | |||
139 | static::assertSame($parametersConfigMapping[$key], $value); | ||
140 | } | ||
141 | ); | ||
142 | |||
143 | $result = $this->controller->save($request, $response); | ||
144 | static::assertSame(302, $result->getStatusCode()); | ||
145 | static::assertSame(['/subfolder/admin/configure'], $result->getHeader('Location')); | ||
146 | |||
147 | static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
148 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
149 | static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
150 | static::assertSame(['Configuration was saved.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * Test posting a new config - wrong token. | ||
155 | */ | ||
156 | public function testSaveNewConfigWrongToken(): void | ||
157 | { | ||
158 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
159 | $this->container->sessionManager->method('checkToken')->willReturn(false); | ||
160 | |||
161 | $this->container->conf->expects(static::never())->method('set'); | ||
162 | $this->container->conf->expects(static::never())->method('write'); | ||
163 | |||
164 | $request = $this->createMock(Request::class); | ||
165 | $response = new Response(); | ||
166 | |||
167 | $this->expectException(WrongTokenException::class); | ||
168 | |||
169 | $this->controller->save($request, $response); | ||
170 | } | ||
171 | |||
172 | /** | ||
173 | * Test posting a new config - thumbnail activation. | ||
174 | */ | ||
175 | public function testSaveNewConfigThumbnailsActivation(): void | ||
176 | { | ||
177 | $session = []; | ||
178 | $this->assignSessionVars($session); | ||
179 | |||
180 | $request = $this->createMock(Request::class); | ||
181 | $request | ||
182 | ->expects(static::atLeastOnce()) | ||
183 | ->method('getParam')->willReturnCallback(function (string $key) { | ||
184 | if ('enableThumbnails' === $key) { | ||
185 | return Thumbnailer::MODE_ALL; | ||
186 | } | ||
187 | |||
188 | return $key; | ||
189 | }) | ||
190 | ; | ||
191 | $response = new Response(); | ||
192 | |||
193 | $result = $this->controller->save($request, $response); | ||
194 | |||
195 | static::assertSame(302, $result->getStatusCode()); | ||
196 | static::assertSame(['/subfolder/admin/configure'], $result->getHeader('Location')); | ||
197 | |||
198 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
199 | static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
200 | static::assertStringContainsString( | ||
201 | 'You have enabled or changed thumbnails mode', | ||
202 | $session[SessionManager::KEY_WARNING_MESSAGES][0] | ||
203 | ); | ||
204 | static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
205 | static::assertSame(['Configuration was saved.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * Test posting a new config - thumbnail activation. | ||
210 | */ | ||
211 | public function testSaveNewConfigThumbnailsAlreadyActive(): void | ||
212 | { | ||
213 | $session = []; | ||
214 | $this->assignSessionVars($session); | ||
215 | |||
216 | $request = $this->createMock(Request::class); | ||
217 | $request | ||
218 | ->expects(static::atLeastOnce()) | ||
219 | ->method('getParam')->willReturnCallback(function (string $key) { | ||
220 | if ('enableThumbnails' === $key) { | ||
221 | return Thumbnailer::MODE_ALL; | ||
222 | } | ||
223 | |||
224 | return $key; | ||
225 | }) | ||
226 | ; | ||
227 | $response = new Response(); | ||
228 | |||
229 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
230 | $this->container->conf | ||
231 | ->expects(static::atLeastOnce()) | ||
232 | ->method('get') | ||
233 | ->willReturnCallback(function (string $key): string { | ||
234 | if ('thumbnails.mode' === $key) { | ||
235 | return Thumbnailer::MODE_ALL; | ||
236 | } | ||
237 | |||
238 | return $key; | ||
239 | }) | ||
240 | ; | ||
241 | |||
242 | $result = $this->controller->save($request, $response); | ||
243 | |||
244 | static::assertSame(302, $result->getStatusCode()); | ||
245 | static::assertSame(['/subfolder/admin/configure'], $result->getHeader('Location')); | ||
246 | |||
247 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
248 | static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
249 | static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
250 | static::assertSame(['Configuration was saved.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); | ||
251 | } | ||
252 | } | ||
diff --git a/tests/front/controller/admin/ExportControllerTest.php b/tests/front/controller/admin/ExportControllerTest.php new file mode 100644 index 00000000..0e6f2762 --- /dev/null +++ b/tests/front/controller/admin/ExportControllerTest.php | |||
@@ -0,0 +1,163 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Formatter\BookmarkFormatter; | ||
9 | use Shaarli\Formatter\BookmarkRawFormatter; | ||
10 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
11 | use Shaarli\Security\SessionManager; | ||
12 | use Shaarli\TestCase; | ||
13 | use Slim\Http\Request; | ||
14 | use Slim\Http\Response; | ||
15 | |||
16 | class ExportControllerTest extends TestCase | ||
17 | { | ||
18 | use FrontAdminControllerMockHelper; | ||
19 | |||
20 | /** @var ExportController */ | ||
21 | protected $controller; | ||
22 | |||
23 | public function setUp(): void | ||
24 | { | ||
25 | $this->createContainer(); | ||
26 | |||
27 | $this->controller = new ExportController($this->container); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Test displaying export page | ||
32 | */ | ||
33 | public function testIndex(): void | ||
34 | { | ||
35 | $assignedVariables = []; | ||
36 | $this->assignTemplateVars($assignedVariables); | ||
37 | |||
38 | $request = $this->createMock(Request::class); | ||
39 | $response = new Response(); | ||
40 | |||
41 | $result = $this->controller->index($request, $response); | ||
42 | |||
43 | static::assertSame(200, $result->getStatusCode()); | ||
44 | static::assertSame('export', (string) $result->getBody()); | ||
45 | |||
46 | static::assertSame('Export - Shaarli', $assignedVariables['pagetitle']); | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * Test posting an export request | ||
51 | */ | ||
52 | public function testExportDefault(): void | ||
53 | { | ||
54 | $assignedVariables = []; | ||
55 | $this->assignTemplateVars($assignedVariables); | ||
56 | |||
57 | $parameters = [ | ||
58 | 'selection' => 'all', | ||
59 | 'prepend_note_url' => 'on', | ||
60 | ]; | ||
61 | |||
62 | $request = $this->createMock(Request::class); | ||
63 | $request->method('getParam')->willReturnCallback(function (string $key) use ($parameters) { | ||
64 | return $parameters[$key] ?? null; | ||
65 | }); | ||
66 | $response = new Response(); | ||
67 | |||
68 | $bookmarks = [ | ||
69 | (new Bookmark())->setUrl('http://link1.tld')->setTitle('Title 1'), | ||
70 | (new Bookmark())->setUrl('http://link2.tld')->setTitle('Title 2'), | ||
71 | ]; | ||
72 | |||
73 | $this->container->netscapeBookmarkUtils = $this->createMock(NetscapeBookmarkUtils::class); | ||
74 | $this->container->netscapeBookmarkUtils | ||
75 | ->expects(static::once()) | ||
76 | ->method('filterAndFormat') | ||
77 | ->willReturnCallback( | ||
78 | function ( | ||
79 | BookmarkFormatter $formatter, | ||
80 | string $selection, | ||
81 | bool $prependNoteUrl, | ||
82 | string $indexUrl | ||
83 | ) use ($parameters, $bookmarks): array { | ||
84 | static::assertInstanceOf(BookmarkRawFormatter::class, $formatter); | ||
85 | static::assertSame($parameters['selection'], $selection); | ||
86 | static::assertTrue($prependNoteUrl); | ||
87 | static::assertSame('http://shaarli/subfolder/', $indexUrl); | ||
88 | |||
89 | return $bookmarks; | ||
90 | } | ||
91 | ) | ||
92 | ; | ||
93 | |||
94 | $result = $this->controller->export($request, $response); | ||
95 | |||
96 | static::assertSame(200, $result->getStatusCode()); | ||
97 | static::assertSame('export.bookmarks', (string) $result->getBody()); | ||
98 | static::assertSame(['text/html; charset=utf-8'], $result->getHeader('content-type')); | ||
99 | static::assertRegExp( | ||
100 | '/attachment; filename=bookmarks_all_[\d]{8}_[\d]{6}\.html/', | ||
101 | $result->getHeader('content-disposition')[0] | ||
102 | ); | ||
103 | |||
104 | static::assertNotEmpty($assignedVariables['date']); | ||
105 | static::assertSame(PHP_EOL, $assignedVariables['eol']); | ||
106 | static::assertSame('all', $assignedVariables['selection']); | ||
107 | static::assertSame($bookmarks, $assignedVariables['links']); | ||
108 | } | ||
109 | |||
110 | /** | ||
111 | * Test posting an export request - without selection parameter | ||
112 | */ | ||
113 | public function testExportSelectionMissing(): void | ||
114 | { | ||
115 | $request = $this->createMock(Request::class); | ||
116 | $response = new Response(); | ||
117 | |||
118 | $this->container->sessionManager | ||
119 | ->expects(static::once()) | ||
120 | ->method('setSessionParameter') | ||
121 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Please select an export mode.']) | ||
122 | ; | ||
123 | |||
124 | $result = $this->controller->export($request, $response); | ||
125 | |||
126 | static::assertSame(302, $result->getStatusCode()); | ||
127 | static::assertSame(['/subfolder/admin/export'], $result->getHeader('location')); | ||
128 | } | ||
129 | |||
130 | /** | ||
131 | * Test posting an export request - without selection parameter | ||
132 | */ | ||
133 | public function testExportErrorEncountered(): void | ||
134 | { | ||
135 | $parameters = [ | ||
136 | 'selection' => 'all', | ||
137 | ]; | ||
138 | |||
139 | $request = $this->createMock(Request::class); | ||
140 | $request->method('getParam')->willReturnCallback(function (string $key) use ($parameters) { | ||
141 | return $parameters[$key] ?? null; | ||
142 | }); | ||
143 | $response = new Response(); | ||
144 | |||
145 | $this->container->netscapeBookmarkUtils = $this->createMock(NetscapeBookmarkUtils::class); | ||
146 | $this->container->netscapeBookmarkUtils | ||
147 | ->expects(static::once()) | ||
148 | ->method('filterAndFormat') | ||
149 | ->willThrowException(new \Exception($message = 'error message')); | ||
150 | ; | ||
151 | |||
152 | $this->container->sessionManager | ||
153 | ->expects(static::once()) | ||
154 | ->method('setSessionParameter') | ||
155 | ->with(SessionManager::KEY_ERROR_MESSAGES, [$message]) | ||
156 | ; | ||
157 | |||
158 | $result = $this->controller->export($request, $response); | ||
159 | |||
160 | static::assertSame(302, $result->getStatusCode()); | ||
161 | static::assertSame(['/subfolder/admin/export'], $result->getHeader('location')); | ||
162 | } | ||
163 | } | ||
diff --git a/tests/front/controller/admin/FrontAdminControllerMockHelper.php b/tests/front/controller/admin/FrontAdminControllerMockHelper.php new file mode 100644 index 00000000..2b9f2ef1 --- /dev/null +++ b/tests/front/controller/admin/FrontAdminControllerMockHelper.php | |||
@@ -0,0 +1,56 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Container\ShaarliTestContainer; | ||
8 | use Shaarli\Front\Controller\Visitor\FrontControllerMockHelper; | ||
9 | use Shaarli\History; | ||
10 | |||
11 | /** | ||
12 | * Trait FrontControllerMockHelper | ||
13 | * | ||
14 | * Helper trait used to initialize the ShaarliContainer and mock its services for admin controller tests. | ||
15 | * | ||
16 | * @property ShaarliTestContainer $container | ||
17 | */ | ||
18 | trait FrontAdminControllerMockHelper | ||
19 | { | ||
20 | use FrontControllerMockHelper { | ||
21 | FrontControllerMockHelper::createContainer as parentCreateContainer; | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Mock the container instance | ||
26 | */ | ||
27 | protected function createContainer(): void | ||
28 | { | ||
29 | $this->parentCreateContainer(); | ||
30 | |||
31 | $this->container->history = $this->createMock(History::class); | ||
32 | |||
33 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
34 | $this->container->sessionManager->method('checkToken')->willReturn(true); | ||
35 | } | ||
36 | |||
37 | |||
38 | /** | ||
39 | * Pass a reference of an array which will be populated by `sessionManager->setSessionParameter` | ||
40 | * calls during execution. | ||
41 | * | ||
42 | * @param mixed $variables Array reference to populate. | ||
43 | */ | ||
44 | protected function assignSessionVars(array &$variables): void | ||
45 | { | ||
46 | $this->container->sessionManager | ||
47 | ->expects(static::atLeastOnce()) | ||
48 | ->method('setSessionParameter') | ||
49 | ->willReturnCallback(function ($key, $value) use (&$variables) { | ||
50 | $variables[$key] = $value; | ||
51 | |||
52 | return $this->container->sessionManager; | ||
53 | }) | ||
54 | ; | ||
55 | } | ||
56 | } | ||
diff --git a/tests/front/controller/admin/ImportControllerTest.php b/tests/front/controller/admin/ImportControllerTest.php new file mode 100644 index 00000000..c266caa5 --- /dev/null +++ b/tests/front/controller/admin/ImportControllerTest.php | |||
@@ -0,0 +1,148 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Psr\Http\Message\UploadedFileInterface; | ||
8 | use Shaarli\Netscape\NetscapeBookmarkUtils; | ||
9 | use Shaarli\Security\SessionManager; | ||
10 | use Shaarli\TestCase; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | use Slim\Http\UploadedFile; | ||
14 | |||
15 | class ImportControllerTest extends TestCase | ||
16 | { | ||
17 | use FrontAdminControllerMockHelper; | ||
18 | |||
19 | /** @var ImportController */ | ||
20 | protected $controller; | ||
21 | |||
22 | public function setUp(): void | ||
23 | { | ||
24 | $this->createContainer(); | ||
25 | |||
26 | $this->controller = new ImportController($this->container); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Test displaying import page | ||
31 | */ | ||
32 | public function testIndex(): void | ||
33 | { | ||
34 | $assignedVariables = []; | ||
35 | $this->assignTemplateVars($assignedVariables); | ||
36 | |||
37 | $request = $this->createMock(Request::class); | ||
38 | $response = new Response(); | ||
39 | |||
40 | $result = $this->controller->index($request, $response); | ||
41 | |||
42 | static::assertSame(200, $result->getStatusCode()); | ||
43 | static::assertSame('import', (string) $result->getBody()); | ||
44 | |||
45 | static::assertSame('Import - Shaarli', $assignedVariables['pagetitle']); | ||
46 | static::assertIsInt($assignedVariables['maxfilesize']); | ||
47 | static::assertRegExp('/\d+[KM]iB/', $assignedVariables['maxfilesizeHuman']); | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * Test importing a file with default and valid parameters | ||
52 | */ | ||
53 | public function testImportDefault(): void | ||
54 | { | ||
55 | $parameters = [ | ||
56 | 'abc' => 'def', | ||
57 | 'other' => 'param', | ||
58 | ]; | ||
59 | |||
60 | $requestFile = new UploadedFile('file', 'name', 'type', 123); | ||
61 | |||
62 | $request = $this->createMock(Request::class); | ||
63 | $request->method('getParams')->willReturnCallback(function () use ($parameters) { | ||
64 | return $parameters; | ||
65 | }); | ||
66 | $request->method('getUploadedFiles')->willReturn(['filetoupload' => $requestFile]); | ||
67 | $response = new Response(); | ||
68 | |||
69 | $this->container->netscapeBookmarkUtils = $this->createMock(NetscapeBookmarkUtils::class); | ||
70 | $this->container->netscapeBookmarkUtils | ||
71 | ->expects(static::once()) | ||
72 | ->method('import') | ||
73 | ->willReturnCallback( | ||
74 | function ( | ||
75 | array $post, | ||
76 | UploadedFileInterface $file | ||
77 | ) use ($parameters, $requestFile): string { | ||
78 | static::assertSame($parameters, $post); | ||
79 | static::assertSame($requestFile, $file); | ||
80 | |||
81 | return 'status'; | ||
82 | } | ||
83 | ) | ||
84 | ; | ||
85 | |||
86 | $this->container->sessionManager | ||
87 | ->expects(static::once()) | ||
88 | ->method('setSessionParameter') | ||
89 | ->with(SessionManager::KEY_SUCCESS_MESSAGES, ['status']) | ||
90 | ; | ||
91 | |||
92 | $result = $this->controller->import($request, $response); | ||
93 | |||
94 | static::assertSame(302, $result->getStatusCode()); | ||
95 | static::assertSame(['/subfolder/admin/import'], $result->getHeader('location')); | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * Test posting an import request - without import file | ||
100 | */ | ||
101 | public function testImportFileMissing(): void | ||
102 | { | ||
103 | $request = $this->createMock(Request::class); | ||
104 | $response = new Response(); | ||
105 | |||
106 | $this->container->sessionManager | ||
107 | ->expects(static::once()) | ||
108 | ->method('setSessionParameter') | ||
109 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['No import file provided.']) | ||
110 | ; | ||
111 | |||
112 | $result = $this->controller->import($request, $response); | ||
113 | |||
114 | static::assertSame(302, $result->getStatusCode()); | ||
115 | static::assertSame(['/subfolder/admin/import'], $result->getHeader('location')); | ||
116 | } | ||
117 | |||
118 | /** | ||
119 | * Test posting an import request - with an empty file | ||
120 | */ | ||
121 | public function testImportEmptyFile(): void | ||
122 | { | ||
123 | $requestFile = new UploadedFile('file', 'name', 'type', 0); | ||
124 | |||
125 | $request = $this->createMock(Request::class); | ||
126 | $request->method('getUploadedFiles')->willReturn(['filetoupload' => $requestFile]); | ||
127 | $response = new Response(); | ||
128 | |||
129 | $this->container->netscapeBookmarkUtils = $this->createMock(NetscapeBookmarkUtils::class); | ||
130 | $this->container->netscapeBookmarkUtils->expects(static::never())->method('filterAndFormat'); | ||
131 | |||
132 | $this->container->sessionManager | ||
133 | ->expects(static::once()) | ||
134 | ->method('setSessionParameter') | ||
135 | ->willReturnCallback(function (string $key, array $value): SessionManager { | ||
136 | static::assertSame(SessionManager::KEY_ERROR_MESSAGES, $key); | ||
137 | static::assertStringStartsWith('The file you are trying to upload is probably bigger', $value[0]); | ||
138 | |||
139 | return $this->container->sessionManager; | ||
140 | }) | ||
141 | ; | ||
142 | |||
143 | $result = $this->controller->import($request, $response); | ||
144 | |||
145 | static::assertSame(302, $result->getStatusCode()); | ||
146 | static::assertSame(['/subfolder/admin/import'], $result->getHeader('location')); | ||
147 | } | ||
148 | } | ||
diff --git a/tests/front/controller/admin/LogoutControllerTest.php b/tests/front/controller/admin/LogoutControllerTest.php new file mode 100644 index 00000000..94e53019 --- /dev/null +++ b/tests/front/controller/admin/LogoutControllerTest.php | |||
@@ -0,0 +1,50 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Security\CookieManager; | ||
8 | use Shaarli\Security\SessionManager; | ||
9 | use Shaarli\TestCase; | ||
10 | use Slim\Http\Request; | ||
11 | use Slim\Http\Response; | ||
12 | |||
13 | class LogoutControllerTest extends TestCase | ||
14 | { | ||
15 | use FrontAdminControllerMockHelper; | ||
16 | |||
17 | /** @var LogoutController */ | ||
18 | protected $controller; | ||
19 | |||
20 | public function setUp(): void | ||
21 | { | ||
22 | $this->createContainer(); | ||
23 | |||
24 | $this->controller = new LogoutController($this->container); | ||
25 | } | ||
26 | |||
27 | public function testValidControllerInvoke(): void | ||
28 | { | ||
29 | $request = $this->createMock(Request::class); | ||
30 | $response = new Response(); | ||
31 | |||
32 | $this->container->pageCacheManager->expects(static::once())->method('invalidateCaches'); | ||
33 | |||
34 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
35 | $this->container->sessionManager->expects(static::once())->method('logout'); | ||
36 | |||
37 | $this->container->cookieManager = $this->createMock(CookieManager::class); | ||
38 | $this->container->cookieManager | ||
39 | ->expects(static::once()) | ||
40 | ->method('setCookieParameter') | ||
41 | ->with(CookieManager::STAY_SIGNED_IN, 'false', 0, '/subfolder/') | ||
42 | ; | ||
43 | |||
44 | $result = $this->controller->index($request, $response); | ||
45 | |||
46 | static::assertInstanceOf(Response::class, $result); | ||
47 | static::assertSame(302, $result->getStatusCode()); | ||
48 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
49 | } | ||
50 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/AddShaareTest.php b/tests/front/controller/admin/ManageShaareControllerTest/AddShaareTest.php new file mode 100644 index 00000000..0f27ec2f --- /dev/null +++ b/tests/front/controller/admin/ManageShaareControllerTest/AddShaareTest.php | |||
@@ -0,0 +1,47 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
8 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
9 | use Shaarli\Http\HttpAccess; | ||
10 | use Shaarli\TestCase; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | |||
14 | class AddShaareTest extends TestCase | ||
15 | { | ||
16 | use FrontAdminControllerMockHelper; | ||
17 | |||
18 | /** @var ManageShaareController */ | ||
19 | protected $controller; | ||
20 | |||
21 | public function setUp(): void | ||
22 | { | ||
23 | $this->createContainer(); | ||
24 | |||
25 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
26 | $this->controller = new ManageShaareController($this->container); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Test displaying add link page | ||
31 | */ | ||
32 | public function testAddShaare(): void | ||
33 | { | ||
34 | $assignedVariables = []; | ||
35 | $this->assignTemplateVars($assignedVariables); | ||
36 | |||
37 | $request = $this->createMock(Request::class); | ||
38 | $response = new Response(); | ||
39 | |||
40 | $result = $this->controller->addShaare($request, $response); | ||
41 | |||
42 | static::assertSame(200, $result->getStatusCode()); | ||
43 | static::assertSame('addlink', (string) $result->getBody()); | ||
44 | |||
45 | static::assertSame('Shaare a new link - Shaarli', $assignedVariables['pagetitle']); | ||
46 | } | ||
47 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/ChangeVisibilityBookmarkTest.php b/tests/front/controller/admin/ManageShaareControllerTest/ChangeVisibilityBookmarkTest.php new file mode 100644 index 00000000..096d0774 --- /dev/null +++ b/tests/front/controller/admin/ManageShaareControllerTest/ChangeVisibilityBookmarkTest.php | |||
@@ -0,0 +1,418 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Formatter\BookmarkFormatter; | ||
10 | use Shaarli\Formatter\BookmarkRawFormatter; | ||
11 | use Shaarli\Formatter\FormatterFactory; | ||
12 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
13 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
14 | use Shaarli\Http\HttpAccess; | ||
15 | use Shaarli\Security\SessionManager; | ||
16 | use Shaarli\TestCase; | ||
17 | use Slim\Http\Request; | ||
18 | use Slim\Http\Response; | ||
19 | |||
20 | class ChangeVisibilityBookmarkTest extends TestCase | ||
21 | { | ||
22 | use FrontAdminControllerMockHelper; | ||
23 | |||
24 | /** @var ManageShaareController */ | ||
25 | protected $controller; | ||
26 | |||
27 | public function setUp(): void | ||
28 | { | ||
29 | $this->createContainer(); | ||
30 | |||
31 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
32 | $this->controller = new ManageShaareController($this->container); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Change bookmark visibility - Set private - Single public bookmark with valid parameters | ||
37 | */ | ||
38 | public function testSetSingleBookmarkPrivate(): void | ||
39 | { | ||
40 | $parameters = ['id' => '123', 'newVisibility' => 'private']; | ||
41 | |||
42 | $request = $this->createMock(Request::class); | ||
43 | $request | ||
44 | ->method('getParam') | ||
45 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
46 | return $parameters[$key] ?? null; | ||
47 | }) | ||
48 | ; | ||
49 | $response = new Response(); | ||
50 | |||
51 | $bookmark = (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123')->setPrivate(false); | ||
52 | |||
53 | static::assertFalse($bookmark->isPrivate()); | ||
54 | |||
55 | $this->container->bookmarkService->expects(static::once())->method('get')->with(123)->willReturn($bookmark); | ||
56 | $this->container->bookmarkService->expects(static::once())->method('set')->with($bookmark, false); | ||
57 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
58 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
59 | $this->container->formatterFactory | ||
60 | ->expects(static::once()) | ||
61 | ->method('getFormatter') | ||
62 | ->with('raw') | ||
63 | ->willReturnCallback(function () use ($bookmark): BookmarkFormatter { | ||
64 | return new BookmarkRawFormatter($this->container->conf, true); | ||
65 | }) | ||
66 | ; | ||
67 | |||
68 | // Make sure that PluginManager hook is triggered | ||
69 | $this->container->pluginManager | ||
70 | ->expects(static::once()) | ||
71 | ->method('executeHooks') | ||
72 | ->with('save_link') | ||
73 | ; | ||
74 | |||
75 | $result = $this->controller->changeVisibility($request, $response); | ||
76 | |||
77 | static::assertTrue($bookmark->isPrivate()); | ||
78 | |||
79 | static::assertSame(302, $result->getStatusCode()); | ||
80 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * Change bookmark visibility - Set public - Single private bookmark with valid parameters | ||
85 | */ | ||
86 | public function testSetSingleBookmarkPublic(): void | ||
87 | { | ||
88 | $parameters = ['id' => '123', 'newVisibility' => 'public']; | ||
89 | |||
90 | $request = $this->createMock(Request::class); | ||
91 | $request | ||
92 | ->method('getParam') | ||
93 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
94 | return $parameters[$key] ?? null; | ||
95 | }) | ||
96 | ; | ||
97 | $response = new Response(); | ||
98 | |||
99 | $bookmark = (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123')->setPrivate(true); | ||
100 | |||
101 | static::assertTrue($bookmark->isPrivate()); | ||
102 | |||
103 | $this->container->bookmarkService->expects(static::once())->method('get')->with(123)->willReturn($bookmark); | ||
104 | $this->container->bookmarkService->expects(static::once())->method('set')->with($bookmark, false); | ||
105 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
106 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
107 | $this->container->formatterFactory | ||
108 | ->expects(static::once()) | ||
109 | ->method('getFormatter') | ||
110 | ->with('raw') | ||
111 | ->willReturn(new BookmarkRawFormatter($this->container->conf, true)) | ||
112 | ; | ||
113 | |||
114 | // Make sure that PluginManager hook is triggered | ||
115 | $this->container->pluginManager | ||
116 | ->expects(static::once()) | ||
117 | ->method('executeHooks') | ||
118 | ->with('save_link') | ||
119 | ; | ||
120 | |||
121 | $result = $this->controller->changeVisibility($request, $response); | ||
122 | |||
123 | static::assertFalse($bookmark->isPrivate()); | ||
124 | |||
125 | static::assertSame(302, $result->getStatusCode()); | ||
126 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
127 | } | ||
128 | |||
129 | /** | ||
130 | * Change bookmark visibility - Set private on single already private bookmark | ||
131 | */ | ||
132 | public function testSetSinglePrivateBookmarkPrivate(): void | ||
133 | { | ||
134 | $parameters = ['id' => '123', 'newVisibility' => 'private']; | ||
135 | |||
136 | $request = $this->createMock(Request::class); | ||
137 | $request | ||
138 | ->method('getParam') | ||
139 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
140 | return $parameters[$key] ?? null; | ||
141 | }) | ||
142 | ; | ||
143 | $response = new Response(); | ||
144 | |||
145 | $bookmark = (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123')->setPrivate(true); | ||
146 | |||
147 | static::assertTrue($bookmark->isPrivate()); | ||
148 | |||
149 | $this->container->bookmarkService->expects(static::once())->method('get')->with(123)->willReturn($bookmark); | ||
150 | $this->container->bookmarkService->expects(static::once())->method('set')->with($bookmark, false); | ||
151 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
152 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
153 | $this->container->formatterFactory | ||
154 | ->expects(static::once()) | ||
155 | ->method('getFormatter') | ||
156 | ->with('raw') | ||
157 | ->willReturn(new BookmarkRawFormatter($this->container->conf, true)) | ||
158 | ; | ||
159 | |||
160 | // Make sure that PluginManager hook is triggered | ||
161 | $this->container->pluginManager | ||
162 | ->expects(static::once()) | ||
163 | ->method('executeHooks') | ||
164 | ->with('save_link') | ||
165 | ; | ||
166 | |||
167 | $result = $this->controller->changeVisibility($request, $response); | ||
168 | |||
169 | static::assertTrue($bookmark->isPrivate()); | ||
170 | |||
171 | static::assertSame(302, $result->getStatusCode()); | ||
172 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
173 | } | ||
174 | |||
175 | /** | ||
176 | * Change bookmark visibility - Set multiple bookmarks private | ||
177 | */ | ||
178 | public function testSetMultipleBookmarksPrivate(): void | ||
179 | { | ||
180 | $parameters = ['id' => '123 456 789', 'newVisibility' => 'private']; | ||
181 | |||
182 | $request = $this->createMock(Request::class); | ||
183 | $request | ||
184 | ->method('getParam') | ||
185 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
186 | return $parameters[$key] ?? null; | ||
187 | }) | ||
188 | ; | ||
189 | $response = new Response(); | ||
190 | |||
191 | $bookmarks = [ | ||
192 | (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123')->setPrivate(false), | ||
193 | (new Bookmark())->setId(456)->setUrl('http://domain.tld')->setTitle('Title 456')->setPrivate(true), | ||
194 | (new Bookmark())->setId(789)->setUrl('http://domain.tld')->setTitle('Title 789')->setPrivate(false), | ||
195 | ]; | ||
196 | |||
197 | $this->container->bookmarkService | ||
198 | ->expects(static::exactly(3)) | ||
199 | ->method('get') | ||
200 | ->withConsecutive([123], [456], [789]) | ||
201 | ->willReturnOnConsecutiveCalls(...$bookmarks) | ||
202 | ; | ||
203 | $this->container->bookmarkService | ||
204 | ->expects(static::exactly(3)) | ||
205 | ->method('set') | ||
206 | ->withConsecutive(...array_map(function (Bookmark $bookmark): array { | ||
207 | return [$bookmark, false]; | ||
208 | }, $bookmarks)) | ||
209 | ; | ||
210 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
211 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
212 | $this->container->formatterFactory | ||
213 | ->expects(static::once()) | ||
214 | ->method('getFormatter') | ||
215 | ->with('raw') | ||
216 | ->willReturn(new BookmarkRawFormatter($this->container->conf, true)) | ||
217 | ; | ||
218 | |||
219 | // Make sure that PluginManager hook is triggered | ||
220 | $this->container->pluginManager | ||
221 | ->expects(static::exactly(3)) | ||
222 | ->method('executeHooks') | ||
223 | ->with('save_link') | ||
224 | ; | ||
225 | |||
226 | $result = $this->controller->changeVisibility($request, $response); | ||
227 | |||
228 | static::assertTrue($bookmarks[0]->isPrivate()); | ||
229 | static::assertTrue($bookmarks[1]->isPrivate()); | ||
230 | static::assertTrue($bookmarks[2]->isPrivate()); | ||
231 | |||
232 | static::assertSame(302, $result->getStatusCode()); | ||
233 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
234 | } | ||
235 | |||
236 | /** | ||
237 | * Change bookmark visibility - Single bookmark not found. | ||
238 | */ | ||
239 | public function testChangeVisibilitySingleBookmarkNotFound(): void | ||
240 | { | ||
241 | $parameters = ['id' => '123', 'newVisibility' => 'private']; | ||
242 | |||
243 | $request = $this->createMock(Request::class); | ||
244 | $request | ||
245 | ->method('getParam') | ||
246 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
247 | return $parameters[$key] ?? null; | ||
248 | }) | ||
249 | ; | ||
250 | $response = new Response(); | ||
251 | |||
252 | $this->container->bookmarkService | ||
253 | ->expects(static::once()) | ||
254 | ->method('get') | ||
255 | ->willThrowException(new BookmarkNotFoundException()) | ||
256 | ; | ||
257 | $this->container->bookmarkService->expects(static::never())->method('set'); | ||
258 | $this->container->bookmarkService->expects(static::never())->method('save'); | ||
259 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
260 | $this->container->formatterFactory | ||
261 | ->expects(static::once()) | ||
262 | ->method('getFormatter') | ||
263 | ->with('raw') | ||
264 | ->willReturn(new BookmarkRawFormatter($this->container->conf, true)) | ||
265 | ; | ||
266 | |||
267 | // Make sure that PluginManager hook is not triggered | ||
268 | $this->container->pluginManager | ||
269 | ->expects(static::never()) | ||
270 | ->method('executeHooks') | ||
271 | ->with('save_link') | ||
272 | ; | ||
273 | |||
274 | $result = $this->controller->changeVisibility($request, $response); | ||
275 | |||
276 | static::assertSame(302, $result->getStatusCode()); | ||
277 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * Change bookmark visibility - Multiple bookmarks with one not found. | ||
282 | */ | ||
283 | public function testChangeVisibilityMultipleBookmarksOneNotFound(): void | ||
284 | { | ||
285 | $parameters = ['id' => '123 456 789', 'newVisibility' => 'public']; | ||
286 | |||
287 | $request = $this->createMock(Request::class); | ||
288 | $request | ||
289 | ->method('getParam') | ||
290 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
291 | return $parameters[$key] ?? null; | ||
292 | }) | ||
293 | ; | ||
294 | $response = new Response(); | ||
295 | |||
296 | $bookmarks = [ | ||
297 | (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123')->setPrivate(true), | ||
298 | (new Bookmark())->setId(789)->setUrl('http://domain.tld')->setTitle('Title 789')->setPrivate(false), | ||
299 | ]; | ||
300 | |||
301 | $this->container->bookmarkService | ||
302 | ->expects(static::exactly(3)) | ||
303 | ->method('get') | ||
304 | ->withConsecutive([123], [456], [789]) | ||
305 | ->willReturnCallback(function (int $id) use ($bookmarks): Bookmark { | ||
306 | if ($id === 123) { | ||
307 | return $bookmarks[0]; | ||
308 | } | ||
309 | if ($id === 789) { | ||
310 | return $bookmarks[1]; | ||
311 | } | ||
312 | throw new BookmarkNotFoundException(); | ||
313 | }) | ||
314 | ; | ||
315 | $this->container->bookmarkService | ||
316 | ->expects(static::exactly(2)) | ||
317 | ->method('set') | ||
318 | ->withConsecutive(...array_map(function (Bookmark $bookmark): array { | ||
319 | return [$bookmark, false]; | ||
320 | }, $bookmarks)) | ||
321 | ; | ||
322 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
323 | |||
324 | // Make sure that PluginManager hook is not triggered | ||
325 | $this->container->pluginManager | ||
326 | ->expects(static::exactly(2)) | ||
327 | ->method('executeHooks') | ||
328 | ->with('save_link') | ||
329 | ; | ||
330 | |||
331 | $this->container->sessionManager | ||
332 | ->expects(static::once()) | ||
333 | ->method('setSessionParameter') | ||
334 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Bookmark with identifier 456 could not be found.']) | ||
335 | ; | ||
336 | |||
337 | $result = $this->controller->changeVisibility($request, $response); | ||
338 | |||
339 | static::assertSame(302, $result->getStatusCode()); | ||
340 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
341 | } | ||
342 | |||
343 | /** | ||
344 | * Change bookmark visibility - Invalid ID | ||
345 | */ | ||
346 | public function testChangeVisibilityInvalidId(): void | ||
347 | { | ||
348 | $parameters = ['id' => 'nope not an ID', 'newVisibility' => 'private']; | ||
349 | |||
350 | $request = $this->createMock(Request::class); | ||
351 | $request | ||
352 | ->method('getParam') | ||
353 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
354 | return $parameters[$key] ?? null; | ||
355 | }) | ||
356 | ; | ||
357 | $response = new Response(); | ||
358 | |||
359 | $this->container->sessionManager | ||
360 | ->expects(static::once()) | ||
361 | ->method('setSessionParameter') | ||
362 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Invalid bookmark ID provided.']) | ||
363 | ; | ||
364 | |||
365 | $result = $this->controller->changeVisibility($request, $response); | ||
366 | |||
367 | static::assertSame(302, $result->getStatusCode()); | ||
368 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
369 | } | ||
370 | |||
371 | /** | ||
372 | * Change bookmark visibility - Empty ID | ||
373 | */ | ||
374 | public function testChangeVisibilityEmptyId(): void | ||
375 | { | ||
376 | $request = $this->createMock(Request::class); | ||
377 | $response = new Response(); | ||
378 | |||
379 | $this->container->sessionManager | ||
380 | ->expects(static::once()) | ||
381 | ->method('setSessionParameter') | ||
382 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Invalid bookmark ID provided.']) | ||
383 | ; | ||
384 | |||
385 | $result = $this->controller->changeVisibility($request, $response); | ||
386 | |||
387 | static::assertSame(302, $result->getStatusCode()); | ||
388 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
389 | } | ||
390 | |||
391 | /** | ||
392 | * Change bookmark visibility - with invalid visibility | ||
393 | */ | ||
394 | public function testChangeVisibilityWithInvalidVisibility(): void | ||
395 | { | ||
396 | $parameters = ['id' => '123', 'newVisibility' => 'invalid']; | ||
397 | |||
398 | $request = $this->createMock(Request::class); | ||
399 | $request | ||
400 | ->method('getParam') | ||
401 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
402 | return $parameters[$key] ?? null; | ||
403 | }) | ||
404 | ; | ||
405 | $response = new Response(); | ||
406 | |||
407 | $this->container->sessionManager | ||
408 | ->expects(static::once()) | ||
409 | ->method('setSessionParameter') | ||
410 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Invalid visibility provided.']) | ||
411 | ; | ||
412 | |||
413 | $result = $this->controller->changeVisibility($request, $response); | ||
414 | |||
415 | static::assertSame(302, $result->getStatusCode()); | ||
416 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
417 | } | ||
418 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php b/tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php new file mode 100644 index 00000000..ba774e21 --- /dev/null +++ b/tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php | |||
@@ -0,0 +1,376 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Formatter\BookmarkFormatter; | ||
10 | use Shaarli\Formatter\FormatterFactory; | ||
11 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
12 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
13 | use Shaarli\Http\HttpAccess; | ||
14 | use Shaarli\Security\SessionManager; | ||
15 | use Shaarli\TestCase; | ||
16 | use Slim\Http\Request; | ||
17 | use Slim\Http\Response; | ||
18 | |||
19 | class DeleteBookmarkTest extends TestCase | ||
20 | { | ||
21 | use FrontAdminControllerMockHelper; | ||
22 | |||
23 | /** @var ManageShaareController */ | ||
24 | protected $controller; | ||
25 | |||
26 | public function setUp(): void | ||
27 | { | ||
28 | $this->createContainer(); | ||
29 | |||
30 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
31 | $this->controller = new ManageShaareController($this->container); | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Delete bookmark - Single bookmark with valid parameters | ||
36 | */ | ||
37 | public function testDeleteSingleBookmark(): void | ||
38 | { | ||
39 | $parameters = ['id' => '123']; | ||
40 | |||
41 | $request = $this->createMock(Request::class); | ||
42 | $request | ||
43 | ->method('getParam') | ||
44 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
45 | return $parameters[$key] ?? null; | ||
46 | }) | ||
47 | ; | ||
48 | $response = new Response(); | ||
49 | |||
50 | $bookmark = (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123'); | ||
51 | |||
52 | $this->container->bookmarkService->expects(static::once())->method('get')->with(123)->willReturn($bookmark); | ||
53 | $this->container->bookmarkService->expects(static::once())->method('remove')->with($bookmark, false); | ||
54 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
55 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
56 | $this->container->formatterFactory | ||
57 | ->expects(static::once()) | ||
58 | ->method('getFormatter') | ||
59 | ->with('raw') | ||
60 | ->willReturnCallback(function () use ($bookmark): BookmarkFormatter { | ||
61 | $formatter = $this->createMock(BookmarkFormatter::class); | ||
62 | $formatter | ||
63 | ->expects(static::once()) | ||
64 | ->method('format') | ||
65 | ->with($bookmark) | ||
66 | ->willReturn(['formatted' => $bookmark]) | ||
67 | ; | ||
68 | |||
69 | return $formatter; | ||
70 | }) | ||
71 | ; | ||
72 | |||
73 | // Make sure that PluginManager hook is triggered | ||
74 | $this->container->pluginManager | ||
75 | ->expects(static::once()) | ||
76 | ->method('executeHooks') | ||
77 | ->with('delete_link', ['formatted' => $bookmark]) | ||
78 | ; | ||
79 | |||
80 | $result = $this->controller->deleteBookmark($request, $response); | ||
81 | |||
82 | static::assertSame(302, $result->getStatusCode()); | ||
83 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * Delete bookmark - Multiple bookmarks with valid parameters | ||
88 | */ | ||
89 | public function testDeleteMultipleBookmarks(): void | ||
90 | { | ||
91 | $parameters = ['id' => '123 456 789']; | ||
92 | |||
93 | $request = $this->createMock(Request::class); | ||
94 | $request | ||
95 | ->method('getParam') | ||
96 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
97 | return $parameters[$key] ?? null; | ||
98 | }) | ||
99 | ; | ||
100 | $response = new Response(); | ||
101 | |||
102 | $bookmarks = [ | ||
103 | (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123'), | ||
104 | (new Bookmark())->setId(456)->setUrl('http://domain.tld')->setTitle('Title 456'), | ||
105 | (new Bookmark())->setId(789)->setUrl('http://domain.tld')->setTitle('Title 789'), | ||
106 | ]; | ||
107 | |||
108 | $this->container->bookmarkService | ||
109 | ->expects(static::exactly(3)) | ||
110 | ->method('get') | ||
111 | ->withConsecutive([123], [456], [789]) | ||
112 | ->willReturnOnConsecutiveCalls(...$bookmarks) | ||
113 | ; | ||
114 | $this->container->bookmarkService | ||
115 | ->expects(static::exactly(3)) | ||
116 | ->method('remove') | ||
117 | ->withConsecutive(...array_map(function (Bookmark $bookmark): array { | ||
118 | return [$bookmark, false]; | ||
119 | }, $bookmarks)) | ||
120 | ; | ||
121 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
122 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
123 | $this->container->formatterFactory | ||
124 | ->expects(static::once()) | ||
125 | ->method('getFormatter') | ||
126 | ->with('raw') | ||
127 | ->willReturnCallback(function () use ($bookmarks): BookmarkFormatter { | ||
128 | $formatter = $this->createMock(BookmarkFormatter::class); | ||
129 | |||
130 | $formatter | ||
131 | ->expects(static::exactly(3)) | ||
132 | ->method('format') | ||
133 | ->withConsecutive(...array_map(function (Bookmark $bookmark): array { | ||
134 | return [$bookmark]; | ||
135 | }, $bookmarks)) | ||
136 | ->willReturnOnConsecutiveCalls(...array_map(function (Bookmark $bookmark): array { | ||
137 | return ['formatted' => $bookmark]; | ||
138 | }, $bookmarks)) | ||
139 | ; | ||
140 | |||
141 | return $formatter; | ||
142 | }) | ||
143 | ; | ||
144 | |||
145 | // Make sure that PluginManager hook is triggered | ||
146 | $this->container->pluginManager | ||
147 | ->expects(static::exactly(3)) | ||
148 | ->method('executeHooks') | ||
149 | ->with('delete_link') | ||
150 | ; | ||
151 | |||
152 | $result = $this->controller->deleteBookmark($request, $response); | ||
153 | |||
154 | static::assertSame(302, $result->getStatusCode()); | ||
155 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Delete bookmark - Single bookmark not found in the data store | ||
160 | */ | ||
161 | public function testDeleteSingleBookmarkNotFound(): void | ||
162 | { | ||
163 | $parameters = ['id' => '123']; | ||
164 | |||
165 | $request = $this->createMock(Request::class); | ||
166 | $request | ||
167 | ->method('getParam') | ||
168 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
169 | return $parameters[$key] ?? null; | ||
170 | }) | ||
171 | ; | ||
172 | $response = new Response(); | ||
173 | |||
174 | $this->container->bookmarkService | ||
175 | ->expects(static::once()) | ||
176 | ->method('get') | ||
177 | ->willThrowException(new BookmarkNotFoundException()) | ||
178 | ; | ||
179 | $this->container->bookmarkService->expects(static::never())->method('remove'); | ||
180 | $this->container->bookmarkService->expects(static::never())->method('save'); | ||
181 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
182 | $this->container->formatterFactory | ||
183 | ->expects(static::once()) | ||
184 | ->method('getFormatter') | ||
185 | ->with('raw') | ||
186 | ->willReturnCallback(function (): BookmarkFormatter { | ||
187 | $formatter = $this->createMock(BookmarkFormatter::class); | ||
188 | |||
189 | $formatter->expects(static::never())->method('format'); | ||
190 | |||
191 | return $formatter; | ||
192 | }) | ||
193 | ; | ||
194 | // Make sure that PluginManager hook is not triggered | ||
195 | $this->container->pluginManager | ||
196 | ->expects(static::never()) | ||
197 | ->method('executeHooks') | ||
198 | ->with('delete_link') | ||
199 | ; | ||
200 | |||
201 | $result = $this->controller->deleteBookmark($request, $response); | ||
202 | |||
203 | static::assertSame(302, $result->getStatusCode()); | ||
204 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
205 | } | ||
206 | |||
207 | /** | ||
208 | * Delete bookmark - Multiple bookmarks with one not found in the data store | ||
209 | */ | ||
210 | public function testDeleteMultipleBookmarksOneNotFound(): void | ||
211 | { | ||
212 | $parameters = ['id' => '123 456 789']; | ||
213 | |||
214 | $request = $this->createMock(Request::class); | ||
215 | $request | ||
216 | ->method('getParam') | ||
217 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
218 | return $parameters[$key] ?? null; | ||
219 | }) | ||
220 | ; | ||
221 | $response = new Response(); | ||
222 | |||
223 | $bookmarks = [ | ||
224 | (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123'), | ||
225 | (new Bookmark())->setId(789)->setUrl('http://domain.tld')->setTitle('Title 789'), | ||
226 | ]; | ||
227 | |||
228 | $this->container->bookmarkService | ||
229 | ->expects(static::exactly(3)) | ||
230 | ->method('get') | ||
231 | ->withConsecutive([123], [456], [789]) | ||
232 | ->willReturnCallback(function (int $id) use ($bookmarks): Bookmark { | ||
233 | if ($id === 123) { | ||
234 | return $bookmarks[0]; | ||
235 | } | ||
236 | if ($id === 789) { | ||
237 | return $bookmarks[1]; | ||
238 | } | ||
239 | throw new BookmarkNotFoundException(); | ||
240 | }) | ||
241 | ; | ||
242 | $this->container->bookmarkService | ||
243 | ->expects(static::exactly(2)) | ||
244 | ->method('remove') | ||
245 | ->withConsecutive(...array_map(function (Bookmark $bookmark): array { | ||
246 | return [$bookmark, false]; | ||
247 | }, $bookmarks)) | ||
248 | ; | ||
249 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
250 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
251 | $this->container->formatterFactory | ||
252 | ->expects(static::once()) | ||
253 | ->method('getFormatter') | ||
254 | ->with('raw') | ||
255 | ->willReturnCallback(function () use ($bookmarks): BookmarkFormatter { | ||
256 | $formatter = $this->createMock(BookmarkFormatter::class); | ||
257 | |||
258 | $formatter | ||
259 | ->expects(static::exactly(2)) | ||
260 | ->method('format') | ||
261 | ->withConsecutive(...array_map(function (Bookmark $bookmark): array { | ||
262 | return [$bookmark]; | ||
263 | }, $bookmarks)) | ||
264 | ->willReturnOnConsecutiveCalls(...array_map(function (Bookmark $bookmark): array { | ||
265 | return ['formatted' => $bookmark]; | ||
266 | }, $bookmarks)) | ||
267 | ; | ||
268 | |||
269 | return $formatter; | ||
270 | }) | ||
271 | ; | ||
272 | |||
273 | // Make sure that PluginManager hook is not triggered | ||
274 | $this->container->pluginManager | ||
275 | ->expects(static::exactly(2)) | ||
276 | ->method('executeHooks') | ||
277 | ->with('delete_link') | ||
278 | ; | ||
279 | |||
280 | $this->container->sessionManager | ||
281 | ->expects(static::once()) | ||
282 | ->method('setSessionParameter') | ||
283 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Bookmark with identifier 456 could not be found.']) | ||
284 | ; | ||
285 | |||
286 | $result = $this->controller->deleteBookmark($request, $response); | ||
287 | |||
288 | static::assertSame(302, $result->getStatusCode()); | ||
289 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
290 | } | ||
291 | |||
292 | /** | ||
293 | * Delete bookmark - Invalid ID | ||
294 | */ | ||
295 | public function testDeleteInvalidId(): void | ||
296 | { | ||
297 | $parameters = ['id' => 'nope not an ID']; | ||
298 | |||
299 | $request = $this->createMock(Request::class); | ||
300 | $request | ||
301 | ->method('getParam') | ||
302 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
303 | return $parameters[$key] ?? null; | ||
304 | }) | ||
305 | ; | ||
306 | $response = new Response(); | ||
307 | |||
308 | $this->container->sessionManager | ||
309 | ->expects(static::once()) | ||
310 | ->method('setSessionParameter') | ||
311 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Invalid bookmark ID provided.']) | ||
312 | ; | ||
313 | |||
314 | $result = $this->controller->deleteBookmark($request, $response); | ||
315 | |||
316 | static::assertSame(302, $result->getStatusCode()); | ||
317 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
318 | } | ||
319 | |||
320 | /** | ||
321 | * Delete bookmark - Empty ID | ||
322 | */ | ||
323 | public function testDeleteEmptyId(): void | ||
324 | { | ||
325 | $request = $this->createMock(Request::class); | ||
326 | $response = new Response(); | ||
327 | |||
328 | $this->container->sessionManager | ||
329 | ->expects(static::once()) | ||
330 | ->method('setSessionParameter') | ||
331 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Invalid bookmark ID provided.']) | ||
332 | ; | ||
333 | |||
334 | $result = $this->controller->deleteBookmark($request, $response); | ||
335 | |||
336 | static::assertSame(302, $result->getStatusCode()); | ||
337 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
338 | } | ||
339 | |||
340 | /** | ||
341 | * Delete bookmark - from bookmarklet | ||
342 | */ | ||
343 | public function testDeleteBookmarkFromBookmarklet(): void | ||
344 | { | ||
345 | $parameters = [ | ||
346 | 'id' => '123', | ||
347 | 'source' => 'bookmarklet', | ||
348 | ]; | ||
349 | |||
350 | $request = $this->createMock(Request::class); | ||
351 | $request | ||
352 | ->method('getParam') | ||
353 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
354 | return $parameters[$key] ?? null; | ||
355 | }) | ||
356 | ; | ||
357 | $response = new Response(); | ||
358 | |||
359 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
360 | $this->container->formatterFactory | ||
361 | ->expects(static::once()) | ||
362 | ->method('getFormatter') | ||
363 | ->willReturnCallback(function (): BookmarkFormatter { | ||
364 | $formatter = $this->createMock(BookmarkFormatter::class); | ||
365 | $formatter->method('format')->willReturn(['formatted']); | ||
366 | |||
367 | return $formatter; | ||
368 | }) | ||
369 | ; | ||
370 | |||
371 | $result = $this->controller->deleteBookmark($request, $response); | ||
372 | |||
373 | static::assertSame(200, $result->getStatusCode()); | ||
374 | static::assertSame('<script>self.close();</script>', (string) $result->getBody('location')); | ||
375 | } | ||
376 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/DisplayCreateFormTest.php b/tests/front/controller/admin/ManageShaareControllerTest/DisplayCreateFormTest.php new file mode 100644 index 00000000..2eb95251 --- /dev/null +++ b/tests/front/controller/admin/ManageShaareControllerTest/DisplayCreateFormTest.php | |||
@@ -0,0 +1,317 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
10 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
11 | use Shaarli\Http\HttpAccess; | ||
12 | use Shaarli\TestCase; | ||
13 | use Slim\Http\Request; | ||
14 | use Slim\Http\Response; | ||
15 | |||
16 | class DisplayCreateFormTest extends TestCase | ||
17 | { | ||
18 | use FrontAdminControllerMockHelper; | ||
19 | |||
20 | /** @var ManageShaareController */ | ||
21 | protected $controller; | ||
22 | |||
23 | public function setUp(): void | ||
24 | { | ||
25 | $this->createContainer(); | ||
26 | |||
27 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
28 | $this->controller = new ManageShaareController($this->container); | ||
29 | } | ||
30 | |||
31 | /** | ||
32 | * Test displaying bookmark create form | ||
33 | * Ensure that every step of the standard workflow works properly. | ||
34 | */ | ||
35 | public function testDisplayCreateFormWithUrl(): void | ||
36 | { | ||
37 | $this->container->environment = [ | ||
38 | 'HTTP_REFERER' => $referer = 'http://shaarli/subfolder/controller/?searchtag=abc' | ||
39 | ]; | ||
40 | |||
41 | $assignedVariables = []; | ||
42 | $this->assignTemplateVars($assignedVariables); | ||
43 | |||
44 | $url = 'http://url.tld/other?part=3&utm_ad=pay#hash'; | ||
45 | $expectedUrl = str_replace('&utm_ad=pay', '', $url); | ||
46 | $remoteTitle = 'Remote Title'; | ||
47 | $remoteDesc = 'Sometimes the meta description is relevant.'; | ||
48 | $remoteTags = 'abc def'; | ||
49 | |||
50 | $request = $this->createMock(Request::class); | ||
51 | $request->method('getParam')->willReturnCallback(function (string $key) use ($url): ?string { | ||
52 | return $key === 'post' ? $url : null; | ||
53 | }); | ||
54 | $response = new Response(); | ||
55 | |||
56 | $this->container->httpAccess | ||
57 | ->expects(static::once()) | ||
58 | ->method('getCurlDownloadCallback') | ||
59 | ->willReturnCallback( | ||
60 | function (&$charset, &$title, &$description, &$tags) use ( | ||
61 | $remoteTitle, | ||
62 | $remoteDesc, | ||
63 | $remoteTags | ||
64 | ): callable { | ||
65 | return function () use ( | ||
66 | &$charset, | ||
67 | &$title, | ||
68 | &$description, | ||
69 | &$tags, | ||
70 | $remoteTitle, | ||
71 | $remoteDesc, | ||
72 | $remoteTags | ||
73 | ): void { | ||
74 | $charset = 'ISO-8859-1'; | ||
75 | $title = $remoteTitle; | ||
76 | $description = $remoteDesc; | ||
77 | $tags = $remoteTags; | ||
78 | }; | ||
79 | } | ||
80 | ) | ||
81 | ; | ||
82 | $this->container->httpAccess | ||
83 | ->expects(static::once()) | ||
84 | ->method('getHttpResponse') | ||
85 | ->with($expectedUrl, 30, 4194304) | ||
86 | ->willReturnCallback(function($url, $timeout, $maxBytes, $callback): void { | ||
87 | $callback(); | ||
88 | }) | ||
89 | ; | ||
90 | |||
91 | $this->container->bookmarkService | ||
92 | ->expects(static::once()) | ||
93 | ->method('bookmarksCountPerTag') | ||
94 | ->willReturn($tags = ['tag1' => 2, 'tag2' => 1]) | ||
95 | ; | ||
96 | |||
97 | // Make sure that PluginManager hook is triggered | ||
98 | $this->container->pluginManager | ||
99 | ->expects(static::atLeastOnce()) | ||
100 | ->method('executeHooks') | ||
101 | ->withConsecutive(['render_editlink'], ['render_includes']) | ||
102 | ->willReturnCallback(function (string $hook, array $data) use ($remoteTitle, $remoteDesc): array { | ||
103 | if ('render_editlink' === $hook) { | ||
104 | static::assertSame($remoteTitle, $data['link']['title']); | ||
105 | static::assertSame($remoteDesc, $data['link']['description']); | ||
106 | } | ||
107 | |||
108 | return $data; | ||
109 | }) | ||
110 | ; | ||
111 | |||
112 | $result = $this->controller->displayCreateForm($request, $response); | ||
113 | |||
114 | static::assertSame(200, $result->getStatusCode()); | ||
115 | static::assertSame('editlink', (string) $result->getBody()); | ||
116 | |||
117 | static::assertSame('Shaare - Shaarli', $assignedVariables['pagetitle']); | ||
118 | |||
119 | static::assertSame($expectedUrl, $assignedVariables['link']['url']); | ||
120 | static::assertSame($remoteTitle, $assignedVariables['link']['title']); | ||
121 | static::assertSame($remoteDesc, $assignedVariables['link']['description']); | ||
122 | static::assertSame($remoteTags, $assignedVariables['link']['tags']); | ||
123 | static::assertFalse($assignedVariables['link']['private']); | ||
124 | |||
125 | static::assertTrue($assignedVariables['link_is_new']); | ||
126 | static::assertSame($referer, $assignedVariables['http_referer']); | ||
127 | static::assertSame($tags, $assignedVariables['tags']); | ||
128 | static::assertArrayHasKey('source', $assignedVariables); | ||
129 | static::assertArrayHasKey('default_private_links', $assignedVariables); | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * Test displaying bookmark create form | ||
134 | * Ensure all available query parameters are handled properly. | ||
135 | */ | ||
136 | public function testDisplayCreateFormWithFullParameters(): void | ||
137 | { | ||
138 | $assignedVariables = []; | ||
139 | $this->assignTemplateVars($assignedVariables); | ||
140 | |||
141 | $parameters = [ | ||
142 | 'post' => 'http://url.tld/other?part=3&utm_ad=pay#hash', | ||
143 | 'title' => 'Provided Title', | ||
144 | 'description' => 'Provided description.', | ||
145 | 'tags' => 'abc def', | ||
146 | 'private' => '1', | ||
147 | 'source' => 'apps', | ||
148 | ]; | ||
149 | $expectedUrl = str_replace('&utm_ad=pay', '', $parameters['post']); | ||
150 | |||
151 | $request = $this->createMock(Request::class); | ||
152 | $request | ||
153 | ->method('getParam') | ||
154 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
155 | return $parameters[$key] ?? null; | ||
156 | }); | ||
157 | $response = new Response(); | ||
158 | |||
159 | $result = $this->controller->displayCreateForm($request, $response); | ||
160 | |||
161 | static::assertSame(200, $result->getStatusCode()); | ||
162 | static::assertSame('editlink', (string) $result->getBody()); | ||
163 | |||
164 | static::assertSame('Shaare - Shaarli', $assignedVariables['pagetitle']); | ||
165 | |||
166 | static::assertSame($expectedUrl, $assignedVariables['link']['url']); | ||
167 | static::assertSame($parameters['title'], $assignedVariables['link']['title']); | ||
168 | static::assertSame($parameters['description'], $assignedVariables['link']['description']); | ||
169 | static::assertSame($parameters['tags'], $assignedVariables['link']['tags']); | ||
170 | static::assertTrue($assignedVariables['link']['private']); | ||
171 | static::assertTrue($assignedVariables['link_is_new']); | ||
172 | static::assertSame($parameters['source'], $assignedVariables['source']); | ||
173 | } | ||
174 | |||
175 | /** | ||
176 | * Test displaying bookmark create form | ||
177 | * Without any parameter. | ||
178 | */ | ||
179 | public function testDisplayCreateFormEmpty(): void | ||
180 | { | ||
181 | $assignedVariables = []; | ||
182 | $this->assignTemplateVars($assignedVariables); | ||
183 | |||
184 | $request = $this->createMock(Request::class); | ||
185 | $response = new Response(); | ||
186 | |||
187 | $this->container->httpAccess->expects(static::never())->method('getHttpResponse'); | ||
188 | $this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback'); | ||
189 | |||
190 | $result = $this->controller->displayCreateForm($request, $response); | ||
191 | |||
192 | static::assertSame(200, $result->getStatusCode()); | ||
193 | static::assertSame('editlink', (string) $result->getBody()); | ||
194 | static::assertSame('', $assignedVariables['link']['url']); | ||
195 | static::assertSame('Note: ', $assignedVariables['link']['title']); | ||
196 | static::assertSame('', $assignedVariables['link']['description']); | ||
197 | static::assertSame('', $assignedVariables['link']['tags']); | ||
198 | static::assertFalse($assignedVariables['link']['private']); | ||
199 | static::assertTrue($assignedVariables['link_is_new']); | ||
200 | } | ||
201 | |||
202 | /** | ||
203 | * Test displaying bookmark create form | ||
204 | * URL not using HTTP protocol: do not try to retrieve the title | ||
205 | */ | ||
206 | public function testDisplayCreateFormNotHttp(): void | ||
207 | { | ||
208 | $assignedVariables = []; | ||
209 | $this->assignTemplateVars($assignedVariables); | ||
210 | |||
211 | $url = 'magnet://kubuntu.torrent'; | ||
212 | $request = $this->createMock(Request::class); | ||
213 | $request | ||
214 | ->method('getParam') | ||
215 | ->willReturnCallback(function (string $key) use ($url): ?string { | ||
216 | return $key === 'post' ? $url : null; | ||
217 | }); | ||
218 | $response = new Response(); | ||
219 | |||
220 | $this->container->httpAccess->expects(static::never())->method('getHttpResponse'); | ||
221 | $this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback'); | ||
222 | |||
223 | $result = $this->controller->displayCreateForm($request, $response); | ||
224 | |||
225 | static::assertSame(200, $result->getStatusCode()); | ||
226 | static::assertSame('editlink', (string) $result->getBody()); | ||
227 | static::assertSame($url, $assignedVariables['link']['url']); | ||
228 | static::assertTrue($assignedVariables['link_is_new']); | ||
229 | } | ||
230 | |||
231 | /** | ||
232 | * Test displaying bookmark create form | ||
233 | * When markdown formatter is enabled, the no markdown tag should be added to existing tags. | ||
234 | */ | ||
235 | public function testDisplayCreateFormWithMarkdownEnabled(): void | ||
236 | { | ||
237 | $assignedVariables = []; | ||
238 | $this->assignTemplateVars($assignedVariables); | ||
239 | |||
240 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
241 | $this->container->conf | ||
242 | ->expects(static::atLeastOnce()) | ||
243 | ->method('get')->willReturnCallback(function (string $key): ?string { | ||
244 | if ($key === 'formatter') { | ||
245 | return 'markdown'; | ||
246 | } | ||
247 | |||
248 | return $key; | ||
249 | }) | ||
250 | ; | ||
251 | |||
252 | $request = $this->createMock(Request::class); | ||
253 | $response = new Response(); | ||
254 | |||
255 | $result = $this->controller->displayCreateForm($request, $response); | ||
256 | |||
257 | static::assertSame(200, $result->getStatusCode()); | ||
258 | static::assertSame('editlink', (string) $result->getBody()); | ||
259 | static::assertSame(['nomarkdown' => 1], $assignedVariables['tags']); | ||
260 | } | ||
261 | |||
262 | /** | ||
263 | * Test displaying bookmark create form | ||
264 | * When an existing URL is submitted, we want to edit the existing link. | ||
265 | */ | ||
266 | public function testDisplayCreateFormWithExistingUrl(): void | ||
267 | { | ||
268 | $assignedVariables = []; | ||
269 | $this->assignTemplateVars($assignedVariables); | ||
270 | |||
271 | $url = 'http://url.tld/other?part=3&utm_ad=pay#hash'; | ||
272 | $expectedUrl = str_replace('&utm_ad=pay', '', $url); | ||
273 | |||
274 | $request = $this->createMock(Request::class); | ||
275 | $request | ||
276 | ->method('getParam') | ||
277 | ->willReturnCallback(function (string $key) use ($url): ?string { | ||
278 | return $key === 'post' ? $url : null; | ||
279 | }); | ||
280 | $response = new Response(); | ||
281 | |||
282 | $this->container->httpAccess->expects(static::never())->method('getHttpResponse'); | ||
283 | $this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback'); | ||
284 | |||
285 | $this->container->bookmarkService | ||
286 | ->expects(static::once()) | ||
287 | ->method('findByUrl') | ||
288 | ->with($expectedUrl) | ||
289 | ->willReturn( | ||
290 | (new Bookmark()) | ||
291 | ->setId($id = 23) | ||
292 | ->setUrl($expectedUrl) | ||
293 | ->setTitle($title = 'Bookmark Title') | ||
294 | ->setDescription($description = 'Bookmark description.') | ||
295 | ->setTags($tags = ['abc', 'def']) | ||
296 | ->setPrivate(true) | ||
297 | ->setCreated($createdAt = new \DateTime('2020-06-10 18:45:44')) | ||
298 | ) | ||
299 | ; | ||
300 | |||
301 | $result = $this->controller->displayCreateForm($request, $response); | ||
302 | |||
303 | static::assertSame(200, $result->getStatusCode()); | ||
304 | static::assertSame('editlink', (string) $result->getBody()); | ||
305 | |||
306 | static::assertSame('Edit Shaare - Shaarli', $assignedVariables['pagetitle']); | ||
307 | static::assertFalse($assignedVariables['link_is_new']); | ||
308 | |||
309 | static::assertSame($id, $assignedVariables['link']['id']); | ||
310 | static::assertSame($expectedUrl, $assignedVariables['link']['url']); | ||
311 | static::assertSame($title, $assignedVariables['link']['title']); | ||
312 | static::assertSame($description, $assignedVariables['link']['description']); | ||
313 | static::assertSame(implode(' ', $tags), $assignedVariables['link']['tags']); | ||
314 | static::assertTrue($assignedVariables['link']['private']); | ||
315 | static::assertSame($createdAt, $assignedVariables['link']['created']); | ||
316 | } | ||
317 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/DisplayEditFormTest.php b/tests/front/controller/admin/ManageShaareControllerTest/DisplayEditFormTest.php new file mode 100644 index 00000000..2dc3f41c --- /dev/null +++ b/tests/front/controller/admin/ManageShaareControllerTest/DisplayEditFormTest.php | |||
@@ -0,0 +1,155 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
10 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
11 | use Shaarli\Http\HttpAccess; | ||
12 | use Shaarli\Security\SessionManager; | ||
13 | use Shaarli\TestCase; | ||
14 | use Slim\Http\Request; | ||
15 | use Slim\Http\Response; | ||
16 | |||
17 | class DisplayEditFormTest extends TestCase | ||
18 | { | ||
19 | use FrontAdminControllerMockHelper; | ||
20 | |||
21 | /** @var ManageShaareController */ | ||
22 | protected $controller; | ||
23 | |||
24 | public function setUp(): void | ||
25 | { | ||
26 | $this->createContainer(); | ||
27 | |||
28 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
29 | $this->controller = new ManageShaareController($this->container); | ||
30 | } | ||
31 | |||
32 | /** | ||
33 | * Test displaying bookmark edit form | ||
34 | * When an existing ID is provided, ensure that default workflow works properly. | ||
35 | */ | ||
36 | public function testDisplayEditFormDefault(): void | ||
37 | { | ||
38 | $assignedVariables = []; | ||
39 | $this->assignTemplateVars($assignedVariables); | ||
40 | |||
41 | $id = 11; | ||
42 | |||
43 | $request = $this->createMock(Request::class); | ||
44 | $response = new Response(); | ||
45 | |||
46 | $this->container->httpAccess->expects(static::never())->method('getHttpResponse'); | ||
47 | $this->container->httpAccess->expects(static::never())->method('getCurlDownloadCallback'); | ||
48 | |||
49 | $this->container->bookmarkService | ||
50 | ->expects(static::once()) | ||
51 | ->method('get') | ||
52 | ->with($id) | ||
53 | ->willReturn( | ||
54 | (new Bookmark()) | ||
55 | ->setId($id) | ||
56 | ->setUrl($url = 'http://domain.tld') | ||
57 | ->setTitle($title = 'Bookmark Title') | ||
58 | ->setDescription($description = 'Bookmark description.') | ||
59 | ->setTags($tags = ['abc', 'def']) | ||
60 | ->setPrivate(true) | ||
61 | ->setCreated($createdAt = new \DateTime('2020-06-10 18:45:44')) | ||
62 | ) | ||
63 | ; | ||
64 | |||
65 | $result = $this->controller->displayEditForm($request, $response, ['id' => (string) $id]); | ||
66 | |||
67 | static::assertSame(200, $result->getStatusCode()); | ||
68 | static::assertSame('editlink', (string) $result->getBody()); | ||
69 | |||
70 | static::assertSame('Edit Shaare - Shaarli', $assignedVariables['pagetitle']); | ||
71 | static::assertFalse($assignedVariables['link_is_new']); | ||
72 | |||
73 | static::assertSame($id, $assignedVariables['link']['id']); | ||
74 | static::assertSame($url, $assignedVariables['link']['url']); | ||
75 | static::assertSame($title, $assignedVariables['link']['title']); | ||
76 | static::assertSame($description, $assignedVariables['link']['description']); | ||
77 | static::assertSame(implode(' ', $tags), $assignedVariables['link']['tags']); | ||
78 | static::assertTrue($assignedVariables['link']['private']); | ||
79 | static::assertSame($createdAt, $assignedVariables['link']['created']); | ||
80 | } | ||
81 | |||
82 | /** | ||
83 | * Test displaying bookmark edit form | ||
84 | * Invalid ID provided. | ||
85 | */ | ||
86 | public function testDisplayEditFormInvalidId(): void | ||
87 | { | ||
88 | $id = 'invalid'; | ||
89 | |||
90 | $request = $this->createMock(Request::class); | ||
91 | $response = new Response(); | ||
92 | |||
93 | $this->container->sessionManager | ||
94 | ->expects(static::once()) | ||
95 | ->method('setSessionParameter') | ||
96 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Bookmark with identifier invalid could not be found.']) | ||
97 | ; | ||
98 | |||
99 | $result = $this->controller->displayEditForm($request, $response, ['id' => $id]); | ||
100 | |||
101 | static::assertSame(302, $result->getStatusCode()); | ||
102 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
103 | } | ||
104 | |||
105 | /** | ||
106 | * Test displaying bookmark edit form | ||
107 | * ID not provided. | ||
108 | */ | ||
109 | public function testDisplayEditFormIdNotProvided(): void | ||
110 | { | ||
111 | $request = $this->createMock(Request::class); | ||
112 | $response = new Response(); | ||
113 | |||
114 | $this->container->sessionManager | ||
115 | ->expects(static::once()) | ||
116 | ->method('setSessionParameter') | ||
117 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Bookmark with identifier could not be found.']) | ||
118 | ; | ||
119 | |||
120 | $result = $this->controller->displayEditForm($request, $response, []); | ||
121 | |||
122 | static::assertSame(302, $result->getStatusCode()); | ||
123 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
124 | } | ||
125 | |||
126 | /** | ||
127 | * Test displaying bookmark edit form | ||
128 | * Bookmark not found. | ||
129 | */ | ||
130 | public function testDisplayEditFormBookmarkNotFound(): void | ||
131 | { | ||
132 | $id = 123; | ||
133 | |||
134 | $request = $this->createMock(Request::class); | ||
135 | $response = new Response(); | ||
136 | |||
137 | $this->container->bookmarkService | ||
138 | ->expects(static::once()) | ||
139 | ->method('get') | ||
140 | ->with($id) | ||
141 | ->willThrowException(new BookmarkNotFoundException()) | ||
142 | ; | ||
143 | |||
144 | $this->container->sessionManager | ||
145 | ->expects(static::once()) | ||
146 | ->method('setSessionParameter') | ||
147 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Bookmark with identifier 123 could not be found.']) | ||
148 | ; | ||
149 | |||
150 | $result = $this->controller->displayEditForm($request, $response, ['id' => (string) $id]); | ||
151 | |||
152 | static::assertSame(302, $result->getStatusCode()); | ||
153 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
154 | } | ||
155 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/PinBookmarkTest.php b/tests/front/controller/admin/ManageShaareControllerTest/PinBookmarkTest.php new file mode 100644 index 00000000..50ce7df1 --- /dev/null +++ b/tests/front/controller/admin/ManageShaareControllerTest/PinBookmarkTest.php | |||
@@ -0,0 +1,145 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
10 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
11 | use Shaarli\Http\HttpAccess; | ||
12 | use Shaarli\Security\SessionManager; | ||
13 | use Shaarli\TestCase; | ||
14 | use Slim\Http\Request; | ||
15 | use Slim\Http\Response; | ||
16 | |||
17 | class PinBookmarkTest extends TestCase | ||
18 | { | ||
19 | use FrontAdminControllerMockHelper; | ||
20 | |||
21 | /** @var ManageShaareController */ | ||
22 | protected $controller; | ||
23 | |||
24 | public function setUp(): void | ||
25 | { | ||
26 | $this->createContainer(); | ||
27 | |||
28 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
29 | $this->controller = new ManageShaareController($this->container); | ||
30 | } | ||
31 | |||
32 | /** | ||
33 | * Test pin bookmark - with valid input | ||
34 | * | ||
35 | * @dataProvider initialStickyValuesProvider() | ||
36 | */ | ||
37 | public function testPinBookmarkIsStickyNull(?bool $sticky, bool $expectedValue): void | ||
38 | { | ||
39 | $id = 123; | ||
40 | |||
41 | $request = $this->createMock(Request::class); | ||
42 | $response = new Response(); | ||
43 | |||
44 | $bookmark = (new Bookmark()) | ||
45 | ->setId(123) | ||
46 | ->setUrl('http://domain.tld') | ||
47 | ->setTitle('Title 123') | ||
48 | ->setSticky($sticky) | ||
49 | ; | ||
50 | |||
51 | $this->container->bookmarkService->expects(static::once())->method('get')->with(123)->willReturn($bookmark); | ||
52 | $this->container->bookmarkService->expects(static::once())->method('set')->with($bookmark, true); | ||
53 | |||
54 | // Make sure that PluginManager hook is triggered | ||
55 | $this->container->pluginManager | ||
56 | ->expects(static::once()) | ||
57 | ->method('executeHooks') | ||
58 | ->with('save_link') | ||
59 | ; | ||
60 | |||
61 | $result = $this->controller->pinBookmark($request, $response, ['id' => (string) $id]); | ||
62 | |||
63 | static::assertSame(302, $result->getStatusCode()); | ||
64 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
65 | |||
66 | static::assertSame($expectedValue, $bookmark->isSticky()); | ||
67 | } | ||
68 | |||
69 | public function initialStickyValuesProvider(): array | ||
70 | { | ||
71 | // [initialStickyState, isStickyAfterPin] | ||
72 | return [[null, true], [false, true], [true, false]]; | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Test pin bookmark - invalid bookmark ID | ||
77 | */ | ||
78 | public function testDisplayEditFormInvalidId(): void | ||
79 | { | ||
80 | $id = 'invalid'; | ||
81 | |||
82 | $request = $this->createMock(Request::class); | ||
83 | $response = new Response(); | ||
84 | |||
85 | $this->container->sessionManager | ||
86 | ->expects(static::once()) | ||
87 | ->method('setSessionParameter') | ||
88 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Bookmark with identifier invalid could not be found.']) | ||
89 | ; | ||
90 | |||
91 | $result = $this->controller->pinBookmark($request, $response, ['id' => $id]); | ||
92 | |||
93 | static::assertSame(302, $result->getStatusCode()); | ||
94 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
95 | } | ||
96 | |||
97 | /** | ||
98 | * Test pin bookmark - Bookmark ID not provided | ||
99 | */ | ||
100 | public function testDisplayEditFormIdNotProvided(): void | ||
101 | { | ||
102 | $request = $this->createMock(Request::class); | ||
103 | $response = new Response(); | ||
104 | |||
105 | $this->container->sessionManager | ||
106 | ->expects(static::once()) | ||
107 | ->method('setSessionParameter') | ||
108 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Bookmark with identifier could not be found.']) | ||
109 | ; | ||
110 | |||
111 | $result = $this->controller->pinBookmark($request, $response, []); | ||
112 | |||
113 | static::assertSame(302, $result->getStatusCode()); | ||
114 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * Test pin bookmark - bookmark not found | ||
119 | */ | ||
120 | public function testDisplayEditFormBookmarkNotFound(): void | ||
121 | { | ||
122 | $id = 123; | ||
123 | |||
124 | $request = $this->createMock(Request::class); | ||
125 | $response = new Response(); | ||
126 | |||
127 | $this->container->bookmarkService | ||
128 | ->expects(static::once()) | ||
129 | ->method('get') | ||
130 | ->with($id) | ||
131 | ->willThrowException(new BookmarkNotFoundException()) | ||
132 | ; | ||
133 | |||
134 | $this->container->sessionManager | ||
135 | ->expects(static::once()) | ||
136 | ->method('setSessionParameter') | ||
137 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Bookmark with identifier 123 could not be found.']) | ||
138 | ; | ||
139 | |||
140 | $result = $this->controller->pinBookmark($request, $response, ['id' => (string) $id]); | ||
141 | |||
142 | static::assertSame(302, $result->getStatusCode()); | ||
143 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
144 | } | ||
145 | } | ||
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php b/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php new file mode 100644 index 00000000..f7a68226 --- /dev/null +++ b/tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php | |||
@@ -0,0 +1,308 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
10 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
11 | use Shaarli\Front\Exception\WrongTokenException; | ||
12 | use Shaarli\Http\HttpAccess; | ||
13 | use Shaarli\Security\SessionManager; | ||
14 | use Shaarli\TestCase; | ||
15 | use Shaarli\Thumbnailer; | ||
16 | use Slim\Http\Request; | ||
17 | use Slim\Http\Response; | ||
18 | |||
19 | class SaveBookmarkTest extends TestCase | ||
20 | { | ||
21 | use FrontAdminControllerMockHelper; | ||
22 | |||
23 | /** @var ManageShaareController */ | ||
24 | protected $controller; | ||
25 | |||
26 | public function setUp(): void | ||
27 | { | ||
28 | $this->createContainer(); | ||
29 | |||
30 | $this->container->httpAccess = $this->createMock(HttpAccess::class); | ||
31 | $this->controller = new ManageShaareController($this->container); | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Test save a new bookmark | ||
36 | */ | ||
37 | public function testSaveBookmark(): void | ||
38 | { | ||
39 | $id = 21; | ||
40 | $parameters = [ | ||
41 | 'lf_url' => 'http://url.tld/other?part=3#hash', | ||
42 | 'lf_title' => 'Provided Title', | ||
43 | 'lf_description' => 'Provided description.', | ||
44 | 'lf_tags' => 'abc def', | ||
45 | 'lf_private' => '1', | ||
46 | 'returnurl' => 'http://shaarli/subfolder/admin/add-shaare' | ||
47 | ]; | ||
48 | |||
49 | $request = $this->createMock(Request::class); | ||
50 | $request | ||
51 | ->method('getParam') | ||
52 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
53 | return $parameters[$key] ?? null; | ||
54 | }) | ||
55 | ; | ||
56 | $response = new Response(); | ||
57 | |||
58 | $checkBookmark = function (Bookmark $bookmark) use ($parameters) { | ||
59 | static::assertSame($parameters['lf_url'], $bookmark->getUrl()); | ||
60 | static::assertSame($parameters['lf_title'], $bookmark->getTitle()); | ||
61 | static::assertSame($parameters['lf_description'], $bookmark->getDescription()); | ||
62 | static::assertSame($parameters['lf_tags'], $bookmark->getTagsString()); | ||
63 | static::assertTrue($bookmark->isPrivate()); | ||
64 | }; | ||
65 | |||
66 | $this->container->bookmarkService | ||
67 | ->expects(static::once()) | ||
68 | ->method('addOrSet') | ||
69 | ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { | ||
70 | static::assertFalse($save); | ||
71 | |||
72 | $checkBookmark($bookmark); | ||
73 | |||
74 | $bookmark->setId($id); | ||
75 | }) | ||
76 | ; | ||
77 | $this->container->bookmarkService | ||
78 | ->expects(static::once()) | ||
79 | ->method('set') | ||
80 | ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { | ||
81 | static::assertTrue($save); | ||
82 | |||
83 | $checkBookmark($bookmark); | ||
84 | |||
85 | static::assertSame($id, $bookmark->getId()); | ||
86 | }) | ||
87 | ; | ||
88 | |||
89 | // Make sure that PluginManager hook is triggered | ||
90 | $this->container->pluginManager | ||
91 | ->expects(static::atLeastOnce()) | ||
92 | ->method('executeHooks') | ||
93 | ->withConsecutive(['save_link']) | ||
94 | ->willReturnCallback(function (string $hook, array $data) use ($parameters, $id): array { | ||
95 | if ('save_link' === $hook) { | ||
96 | static::assertSame($id, $data['id']); | ||
97 | static::assertSame($parameters['lf_url'], $data['url']); | ||
98 | static::assertSame($parameters['lf_title'], $data['title']); | ||
99 | static::assertSame($parameters['lf_description'], $data['description']); | ||
100 | static::assertSame($parameters['lf_tags'], $data['tags']); | ||
101 | static::assertTrue($data['private']); | ||
102 | } | ||
103 | |||
104 | return $data; | ||
105 | }) | ||
106 | ; | ||
107 | |||
108 | $result = $this->controller->save($request, $response); | ||
109 | |||
110 | static::assertSame(302, $result->getStatusCode()); | ||
111 | static::assertRegExp('@/subfolder/#[\w\-]{6}@', $result->getHeader('location')[0]); | ||
112 | } | ||
113 | |||
114 | |||
115 | /** | ||
116 | * Test save an existing bookmark | ||
117 | */ | ||
118 | public function testSaveExistingBookmark(): void | ||
119 | { | ||
120 | $id = 21; | ||
121 | $parameters = [ | ||
122 | 'lf_id' => (string) $id, | ||
123 | 'lf_url' => 'http://url.tld/other?part=3#hash', | ||
124 | 'lf_title' => 'Provided Title', | ||
125 | 'lf_description' => 'Provided description.', | ||
126 | 'lf_tags' => 'abc def', | ||
127 | 'lf_private' => '1', | ||
128 | 'returnurl' => 'http://shaarli/subfolder/?page=2' | ||
129 | ]; | ||
130 | |||
131 | $request = $this->createMock(Request::class); | ||
132 | $request | ||
133 | ->method('getParam') | ||
134 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
135 | return $parameters[$key] ?? null; | ||
136 | }) | ||
137 | ; | ||
138 | $response = new Response(); | ||
139 | |||
140 | $checkBookmark = function (Bookmark $bookmark) use ($parameters, $id) { | ||
141 | static::assertSame($id, $bookmark->getId()); | ||
142 | static::assertSame($parameters['lf_url'], $bookmark->getUrl()); | ||
143 | static::assertSame($parameters['lf_title'], $bookmark->getTitle()); | ||
144 | static::assertSame($parameters['lf_description'], $bookmark->getDescription()); | ||
145 | static::assertSame($parameters['lf_tags'], $bookmark->getTagsString()); | ||
146 | static::assertTrue($bookmark->isPrivate()); | ||
147 | }; | ||
148 | |||
149 | $this->container->bookmarkService->expects(static::atLeastOnce())->method('exists')->willReturn(true); | ||
150 | $this->container->bookmarkService | ||
151 | ->expects(static::once()) | ||
152 | ->method('get') | ||
153 | ->willReturn((new Bookmark())->setId($id)->setUrl('http://other.url')) | ||
154 | ; | ||
155 | $this->container->bookmarkService | ||
156 | ->expects(static::once()) | ||
157 | ->method('addOrSet') | ||
158 | ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { | ||
159 | static::assertFalse($save); | ||
160 | |||
161 | $checkBookmark($bookmark); | ||
162 | }) | ||
163 | ; | ||
164 | $this->container->bookmarkService | ||
165 | ->expects(static::once()) | ||
166 | ->method('set') | ||
167 | ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void { | ||
168 | static::assertTrue($save); | ||
169 | |||
170 | $checkBookmark($bookmark); | ||
171 | |||
172 | static::assertSame($id, $bookmark->getId()); | ||
173 | }) | ||
174 | ; | ||
175 | |||
176 | // Make sure that PluginManager hook is triggered | ||
177 | $this->container->pluginManager | ||
178 | ->expects(static::atLeastOnce()) | ||
179 | ->method('executeHooks') | ||
180 | ->withConsecutive(['save_link']) | ||
181 | ->willReturnCallback(function (string $hook, array $data) use ($parameters, $id): array { | ||
182 | if ('save_link' === $hook) { | ||
183 | static::assertSame($id, $data['id']); | ||
184 | static::assertSame($parameters['lf_url'], $data['url']); | ||
185 | static::assertSame($parameters['lf_title'], $data['title']); | ||
186 | static::assertSame($parameters['lf_description'], $data['description']); | ||
187 | static::assertSame($parameters['lf_tags'], $data['tags']); | ||
188 | static::assertTrue($data['private']); | ||
189 | } | ||
190 | |||
191 | return $data; | ||
192 | }) | ||
193 | ; | ||
194 | |||
195 | $result = $this->controller->save($request, $response); | ||
196 | |||
197 | static::assertSame(302, $result->getStatusCode()); | ||
198 | static::assertRegExp('@/subfolder/\?page=2#[\w\-]{6}@', $result->getHeader('location')[0]); | ||
199 | } | ||
200 | |||
201 | /** | ||
202 | * Test save a bookmark - try to retrieve the thumbnail | ||
203 | */ | ||
204 | public function testSaveBookmarkWithThumbnail(): void | ||
205 | { | ||
206 | $parameters = ['lf_url' => 'http://url.tld/other?part=3#hash']; | ||
207 | |||
208 | $request = $this->createMock(Request::class); | ||
209 | $request | ||
210 | ->method('getParam') | ||
211 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
212 | return $parameters[$key] ?? null; | ||
213 | }) | ||
214 | ; | ||
215 | $response = new Response(); | ||
216 | |||
217 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
218 | $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { | ||
219 | return $key === 'thumbnails.mode' ? Thumbnailer::MODE_ALL : $default; | ||
220 | }); | ||
221 | |||
222 | $this->container->thumbnailer = $this->createMock(Thumbnailer::class); | ||
223 | $this->container->thumbnailer | ||
224 | ->expects(static::once()) | ||
225 | ->method('get') | ||
226 | ->with($parameters['lf_url']) | ||
227 | ->willReturn($thumb = 'http://thumb.url') | ||
228 | ; | ||
229 | |||
230 | $this->container->bookmarkService | ||
231 | ->expects(static::once()) | ||
232 | ->method('addOrSet') | ||
233 | ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($thumb): void { | ||
234 | static::assertSame($thumb, $bookmark->getThumbnail()); | ||
235 | }) | ||
236 | ; | ||
237 | |||
238 | $result = $this->controller->save($request, $response); | ||
239 | |||
240 | static::assertSame(302, $result->getStatusCode()); | ||
241 | } | ||
242 | |||
243 | /** | ||
244 | * Test save a bookmark - with ID #0 | ||
245 | */ | ||
246 | public function testSaveBookmarkWithIdZero(): void | ||
247 | { | ||
248 | $parameters = ['lf_id' => '0']; | ||
249 | |||
250 | $request = $this->createMock(Request::class); | ||
251 | $request | ||
252 | ->method('getParam') | ||
253 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
254 | return $parameters[$key] ?? null; | ||
255 | }) | ||
256 | ; | ||
257 | $response = new Response(); | ||
258 | |||
259 | $this->container->bookmarkService->expects(static::once())->method('exists')->with(0)->willReturn(true); | ||
260 | $this->container->bookmarkService->expects(static::once())->method('get')->with(0)->willReturn(new Bookmark()); | ||
261 | |||
262 | $result = $this->controller->save($request, $response); | ||
263 | |||
264 | static::assertSame(302, $result->getStatusCode()); | ||
265 | } | ||
266 | |||
267 | /** | ||
268 | * Change the password with a wrong existing password | ||
269 | */ | ||
270 | public function testSaveBookmarkFromBookmarklet(): void | ||
271 | { | ||
272 | $parameters = ['source' => 'bookmarklet']; | ||
273 | |||
274 | $request = $this->createMock(Request::class); | ||
275 | $request | ||
276 | ->method('getParam') | ||
277 | ->willReturnCallback(function (string $key) use ($parameters): ?string { | ||
278 | return $parameters[$key] ?? null; | ||
279 | }) | ||
280 | ; | ||
281 | $response = new Response(); | ||
282 | |||
283 | $result = $this->controller->save($request, $response); | ||
284 | |||
285 | static::assertSame(200, $result->getStatusCode()); | ||
286 | static::assertSame('<script>self.close();</script>', (string) $result->getBody()); | ||
287 | } | ||
288 | |||
289 | /** | ||
290 | * Change the password with a wrong existing password | ||
291 | */ | ||
292 | public function testSaveBookmarkWrongToken(): void | ||
293 | { | ||
294 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
295 | $this->container->sessionManager->method('checkToken')->willReturn(false); | ||
296 | |||
297 | $this->container->bookmarkService->expects(static::never())->method('addOrSet'); | ||
298 | $this->container->bookmarkService->expects(static::never())->method('set'); | ||
299 | |||
300 | $request = $this->createMock(Request::class); | ||
301 | $response = new Response(); | ||
302 | |||
303 | $this->expectException(WrongTokenException::class); | ||
304 | |||
305 | $this->controller->save($request, $response); | ||
306 | } | ||
307 | |||
308 | } | ||
diff --git a/tests/front/controller/admin/ManageTagControllerTest.php b/tests/front/controller/admin/ManageTagControllerTest.php new file mode 100644 index 00000000..8a0ff7a9 --- /dev/null +++ b/tests/front/controller/admin/ManageTagControllerTest.php | |||
@@ -0,0 +1,272 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\BookmarkFilter; | ||
9 | use Shaarli\Front\Exception\WrongTokenException; | ||
10 | use Shaarli\Security\SessionManager; | ||
11 | use Shaarli\TestCase; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | class ManageTagControllerTest extends TestCase | ||
16 | { | ||
17 | use FrontAdminControllerMockHelper; | ||
18 | |||
19 | /** @var ManageTagController */ | ||
20 | protected $controller; | ||
21 | |||
22 | public function setUp(): void | ||
23 | { | ||
24 | $this->createContainer(); | ||
25 | |||
26 | $this->controller = new ManageTagController($this->container); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Test displaying manage tag page | ||
31 | */ | ||
32 | public function testIndex(): void | ||
33 | { | ||
34 | $assignedVariables = []; | ||
35 | $this->assignTemplateVars($assignedVariables); | ||
36 | |||
37 | $request = $this->createMock(Request::class); | ||
38 | $request->method('getParam')->with('fromtag')->willReturn('fromtag'); | ||
39 | $response = new Response(); | ||
40 | |||
41 | $result = $this->controller->index($request, $response); | ||
42 | |||
43 | static::assertSame(200, $result->getStatusCode()); | ||
44 | static::assertSame('changetag', (string) $result->getBody()); | ||
45 | |||
46 | static::assertSame('fromtag', $assignedVariables['fromtag']); | ||
47 | static::assertSame('Manage tags - Shaarli', $assignedVariables['pagetitle']); | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * Test posting a tag update - rename tag - valid info provided. | ||
52 | */ | ||
53 | public function testSaveRenameTagValid(): void | ||
54 | { | ||
55 | $session = []; | ||
56 | $this->assignSessionVars($session); | ||
57 | |||
58 | $requestParameters = [ | ||
59 | 'renametag' => 'rename', | ||
60 | 'fromtag' => 'old-tag', | ||
61 | 'totag' => 'new-tag', | ||
62 | ]; | ||
63 | $request = $this->createMock(Request::class); | ||
64 | $request | ||
65 | ->expects(static::atLeastOnce()) | ||
66 | ->method('getParam') | ||
67 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
68 | return $requestParameters[$key] ?? null; | ||
69 | }) | ||
70 | ; | ||
71 | $response = new Response(); | ||
72 | |||
73 | $bookmark1 = $this->createMock(Bookmark::class); | ||
74 | $bookmark2 = $this->createMock(Bookmark::class); | ||
75 | $this->container->bookmarkService | ||
76 | ->expects(static::once()) | ||
77 | ->method('search') | ||
78 | ->with(['searchtags' => 'old-tag'], BookmarkFilter::$ALL, true) | ||
79 | ->willReturnCallback(function () use ($bookmark1, $bookmark2): array { | ||
80 | $bookmark1->expects(static::once())->method('renameTag')->with('old-tag', 'new-tag'); | ||
81 | $bookmark2->expects(static::once())->method('renameTag')->with('old-tag', 'new-tag'); | ||
82 | |||
83 | return [$bookmark1, $bookmark2]; | ||
84 | }) | ||
85 | ; | ||
86 | $this->container->bookmarkService | ||
87 | ->expects(static::exactly(2)) | ||
88 | ->method('set') | ||
89 | ->withConsecutive([$bookmark1, false], [$bookmark2, false]) | ||
90 | ; | ||
91 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
92 | |||
93 | $result = $this->controller->save($request, $response); | ||
94 | |||
95 | static::assertSame(302, $result->getStatusCode()); | ||
96 | static::assertSame(['/subfolder/?searchtags=new-tag'], $result->getHeader('location')); | ||
97 | |||
98 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
99 | static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
100 | static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
101 | static::assertSame(['The tag was renamed in 2 bookmarks.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); | ||
102 | } | ||
103 | |||
104 | /** | ||
105 | * Test posting a tag update - delete tag - valid info provided. | ||
106 | */ | ||
107 | public function testSaveDeleteTagValid(): void | ||
108 | { | ||
109 | $session = []; | ||
110 | $this->assignSessionVars($session); | ||
111 | |||
112 | $requestParameters = [ | ||
113 | 'deletetag' => 'delete', | ||
114 | 'fromtag' => 'old-tag', | ||
115 | ]; | ||
116 | $request = $this->createMock(Request::class); | ||
117 | $request | ||
118 | ->expects(static::atLeastOnce()) | ||
119 | ->method('getParam') | ||
120 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
121 | return $requestParameters[$key] ?? null; | ||
122 | }) | ||
123 | ; | ||
124 | $response = new Response(); | ||
125 | |||
126 | $bookmark1 = $this->createMock(Bookmark::class); | ||
127 | $bookmark2 = $this->createMock(Bookmark::class); | ||
128 | $this->container->bookmarkService | ||
129 | ->expects(static::once()) | ||
130 | ->method('search') | ||
131 | ->with(['searchtags' => 'old-tag'], BookmarkFilter::$ALL, true) | ||
132 | ->willReturnCallback(function () use ($bookmark1, $bookmark2): array { | ||
133 | $bookmark1->expects(static::once())->method('deleteTag')->with('old-tag'); | ||
134 | $bookmark2->expects(static::once())->method('deleteTag')->with('old-tag'); | ||
135 | |||
136 | return [$bookmark1, $bookmark2]; | ||
137 | }) | ||
138 | ; | ||
139 | $this->container->bookmarkService | ||
140 | ->expects(static::exactly(2)) | ||
141 | ->method('set') | ||
142 | ->withConsecutive([$bookmark1, false], [$bookmark2, false]) | ||
143 | ; | ||
144 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
145 | |||
146 | $result = $this->controller->save($request, $response); | ||
147 | |||
148 | static::assertSame(302, $result->getStatusCode()); | ||
149 | static::assertSame(['/subfolder/admin/tags'], $result->getHeader('location')); | ||
150 | |||
151 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
152 | static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
153 | static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
154 | static::assertSame(['The tag was removed from 2 bookmarks.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); | ||
155 | } | ||
156 | |||
157 | /** | ||
158 | * Test posting a tag update - wrong token. | ||
159 | */ | ||
160 | public function testSaveWrongToken(): void | ||
161 | { | ||
162 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
163 | $this->container->sessionManager->method('checkToken')->willReturn(false); | ||
164 | |||
165 | $this->container->conf->expects(static::never())->method('set'); | ||
166 | $this->container->conf->expects(static::never())->method('write'); | ||
167 | |||
168 | $request = $this->createMock(Request::class); | ||
169 | $response = new Response(); | ||
170 | |||
171 | $this->expectException(WrongTokenException::class); | ||
172 | |||
173 | $this->controller->save($request, $response); | ||
174 | } | ||
175 | |||
176 | /** | ||
177 | * Test posting a tag update - rename tag - missing "FROM" tag. | ||
178 | */ | ||
179 | public function testSaveRenameTagMissingFrom(): void | ||
180 | { | ||
181 | $session = []; | ||
182 | $this->assignSessionVars($session); | ||
183 | |||
184 | $requestParameters = [ | ||
185 | 'renametag' => 'rename', | ||
186 | ]; | ||
187 | $request = $this->createMock(Request::class); | ||
188 | $request | ||
189 | ->expects(static::atLeastOnce()) | ||
190 | ->method('getParam') | ||
191 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
192 | return $requestParameters[$key] ?? null; | ||
193 | }) | ||
194 | ; | ||
195 | $response = new Response(); | ||
196 | |||
197 | $result = $this->controller->save($request, $response); | ||
198 | |||
199 | static::assertSame(302, $result->getStatusCode()); | ||
200 | static::assertSame(['/subfolder/admin/tags'], $result->getHeader('location')); | ||
201 | |||
202 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
203 | static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
204 | static::assertArrayNotHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
205 | static::assertSame(['Invalid tags provided.'], $session[SessionManager::KEY_WARNING_MESSAGES]); | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * Test posting a tag update - delete tag - missing "FROM" tag. | ||
210 | */ | ||
211 | public function testSaveDeleteTagMissingFrom(): void | ||
212 | { | ||
213 | $session = []; | ||
214 | $this->assignSessionVars($session); | ||
215 | |||
216 | $requestParameters = [ | ||
217 | 'deletetag' => 'delete', | ||
218 | ]; | ||
219 | $request = $this->createMock(Request::class); | ||
220 | $request | ||
221 | ->expects(static::atLeastOnce()) | ||
222 | ->method('getParam') | ||
223 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
224 | return $requestParameters[$key] ?? null; | ||
225 | }) | ||
226 | ; | ||
227 | $response = new Response(); | ||
228 | |||
229 | $result = $this->controller->save($request, $response); | ||
230 | |||
231 | static::assertSame(302, $result->getStatusCode()); | ||
232 | static::assertSame(['/subfolder/admin/tags'], $result->getHeader('location')); | ||
233 | |||
234 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
235 | static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
236 | static::assertArrayNotHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
237 | static::assertSame(['Invalid tags provided.'], $session[SessionManager::KEY_WARNING_MESSAGES]); | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * Test posting a tag update - rename tag - missing "TO" tag. | ||
242 | */ | ||
243 | public function testSaveRenameTagMissingTo(): void | ||
244 | { | ||
245 | $session = []; | ||
246 | $this->assignSessionVars($session); | ||
247 | |||
248 | $requestParameters = [ | ||
249 | 'renametag' => 'rename', | ||
250 | 'fromtag' => 'old-tag' | ||
251 | ]; | ||
252 | $request = $this->createMock(Request::class); | ||
253 | $request | ||
254 | ->expects(static::atLeastOnce()) | ||
255 | ->method('getParam') | ||
256 | ->willReturnCallback(function (string $key) use ($requestParameters): ?string { | ||
257 | return $requestParameters[$key] ?? null; | ||
258 | }) | ||
259 | ; | ||
260 | $response = new Response(); | ||
261 | |||
262 | $result = $this->controller->save($request, $response); | ||
263 | |||
264 | static::assertSame(302, $result->getStatusCode()); | ||
265 | static::assertSame(['/subfolder/admin/tags'], $result->getHeader('location')); | ||
266 | |||
267 | static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); | ||
268 | static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); | ||
269 | static::assertArrayNotHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); | ||
270 | static::assertSame(['Invalid tags provided.'], $session[SessionManager::KEY_WARNING_MESSAGES]); | ||
271 | } | ||
272 | } | ||
diff --git a/tests/front/controller/admin/PasswordControllerTest.php b/tests/front/controller/admin/PasswordControllerTest.php new file mode 100644 index 00000000..58f47b49 --- /dev/null +++ b/tests/front/controller/admin/PasswordControllerTest.php | |||
@@ -0,0 +1,203 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Front\Exception\OpenShaarliPasswordException; | ||
9 | use Shaarli\Front\Exception\WrongTokenException; | ||
10 | use Shaarli\Security\SessionManager; | ||
11 | use Shaarli\TestCase; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | class PasswordControllerTest extends TestCase | ||
16 | { | ||
17 | use FrontAdminControllerMockHelper; | ||
18 | |||
19 | /** @var PasswordController */ | ||
20 | protected $controller; | ||
21 | |||
22 | /** @var mixed[] Variables assigned to the template */ | ||
23 | protected $assignedVariables = []; | ||
24 | |||
25 | public function setUp(): void | ||
26 | { | ||
27 | $this->createContainer(); | ||
28 | $this->assignTemplateVars($this->assignedVariables); | ||
29 | |||
30 | $this->controller = new PasswordController($this->container); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Test displaying the change password page. | ||
35 | */ | ||
36 | public function testGetPage(): void | ||
37 | { | ||
38 | $request = $this->createMock(Request::class); | ||
39 | $response = new Response(); | ||
40 | |||
41 | $result = $this->controller->index($request, $response); | ||
42 | |||
43 | static::assertSame(200, $result->getStatusCode()); | ||
44 | static::assertSame('changepassword', (string) $result->getBody()); | ||
45 | static::assertSame('Change password - Shaarli', $this->assignedVariables['pagetitle']); | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * Change the password with valid parameters | ||
50 | */ | ||
51 | public function testPostNewPasswordDefault(): void | ||
52 | { | ||
53 | $request = $this->createMock(Request::class); | ||
54 | $request->method('getParam')->willReturnCallback(function (string $key): string { | ||
55 | if ('oldpassword' === $key) { | ||
56 | return 'old'; | ||
57 | } | ||
58 | if ('setpassword' === $key) { | ||
59 | return 'new'; | ||
60 | } | ||
61 | |||
62 | return $key; | ||
63 | }); | ||
64 | $response = new Response(); | ||
65 | |||
66 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
67 | $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { | ||
68 | if ('credentials.hash' === $key) { | ||
69 | return sha1('old' . 'credentials.login' . 'credentials.salt'); | ||
70 | } | ||
71 | |||
72 | return strpos($key, 'credentials') !== false ? $key : $default; | ||
73 | }); | ||
74 | $this->container->conf->expects(static::once())->method('write')->with(true); | ||
75 | |||
76 | $this->container->conf | ||
77 | ->method('set') | ||
78 | ->willReturnCallback(function (string $key, string $value) { | ||
79 | if ('credentials.hash' === $key) { | ||
80 | static::assertSame(sha1('new' . 'credentials.login' . 'credentials.salt'), $value); | ||
81 | } | ||
82 | }) | ||
83 | ; | ||
84 | |||
85 | $result = $this->controller->change($request, $response); | ||
86 | |||
87 | static::assertSame(200, $result->getStatusCode()); | ||
88 | static::assertSame('changepassword', (string) $result->getBody()); | ||
89 | static::assertSame('Change password - Shaarli', $this->assignedVariables['pagetitle']); | ||
90 | } | ||
91 | |||
92 | /** | ||
93 | * Change the password with a wrong existing password | ||
94 | */ | ||
95 | public function testPostNewPasswordWrongOldPassword(): void | ||
96 | { | ||
97 | $request = $this->createMock(Request::class); | ||
98 | $request->method('getParam')->willReturnCallback(function (string $key): string { | ||
99 | if ('oldpassword' === $key) { | ||
100 | return 'wrong'; | ||
101 | } | ||
102 | if ('setpassword' === $key) { | ||
103 | return 'new'; | ||
104 | } | ||
105 | |||
106 | return $key; | ||
107 | }); | ||
108 | $response = new Response(); | ||
109 | |||
110 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
111 | $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { | ||
112 | if ('credentials.hash' === $key) { | ||
113 | return sha1('old' . 'credentials.login' . 'credentials.salt'); | ||
114 | } | ||
115 | |||
116 | return strpos($key, 'credentials') !== false ? $key : $default; | ||
117 | }); | ||
118 | |||
119 | $this->container->conf->expects(static::never())->method('set'); | ||
120 | $this->container->conf->expects(static::never())->method('write'); | ||
121 | |||
122 | $this->container->sessionManager | ||
123 | ->expects(static::once()) | ||
124 | ->method('setSessionParameter') | ||
125 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['The old password is not correct.']) | ||
126 | ; | ||
127 | |||
128 | $result = $this->controller->change($request, $response); | ||
129 | |||
130 | static::assertSame(400, $result->getStatusCode()); | ||
131 | static::assertSame('changepassword', (string) $result->getBody()); | ||
132 | static::assertSame('Change password - Shaarli', $this->assignedVariables['pagetitle']); | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * Change the password with a wrong existing password | ||
137 | */ | ||
138 | public function testPostNewPasswordWrongToken(): void | ||
139 | { | ||
140 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
141 | $this->container->sessionManager->method('checkToken')->willReturn(false); | ||
142 | |||
143 | $this->container->conf->expects(static::never())->method('set'); | ||
144 | $this->container->conf->expects(static::never())->method('write'); | ||
145 | |||
146 | $request = $this->createMock(Request::class); | ||
147 | $response = new Response(); | ||
148 | |||
149 | $this->expectException(WrongTokenException::class); | ||
150 | |||
151 | $this->controller->change($request, $response); | ||
152 | } | ||
153 | |||
154 | /** | ||
155 | * Change the password with an empty new password | ||
156 | */ | ||
157 | public function testPostNewEmptyPassword(): void | ||
158 | { | ||
159 | $this->container->sessionManager | ||
160 | ->expects(static::once()) | ||
161 | ->method('setSessionParameter') | ||
162 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['You must provide the current and new password to change it.']) | ||
163 | ; | ||
164 | |||
165 | $this->container->conf->expects(static::never())->method('set'); | ||
166 | $this->container->conf->expects(static::never())->method('write'); | ||
167 | |||
168 | $request = $this->createMock(Request::class); | ||
169 | $request->method('getParam')->willReturnCallback(function (string $key): string { | ||
170 | if ('oldpassword' === $key) { | ||
171 | return 'old'; | ||
172 | } | ||
173 | if ('setpassword' === $key) { | ||
174 | return ''; | ||
175 | } | ||
176 | |||
177 | return $key; | ||
178 | }); | ||
179 | $response = new Response(); | ||
180 | |||
181 | $result = $this->controller->change($request, $response); | ||
182 | |||
183 | static::assertSame(400, $result->getStatusCode()); | ||
184 | static::assertSame('changepassword', (string) $result->getBody()); | ||
185 | static::assertSame('Change password - Shaarli', $this->assignedVariables['pagetitle']); | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * Change the password on an open shaarli | ||
190 | */ | ||
191 | public function testPostNewPasswordOnOpenShaarli(): void | ||
192 | { | ||
193 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
194 | $this->container->conf->method('get')->with('security.open_shaarli')->willReturn(true); | ||
195 | |||
196 | $request = $this->createMock(Request::class); | ||
197 | $response = new Response(); | ||
198 | |||
199 | $this->expectException(OpenShaarliPasswordException::class); | ||
200 | |||
201 | $this->controller->change($request, $response); | ||
202 | } | ||
203 | } | ||
diff --git a/tests/front/controller/admin/PluginsControllerTest.php b/tests/front/controller/admin/PluginsControllerTest.php new file mode 100644 index 00000000..974d614d --- /dev/null +++ b/tests/front/controller/admin/PluginsControllerTest.php | |||
@@ -0,0 +1,205 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Front\Exception\WrongTokenException; | ||
9 | use Shaarli\Plugin\PluginManager; | ||
10 | use Shaarli\Security\SessionManager; | ||
11 | use Shaarli\TestCase; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | class PluginsControllerTest extends TestCase | ||
16 | { | ||
17 | use FrontAdminControllerMockHelper; | ||
18 | |||
19 | const PLUGIN_NAMES = ['plugin1', 'plugin2', 'plugin3', 'plugin4']; | ||
20 | |||
21 | /** @var PluginsController */ | ||
22 | protected $controller; | ||
23 | |||
24 | public function setUp(): void | ||
25 | { | ||
26 | $this->createContainer(); | ||
27 | |||
28 | $this->controller = new PluginsController($this->container); | ||
29 | |||
30 | mkdir($path = __DIR__ . '/folder'); | ||
31 | PluginManager::$PLUGINS_PATH = $path; | ||
32 | array_map(function (string $plugin) use ($path) { touch($path . '/' . $plugin); }, static::PLUGIN_NAMES); | ||
33 | } | ||
34 | |||
35 | public function tearDown(): void | ||
36 | { | ||
37 | $path = __DIR__ . '/folder'; | ||
38 | array_map(function (string $plugin) use ($path) { unlink($path . '/' . $plugin); }, static::PLUGIN_NAMES); | ||
39 | rmdir($path); | ||
40 | } | ||
41 | |||
42 | /** | ||
43 | * Test displaying plugins admin page | ||
44 | */ | ||
45 | public function testIndex(): void | ||
46 | { | ||
47 | $assignedVariables = []; | ||
48 | $this->assignTemplateVars($assignedVariables); | ||
49 | |||
50 | $request = $this->createMock(Request::class); | ||
51 | $response = new Response(); | ||
52 | |||
53 | $data = [ | ||
54 | 'plugin1' => ['order' => 2, 'other' => 'field'], | ||
55 | 'plugin2' => ['order' => 1], | ||
56 | 'plugin3' => ['order' => false, 'abc' => 'def'], | ||
57 | 'plugin4' => [], | ||
58 | ]; | ||
59 | |||
60 | $this->container->pluginManager | ||
61 | ->expects(static::once()) | ||
62 | ->method('getPluginsMeta') | ||
63 | ->willReturn($data); | ||
64 | |||
65 | $result = $this->controller->index($request, $response); | ||
66 | |||
67 | static::assertSame(200, $result->getStatusCode()); | ||
68 | static::assertSame('pluginsadmin', (string) $result->getBody()); | ||
69 | |||
70 | static::assertSame('Plugin Administration - Shaarli', $assignedVariables['pagetitle']); | ||
71 | static::assertSame( | ||
72 | ['plugin2' => $data['plugin2'], 'plugin1' => $data['plugin1']], | ||
73 | $assignedVariables['enabledPlugins'] | ||
74 | ); | ||
75 | static::assertSame( | ||
76 | ['plugin3' => $data['plugin3'], 'plugin4' => $data['plugin4']], | ||
77 | $assignedVariables['disabledPlugins'] | ||
78 | ); | ||
79 | } | ||
80 | |||
81 | /** | ||
82 | * Test save plugins admin page | ||
83 | */ | ||
84 | public function testSaveEnabledPlugins(): void | ||
85 | { | ||
86 | $parameters = [ | ||
87 | 'plugin1' => 'on', | ||
88 | 'order_plugin1' => '2', | ||
89 | 'plugin2' => 'on', | ||
90 | ]; | ||
91 | |||
92 | $request = $this->createMock(Request::class); | ||
93 | $request | ||
94 | ->expects(static::atLeastOnce()) | ||
95 | ->method('getParams') | ||
96 | ->willReturnCallback(function () use ($parameters): array { | ||
97 | return $parameters; | ||
98 | }) | ||
99 | ; | ||
100 | $response = new Response(); | ||
101 | |||
102 | $this->container->pluginManager | ||
103 | ->expects(static::once()) | ||
104 | ->method('executeHooks') | ||
105 | ->with('save_plugin_parameters', $parameters) | ||
106 | ; | ||
107 | $this->container->conf | ||
108 | ->expects(static::atLeastOnce()) | ||
109 | ->method('set') | ||
110 | ->with('general.enabled_plugins', ['plugin1', 'plugin2']) | ||
111 | ; | ||
112 | |||
113 | $result = $this->controller->save($request, $response); | ||
114 | |||
115 | static::assertSame(302, $result->getStatusCode()); | ||
116 | static::assertSame(['/subfolder/admin/plugins'], $result->getHeader('location')); | ||
117 | } | ||
118 | |||
119 | /** | ||
120 | * Test save plugin parameters | ||
121 | */ | ||
122 | public function testSavePluginParameters(): void | ||
123 | { | ||
124 | $parameters = [ | ||
125 | 'parameters_form' => true, | ||
126 | 'parameter1' => 'blip', | ||
127 | 'parameter2' => 'blop', | ||
128 | 'token' => 'this parameter should not be saved' | ||
129 | ]; | ||
130 | |||
131 | $request = $this->createMock(Request::class); | ||
132 | $request | ||
133 | ->expects(static::atLeastOnce()) | ||
134 | ->method('getParams') | ||
135 | ->willReturnCallback(function () use ($parameters): array { | ||
136 | return $parameters; | ||
137 | }) | ||
138 | ; | ||
139 | $response = new Response(); | ||
140 | |||
141 | $this->container->pluginManager | ||
142 | ->expects(static::once()) | ||
143 | ->method('executeHooks') | ||
144 | ->with('save_plugin_parameters', $parameters) | ||
145 | ; | ||
146 | $this->container->conf | ||
147 | ->expects(static::exactly(2)) | ||
148 | ->method('set') | ||
149 | ->withConsecutive(['plugins.parameter1', 'blip'], ['plugins.parameter2', 'blop']) | ||
150 | ; | ||
151 | |||
152 | $result = $this->controller->save($request, $response); | ||
153 | |||
154 | static::assertSame(302, $result->getStatusCode()); | ||
155 | static::assertSame(['/subfolder/admin/plugins'], $result->getHeader('location')); | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Test save plugin parameters - error encountered | ||
160 | */ | ||
161 | public function testSaveWithError(): void | ||
162 | { | ||
163 | $request = $this->createMock(Request::class); | ||
164 | $response = new Response(); | ||
165 | |||
166 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
167 | $this->container->conf | ||
168 | ->expects(static::atLeastOnce()) | ||
169 | ->method('write') | ||
170 | ->willThrowException(new \Exception($message = 'error message')) | ||
171 | ; | ||
172 | |||
173 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
174 | $this->container->sessionManager->method('checkToken')->willReturn(true); | ||
175 | $this->container->sessionManager | ||
176 | ->expects(static::once()) | ||
177 | ->method('setSessionParameter') | ||
178 | ->with( | ||
179 | SessionManager::KEY_ERROR_MESSAGES, | ||
180 | ['Error while saving plugin configuration: ' . PHP_EOL . $message] | ||
181 | ) | ||
182 | ; | ||
183 | |||
184 | $result = $this->controller->save($request, $response); | ||
185 | |||
186 | static::assertSame(302, $result->getStatusCode()); | ||
187 | static::assertSame(['/subfolder/admin/plugins'], $result->getHeader('location')); | ||
188 | } | ||
189 | |||
190 | /** | ||
191 | * Test save plugin parameters - wrong token | ||
192 | */ | ||
193 | public function testSaveWrongToken(): void | ||
194 | { | ||
195 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
196 | $this->container->sessionManager->method('checkToken')->willReturn(false); | ||
197 | |||
198 | $request = $this->createMock(Request::class); | ||
199 | $response = new Response(); | ||
200 | |||
201 | $this->expectException(WrongTokenException::class); | ||
202 | |||
203 | $this->controller->save($request, $response); | ||
204 | } | ||
205 | } | ||
diff --git a/tests/front/controller/admin/SessionFilterControllerTest.php b/tests/front/controller/admin/SessionFilterControllerTest.php new file mode 100644 index 00000000..712a625b --- /dev/null +++ b/tests/front/controller/admin/SessionFilterControllerTest.php | |||
@@ -0,0 +1,177 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Security\LoginManager; | ||
8 | use Shaarli\Security\SessionManager; | ||
9 | use Shaarli\TestCase; | ||
10 | use Slim\Http\Request; | ||
11 | use Slim\Http\Response; | ||
12 | |||
13 | class SessionFilterControllerTest extends TestCase | ||
14 | { | ||
15 | use FrontAdminControllerMockHelper; | ||
16 | |||
17 | /** @var SessionFilterController */ | ||
18 | protected $controller; | ||
19 | |||
20 | public function setUp(): void | ||
21 | { | ||
22 | $this->createContainer(); | ||
23 | |||
24 | $this->controller = new SessionFilterController($this->container); | ||
25 | } | ||
26 | |||
27 | /** | ||
28 | * Visibility - Default call for private filter while logged in without current value | ||
29 | */ | ||
30 | public function testVisibility(): void | ||
31 | { | ||
32 | $arg = ['visibility' => 'private']; | ||
33 | |||
34 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller/?searchtag=abc'; | ||
35 | |||
36 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
37 | $this->container->sessionManager | ||
38 | ->expects(static::once()) | ||
39 | ->method('setSessionParameter') | ||
40 | ->with(SessionManager::KEY_VISIBILITY, 'private') | ||
41 | ; | ||
42 | |||
43 | $request = $this->createMock(Request::class); | ||
44 | $response = new Response(); | ||
45 | |||
46 | $result = $this->controller->visibility($request, $response, $arg); | ||
47 | |||
48 | static::assertInstanceOf(Response::class, $result); | ||
49 | static::assertSame(302, $result->getStatusCode()); | ||
50 | static::assertSame(['/subfolder/controller/?searchtag=abc'], $result->getHeader('location')); | ||
51 | } | ||
52 | |||
53 | /** | ||
54 | * Visibility - Toggle off private visibility | ||
55 | */ | ||
56 | public function testVisibilityToggleOff(): void | ||
57 | { | ||
58 | $arg = ['visibility' => 'private']; | ||
59 | |||
60 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller/?searchtag=abc'; | ||
61 | |||
62 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
63 | $this->container->sessionManager | ||
64 | ->method('getSessionParameter') | ||
65 | ->with(SessionManager::KEY_VISIBILITY) | ||
66 | ->willReturn('private') | ||
67 | ; | ||
68 | $this->container->sessionManager | ||
69 | ->expects(static::never()) | ||
70 | ->method('setSessionParameter') | ||
71 | ; | ||
72 | $this->container->sessionManager | ||
73 | ->expects(static::once()) | ||
74 | ->method('deleteSessionParameter') | ||
75 | ->with(SessionManager::KEY_VISIBILITY) | ||
76 | ; | ||
77 | |||
78 | $request = $this->createMock(Request::class); | ||
79 | $response = new Response(); | ||
80 | |||
81 | $result = $this->controller->visibility($request, $response, $arg); | ||
82 | |||
83 | static::assertInstanceOf(Response::class, $result); | ||
84 | static::assertSame(302, $result->getStatusCode()); | ||
85 | static::assertSame(['/subfolder/controller/?searchtag=abc'], $result->getHeader('location')); | ||
86 | } | ||
87 | |||
88 | /** | ||
89 | * Visibility - Change private to public | ||
90 | */ | ||
91 | public function testVisibilitySwitch(): void | ||
92 | { | ||
93 | $arg = ['visibility' => 'private']; | ||
94 | |||
95 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
96 | $this->container->sessionManager | ||
97 | ->method('getSessionParameter') | ||
98 | ->with(SessionManager::KEY_VISIBILITY) | ||
99 | ->willReturn('public') | ||
100 | ; | ||
101 | $this->container->sessionManager | ||
102 | ->expects(static::once()) | ||
103 | ->method('setSessionParameter') | ||
104 | ->with(SessionManager::KEY_VISIBILITY, 'private') | ||
105 | ; | ||
106 | |||
107 | $request = $this->createMock(Request::class); | ||
108 | $response = new Response(); | ||
109 | |||
110 | $result = $this->controller->visibility($request, $response, $arg); | ||
111 | |||
112 | static::assertInstanceOf(Response::class, $result); | ||
113 | static::assertSame(302, $result->getStatusCode()); | ||
114 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * Visibility - With invalid value - should remove any visibility setting | ||
119 | */ | ||
120 | public function testVisibilityInvalidValue(): void | ||
121 | { | ||
122 | $arg = ['visibility' => 'test']; | ||
123 | |||
124 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller/?searchtag=abc'; | ||
125 | |||
126 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
127 | $this->container->sessionManager | ||
128 | ->expects(static::never()) | ||
129 | ->method('setSessionParameter') | ||
130 | ; | ||
131 | $this->container->sessionManager | ||
132 | ->expects(static::once()) | ||
133 | ->method('deleteSessionParameter') | ||
134 | ->with(SessionManager::KEY_VISIBILITY) | ||
135 | ; | ||
136 | |||
137 | $request = $this->createMock(Request::class); | ||
138 | $response = new Response(); | ||
139 | |||
140 | $result = $this->controller->visibility($request, $response, $arg); | ||
141 | |||
142 | static::assertInstanceOf(Response::class, $result); | ||
143 | static::assertSame(302, $result->getStatusCode()); | ||
144 | static::assertSame(['/subfolder/controller/?searchtag=abc'], $result->getHeader('location')); | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * Visibility - Try to change visibility while logged out | ||
149 | */ | ||
150 | public function testVisibilityLoggedOut(): void | ||
151 | { | ||
152 | $arg = ['visibility' => 'test']; | ||
153 | |||
154 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller/?searchtag=abc'; | ||
155 | |||
156 | $this->container->loginManager = $this->createMock(LoginManager::class); | ||
157 | $this->container->loginManager->method('isLoggedIn')->willReturn(false); | ||
158 | $this->container->sessionManager | ||
159 | ->expects(static::never()) | ||
160 | ->method('setSessionParameter') | ||
161 | ; | ||
162 | $this->container->sessionManager | ||
163 | ->expects(static::never()) | ||
164 | ->method('deleteSessionParameter') | ||
165 | ->with(SessionManager::KEY_VISIBILITY) | ||
166 | ; | ||
167 | |||
168 | $request = $this->createMock(Request::class); | ||
169 | $response = new Response(); | ||
170 | |||
171 | $result = $this->controller->visibility($request, $response, $arg); | ||
172 | |||
173 | static::assertInstanceOf(Response::class, $result); | ||
174 | static::assertSame(302, $result->getStatusCode()); | ||
175 | static::assertSame(['/subfolder/controller/?searchtag=abc'], $result->getHeader('location')); | ||
176 | } | ||
177 | } | ||
diff --git a/tests/front/controller/admin/ShaarliAdminControllerTest.php b/tests/front/controller/admin/ShaarliAdminControllerTest.php new file mode 100644 index 00000000..486d5d2d --- /dev/null +++ b/tests/front/controller/admin/ShaarliAdminControllerTest.php | |||
@@ -0,0 +1,184 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Front\Exception\WrongTokenException; | ||
8 | use Shaarli\Security\SessionManager; | ||
9 | use Shaarli\TestCase; | ||
10 | use Slim\Http\Request; | ||
11 | |||
12 | /** | ||
13 | * Class ShaarliControllerTest | ||
14 | * | ||
15 | * This class is used to test default behavior of ShaarliAdminController abstract class. | ||
16 | * It uses a dummy non abstract controller. | ||
17 | */ | ||
18 | class ShaarliAdminControllerTest extends TestCase | ||
19 | { | ||
20 | use FrontAdminControllerMockHelper; | ||
21 | |||
22 | /** @var ShaarliAdminController */ | ||
23 | protected $controller; | ||
24 | |||
25 | public function setUp(): void | ||
26 | { | ||
27 | $this->createContainer(); | ||
28 | |||
29 | $this->controller = new class($this->container) extends ShaarliAdminController | ||
30 | { | ||
31 | public function checkToken(Request $request): bool | ||
32 | { | ||
33 | return parent::checkToken($request); | ||
34 | } | ||
35 | |||
36 | public function saveSuccessMessage(string $message): void | ||
37 | { | ||
38 | parent::saveSuccessMessage($message); | ||
39 | } | ||
40 | |||
41 | public function saveWarningMessage(string $message): void | ||
42 | { | ||
43 | parent::saveWarningMessage($message); | ||
44 | } | ||
45 | |||
46 | public function saveErrorMessage(string $message): void | ||
47 | { | ||
48 | parent::saveErrorMessage($message); | ||
49 | } | ||
50 | }; | ||
51 | } | ||
52 | |||
53 | /** | ||
54 | * Trigger controller's checkToken with a valid token. | ||
55 | */ | ||
56 | public function testCheckTokenWithValidToken(): void | ||
57 | { | ||
58 | $request = $this->createMock(Request::class); | ||
59 | $request->method('getParam')->with('token')->willReturn($token = '12345'); | ||
60 | |||
61 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
62 | $this->container->sessionManager->method('checkToken')->with($token)->willReturn(true); | ||
63 | |||
64 | static::assertTrue($this->controller->checkToken($request)); | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * Trigger controller's checkToken with na valid token should raise an exception. | ||
69 | */ | ||
70 | public function testCheckTokenWithNotValidToken(): void | ||
71 | { | ||
72 | $request = $this->createMock(Request::class); | ||
73 | $request->method('getParam')->with('token')->willReturn($token = '12345'); | ||
74 | |||
75 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
76 | $this->container->sessionManager->method('checkToken')->with($token)->willReturn(false); | ||
77 | |||
78 | $this->expectException(WrongTokenException::class); | ||
79 | |||
80 | $this->controller->checkToken($request); | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * Test saveSuccessMessage() with a first message. | ||
85 | */ | ||
86 | public function testSaveSuccessMessage(): void | ||
87 | { | ||
88 | $this->container->sessionManager | ||
89 | ->expects(static::once()) | ||
90 | ->method('setSessionParameter') | ||
91 | ->with(SessionManager::KEY_SUCCESS_MESSAGES, [$message = 'bravo!']) | ||
92 | ; | ||
93 | |||
94 | $this->controller->saveSuccessMessage($message); | ||
95 | } | ||
96 | |||
97 | /** | ||
98 | * Test saveSuccessMessage() with existing messages. | ||
99 | */ | ||
100 | public function testSaveSuccessMessageWithExistingMessages(): void | ||
101 | { | ||
102 | $this->container->sessionManager | ||
103 | ->expects(static::once()) | ||
104 | ->method('getSessionParameter') | ||
105 | ->with(SessionManager::KEY_SUCCESS_MESSAGES) | ||
106 | ->willReturn(['success1', 'success2']) | ||
107 | ; | ||
108 | $this->container->sessionManager | ||
109 | ->expects(static::once()) | ||
110 | ->method('setSessionParameter') | ||
111 | ->with(SessionManager::KEY_SUCCESS_MESSAGES, ['success1', 'success2', $message = 'bravo!']) | ||
112 | ; | ||
113 | |||
114 | $this->controller->saveSuccessMessage($message); | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * Test saveWarningMessage() with a first message. | ||
119 | */ | ||
120 | public function testSaveWarningMessage(): void | ||
121 | { | ||
122 | $this->container->sessionManager | ||
123 | ->expects(static::once()) | ||
124 | ->method('setSessionParameter') | ||
125 | ->with(SessionManager::KEY_WARNING_MESSAGES, [$message = 'warning!']) | ||
126 | ; | ||
127 | |||
128 | $this->controller->saveWarningMessage($message); | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * Test saveWarningMessage() with existing messages. | ||
133 | */ | ||
134 | public function testSaveWarningMessageWithExistingMessages(): void | ||
135 | { | ||
136 | $this->container->sessionManager | ||
137 | ->expects(static::once()) | ||
138 | ->method('getSessionParameter') | ||
139 | ->with(SessionManager::KEY_WARNING_MESSAGES) | ||
140 | ->willReturn(['warning1', 'warning2']) | ||
141 | ; | ||
142 | $this->container->sessionManager | ||
143 | ->expects(static::once()) | ||
144 | ->method('setSessionParameter') | ||
145 | ->with(SessionManager::KEY_WARNING_MESSAGES, ['warning1', 'warning2', $message = 'warning!']) | ||
146 | ; | ||
147 | |||
148 | $this->controller->saveWarningMessage($message); | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * Test saveErrorMessage() with a first message. | ||
153 | */ | ||
154 | public function testSaveErrorMessage(): void | ||
155 | { | ||
156 | $this->container->sessionManager | ||
157 | ->expects(static::once()) | ||
158 | ->method('setSessionParameter') | ||
159 | ->with(SessionManager::KEY_ERROR_MESSAGES, [$message = 'error!']) | ||
160 | ; | ||
161 | |||
162 | $this->controller->saveErrorMessage($message); | ||
163 | } | ||
164 | |||
165 | /** | ||
166 | * Test saveErrorMessage() with existing messages. | ||
167 | */ | ||
168 | public function testSaveErrorMessageWithExistingMessages(): void | ||
169 | { | ||
170 | $this->container->sessionManager | ||
171 | ->expects(static::once()) | ||
172 | ->method('getSessionParameter') | ||
173 | ->with(SessionManager::KEY_ERROR_MESSAGES) | ||
174 | ->willReturn(['error1', 'error2']) | ||
175 | ; | ||
176 | $this->container->sessionManager | ||
177 | ->expects(static::once()) | ||
178 | ->method('setSessionParameter') | ||
179 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['error1', 'error2', $message = 'error!']) | ||
180 | ; | ||
181 | |||
182 | $this->controller->saveErrorMessage($message); | ||
183 | } | ||
184 | } | ||
diff --git a/tests/front/controller/admin/ThumbnailsControllerTest.php b/tests/front/controller/admin/ThumbnailsControllerTest.php new file mode 100644 index 00000000..f4a8acff --- /dev/null +++ b/tests/front/controller/admin/ThumbnailsControllerTest.php | |||
@@ -0,0 +1,154 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\TestCase; | ||
10 | use Shaarli\Thumbnailer; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | |||
14 | class ThumbnailsControllerTest extends TestCase | ||
15 | { | ||
16 | use FrontAdminControllerMockHelper; | ||
17 | |||
18 | /** @var ThumbnailsController */ | ||
19 | protected $controller; | ||
20 | |||
21 | public function setUp(): void | ||
22 | { | ||
23 | $this->createContainer(); | ||
24 | |||
25 | $this->controller = new ThumbnailsController($this->container); | ||
26 | } | ||
27 | |||
28 | /** | ||
29 | * Test displaying the thumbnails update page | ||
30 | * Note that only non-note and HTTP bookmarks should be returned. | ||
31 | */ | ||
32 | public function testIndex(): void | ||
33 | { | ||
34 | $assignedVariables = []; | ||
35 | $this->assignTemplateVars($assignedVariables); | ||
36 | |||
37 | $request = $this->createMock(Request::class); | ||
38 | $response = new Response(); | ||
39 | |||
40 | $this->container->bookmarkService | ||
41 | ->expects(static::once()) | ||
42 | ->method('search') | ||
43 | ->willReturn([ | ||
44 | (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'), | ||
45 | (new Bookmark())->setId(2)->setUrl('?abcdef')->setTitle('Note 1'), | ||
46 | (new Bookmark())->setId(3)->setUrl('http://url2.tld')->setTitle('Title 2'), | ||
47 | (new Bookmark())->setId(4)->setUrl('ftp://domain.tld', ['ftp'])->setTitle('FTP'), | ||
48 | ]) | ||
49 | ; | ||
50 | |||
51 | $result = $this->controller->index($request, $response); | ||
52 | |||
53 | static::assertSame(200, $result->getStatusCode()); | ||
54 | static::assertSame('thumbnails', (string) $result->getBody()); | ||
55 | |||
56 | static::assertSame('Thumbnails update - Shaarli', $assignedVariables['pagetitle']); | ||
57 | static::assertSame([1, 3], $assignedVariables['ids']); | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * Test updating a bookmark thumbnail with valid parameters | ||
62 | */ | ||
63 | public function testAjaxUpdateValid(): void | ||
64 | { | ||
65 | $request = $this->createMock(Request::class); | ||
66 | $response = new Response(); | ||
67 | |||
68 | $bookmark = (new Bookmark()) | ||
69 | ->setId($id = 123) | ||
70 | ->setUrl($url = 'http://url1.tld') | ||
71 | ->setTitle('Title 1') | ||
72 | ->setThumbnail(false) | ||
73 | ; | ||
74 | |||
75 | $this->container->thumbnailer = $this->createMock(Thumbnailer::class); | ||
76 | $this->container->thumbnailer | ||
77 | ->expects(static::once()) | ||
78 | ->method('get') | ||
79 | ->with($url) | ||
80 | ->willReturn($thumb = 'http://img.tld/pic.png') | ||
81 | ; | ||
82 | |||
83 | $this->container->bookmarkService | ||
84 | ->expects(static::once()) | ||
85 | ->method('get') | ||
86 | ->with($id) | ||
87 | ->willReturn($bookmark) | ||
88 | ; | ||
89 | $this->container->bookmarkService | ||
90 | ->expects(static::once()) | ||
91 | ->method('set') | ||
92 | ->willReturnCallback(function (Bookmark $bookmark) use ($thumb) { | ||
93 | static::assertSame($thumb, $bookmark->getThumbnail()); | ||
94 | }) | ||
95 | ; | ||
96 | |||
97 | $result = $this->controller->ajaxUpdate($request, $response, ['id' => (string) $id]); | ||
98 | |||
99 | static::assertSame(200, $result->getStatusCode()); | ||
100 | |||
101 | $payload = json_decode((string) $result->getBody(), true); | ||
102 | |||
103 | static::assertSame($id, $payload['id']); | ||
104 | static::assertSame($url, $payload['url']); | ||
105 | static::assertSame($thumb, $payload['thumbnail']); | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * Test updating a bookmark thumbnail - Invalid ID | ||
110 | */ | ||
111 | public function testAjaxUpdateInvalidId(): void | ||
112 | { | ||
113 | $request = $this->createMock(Request::class); | ||
114 | $response = new Response(); | ||
115 | |||
116 | $result = $this->controller->ajaxUpdate($request, $response, ['id' => 'nope']); | ||
117 | |||
118 | static::assertSame(400, $result->getStatusCode()); | ||
119 | } | ||
120 | |||
121 | /** | ||
122 | * Test updating a bookmark thumbnail - No ID | ||
123 | */ | ||
124 | public function testAjaxUpdateNoId(): void | ||
125 | { | ||
126 | $request = $this->createMock(Request::class); | ||
127 | $response = new Response(); | ||
128 | |||
129 | $result = $this->controller->ajaxUpdate($request, $response, []); | ||
130 | |||
131 | static::assertSame(400, $result->getStatusCode()); | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * Test updating a bookmark thumbnail with valid parameters | ||
136 | */ | ||
137 | public function testAjaxUpdateBookmarkNotFound(): void | ||
138 | { | ||
139 | $id = 123; | ||
140 | $request = $this->createMock(Request::class); | ||
141 | $response = new Response(); | ||
142 | |||
143 | $this->container->bookmarkService | ||
144 | ->expects(static::once()) | ||
145 | ->method('get') | ||
146 | ->with($id) | ||
147 | ->willThrowException(new BookmarkNotFoundException()) | ||
148 | ; | ||
149 | |||
150 | $result = $this->controller->ajaxUpdate($request, $response, ['id' => (string) $id]); | ||
151 | |||
152 | static::assertSame(404, $result->getStatusCode()); | ||
153 | } | ||
154 | } | ||
diff --git a/tests/front/controller/admin/TokenControllerTest.php b/tests/front/controller/admin/TokenControllerTest.php new file mode 100644 index 00000000..d2f0907f --- /dev/null +++ b/tests/front/controller/admin/TokenControllerTest.php | |||
@@ -0,0 +1,41 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\TestCase; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | class TokenControllerTest extends TestCase | ||
12 | { | ||
13 | use FrontAdminControllerMockHelper; | ||
14 | |||
15 | /** @var TokenController */ | ||
16 | protected $controller; | ||
17 | |||
18 | public function setUp(): void | ||
19 | { | ||
20 | $this->createContainer(); | ||
21 | |||
22 | $this->controller = new TokenController($this->container); | ||
23 | } | ||
24 | |||
25 | public function testGetToken(): void | ||
26 | { | ||
27 | $request = $this->createMock(Request::class); | ||
28 | $response = new Response(); | ||
29 | |||
30 | $this->container->sessionManager | ||
31 | ->expects(static::once()) | ||
32 | ->method('generateToken') | ||
33 | ->willReturn($token = 'token1234') | ||
34 | ; | ||
35 | |||
36 | $result = $this->controller->getToken($request, $response); | ||
37 | |||
38 | static::assertSame(200, $result->getStatusCode()); | ||
39 | static::assertSame($token, (string) $result->getBody()); | ||
40 | } | ||
41 | } | ||
diff --git a/tests/front/controller/admin/ToolsControllerTest.php b/tests/front/controller/admin/ToolsControllerTest.php new file mode 100644 index 00000000..e82f8b14 --- /dev/null +++ b/tests/front/controller/admin/ToolsControllerTest.php | |||
@@ -0,0 +1,69 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Shaarli\TestCase; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | class ToolsControllerTest extends TestCase | ||
12 | { | ||
13 | use FrontAdminControllerMockHelper; | ||
14 | |||
15 | /** @var ToolsController */ | ||
16 | protected $controller; | ||
17 | |||
18 | public function setUp(): void | ||
19 | { | ||
20 | $this->createContainer(); | ||
21 | |||
22 | $this->controller = new ToolsController($this->container); | ||
23 | } | ||
24 | |||
25 | public function testDefaultInvokeWithHttps(): void | ||
26 | { | ||
27 | $request = $this->createMock(Request::class); | ||
28 | $response = new Response(); | ||
29 | |||
30 | $this->container->environment = [ | ||
31 | 'SERVER_NAME' => 'shaarli', | ||
32 | 'SERVER_PORT' => 443, | ||
33 | 'HTTPS' => 'on', | ||
34 | ]; | ||
35 | |||
36 | // Save RainTPL assigned variables | ||
37 | $assignedVariables = []; | ||
38 | $this->assignTemplateVars($assignedVariables); | ||
39 | |||
40 | $result = $this->controller->index($request, $response); | ||
41 | |||
42 | static::assertSame(200, $result->getStatusCode()); | ||
43 | static::assertSame('tools', (string) $result->getBody()); | ||
44 | static::assertSame('https://shaarli/', $assignedVariables['pageabsaddr']); | ||
45 | static::assertTrue($assignedVariables['sslenabled']); | ||
46 | } | ||
47 | |||
48 | public function testDefaultInvokeWithoutHttps(): void | ||
49 | { | ||
50 | $request = $this->createMock(Request::class); | ||
51 | $response = new Response(); | ||
52 | |||
53 | $this->container->environment = [ | ||
54 | 'SERVER_NAME' => 'shaarli', | ||
55 | 'SERVER_PORT' => 80, | ||
56 | ]; | ||
57 | |||
58 | // Save RainTPL assigned variables | ||
59 | $assignedVariables = []; | ||
60 | $this->assignTemplateVars($assignedVariables); | ||
61 | |||
62 | $result = $this->controller->index($request, $response); | ||
63 | |||
64 | static::assertSame(200, $result->getStatusCode()); | ||
65 | static::assertSame('tools', (string) $result->getBody()); | ||
66 | static::assertSame('http://shaarli/', $assignedVariables['pageabsaddr']); | ||
67 | static::assertFalse($assignedVariables['sslenabled']); | ||
68 | } | ||
69 | } | ||
diff --git a/tests/front/controller/visitor/BookmarkListControllerTest.php b/tests/front/controller/visitor/BookmarkListControllerTest.php new file mode 100644 index 00000000..0c95df97 --- /dev/null +++ b/tests/front/controller/visitor/BookmarkListControllerTest.php | |||
@@ -0,0 +1,448 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\Exception\BookmarkNotFoundException; | ||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\Security\LoginManager; | ||
11 | use Shaarli\TestCase; | ||
12 | use Shaarli\Thumbnailer; | ||
13 | use Slim\Http\Request; | ||
14 | use Slim\Http\Response; | ||
15 | |||
16 | class BookmarkListControllerTest extends TestCase | ||
17 | { | ||
18 | use FrontControllerMockHelper; | ||
19 | |||
20 | /** @var BookmarkListController */ | ||
21 | protected $controller; | ||
22 | |||
23 | public function setUp(): void | ||
24 | { | ||
25 | $this->createContainer(); | ||
26 | |||
27 | $this->controller = new BookmarkListController($this->container); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Test rendering list of bookmarks with default parameters (first page). | ||
32 | */ | ||
33 | public function testIndexDefaultFirstPage(): void | ||
34 | { | ||
35 | $assignedVariables = []; | ||
36 | $this->assignTemplateVars($assignedVariables); | ||
37 | |||
38 | $request = $this->createMock(Request::class); | ||
39 | $response = new Response(); | ||
40 | |||
41 | $this->container->bookmarkService | ||
42 | ->expects(static::once()) | ||
43 | ->method('search') | ||
44 | ->with( | ||
45 | ['searchtags' => '', 'searchterm' => ''], | ||
46 | null, | ||
47 | false, | ||
48 | false | ||
49 | ) | ||
50 | ->willReturn([ | ||
51 | (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'), | ||
52 | (new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'), | ||
53 | (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'), | ||
54 | ] | ||
55 | ); | ||
56 | |||
57 | $this->container->sessionManager | ||
58 | ->method('getSessionParameter') | ||
59 | ->willReturnCallback(function (string $parameter, $default = null) { | ||
60 | if ('LINKS_PER_PAGE' === $parameter) { | ||
61 | return 2; | ||
62 | } | ||
63 | |||
64 | return $default; | ||
65 | }) | ||
66 | ; | ||
67 | |||
68 | $result = $this->controller->index($request, $response); | ||
69 | |||
70 | static::assertSame(200, $result->getStatusCode()); | ||
71 | static::assertSame('linklist', (string) $result->getBody()); | ||
72 | |||
73 | static::assertSame('Shaarli', $assignedVariables['pagetitle']); | ||
74 | static::assertSame('?page=2', $assignedVariables['previous_page_url']); | ||
75 | static::assertSame('', $assignedVariables['next_page_url']); | ||
76 | static::assertSame(2, $assignedVariables['page_max']); | ||
77 | static::assertSame('', $assignedVariables['search_tags']); | ||
78 | static::assertSame(3, $assignedVariables['result_count']); | ||
79 | static::assertSame(1, $assignedVariables['page_current']); | ||
80 | static::assertSame('', $assignedVariables['search_term']); | ||
81 | static::assertNull($assignedVariables['visibility']); | ||
82 | static::assertCount(2, $assignedVariables['links']); | ||
83 | |||
84 | $link = $assignedVariables['links'][0]; | ||
85 | |||
86 | static::assertSame(1, $link['id']); | ||
87 | static::assertSame('http://url1.tld', $link['url']); | ||
88 | static::assertSame('Title 1', $link['title']); | ||
89 | |||
90 | $link = $assignedVariables['links'][1]; | ||
91 | |||
92 | static::assertSame(2, $link['id']); | ||
93 | static::assertSame('http://url2.tld', $link['url']); | ||
94 | static::assertSame('Title 2', $link['title']); | ||
95 | } | ||
96 | |||
97 | /** | ||
98 | * Test rendering list of bookmarks with default parameters (second page). | ||
99 | */ | ||
100 | public function testIndexDefaultSecondPage(): void | ||
101 | { | ||
102 | $assignedVariables = []; | ||
103 | $this->assignTemplateVars($assignedVariables); | ||
104 | |||
105 | $request = $this->createMock(Request::class); | ||
106 | $request->method('getParam')->willReturnCallback(function (string $key) { | ||
107 | if ('page' === $key) { | ||
108 | return '2'; | ||
109 | } | ||
110 | |||
111 | return null; | ||
112 | }); | ||
113 | $response = new Response(); | ||
114 | |||
115 | $this->container->bookmarkService | ||
116 | ->expects(static::once()) | ||
117 | ->method('search') | ||
118 | ->with( | ||
119 | ['searchtags' => '', 'searchterm' => ''], | ||
120 | null, | ||
121 | false, | ||
122 | false | ||
123 | ) | ||
124 | ->willReturn([ | ||
125 | (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'), | ||
126 | (new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'), | ||
127 | (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'), | ||
128 | ]) | ||
129 | ; | ||
130 | |||
131 | $this->container->sessionManager | ||
132 | ->method('getSessionParameter') | ||
133 | ->willReturnCallback(function (string $parameter, $default = null) { | ||
134 | if ('LINKS_PER_PAGE' === $parameter) { | ||
135 | return 2; | ||
136 | } | ||
137 | |||
138 | return $default; | ||
139 | }) | ||
140 | ; | ||
141 | |||
142 | $result = $this->controller->index($request, $response); | ||
143 | |||
144 | static::assertSame(200, $result->getStatusCode()); | ||
145 | static::assertSame('linklist', (string) $result->getBody()); | ||
146 | |||
147 | static::assertSame('Shaarli', $assignedVariables['pagetitle']); | ||
148 | static::assertSame('', $assignedVariables['previous_page_url']); | ||
149 | static::assertSame('?page=1', $assignedVariables['next_page_url']); | ||
150 | static::assertSame(2, $assignedVariables['page_max']); | ||
151 | static::assertSame('', $assignedVariables['search_tags']); | ||
152 | static::assertSame(3, $assignedVariables['result_count']); | ||
153 | static::assertSame(2, $assignedVariables['page_current']); | ||
154 | static::assertSame('', $assignedVariables['search_term']); | ||
155 | static::assertNull($assignedVariables['visibility']); | ||
156 | static::assertCount(1, $assignedVariables['links']); | ||
157 | |||
158 | $link = $assignedVariables['links'][2]; | ||
159 | |||
160 | static::assertSame(3, $link['id']); | ||
161 | static::assertSame('http://url3.tld', $link['url']); | ||
162 | static::assertSame('Title 3', $link['title']); | ||
163 | } | ||
164 | |||
165 | /** | ||
166 | * Test rendering list of bookmarks with filters. | ||
167 | */ | ||
168 | public function testIndexDefaultWithFilters(): void | ||
169 | { | ||
170 | $assignedVariables = []; | ||
171 | $this->assignTemplateVars($assignedVariables); | ||
172 | |||
173 | $request = $this->createMock(Request::class); | ||
174 | $request->method('getParam')->willReturnCallback(function (string $key) { | ||
175 | if ('searchtags' === $key) { | ||
176 | return 'abc def'; | ||
177 | } | ||
178 | if ('searchterm' === $key) { | ||
179 | return 'ghi jkl'; | ||
180 | } | ||
181 | |||
182 | return null; | ||
183 | }); | ||
184 | $response = new Response(); | ||
185 | |||
186 | $this->container->sessionManager | ||
187 | ->method('getSessionParameter') | ||
188 | ->willReturnCallback(function (string $key, $default) { | ||
189 | if ('LINKS_PER_PAGE' === $key) { | ||
190 | return 2; | ||
191 | } | ||
192 | if ('visibility' === $key) { | ||
193 | return 'private'; | ||
194 | } | ||
195 | if ('untaggedonly' === $key) { | ||
196 | return true; | ||
197 | } | ||
198 | |||
199 | return $default; | ||
200 | }) | ||
201 | ; | ||
202 | |||
203 | $this->container->bookmarkService | ||
204 | ->expects(static::once()) | ||
205 | ->method('search') | ||
206 | ->with( | ||
207 | ['searchtags' => 'abc def', 'searchterm' => 'ghi jkl'], | ||
208 | 'private', | ||
209 | false, | ||
210 | true | ||
211 | ) | ||
212 | ->willReturn([ | ||
213 | (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'), | ||
214 | (new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'), | ||
215 | (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'), | ||
216 | ]) | ||
217 | ; | ||
218 | |||
219 | $result = $this->controller->index($request, $response); | ||
220 | |||
221 | static::assertSame(200, $result->getStatusCode()); | ||
222 | static::assertSame('linklist', (string) $result->getBody()); | ||
223 | |||
224 | static::assertSame('Search: ghi jkl [abc] [def] - Shaarli', $assignedVariables['pagetitle']); | ||
225 | static::assertSame('?page=2&searchterm=ghi+jkl&searchtags=abc+def', $assignedVariables['previous_page_url']); | ||
226 | } | ||
227 | |||
228 | /** | ||
229 | * Test displaying a permalink with valid parameters | ||
230 | */ | ||
231 | public function testPermalinkValid(): void | ||
232 | { | ||
233 | $hash = 'abcdef'; | ||
234 | |||
235 | $assignedVariables = []; | ||
236 | $this->assignTemplateVars($assignedVariables); | ||
237 | |||
238 | $request = $this->createMock(Request::class); | ||
239 | $response = new Response(); | ||
240 | |||
241 | $this->container->bookmarkService | ||
242 | ->expects(static::once()) | ||
243 | ->method('findByHash') | ||
244 | ->with($hash) | ||
245 | ->willReturn((new Bookmark())->setId(123)->setTitle('Title 1')->setUrl('http://url1.tld')) | ||
246 | ; | ||
247 | |||
248 | $result = $this->controller->permalink($request, $response, ['hash' => $hash]); | ||
249 | |||
250 | static::assertSame(200, $result->getStatusCode()); | ||
251 | static::assertSame('linklist', (string) $result->getBody()); | ||
252 | |||
253 | static::assertSame('Title 1 - Shaarli', $assignedVariables['pagetitle']); | ||
254 | static::assertCount(1, $assignedVariables['links']); | ||
255 | |||
256 | $link = $assignedVariables['links'][0]; | ||
257 | |||
258 | static::assertSame(123, $link['id']); | ||
259 | static::assertSame('http://url1.tld', $link['url']); | ||
260 | static::assertSame('Title 1', $link['title']); | ||
261 | } | ||
262 | |||
263 | /** | ||
264 | * Test displaying a permalink with an unknown small hash : renders a 404 template error | ||
265 | */ | ||
266 | public function testPermalinkNotFound(): void | ||
267 | { | ||
268 | $hash = 'abcdef'; | ||
269 | |||
270 | $assignedVariables = []; | ||
271 | $this->assignTemplateVars($assignedVariables); | ||
272 | |||
273 | $request = $this->createMock(Request::class); | ||
274 | $response = new Response(); | ||
275 | |||
276 | $this->container->bookmarkService | ||
277 | ->expects(static::once()) | ||
278 | ->method('findByHash') | ||
279 | ->with($hash) | ||
280 | ->willThrowException(new BookmarkNotFoundException()) | ||
281 | ; | ||
282 | |||
283 | $result = $this->controller->permalink($request, $response, ['hash' => $hash]); | ||
284 | |||
285 | static::assertSame(200, $result->getStatusCode()); | ||
286 | static::assertSame('404', (string) $result->getBody()); | ||
287 | |||
288 | static::assertSame( | ||
289 | 'The link you are trying to reach does not exist or has been deleted.', | ||
290 | $assignedVariables['error_message'] | ||
291 | ); | ||
292 | } | ||
293 | |||
294 | /** | ||
295 | * Test getting link list with thumbnail updates. | ||
296 | * -> 2 thumbnails update, only 1 datastore write | ||
297 | */ | ||
298 | public function testThumbnailUpdateFromLinkList(): void | ||
299 | { | ||
300 | $request = $this->createMock(Request::class); | ||
301 | $response = new Response(); | ||
302 | |||
303 | $this->container->loginManager = $this->createMock(LoginManager::class); | ||
304 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
305 | |||
306 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
307 | $this->container->conf | ||
308 | ->method('get') | ||
309 | ->willReturnCallback(function (string $key, $default) { | ||
310 | return $key === 'thumbnails.mode' ? Thumbnailer::MODE_ALL : $default; | ||
311 | }) | ||
312 | ; | ||
313 | |||
314 | $this->container->thumbnailer = $this->createMock(Thumbnailer::class); | ||
315 | $this->container->thumbnailer | ||
316 | ->expects(static::exactly(2)) | ||
317 | ->method('get') | ||
318 | ->withConsecutive(['https://url2.tld'], ['https://url4.tld']) | ||
319 | ; | ||
320 | |||
321 | $this->container->bookmarkService | ||
322 | ->expects(static::once()) | ||
323 | ->method('search') | ||
324 | ->willReturn([ | ||
325 | (new Bookmark())->setId(1)->setUrl('https://url1.tld')->setTitle('Title 1')->setThumbnail(false), | ||
326 | $b1 = (new Bookmark())->setId(2)->setUrl('https://url2.tld')->setTitle('Title 2'), | ||
327 | (new Bookmark())->setId(3)->setUrl('https://url3.tld')->setTitle('Title 3')->setThumbnail(false), | ||
328 | $b2 = (new Bookmark())->setId(2)->setUrl('https://url4.tld')->setTitle('Title 4'), | ||
329 | (new Bookmark())->setId(2)->setUrl('ftp://url5.tld', ['ftp'])->setTitle('Title 5'), | ||
330 | ]) | ||
331 | ; | ||
332 | $this->container->bookmarkService | ||
333 | ->expects(static::exactly(2)) | ||
334 | ->method('set') | ||
335 | ->withConsecutive([$b1, false], [$b2, false]) | ||
336 | ; | ||
337 | $this->container->bookmarkService->expects(static::once())->method('save'); | ||
338 | |||
339 | $result = $this->controller->index($request, $response); | ||
340 | |||
341 | static::assertSame(200, $result->getStatusCode()); | ||
342 | static::assertSame('linklist', (string) $result->getBody()); | ||
343 | } | ||
344 | |||
345 | /** | ||
346 | * Test getting a permalink with thumbnail update. | ||
347 | */ | ||
348 | public function testThumbnailUpdateFromPermalink(): void | ||
349 | { | ||
350 | $request = $this->createMock(Request::class); | ||
351 | $response = new Response(); | ||
352 | |||
353 | $this->container->loginManager = $this->createMock(LoginManager::class); | ||
354 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
355 | |||
356 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
357 | $this->container->conf | ||
358 | ->method('get') | ||
359 | ->willReturnCallback(function (string $key, $default) { | ||
360 | return $key === 'thumbnails.mode' ? Thumbnailer::MODE_ALL : $default; | ||
361 | }) | ||
362 | ; | ||
363 | |||
364 | $this->container->thumbnailer = $this->createMock(Thumbnailer::class); | ||
365 | $this->container->thumbnailer->expects(static::once())->method('get')->withConsecutive(['https://url.tld']); | ||
366 | |||
367 | $this->container->bookmarkService | ||
368 | ->expects(static::once()) | ||
369 | ->method('findByHash') | ||
370 | ->willReturn($bookmark = (new Bookmark())->setId(2)->setUrl('https://url.tld')->setTitle('Title 1')) | ||
371 | ; | ||
372 | $this->container->bookmarkService->expects(static::once())->method('set')->with($bookmark, true); | ||
373 | $this->container->bookmarkService->expects(static::never())->method('save'); | ||
374 | |||
375 | $result = $this->controller->permalink($request, $response, ['hash' => 'abc']); | ||
376 | |||
377 | static::assertSame(200, $result->getStatusCode()); | ||
378 | static::assertSame('linklist', (string) $result->getBody()); | ||
379 | } | ||
380 | |||
381 | /** | ||
382 | * Trigger legacy controller in link list controller: permalink | ||
383 | */ | ||
384 | public function testLegacyControllerPermalink(): void | ||
385 | { | ||
386 | $hash = 'abcdef'; | ||
387 | $this->container->environment['QUERY_STRING'] = $hash; | ||
388 | |||
389 | $request = $this->createMock(Request::class); | ||
390 | $response = new Response(); | ||
391 | |||
392 | $result = $this->controller->index($request, $response); | ||
393 | |||
394 | static::assertSame(302, $result->getStatusCode()); | ||
395 | static::assertSame('/subfolder/shaare/' . $hash, $result->getHeader('location')[0]); | ||
396 | } | ||
397 | |||
398 | /** | ||
399 | * Trigger legacy controller in link list controller: ?do= query parameter | ||
400 | */ | ||
401 | public function testLegacyControllerDoPage(): void | ||
402 | { | ||
403 | $request = $this->createMock(Request::class); | ||
404 | $request->method('getQueryParam')->with('do')->willReturn('picwall'); | ||
405 | $response = new Response(); | ||
406 | |||
407 | $result = $this->controller->index($request, $response); | ||
408 | |||
409 | static::assertSame(302, $result->getStatusCode()); | ||
410 | static::assertSame('/subfolder/picture-wall', $result->getHeader('location')[0]); | ||
411 | } | ||
412 | |||
413 | /** | ||
414 | * Trigger legacy controller in link list controller: ?do= query parameter with unknown legacy route | ||
415 | */ | ||
416 | public function testLegacyControllerUnknownDoPage(): void | ||
417 | { | ||
418 | $request = $this->createMock(Request::class); | ||
419 | $request->method('getQueryParam')->with('do')->willReturn('nope'); | ||
420 | $response = new Response(); | ||
421 | |||
422 | $result = $this->controller->index($request, $response); | ||
423 | |||
424 | static::assertSame(200, $result->getStatusCode()); | ||
425 | static::assertSame('linklist', (string) $result->getBody()); | ||
426 | } | ||
427 | |||
428 | /** | ||
429 | * Trigger legacy controller in link list controller: other GET route (e.g. ?post) | ||
430 | */ | ||
431 | public function testLegacyControllerGetParameter(): void | ||
432 | { | ||
433 | $request = $this->createMock(Request::class); | ||
434 | $request->method('getQueryParams')->willReturn(['post' => $url = 'http://url.tld']); | ||
435 | $response = new Response(); | ||
436 | |||
437 | $this->container->loginManager = $this->createMock(LoginManager::class); | ||
438 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
439 | |||
440 | $result = $this->controller->index($request, $response); | ||
441 | |||
442 | static::assertSame(302, $result->getStatusCode()); | ||
443 | static::assertSame( | ||
444 | '/subfolder/admin/shaare?post=' . urlencode($url), | ||
445 | $result->getHeader('location')[0] | ||
446 | ); | ||
447 | } | ||
448 | } | ||
diff --git a/tests/front/controller/visitor/DailyControllerTest.php b/tests/front/controller/visitor/DailyControllerTest.php new file mode 100644 index 00000000..fc78bc13 --- /dev/null +++ b/tests/front/controller/visitor/DailyControllerTest.php | |||
@@ -0,0 +1,478 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Feed\CachedPage; | ||
9 | use Shaarli\TestCase; | ||
10 | use Slim\Http\Request; | ||
11 | use Slim\Http\Response; | ||
12 | |||
13 | class DailyControllerTest extends TestCase | ||
14 | { | ||
15 | use FrontControllerMockHelper; | ||
16 | |||
17 | /** @var DailyController */ | ||
18 | protected $controller; | ||
19 | |||
20 | public function setUp(): void | ||
21 | { | ||
22 | $this->createContainer(); | ||
23 | |||
24 | $this->controller = new DailyController($this->container); | ||
25 | DailyController::$DAILY_RSS_NB_DAYS = 2; | ||
26 | } | ||
27 | |||
28 | public function testValidIndexControllerInvokeDefault(): void | ||
29 | { | ||
30 | $currentDay = new \DateTimeImmutable('2020-05-13'); | ||
31 | |||
32 | $request = $this->createMock(Request::class); | ||
33 | $request->method('getQueryParam')->willReturn($currentDay->format('Ymd')); | ||
34 | $response = new Response(); | ||
35 | |||
36 | // Save RainTPL assigned variables | ||
37 | $assignedVariables = []; | ||
38 | $this->assignTemplateVars($assignedVariables); | ||
39 | |||
40 | // Links dataset: 2 links with thumbnails | ||
41 | $this->container->bookmarkService | ||
42 | ->expects(static::once()) | ||
43 | ->method('days') | ||
44 | ->willReturnCallback(function () use ($currentDay): array { | ||
45 | return [ | ||
46 | '20200510', | ||
47 | $currentDay->format('Ymd'), | ||
48 | '20200516', | ||
49 | ]; | ||
50 | }) | ||
51 | ; | ||
52 | $this->container->bookmarkService | ||
53 | ->expects(static::once()) | ||
54 | ->method('filterDay') | ||
55 | ->willReturnCallback(function (): array { | ||
56 | return [ | ||
57 | (new Bookmark()) | ||
58 | ->setId(1) | ||
59 | ->setUrl('http://url.tld') | ||
60 | ->setTitle(static::generateString(50)) | ||
61 | ->setDescription(static::generateString(500)) | ||
62 | , | ||
63 | (new Bookmark()) | ||
64 | ->setId(2) | ||
65 | ->setUrl('http://url2.tld') | ||
66 | ->setTitle(static::generateString(50)) | ||
67 | ->setDescription(static::generateString(500)) | ||
68 | , | ||
69 | (new Bookmark()) | ||
70 | ->setId(3) | ||
71 | ->setUrl('http://url3.tld') | ||
72 | ->setTitle(static::generateString(50)) | ||
73 | ->setDescription(static::generateString(500)) | ||
74 | , | ||
75 | ]; | ||
76 | }) | ||
77 | ; | ||
78 | |||
79 | // Make sure that PluginManager hook is triggered | ||
80 | $this->container->pluginManager | ||
81 | ->expects(static::atLeastOnce()) | ||
82 | ->method('executeHooks') | ||
83 | ->withConsecutive(['render_daily']) | ||
84 | ->willReturnCallback(function (string $hook, array $data, array $param) use ($currentDay): array { | ||
85 | if ('render_daily' === $hook) { | ||
86 | static::assertArrayHasKey('linksToDisplay', $data); | ||
87 | static::assertCount(3, $data['linksToDisplay']); | ||
88 | static::assertSame(1, $data['linksToDisplay'][0]['id']); | ||
89 | static::assertSame($currentDay->getTimestamp(), $data['day']); | ||
90 | static::assertSame('20200510', $data['previousday']); | ||
91 | static::assertSame('20200516', $data['nextday']); | ||
92 | |||
93 | static::assertArrayHasKey('loggedin', $param); | ||
94 | } | ||
95 | |||
96 | return $data; | ||
97 | }) | ||
98 | ; | ||
99 | |||
100 | $result = $this->controller->index($request, $response); | ||
101 | |||
102 | static::assertSame(200, $result->getStatusCode()); | ||
103 | static::assertSame('daily', (string) $result->getBody()); | ||
104 | static::assertSame( | ||
105 | 'Daily - '. format_date($currentDay, false, true) .' - Shaarli', | ||
106 | $assignedVariables['pagetitle'] | ||
107 | ); | ||
108 | static::assertEquals($currentDay, $assignedVariables['dayDate']); | ||
109 | static::assertEquals($currentDay->getTimestamp(), $assignedVariables['day']); | ||
110 | static::assertCount(3, $assignedVariables['linksToDisplay']); | ||
111 | |||
112 | $link = $assignedVariables['linksToDisplay'][0]; | ||
113 | |||
114 | static::assertSame(1, $link['id']); | ||
115 | static::assertSame('http://url.tld', $link['url']); | ||
116 | static::assertNotEmpty($link['title']); | ||
117 | static::assertNotEmpty($link['description']); | ||
118 | static::assertNotEmpty($link['formatedDescription']); | ||
119 | |||
120 | $link = $assignedVariables['linksToDisplay'][1]; | ||
121 | |||
122 | static::assertSame(2, $link['id']); | ||
123 | static::assertSame('http://url2.tld', $link['url']); | ||
124 | static::assertNotEmpty($link['title']); | ||
125 | static::assertNotEmpty($link['description']); | ||
126 | static::assertNotEmpty($link['formatedDescription']); | ||
127 | |||
128 | $link = $assignedVariables['linksToDisplay'][2]; | ||
129 | |||
130 | static::assertSame(3, $link['id']); | ||
131 | static::assertSame('http://url3.tld', $link['url']); | ||
132 | static::assertNotEmpty($link['title']); | ||
133 | static::assertNotEmpty($link['description']); | ||
134 | static::assertNotEmpty($link['formatedDescription']); | ||
135 | |||
136 | static::assertCount(3, $assignedVariables['cols']); | ||
137 | static::assertCount(1, $assignedVariables['cols'][0]); | ||
138 | static::assertCount(1, $assignedVariables['cols'][1]); | ||
139 | static::assertCount(1, $assignedVariables['cols'][2]); | ||
140 | |||
141 | $link = $assignedVariables['cols'][0][0]; | ||
142 | |||
143 | static::assertSame(1, $link['id']); | ||
144 | static::assertSame('http://url.tld', $link['url']); | ||
145 | static::assertNotEmpty($link['title']); | ||
146 | static::assertNotEmpty($link['description']); | ||
147 | static::assertNotEmpty($link['formatedDescription']); | ||
148 | |||
149 | $link = $assignedVariables['cols'][1][0]; | ||
150 | |||
151 | static::assertSame(2, $link['id']); | ||
152 | static::assertSame('http://url2.tld', $link['url']); | ||
153 | static::assertNotEmpty($link['title']); | ||
154 | static::assertNotEmpty($link['description']); | ||
155 | static::assertNotEmpty($link['formatedDescription']); | ||
156 | |||
157 | $link = $assignedVariables['cols'][2][0]; | ||
158 | |||
159 | static::assertSame(3, $link['id']); | ||
160 | static::assertSame('http://url3.tld', $link['url']); | ||
161 | static::assertNotEmpty($link['title']); | ||
162 | static::assertNotEmpty($link['description']); | ||
163 | static::assertNotEmpty($link['formatedDescription']); | ||
164 | } | ||
165 | |||
166 | /** | ||
167 | * Daily page - test that everything goes fine with no future or past bookmarks | ||
168 | */ | ||
169 | public function testValidIndexControllerInvokeNoFutureOrPast(): void | ||
170 | { | ||
171 | $currentDay = new \DateTimeImmutable('2020-05-13'); | ||
172 | |||
173 | $request = $this->createMock(Request::class); | ||
174 | $response = new Response(); | ||
175 | |||
176 | // Save RainTPL assigned variables | ||
177 | $assignedVariables = []; | ||
178 | $this->assignTemplateVars($assignedVariables); | ||
179 | |||
180 | // Links dataset: 2 links with thumbnails | ||
181 | $this->container->bookmarkService | ||
182 | ->expects(static::once()) | ||
183 | ->method('days') | ||
184 | ->willReturnCallback(function () use ($currentDay): array { | ||
185 | return [ | ||
186 | $currentDay->format($currentDay->format('Ymd')), | ||
187 | ]; | ||
188 | }) | ||
189 | ; | ||
190 | $this->container->bookmarkService | ||
191 | ->expects(static::once()) | ||
192 | ->method('filterDay') | ||
193 | ->willReturnCallback(function (): array { | ||
194 | return [ | ||
195 | (new Bookmark()) | ||
196 | ->setId(1) | ||
197 | ->setUrl('http://url.tld') | ||
198 | ->setTitle(static::generateString(50)) | ||
199 | ->setDescription(static::generateString(500)) | ||
200 | , | ||
201 | ]; | ||
202 | }) | ||
203 | ; | ||
204 | |||
205 | // Make sure that PluginManager hook is triggered | ||
206 | $this->container->pluginManager | ||
207 | ->expects(static::atLeastOnce()) | ||
208 | ->method('executeHooks') | ||
209 | ->withConsecutive(['render_daily']) | ||
210 | ->willReturnCallback(function (string $hook, array $data, array $param) use ($currentDay): array { | ||
211 | if ('render_daily' === $hook) { | ||
212 | static::assertArrayHasKey('linksToDisplay', $data); | ||
213 | static::assertCount(1, $data['linksToDisplay']); | ||
214 | static::assertSame(1, $data['linksToDisplay'][0]['id']); | ||
215 | static::assertSame($currentDay->getTimestamp(), $data['day']); | ||
216 | static::assertEmpty($data['previousday']); | ||
217 | static::assertEmpty($data['nextday']); | ||
218 | |||
219 | static::assertArrayHasKey('loggedin', $param); | ||
220 | } | ||
221 | |||
222 | return $data; | ||
223 | }); | ||
224 | |||
225 | $result = $this->controller->index($request, $response); | ||
226 | |||
227 | static::assertSame(200, $result->getStatusCode()); | ||
228 | static::assertSame('daily', (string) $result->getBody()); | ||
229 | static::assertSame( | ||
230 | 'Daily - '. format_date($currentDay, false, true) .' - Shaarli', | ||
231 | $assignedVariables['pagetitle'] | ||
232 | ); | ||
233 | static::assertCount(1, $assignedVariables['linksToDisplay']); | ||
234 | |||
235 | $link = $assignedVariables['linksToDisplay'][0]; | ||
236 | static::assertSame(1, $link['id']); | ||
237 | } | ||
238 | |||
239 | /** | ||
240 | * Daily page - test that height adjustment in columns is working | ||
241 | */ | ||
242 | public function testValidIndexControllerInvokeHeightAdjustment(): void | ||
243 | { | ||
244 | $currentDay = new \DateTimeImmutable('2020-05-13'); | ||
245 | |||
246 | $request = $this->createMock(Request::class); | ||
247 | $response = new Response(); | ||
248 | |||
249 | // Save RainTPL assigned variables | ||
250 | $assignedVariables = []; | ||
251 | $this->assignTemplateVars($assignedVariables); | ||
252 | |||
253 | // Links dataset: 2 links with thumbnails | ||
254 | $this->container->bookmarkService | ||
255 | ->expects(static::once()) | ||
256 | ->method('days') | ||
257 | ->willReturnCallback(function () use ($currentDay): array { | ||
258 | return [ | ||
259 | $currentDay->format($currentDay->format('Ymd')), | ||
260 | ]; | ||
261 | }) | ||
262 | ; | ||
263 | $this->container->bookmarkService | ||
264 | ->expects(static::once()) | ||
265 | ->method('filterDay') | ||
266 | ->willReturnCallback(function (): array { | ||
267 | return [ | ||
268 | (new Bookmark())->setId(1)->setUrl('http://url.tld')->setTitle('title'), | ||
269 | (new Bookmark()) | ||
270 | ->setId(2) | ||
271 | ->setUrl('http://url.tld') | ||
272 | ->setTitle(static::generateString(50)) | ||
273 | ->setDescription(static::generateString(5000)) | ||
274 | , | ||
275 | (new Bookmark())->setId(3)->setUrl('http://url.tld')->setTitle('title'), | ||
276 | (new Bookmark())->setId(4)->setUrl('http://url.tld')->setTitle('title'), | ||
277 | (new Bookmark())->setId(5)->setUrl('http://url.tld')->setTitle('title'), | ||
278 | (new Bookmark())->setId(6)->setUrl('http://url.tld')->setTitle('title'), | ||
279 | (new Bookmark())->setId(7)->setUrl('http://url.tld')->setTitle('title'), | ||
280 | ]; | ||
281 | }) | ||
282 | ; | ||
283 | |||
284 | // Make sure that PluginManager hook is triggered | ||
285 | $this->container->pluginManager | ||
286 | ->expects(static::atLeastOnce()) | ||
287 | ->method('executeHooks') | ||
288 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
289 | return $data; | ||
290 | }) | ||
291 | ; | ||
292 | |||
293 | $result = $this->controller->index($request, $response); | ||
294 | |||
295 | static::assertSame(200, $result->getStatusCode()); | ||
296 | static::assertSame('daily', (string) $result->getBody()); | ||
297 | static::assertCount(7, $assignedVariables['linksToDisplay']); | ||
298 | |||
299 | $columnIds = function (array $column): array { | ||
300 | return array_map(function (array $item): int { return $item['id']; }, $column); | ||
301 | }; | ||
302 | |||
303 | static::assertSame([1, 4, 6], $columnIds($assignedVariables['cols'][0])); | ||
304 | static::assertSame([2], $columnIds($assignedVariables['cols'][1])); | ||
305 | static::assertSame([3, 5, 7], $columnIds($assignedVariables['cols'][2])); | ||
306 | } | ||
307 | |||
308 | /** | ||
309 | * Daily page - no bookmark | ||
310 | */ | ||
311 | public function testValidIndexControllerInvokeNoBookmark(): void | ||
312 | { | ||
313 | $request = $this->createMock(Request::class); | ||
314 | $response = new Response(); | ||
315 | |||
316 | // Save RainTPL assigned variables | ||
317 | $assignedVariables = []; | ||
318 | $this->assignTemplateVars($assignedVariables); | ||
319 | |||
320 | // Links dataset: 2 links with thumbnails | ||
321 | $this->container->bookmarkService | ||
322 | ->expects(static::once()) | ||
323 | ->method('days') | ||
324 | ->willReturnCallback(function (): array { | ||
325 | return []; | ||
326 | }) | ||
327 | ; | ||
328 | $this->container->bookmarkService | ||
329 | ->expects(static::once()) | ||
330 | ->method('filterDay') | ||
331 | ->willReturnCallback(function (): array { | ||
332 | return []; | ||
333 | }) | ||
334 | ; | ||
335 | |||
336 | // Make sure that PluginManager hook is triggered | ||
337 | $this->container->pluginManager | ||
338 | ->expects(static::atLeastOnce()) | ||
339 | ->method('executeHooks') | ||
340 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
341 | return $data; | ||
342 | }) | ||
343 | ; | ||
344 | |||
345 | $result = $this->controller->index($request, $response); | ||
346 | |||
347 | static::assertSame(200, $result->getStatusCode()); | ||
348 | static::assertSame('daily', (string) $result->getBody()); | ||
349 | static::assertCount(0, $assignedVariables['linksToDisplay']); | ||
350 | static::assertSame('Today', $assignedVariables['dayDesc']); | ||
351 | static::assertEquals((new \DateTime())->setTime(0, 0)->getTimestamp(), $assignedVariables['day']); | ||
352 | static::assertEquals((new \DateTime())->setTime(0, 0), $assignedVariables['dayDate']); | ||
353 | } | ||
354 | |||
355 | /** | ||
356 | * Daily RSS - default behaviour | ||
357 | */ | ||
358 | public function testValidRssControllerInvokeDefault(): void | ||
359 | { | ||
360 | $dates = [ | ||
361 | new \DateTimeImmutable('2020-05-17'), | ||
362 | new \DateTimeImmutable('2020-05-15'), | ||
363 | new \DateTimeImmutable('2020-05-13'), | ||
364 | ]; | ||
365 | |||
366 | $request = $this->createMock(Request::class); | ||
367 | $response = new Response(); | ||
368 | |||
369 | $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([ | ||
370 | (new Bookmark())->setId(1)->setCreated($dates[0])->setUrl('http://domain.tld/1'), | ||
371 | (new Bookmark())->setId(2)->setCreated($dates[1])->setUrl('http://domain.tld/2'), | ||
372 | (new Bookmark())->setId(3)->setCreated($dates[1])->setUrl('http://domain.tld/3'), | ||
373 | (new Bookmark())->setId(4)->setCreated($dates[2])->setUrl('http://domain.tld/4'), | ||
374 | ]); | ||
375 | |||
376 | $this->container->pageCacheManager | ||
377 | ->expects(static::once()) | ||
378 | ->method('getCachePage') | ||
379 | ->willReturnCallback(function (): CachedPage { | ||
380 | $cachedPage = $this->createMock(CachedPage::class); | ||
381 | $cachedPage->expects(static::once())->method('cache')->with('dailyrss'); | ||
382 | |||
383 | return $cachedPage; | ||
384 | } | ||
385 | ); | ||
386 | |||
387 | // Save RainTPL assigned variables | ||
388 | $assignedVariables = []; | ||
389 | $this->assignTemplateVars($assignedVariables); | ||
390 | |||
391 | $result = $this->controller->rss($request, $response); | ||
392 | |||
393 | static::assertSame(200, $result->getStatusCode()); | ||
394 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
395 | static::assertSame('dailyrss', (string) $result->getBody()); | ||
396 | static::assertSame('Shaarli', $assignedVariables['title']); | ||
397 | static::assertSame('http://shaarli/subfolder/', $assignedVariables['index_url']); | ||
398 | static::assertSame('http://shaarli/subfolder/daily-rss', $assignedVariables['page_url']); | ||
399 | static::assertFalse($assignedVariables['hide_timestamps']); | ||
400 | static::assertCount(2, $assignedVariables['days']); | ||
401 | |||
402 | $day = $assignedVariables['days'][$dates[0]->format('Ymd')]; | ||
403 | |||
404 | static::assertEquals($dates[0], $day['date']); | ||
405 | static::assertSame($dates[0]->format(\DateTime::RSS), $day['date_rss']); | ||
406 | static::assertSame(format_date($dates[0], false), $day['date_human']); | ||
407 | static::assertSame('http://shaarli/subfolder/daily?day='. $dates[0]->format('Ymd'), $day['absolute_url']); | ||
408 | static::assertCount(1, $day['links']); | ||
409 | static::assertSame(1, $day['links'][0]['id']); | ||
410 | static::assertSame('http://domain.tld/1', $day['links'][0]['url']); | ||
411 | static::assertEquals($dates[0], $day['links'][0]['created']); | ||
412 | |||
413 | $day = $assignedVariables['days'][$dates[1]->format('Ymd')]; | ||
414 | |||
415 | static::assertEquals($dates[1], $day['date']); | ||
416 | static::assertSame($dates[1]->format(\DateTime::RSS), $day['date_rss']); | ||
417 | static::assertSame(format_date($dates[1], false), $day['date_human']); | ||
418 | static::assertSame('http://shaarli/subfolder/daily?day='. $dates[1]->format('Ymd'), $day['absolute_url']); | ||
419 | static::assertCount(2, $day['links']); | ||
420 | |||
421 | static::assertSame(2, $day['links'][0]['id']); | ||
422 | static::assertSame('http://domain.tld/2', $day['links'][0]['url']); | ||
423 | static::assertEquals($dates[1], $day['links'][0]['created']); | ||
424 | static::assertSame(3, $day['links'][1]['id']); | ||
425 | static::assertSame('http://domain.tld/3', $day['links'][1]['url']); | ||
426 | static::assertEquals($dates[1], $day['links'][1]['created']); | ||
427 | } | ||
428 | |||
429 | /** | ||
430 | * Daily RSS - trigger cache rendering | ||
431 | */ | ||
432 | public function testValidRssControllerInvokeTriggerCache(): void | ||
433 | { | ||
434 | $request = $this->createMock(Request::class); | ||
435 | $response = new Response(); | ||
436 | |||
437 | $this->container->pageCacheManager->method('getCachePage')->willReturnCallback(function (): CachedPage { | ||
438 | $cachedPage = $this->createMock(CachedPage::class); | ||
439 | $cachedPage->method('cachedVersion')->willReturn('this is cache!'); | ||
440 | |||
441 | return $cachedPage; | ||
442 | }); | ||
443 | |||
444 | $this->container->bookmarkService->expects(static::never())->method('search'); | ||
445 | |||
446 | $result = $this->controller->rss($request, $response); | ||
447 | |||
448 | static::assertSame(200, $result->getStatusCode()); | ||
449 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
450 | static::assertSame('this is cache!', (string) $result->getBody()); | ||
451 | } | ||
452 | |||
453 | /** | ||
454 | * Daily RSS - No bookmark | ||
455 | */ | ||
456 | public function testValidRssControllerInvokeNoBookmark(): void | ||
457 | { | ||
458 | $request = $this->createMock(Request::class); | ||
459 | $response = new Response(); | ||
460 | |||
461 | $this->container->bookmarkService->expects(static::once())->method('search')->willReturn([]); | ||
462 | |||
463 | // Save RainTPL assigned variables | ||
464 | $assignedVariables = []; | ||
465 | $this->assignTemplateVars($assignedVariables); | ||
466 | |||
467 | $result = $this->controller->rss($request, $response); | ||
468 | |||
469 | static::assertSame(200, $result->getStatusCode()); | ||
470 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
471 | static::assertSame('dailyrss', (string) $result->getBody()); | ||
472 | static::assertSame('Shaarli', $assignedVariables['title']); | ||
473 | static::assertSame('http://shaarli/subfolder/', $assignedVariables['index_url']); | ||
474 | static::assertSame('http://shaarli/subfolder/daily-rss', $assignedVariables['page_url']); | ||
475 | static::assertFalse($assignedVariables['hide_timestamps']); | ||
476 | static::assertCount(0, $assignedVariables['days']); | ||
477 | } | ||
478 | } | ||
diff --git a/tests/front/controller/visitor/ErrorControllerTest.php b/tests/front/controller/visitor/ErrorControllerTest.php new file mode 100644 index 00000000..75408cf4 --- /dev/null +++ b/tests/front/controller/visitor/ErrorControllerTest.php | |||
@@ -0,0 +1,70 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Front\Exception\ShaarliFrontException; | ||
8 | use Shaarli\TestCase; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | class ErrorControllerTest extends TestCase | ||
13 | { | ||
14 | use FrontControllerMockHelper; | ||
15 | |||
16 | /** @var ErrorController */ | ||
17 | protected $controller; | ||
18 | |||
19 | public function setUp(): void | ||
20 | { | ||
21 | $this->createContainer(); | ||
22 | |||
23 | $this->controller = new ErrorController($this->container); | ||
24 | } | ||
25 | |||
26 | /** | ||
27 | * Test displaying error with a ShaarliFrontException: display exception message and use its code for HTTTP code | ||
28 | */ | ||
29 | public function testDisplayFrontExceptionError(): void | ||
30 | { | ||
31 | $request = $this->createMock(Request::class); | ||
32 | $response = new Response(); | ||
33 | |||
34 | $message = 'error message'; | ||
35 | $errorCode = 418; | ||
36 | |||
37 | // Save RainTPL assigned variables | ||
38 | $assignedVariables = []; | ||
39 | $this->assignTemplateVars($assignedVariables); | ||
40 | |||
41 | $result = ($this->controller)( | ||
42 | $request, | ||
43 | $response, | ||
44 | new class($message, $errorCode) extends ShaarliFrontException {} | ||
45 | ); | ||
46 | |||
47 | static::assertSame($errorCode, $result->getStatusCode()); | ||
48 | static::assertSame($message, $assignedVariables['message']); | ||
49 | static::assertArrayNotHasKey('stacktrace', $assignedVariables); | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Test displaying error with any exception (no debug): only display an error occurred with HTTP 500. | ||
54 | */ | ||
55 | public function testDisplayAnyExceptionErrorNoDebug(): void | ||
56 | { | ||
57 | $request = $this->createMock(Request::class); | ||
58 | $response = new Response(); | ||
59 | |||
60 | // Save RainTPL assigned variables | ||
61 | $assignedVariables = []; | ||
62 | $this->assignTemplateVars($assignedVariables); | ||
63 | |||
64 | $result = ($this->controller)($request, $response, new \Exception('abc')); | ||
65 | |||
66 | static::assertSame(500, $result->getStatusCode()); | ||
67 | static::assertSame('An unexpected error occurred.', $assignedVariables['message']); | ||
68 | static::assertArrayNotHasKey('stacktrace', $assignedVariables); | ||
69 | } | ||
70 | } | ||
diff --git a/tests/front/controller/visitor/ErrorNotFoundControllerTest.php b/tests/front/controller/visitor/ErrorNotFoundControllerTest.php new file mode 100644 index 00000000..a1cbbecf --- /dev/null +++ b/tests/front/controller/visitor/ErrorNotFoundControllerTest.php | |||
@@ -0,0 +1,81 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\TestCase; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | use Slim\Http\Uri; | ||
11 | |||
12 | class ErrorNotFoundControllerTest extends TestCase | ||
13 | { | ||
14 | use FrontControllerMockHelper; | ||
15 | |||
16 | /** @var ErrorNotFoundController */ | ||
17 | protected $controller; | ||
18 | |||
19 | public function setUp(): void | ||
20 | { | ||
21 | $this->createContainer(); | ||
22 | |||
23 | $this->controller = new ErrorNotFoundController($this->container); | ||
24 | } | ||
25 | |||
26 | /** | ||
27 | * Test displaying 404 error | ||
28 | */ | ||
29 | public function testDisplayNotFoundError(): void | ||
30 | { | ||
31 | $request = $this->createMock(Request::class); | ||
32 | $request->expects(static::once())->method('getRequestTarget')->willReturn('/'); | ||
33 | $request->method('getUri')->willReturnCallback(function (): Uri { | ||
34 | $uri = $this->createMock(Uri::class); | ||
35 | $uri->method('getBasePath')->willReturn('/subfolder'); | ||
36 | |||
37 | return $uri; | ||
38 | }); | ||
39 | |||
40 | $response = new Response(); | ||
41 | |||
42 | // Save RainTPL assigned variables | ||
43 | $assignedVariables = []; | ||
44 | $this->assignTemplateVars($assignedVariables); | ||
45 | |||
46 | $result = ($this->controller)( | ||
47 | $request, | ||
48 | $response | ||
49 | ); | ||
50 | |||
51 | static::assertSame(404, $result->getStatusCode()); | ||
52 | static::assertSame('404', (string) $result->getBody()); | ||
53 | static::assertSame('Requested page could not be found.', $assignedVariables['error_message']); | ||
54 | } | ||
55 | |||
56 | /** | ||
57 | * Test displaying 404 error from REST API | ||
58 | */ | ||
59 | public function testDisplayNotFoundErrorFromAPI(): void | ||
60 | { | ||
61 | $request = $this->createMock(Request::class); | ||
62 | $request->expects(static::once())->method('getRequestTarget')->willReturn('/sufolder/api/v1/links'); | ||
63 | $request->method('getUri')->willReturnCallback(function (): Uri { | ||
64 | $uri = $this->createMock(Uri::class); | ||
65 | $uri->method('getBasePath')->willReturn('/subfolder'); | ||
66 | |||
67 | return $uri; | ||
68 | }); | ||
69 | |||
70 | $response = new Response(); | ||
71 | |||
72 | // Save RainTPL assigned variables | ||
73 | $assignedVariables = []; | ||
74 | $this->assignTemplateVars($assignedVariables); | ||
75 | |||
76 | $result = ($this->controller)($request, $response); | ||
77 | |||
78 | static::assertSame(404, $result->getStatusCode()); | ||
79 | static::assertSame([], $assignedVariables); | ||
80 | } | ||
81 | } | ||
diff --git a/tests/front/controller/visitor/FeedControllerTest.php b/tests/front/controller/visitor/FeedControllerTest.php new file mode 100644 index 00000000..4ae7c925 --- /dev/null +++ b/tests/front/controller/visitor/FeedControllerTest.php | |||
@@ -0,0 +1,151 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Feed\FeedBuilder; | ||
8 | use Shaarli\TestCase; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | class FeedControllerTest extends TestCase | ||
13 | { | ||
14 | use FrontControllerMockHelper; | ||
15 | |||
16 | /** @var FeedController */ | ||
17 | protected $controller; | ||
18 | |||
19 | public function setUp(): void | ||
20 | { | ||
21 | $this->createContainer(); | ||
22 | |||
23 | $this->container->feedBuilder = $this->createMock(FeedBuilder::class); | ||
24 | |||
25 | $this->controller = new FeedController($this->container); | ||
26 | } | ||
27 | |||
28 | /** | ||
29 | * Feed Controller - RSS default behaviour | ||
30 | */ | ||
31 | public function testDefaultRssController(): void | ||
32 | { | ||
33 | $request = $this->createMock(Request::class); | ||
34 | $response = new Response(); | ||
35 | |||
36 | $this->container->feedBuilder->expects(static::once())->method('setLocale'); | ||
37 | $this->container->feedBuilder->expects(static::once())->method('setHideDates')->with(false); | ||
38 | $this->container->feedBuilder->expects(static::once())->method('setUsePermalinks')->with(true); | ||
39 | |||
40 | // Save RainTPL assigned variables | ||
41 | $assignedVariables = []; | ||
42 | $this->assignTemplateVars($assignedVariables); | ||
43 | |||
44 | $this->container->feedBuilder->method('buildData')->willReturn(['content' => 'data']); | ||
45 | |||
46 | // Make sure that PluginManager hook is triggered | ||
47 | $this->container->pluginManager | ||
48 | ->expects(static::atLeastOnce()) | ||
49 | ->method('executeHooks') | ||
50 | ->withConsecutive(['render_feed']) | ||
51 | ->willReturnCallback(function (string $hook, array $data, array $param): void { | ||
52 | if ('render_feed' === $hook) { | ||
53 | static::assertSame('data', $data['content']); | ||
54 | |||
55 | static::assertArrayHasKey('loggedin', $param); | ||
56 | static::assertSame('feed.rss', $param['target']); | ||
57 | } | ||
58 | }) | ||
59 | ; | ||
60 | |||
61 | $result = $this->controller->rss($request, $response); | ||
62 | |||
63 | static::assertSame(200, $result->getStatusCode()); | ||
64 | static::assertStringContainsString('application/rss', $result->getHeader('Content-Type')[0]); | ||
65 | static::assertSame('feed.rss', (string) $result->getBody()); | ||
66 | static::assertSame('data', $assignedVariables['content']); | ||
67 | } | ||
68 | |||
69 | /** | ||
70 | * Feed Controller - ATOM default behaviour | ||
71 | */ | ||
72 | public function testDefaultAtomController(): void | ||
73 | { | ||
74 | $request = $this->createMock(Request::class); | ||
75 | $response = new Response(); | ||
76 | |||
77 | $this->container->feedBuilder->expects(static::once())->method('setLocale'); | ||
78 | $this->container->feedBuilder->expects(static::once())->method('setHideDates')->with(false); | ||
79 | $this->container->feedBuilder->expects(static::once())->method('setUsePermalinks')->with(true); | ||
80 | |||
81 | // Save RainTPL assigned variables | ||
82 | $assignedVariables = []; | ||
83 | $this->assignTemplateVars($assignedVariables); | ||
84 | |||
85 | $this->container->feedBuilder->method('buildData')->willReturn(['content' => 'data']); | ||
86 | |||
87 | // Make sure that PluginManager hook is triggered | ||
88 | $this->container->pluginManager | ||
89 | ->expects(static::atLeastOnce()) | ||
90 | ->method('executeHooks') | ||
91 | ->withConsecutive(['render_feed']) | ||
92 | ->willReturnCallback(function (string $hook, array $data, array $param): void { | ||
93 | if ('render_feed' === $hook) { | ||
94 | static::assertSame('data', $data['content']); | ||
95 | |||
96 | static::assertArrayHasKey('loggedin', $param); | ||
97 | static::assertSame('feed.atom', $param['target']); | ||
98 | } | ||
99 | }) | ||
100 | ; | ||
101 | |||
102 | $result = $this->controller->atom($request, $response); | ||
103 | |||
104 | static::assertSame(200, $result->getStatusCode()); | ||
105 | static::assertStringContainsString('application/atom', $result->getHeader('Content-Type')[0]); | ||
106 | static::assertSame('feed.atom', (string) $result->getBody()); | ||
107 | static::assertSame('data', $assignedVariables['content']); | ||
108 | } | ||
109 | |||
110 | /** | ||
111 | * Feed Controller - ATOM with parameters | ||
112 | */ | ||
113 | public function testAtomControllerWithParameters(): void | ||
114 | { | ||
115 | $request = $this->createMock(Request::class); | ||
116 | $request->method('getParams')->willReturn(['parameter' => 'value']); | ||
117 | $response = new Response(); | ||
118 | |||
119 | // Save RainTPL assigned variables | ||
120 | $assignedVariables = []; | ||
121 | $this->assignTemplateVars($assignedVariables); | ||
122 | |||
123 | $this->container->feedBuilder | ||
124 | ->method('buildData') | ||
125 | ->with('atom', ['parameter' => 'value']) | ||
126 | ->willReturn(['content' => 'data']) | ||
127 | ; | ||
128 | |||
129 | // Make sure that PluginManager hook is triggered | ||
130 | $this->container->pluginManager | ||
131 | ->expects(static::atLeastOnce()) | ||
132 | ->method('executeHooks') | ||
133 | ->withConsecutive(['render_feed']) | ||
134 | ->willReturnCallback(function (string $hook, array $data, array $param): void { | ||
135 | if ('render_feed' === $hook) { | ||
136 | static::assertSame('data', $data['content']); | ||
137 | |||
138 | static::assertArrayHasKey('loggedin', $param); | ||
139 | static::assertSame('feed.atom', $param['target']); | ||
140 | } | ||
141 | }) | ||
142 | ; | ||
143 | |||
144 | $result = $this->controller->atom($request, $response); | ||
145 | |||
146 | static::assertSame(200, $result->getStatusCode()); | ||
147 | static::assertStringContainsString('application/atom', $result->getHeader('Content-Type')[0]); | ||
148 | static::assertSame('feed.atom', (string) $result->getBody()); | ||
149 | static::assertSame('data', $assignedVariables['content']); | ||
150 | } | ||
151 | } | ||
diff --git a/tests/front/controller/visitor/FrontControllerMockHelper.php b/tests/front/controller/visitor/FrontControllerMockHelper.php new file mode 100644 index 00000000..fc0bb7d1 --- /dev/null +++ b/tests/front/controller/visitor/FrontControllerMockHelper.php | |||
@@ -0,0 +1,118 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkServiceInterface; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Container\ShaarliTestContainer; | ||
10 | use Shaarli\Formatter\BookmarkFormatter; | ||
11 | use Shaarli\Formatter\BookmarkRawFormatter; | ||
12 | use Shaarli\Formatter\FormatterFactory; | ||
13 | use Shaarli\Plugin\PluginManager; | ||
14 | use Shaarli\Render\PageBuilder; | ||
15 | use Shaarli\Render\PageCacheManager; | ||
16 | use Shaarli\Security\LoginManager; | ||
17 | use Shaarli\Security\SessionManager; | ||
18 | |||
19 | /** | ||
20 | * Trait FrontControllerMockHelper | ||
21 | * | ||
22 | * Helper trait used to initialize the ShaarliContainer and mock its services for controller tests. | ||
23 | * | ||
24 | * @property ShaarliTestContainer $container | ||
25 | * @package Shaarli\Front\Controller | ||
26 | */ | ||
27 | trait FrontControllerMockHelper | ||
28 | { | ||
29 | /** @var ShaarliTestContainer */ | ||
30 | protected $container; | ||
31 | |||
32 | /** | ||
33 | * Mock the container instance and initialize container's services used by tests | ||
34 | */ | ||
35 | protected function createContainer(): void | ||
36 | { | ||
37 | $this->container = $this->createMock(ShaarliTestContainer::class); | ||
38 | |||
39 | $this->container->loginManager = $this->createMock(LoginManager::class); | ||
40 | |||
41 | // Config | ||
42 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
43 | $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) { | ||
44 | return $default === null ? $parameter : $default; | ||
45 | }); | ||
46 | |||
47 | // PageBuilder | ||
48 | $this->container->pageBuilder = $this->createMock(PageBuilder::class); | ||
49 | $this->container->pageBuilder | ||
50 | ->method('render') | ||
51 | ->willReturnCallback(function (string $template): string { | ||
52 | return $template; | ||
53 | }) | ||
54 | ; | ||
55 | |||
56 | // Plugin Manager | ||
57 | $this->container->pluginManager = $this->createMock(PluginManager::class); | ||
58 | |||
59 | // BookmarkService | ||
60 | $this->container->bookmarkService = $this->createMock(BookmarkServiceInterface::class); | ||
61 | |||
62 | // Formatter | ||
63 | $this->container->formatterFactory = $this->createMock(FormatterFactory::class); | ||
64 | $this->container->formatterFactory | ||
65 | ->method('getFormatter') | ||
66 | ->willReturnCallback(function (): BookmarkFormatter { | ||
67 | return new BookmarkRawFormatter($this->container->conf, true); | ||
68 | }) | ||
69 | ; | ||
70 | |||
71 | // CacheManager | ||
72 | $this->container->pageCacheManager = $this->createMock(PageCacheManager::class); | ||
73 | |||
74 | // SessionManager | ||
75 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
76 | |||
77 | // $_SERVER | ||
78 | $this->container->environment = [ | ||
79 | 'SERVER_NAME' => 'shaarli', | ||
80 | 'SERVER_PORT' => '80', | ||
81 | 'REQUEST_URI' => '/subfolder/daily-rss', | ||
82 | 'REMOTE_ADDR' => '1.2.3.4', | ||
83 | 'SCRIPT_NAME' => '/subfolder/index.php', | ||
84 | ]; | ||
85 | |||
86 | $this->container->basePath = '/subfolder'; | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Pass a reference of an array which will be populated by `pageBuilder->assign` calls during execution. | ||
91 | * | ||
92 | * @param mixed $variables Array reference to populate. | ||
93 | */ | ||
94 | protected function assignTemplateVars(array &$variables): void | ||
95 | { | ||
96 | $this->container->pageBuilder | ||
97 | ->method('assign') | ||
98 | ->willReturnCallback(function ($key, $value) use (&$variables) { | ||
99 | $variables[$key] = $value; | ||
100 | |||
101 | return $this; | ||
102 | }) | ||
103 | ; | ||
104 | } | ||
105 | |||
106 | protected static function generateString(int $length): string | ||
107 | { | ||
108 | // bin2hex(random_bytes) generates string twice as long as given parameter | ||
109 | $length = (int) ceil($length / 2); | ||
110 | |||
111 | return bin2hex(random_bytes($length)); | ||
112 | } | ||
113 | |||
114 | /** | ||
115 | * Force to be used in PHPUnit context. | ||
116 | */ | ||
117 | protected abstract function isInTestsContext(): bool; | ||
118 | } | ||
diff --git a/tests/front/controller/visitor/InstallControllerTest.php b/tests/front/controller/visitor/InstallControllerTest.php new file mode 100644 index 00000000..345ad544 --- /dev/null +++ b/tests/front/controller/visitor/InstallControllerTest.php | |||
@@ -0,0 +1,295 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Front\Exception\AlreadyInstalledException; | ||
9 | use Shaarli\Security\SessionManager; | ||
10 | use Shaarli\TestCase; | ||
11 | use Slim\Http\Request; | ||
12 | use Slim\Http\Response; | ||
13 | |||
14 | class InstallControllerTest extends TestCase | ||
15 | { | ||
16 | use FrontControllerMockHelper; | ||
17 | |||
18 | const MOCK_FILE = '.tmp'; | ||
19 | |||
20 | /** @var InstallController */ | ||
21 | protected $controller; | ||
22 | |||
23 | public function setUp(): void | ||
24 | { | ||
25 | $this->createContainer(); | ||
26 | |||
27 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
28 | $this->container->conf->method('getConfigFileExt')->willReturn(static::MOCK_FILE); | ||
29 | $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { | ||
30 | if ($key === 'resource.raintpl_tpl') { | ||
31 | return '.'; | ||
32 | } | ||
33 | |||
34 | return $default ?? $key; | ||
35 | }); | ||
36 | |||
37 | $this->controller = new InstallController($this->container); | ||
38 | } | ||
39 | |||
40 | protected function tearDown(): void | ||
41 | { | ||
42 | if (file_exists(static::MOCK_FILE)) { | ||
43 | unlink(static::MOCK_FILE); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | /** | ||
48 | * Test displaying install page with valid session. | ||
49 | */ | ||
50 | public function testInstallIndexWithValidSession(): void | ||
51 | { | ||
52 | $assignedVariables = []; | ||
53 | $this->assignTemplateVars($assignedVariables); | ||
54 | |||
55 | $request = $this->createMock(Request::class); | ||
56 | $response = new Response(); | ||
57 | |||
58 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
59 | $this->container->sessionManager | ||
60 | ->method('getSessionParameter') | ||
61 | ->willReturnCallback(function (string $key, $default) { | ||
62 | return $key === 'session_tested' ? 'Working' : $default; | ||
63 | }) | ||
64 | ; | ||
65 | |||
66 | $result = $this->controller->index($request, $response); | ||
67 | |||
68 | static::assertSame(200, $result->getStatusCode()); | ||
69 | static::assertSame('install', (string) $result->getBody()); | ||
70 | |||
71 | static::assertIsArray($assignedVariables['continents']); | ||
72 | static::assertSame('Africa', $assignedVariables['continents'][0]); | ||
73 | static::assertSame('UTC', $assignedVariables['continents']['selected']); | ||
74 | |||
75 | static::assertIsArray($assignedVariables['cities']); | ||
76 | static::assertSame(['continent' => 'Africa', 'city' => 'Abidjan'], $assignedVariables['cities'][0]); | ||
77 | static::assertSame('UTC', $assignedVariables['continents']['selected']); | ||
78 | |||
79 | static::assertIsArray($assignedVariables['languages']); | ||
80 | static::assertSame('Automatic', $assignedVariables['languages']['auto']); | ||
81 | static::assertSame('French', $assignedVariables['languages']['fr']); | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Instantiate the install controller with an existing config file: exception. | ||
86 | */ | ||
87 | public function testInstallWithExistingConfigFile(): void | ||
88 | { | ||
89 | $this->expectException(AlreadyInstalledException::class); | ||
90 | |||
91 | touch(static::MOCK_FILE); | ||
92 | |||
93 | $this->controller = new InstallController($this->container); | ||
94 | } | ||
95 | |||
96 | /** | ||
97 | * Call controller without session yet defined, redirect to test session install page. | ||
98 | */ | ||
99 | public function testInstallRedirectToSessionTest(): void | ||
100 | { | ||
101 | $request = $this->createMock(Request::class); | ||
102 | $response = new Response(); | ||
103 | |||
104 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
105 | $this->container->sessionManager | ||
106 | ->expects(static::once()) | ||
107 | ->method('setSessionParameter') | ||
108 | ->with(InstallController::SESSION_TEST_KEY, InstallController::SESSION_TEST_VALUE) | ||
109 | ; | ||
110 | |||
111 | $result = $this->controller->index($request, $response); | ||
112 | |||
113 | static::assertSame(302, $result->getStatusCode()); | ||
114 | static::assertSame('/subfolder/install/session-test', $result->getHeader('location')[0]); | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * Call controller in session test mode: valid session then redirect to install page. | ||
119 | */ | ||
120 | public function testInstallSessionTestValid(): void | ||
121 | { | ||
122 | $request = $this->createMock(Request::class); | ||
123 | $response = new Response(); | ||
124 | |||
125 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
126 | $this->container->sessionManager | ||
127 | ->method('getSessionParameter') | ||
128 | ->with(InstallController::SESSION_TEST_KEY) | ||
129 | ->willReturn(InstallController::SESSION_TEST_VALUE) | ||
130 | ; | ||
131 | |||
132 | $result = $this->controller->sessionTest($request, $response); | ||
133 | |||
134 | static::assertSame(302, $result->getStatusCode()); | ||
135 | static::assertSame('/subfolder/install', $result->getHeader('location')[0]); | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Call controller in session test mode: invalid session then redirect to error page. | ||
140 | */ | ||
141 | public function testInstallSessionTestError(): void | ||
142 | { | ||
143 | $assignedVars = []; | ||
144 | $this->assignTemplateVars($assignedVars); | ||
145 | |||
146 | $request = $this->createMock(Request::class); | ||
147 | $response = new Response(); | ||
148 | |||
149 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
150 | $this->container->sessionManager | ||
151 | ->method('getSessionParameter') | ||
152 | ->with(InstallController::SESSION_TEST_KEY) | ||
153 | ->willReturn('KO') | ||
154 | ; | ||
155 | |||
156 | $result = $this->controller->sessionTest($request, $response); | ||
157 | |||
158 | static::assertSame(200, $result->getStatusCode()); | ||
159 | static::assertSame('error', (string) $result->getBody()); | ||
160 | static::assertStringStartsWith( | ||
161 | '<pre>Sessions do not seem to work correctly on your server', | ||
162 | $assignedVars['message'] | ||
163 | ); | ||
164 | } | ||
165 | |||
166 | /** | ||
167 | * Test saving valid data from install form. Also initialize datastore. | ||
168 | */ | ||
169 | public function testSaveInstallValid(): void | ||
170 | { | ||
171 | $providedParameters = [ | ||
172 | 'continent' => 'Europe', | ||
173 | 'city' => 'Berlin', | ||
174 | 'setlogin' => 'bob', | ||
175 | 'setpassword' => 'password', | ||
176 | 'title' => 'Shaarli', | ||
177 | 'language' => 'fr', | ||
178 | 'updateCheck' => true, | ||
179 | 'enableApi' => true, | ||
180 | ]; | ||
181 | |||
182 | $expectedSettings = [ | ||
183 | 'general.timezone' => 'Europe/Berlin', | ||
184 | 'credentials.login' => 'bob', | ||
185 | 'credentials.salt' => '_NOT_EMPTY', | ||
186 | 'credentials.hash' => '_NOT_EMPTY', | ||
187 | 'general.title' => 'Shaarli', | ||
188 | 'translation.language' => 'en', | ||
189 | 'updates.check_updates' => true, | ||
190 | 'api.enabled' => true, | ||
191 | 'api.secret' => '_NOT_EMPTY', | ||
192 | 'general.header_link' => '/subfolder', | ||
193 | ]; | ||
194 | |||
195 | $request = $this->createMock(Request::class); | ||
196 | $request->method('getParam')->willReturnCallback(function (string $key) use ($providedParameters) { | ||
197 | return $providedParameters[$key] ?? null; | ||
198 | }); | ||
199 | $response = new Response(); | ||
200 | |||
201 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
202 | $this->container->conf | ||
203 | ->method('get') | ||
204 | ->willReturnCallback(function (string $key, $value) { | ||
205 | if ($key === 'credentials.login') { | ||
206 | return 'bob'; | ||
207 | } elseif ($key === 'credentials.salt') { | ||
208 | return 'salt'; | ||
209 | } | ||
210 | |||
211 | return $value; | ||
212 | }) | ||
213 | ; | ||
214 | $this->container->conf | ||
215 | ->expects(static::exactly(count($expectedSettings))) | ||
216 | ->method('set') | ||
217 | ->willReturnCallback(function (string $key, $value) use ($expectedSettings) { | ||
218 | if ($expectedSettings[$key] ?? null === '_NOT_EMPTY') { | ||
219 | static::assertNotEmpty($value); | ||
220 | } else { | ||
221 | static::assertSame($expectedSettings[$key], $value); | ||
222 | } | ||
223 | }) | ||
224 | ; | ||
225 | $this->container->conf->expects(static::once())->method('write'); | ||
226 | |||
227 | $this->container->sessionManager | ||
228 | ->expects(static::once()) | ||
229 | ->method('setSessionParameter') | ||
230 | ->with(SessionManager::KEY_SUCCESS_MESSAGES) | ||
231 | ; | ||
232 | |||
233 | $result = $this->controller->save($request, $response); | ||
234 | |||
235 | static::assertSame(302, $result->getStatusCode()); | ||
236 | static::assertSame('/subfolder/login', $result->getHeader('location')[0]); | ||
237 | } | ||
238 | |||
239 | /** | ||
240 | * Test default settings (timezone and title). | ||
241 | * Also check that bookmarks are not initialized if | ||
242 | */ | ||
243 | public function testSaveInstallDefaultValues(): void | ||
244 | { | ||
245 | $confSettings = []; | ||
246 | |||
247 | $request = $this->createMock(Request::class); | ||
248 | $response = new Response(); | ||
249 | |||
250 | $this->container->conf->method('set')->willReturnCallback(function (string $key, $value) use (&$confSettings) { | ||
251 | $confSettings[$key] = $value; | ||
252 | }); | ||
253 | |||
254 | $result = $this->controller->save($request, $response); | ||
255 | |||
256 | static::assertSame(302, $result->getStatusCode()); | ||
257 | static::assertSame('/subfolder/login', $result->getHeader('location')[0]); | ||
258 | |||
259 | static::assertSame('UTC', $confSettings['general.timezone']); | ||
260 | static::assertSame('Shared bookmarks on http://shaarli/subfolder/', $confSettings['general.title']); | ||
261 | } | ||
262 | |||
263 | /** | ||
264 | * Same test as testSaveInstallDefaultValues() but for an instance install in root directory. | ||
265 | */ | ||
266 | public function testSaveInstallDefaultValuesWithoutSubfolder(): void | ||
267 | { | ||
268 | $confSettings = []; | ||
269 | |||
270 | $this->container->environment = [ | ||
271 | 'SERVER_NAME' => 'shaarli', | ||
272 | 'SERVER_PORT' => '80', | ||
273 | 'REQUEST_URI' => '/install', | ||
274 | 'REMOTE_ADDR' => '1.2.3.4', | ||
275 | 'SCRIPT_NAME' => '/index.php', | ||
276 | ]; | ||
277 | |||
278 | $this->container->basePath = ''; | ||
279 | |||
280 | $request = $this->createMock(Request::class); | ||
281 | $response = new Response(); | ||
282 | |||
283 | $this->container->conf->method('set')->willReturnCallback(function (string $key, $value) use (&$confSettings) { | ||
284 | $confSettings[$key] = $value; | ||
285 | }); | ||
286 | |||
287 | $result = $this->controller->save($request, $response); | ||
288 | |||
289 | static::assertSame(302, $result->getStatusCode()); | ||
290 | static::assertSame('/login', $result->getHeader('location')[0]); | ||
291 | |||
292 | static::assertSame('UTC', $confSettings['general.timezone']); | ||
293 | static::assertSame('Shared bookmarks on http://shaarli/', $confSettings['general.title']); | ||
294 | } | ||
295 | } | ||
diff --git a/tests/front/controller/visitor/LoginControllerTest.php b/tests/front/controller/visitor/LoginControllerTest.php new file mode 100644 index 00000000..1312ccb7 --- /dev/null +++ b/tests/front/controller/visitor/LoginControllerTest.php | |||
@@ -0,0 +1,404 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Front\Exception\LoginBannedException; | ||
9 | use Shaarli\Front\Exception\WrongTokenException; | ||
10 | use Shaarli\Render\TemplatePage; | ||
11 | use Shaarli\Security\CookieManager; | ||
12 | use Shaarli\Security\SessionManager; | ||
13 | use Shaarli\TestCase; | ||
14 | use Slim\Http\Request; | ||
15 | use Slim\Http\Response; | ||
16 | |||
17 | class LoginControllerTest extends TestCase | ||
18 | { | ||
19 | use FrontControllerMockHelper; | ||
20 | |||
21 | /** @var LoginController */ | ||
22 | protected $controller; | ||
23 | |||
24 | public function setUp(): void | ||
25 | { | ||
26 | $this->createContainer(); | ||
27 | |||
28 | $this->container->cookieManager = $this->createMock(CookieManager::class); | ||
29 | $this->container->sessionManager->method('checkToken')->willReturn(true); | ||
30 | |||
31 | $this->controller = new LoginController($this->container); | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Test displaying login form with valid parameters. | ||
36 | */ | ||
37 | public function testValidControllerInvoke(): void | ||
38 | { | ||
39 | $request = $this->createMock(Request::class); | ||
40 | $request | ||
41 | ->expects(static::atLeastOnce()) | ||
42 | ->method('getParam') | ||
43 | ->willReturnCallback(function (string $key) { | ||
44 | return 'returnurl' === $key ? '> referer' : null; | ||
45 | }) | ||
46 | ; | ||
47 | $response = new Response(); | ||
48 | |||
49 | $assignedVariables = []; | ||
50 | $this->container->pageBuilder | ||
51 | ->method('assign') | ||
52 | ->willReturnCallback(function ($key, $value) use (&$assignedVariables) { | ||
53 | $assignedVariables[$key] = $value; | ||
54 | |||
55 | return $this; | ||
56 | }) | ||
57 | ; | ||
58 | |||
59 | $this->container->loginManager->method('canLogin')->willReturn(true); | ||
60 | |||
61 | $result = $this->controller->index($request, $response); | ||
62 | |||
63 | static::assertInstanceOf(Response::class, $result); | ||
64 | static::assertSame(200, $result->getStatusCode()); | ||
65 | static::assertSame(TemplatePage::LOGIN, (string) $result->getBody()); | ||
66 | |||
67 | static::assertSame('> referer', $assignedVariables['returnurl']); | ||
68 | static::assertSame(true, $assignedVariables['remember_user_default']); | ||
69 | static::assertSame('Login - Shaarli', $assignedVariables['pagetitle']); | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * Test displaying login form with username defined in the request. | ||
74 | */ | ||
75 | public function testValidControllerInvokeWithUserName(): void | ||
76 | { | ||
77 | $this->container->environment = ['HTTP_REFERER' => '> referer']; | ||
78 | |||
79 | $request = $this->createMock(Request::class); | ||
80 | $request | ||
81 | ->expects(static::atLeastOnce()) | ||
82 | ->method('getParam') | ||
83 | ->willReturnCallback(function (string $key, $default) { | ||
84 | if ('login' === $key) { | ||
85 | return 'myUser>'; | ||
86 | } | ||
87 | |||
88 | return $default; | ||
89 | }) | ||
90 | ; | ||
91 | $response = new Response(); | ||
92 | |||
93 | $assignedVariables = []; | ||
94 | $this->container->pageBuilder | ||
95 | ->method('assign') | ||
96 | ->willReturnCallback(function ($key, $value) use (&$assignedVariables) { | ||
97 | $assignedVariables[$key] = $value; | ||
98 | |||
99 | return $this; | ||
100 | }) | ||
101 | ; | ||
102 | |||
103 | $this->container->loginManager->expects(static::once())->method('canLogin')->willReturn(true); | ||
104 | |||
105 | $result = $this->controller->index($request, $response); | ||
106 | |||
107 | static::assertInstanceOf(Response::class, $result); | ||
108 | static::assertSame(200, $result->getStatusCode()); | ||
109 | static::assertSame('loginform', (string) $result->getBody()); | ||
110 | |||
111 | static::assertSame('myUser>', $assignedVariables['username']); | ||
112 | static::assertSame('> referer', $assignedVariables['returnurl']); | ||
113 | static::assertSame(true, $assignedVariables['remember_user_default']); | ||
114 | static::assertSame('Login - Shaarli', $assignedVariables['pagetitle']); | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * Test displaying login page while being logged in. | ||
119 | */ | ||
120 | public function testLoginControllerWhileLoggedIn(): void | ||
121 | { | ||
122 | $request = $this->createMock(Request::class); | ||
123 | $response = new Response(); | ||
124 | |||
125 | $this->container->loginManager->expects(static::once())->method('isLoggedIn')->willReturn(true); | ||
126 | |||
127 | $result = $this->controller->index($request, $response); | ||
128 | |||
129 | static::assertInstanceOf(Response::class, $result); | ||
130 | static::assertSame(302, $result->getStatusCode()); | ||
131 | static::assertSame(['/subfolder/'], $result->getHeader('Location')); | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * Test displaying login page with open shaarli configured: redirect to homepage. | ||
136 | */ | ||
137 | public function testLoginControllerOpenShaarli(): void | ||
138 | { | ||
139 | $request = $this->createMock(Request::class); | ||
140 | $response = new Response(); | ||
141 | |||
142 | $conf = $this->createMock(ConfigManager::class); | ||
143 | $conf->method('get')->willReturnCallback(function (string $parameter, $default) { | ||
144 | if ($parameter === 'security.open_shaarli') { | ||
145 | return true; | ||
146 | } | ||
147 | return $default; | ||
148 | }); | ||
149 | $this->container->conf = $conf; | ||
150 | |||
151 | $result = $this->controller->index($request, $response); | ||
152 | |||
153 | static::assertInstanceOf(Response::class, $result); | ||
154 | static::assertSame(302, $result->getStatusCode()); | ||
155 | static::assertSame(['/subfolder/'], $result->getHeader('Location')); | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Test displaying login page while being banned. | ||
160 | */ | ||
161 | public function testLoginControllerWhileBanned(): void | ||
162 | { | ||
163 | $request = $this->createMock(Request::class); | ||
164 | $response = new Response(); | ||
165 | |||
166 | $this->container->loginManager->method('isLoggedIn')->willReturn(false); | ||
167 | $this->container->loginManager->method('canLogin')->willReturn(false); | ||
168 | |||
169 | $this->expectException(LoginBannedException::class); | ||
170 | |||
171 | $this->controller->index($request, $response); | ||
172 | } | ||
173 | |||
174 | /** | ||
175 | * Test processing login with valid parameters. | ||
176 | */ | ||
177 | public function testProcessLoginWithValidParameters(): void | ||
178 | { | ||
179 | $parameters = [ | ||
180 | 'login' => 'bob', | ||
181 | 'password' => 'pass', | ||
182 | ]; | ||
183 | $request = $this->createMock(Request::class); | ||
184 | $request | ||
185 | ->expects(static::atLeastOnce()) | ||
186 | ->method('getParam') | ||
187 | ->willReturnCallback(function (string $key) use ($parameters) { | ||
188 | return $parameters[$key] ?? null; | ||
189 | }) | ||
190 | ; | ||
191 | $response = new Response(); | ||
192 | |||
193 | $this->container->loginManager->method('canLogin')->willReturn(true); | ||
194 | $this->container->loginManager->expects(static::once())->method('handleSuccessfulLogin'); | ||
195 | $this->container->loginManager | ||
196 | ->expects(static::once()) | ||
197 | ->method('checkCredentials') | ||
198 | ->with('1.2.3.4', '1.2.3.4', 'bob', 'pass') | ||
199 | ->willReturn(true) | ||
200 | ; | ||
201 | $this->container->loginManager->method('getStaySignedInToken')->willReturn(bin2hex(random_bytes(8))); | ||
202 | |||
203 | $this->container->sessionManager->expects(static::never())->method('extendSession'); | ||
204 | $this->container->sessionManager->expects(static::once())->method('destroy'); | ||
205 | $this->container->sessionManager | ||
206 | ->expects(static::once()) | ||
207 | ->method('cookieParameters') | ||
208 | ->with(0, '/subfolder/', 'shaarli') | ||
209 | ; | ||
210 | $this->container->sessionManager->expects(static::once())->method('start'); | ||
211 | $this->container->sessionManager->expects(static::once())->method('regenerateId')->with(true); | ||
212 | |||
213 | $result = $this->controller->login($request, $response); | ||
214 | |||
215 | static::assertSame(302, $result->getStatusCode()); | ||
216 | static::assertSame('/subfolder/', $result->getHeader('location')[0]); | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * Test processing login with return URL. | ||
221 | */ | ||
222 | public function testProcessLoginWithReturnUrl(): void | ||
223 | { | ||
224 | $parameters = [ | ||
225 | 'returnurl' => 'http://shaarli/subfolder/admin/shaare', | ||
226 | ]; | ||
227 | $request = $this->createMock(Request::class); | ||
228 | $request | ||
229 | ->expects(static::atLeastOnce()) | ||
230 | ->method('getParam') | ||
231 | ->willReturnCallback(function (string $key) use ($parameters) { | ||
232 | return $parameters[$key] ?? null; | ||
233 | }) | ||
234 | ; | ||
235 | $response = new Response(); | ||
236 | |||
237 | $this->container->loginManager->method('canLogin')->willReturn(true); | ||
238 | $this->container->loginManager->expects(static::once())->method('handleSuccessfulLogin'); | ||
239 | $this->container->loginManager->expects(static::once())->method('checkCredentials')->willReturn(true); | ||
240 | $this->container->loginManager->method('getStaySignedInToken')->willReturn(bin2hex(random_bytes(8))); | ||
241 | |||
242 | $result = $this->controller->login($request, $response); | ||
243 | |||
244 | static::assertSame(302, $result->getStatusCode()); | ||
245 | static::assertSame('/subfolder/admin/shaare', $result->getHeader('location')[0]); | ||
246 | } | ||
247 | |||
248 | /** | ||
249 | * Test processing login with remember me session enabled. | ||
250 | */ | ||
251 | public function testProcessLoginLongLastingSession(): void | ||
252 | { | ||
253 | $parameters = [ | ||
254 | 'longlastingsession' => true, | ||
255 | ]; | ||
256 | $request = $this->createMock(Request::class); | ||
257 | $request | ||
258 | ->expects(static::atLeastOnce()) | ||
259 | ->method('getParam') | ||
260 | ->willReturnCallback(function (string $key) use ($parameters) { | ||
261 | return $parameters[$key] ?? null; | ||
262 | }) | ||
263 | ; | ||
264 | $response = new Response(); | ||
265 | |||
266 | $this->container->loginManager->method('canLogin')->willReturn(true); | ||
267 | $this->container->loginManager->expects(static::once())->method('handleSuccessfulLogin'); | ||
268 | $this->container->loginManager->expects(static::once())->method('checkCredentials')->willReturn(true); | ||
269 | $this->container->loginManager->method('getStaySignedInToken')->willReturn(bin2hex(random_bytes(8))); | ||
270 | |||
271 | $this->container->sessionManager->expects(static::once())->method('destroy'); | ||
272 | $this->container->sessionManager | ||
273 | ->expects(static::once()) | ||
274 | ->method('cookieParameters') | ||
275 | ->with(42, '/subfolder/', 'shaarli') | ||
276 | ; | ||
277 | $this->container->sessionManager->expects(static::once())->method('start'); | ||
278 | $this->container->sessionManager->expects(static::once())->method('regenerateId')->with(true); | ||
279 | $this->container->sessionManager->expects(static::once())->method('extendSession')->willReturn(42); | ||
280 | |||
281 | $this->container->cookieManager = $this->createMock(CookieManager::class); | ||
282 | $this->container->cookieManager | ||
283 | ->expects(static::once()) | ||
284 | ->method('setCookieParameter') | ||
285 | ->willReturnCallback(function (string $name): CookieManager { | ||
286 | static::assertSame(CookieManager::STAY_SIGNED_IN, $name); | ||
287 | |||
288 | return $this->container->cookieManager; | ||
289 | }) | ||
290 | ; | ||
291 | |||
292 | $result = $this->controller->login($request, $response); | ||
293 | |||
294 | static::assertSame(302, $result->getStatusCode()); | ||
295 | static::assertSame('/subfolder/', $result->getHeader('location')[0]); | ||
296 | } | ||
297 | |||
298 | /** | ||
299 | * Test processing login with invalid credentials | ||
300 | */ | ||
301 | public function testProcessLoginWrongCredentials(): void | ||
302 | { | ||
303 | $parameters = [ | ||
304 | 'returnurl' => 'http://shaarli/subfolder/admin/shaare', | ||
305 | ]; | ||
306 | $request = $this->createMock(Request::class); | ||
307 | $request | ||
308 | ->expects(static::atLeastOnce()) | ||
309 | ->method('getParam') | ||
310 | ->willReturnCallback(function (string $key) use ($parameters) { | ||
311 | return $parameters[$key] ?? null; | ||
312 | }) | ||
313 | ; | ||
314 | $response = new Response(); | ||
315 | |||
316 | $this->container->loginManager->method('canLogin')->willReturn(true); | ||
317 | $this->container->loginManager->expects(static::once())->method('handleFailedLogin'); | ||
318 | $this->container->loginManager->expects(static::once())->method('checkCredentials')->willReturn(false); | ||
319 | |||
320 | $this->container->sessionManager | ||
321 | ->expects(static::once()) | ||
322 | ->method('setSessionParameter') | ||
323 | ->with(SessionManager::KEY_ERROR_MESSAGES, ['Wrong login/password.']) | ||
324 | ; | ||
325 | |||
326 | $result = $this->controller->login($request, $response); | ||
327 | |||
328 | static::assertSame(200, $result->getStatusCode()); | ||
329 | static::assertSame(TemplatePage::LOGIN, (string) $result->getBody()); | ||
330 | } | ||
331 | |||
332 | /** | ||
333 | * Test processing login with wrong token | ||
334 | */ | ||
335 | public function testProcessLoginWrongToken(): void | ||
336 | { | ||
337 | $request = $this->createMock(Request::class); | ||
338 | $response = new Response(); | ||
339 | |||
340 | $this->container->sessionManager = $this->createMock(SessionManager::class); | ||
341 | $this->container->sessionManager->method('checkToken')->willReturn(false); | ||
342 | |||
343 | $this->expectException(WrongTokenException::class); | ||
344 | |||
345 | $this->controller->login($request, $response); | ||
346 | } | ||
347 | |||
348 | /** | ||
349 | * Test processing login with wrong token | ||
350 | */ | ||
351 | public function testProcessLoginAlreadyLoggedIn(): void | ||
352 | { | ||
353 | $request = $this->createMock(Request::class); | ||
354 | $response = new Response(); | ||
355 | |||
356 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
357 | $this->container->loginManager->expects(static::never())->method('handleSuccessfulLogin'); | ||
358 | $this->container->loginManager->expects(static::never())->method('handleFailedLogin'); | ||
359 | |||
360 | $result = $this->controller->login($request, $response); | ||
361 | |||
362 | static::assertSame(302, $result->getStatusCode()); | ||
363 | static::assertSame('/subfolder/', $result->getHeader('location')[0]); | ||
364 | } | ||
365 | |||
366 | /** | ||
367 | * Test processing login with wrong token | ||
368 | */ | ||
369 | public function testProcessLoginInOpenShaarli(): void | ||
370 | { | ||
371 | $request = $this->createMock(Request::class); | ||
372 | $response = new Response(); | ||
373 | |||
374 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
375 | $this->container->conf->method('get')->willReturnCallback(function (string $key, $value) { | ||
376 | return 'security.open_shaarli' === $key ? true : $value; | ||
377 | }); | ||
378 | |||
379 | $this->container->loginManager->expects(static::never())->method('handleSuccessfulLogin'); | ||
380 | $this->container->loginManager->expects(static::never())->method('handleFailedLogin'); | ||
381 | |||
382 | $result = $this->controller->login($request, $response); | ||
383 | |||
384 | static::assertSame(302, $result->getStatusCode()); | ||
385 | static::assertSame('/subfolder/', $result->getHeader('location')[0]); | ||
386 | } | ||
387 | |||
388 | /** | ||
389 | * Test processing login while being banned | ||
390 | */ | ||
391 | public function testProcessLoginWhileBanned(): void | ||
392 | { | ||
393 | $request = $this->createMock(Request::class); | ||
394 | $response = new Response(); | ||
395 | |||
396 | $this->container->loginManager->method('canLogin')->willReturn(false); | ||
397 | $this->container->loginManager->expects(static::never())->method('handleSuccessfulLogin'); | ||
398 | $this->container->loginManager->expects(static::never())->method('handleFailedLogin'); | ||
399 | |||
400 | $this->expectException(LoginBannedException::class); | ||
401 | |||
402 | $this->controller->login($request, $response); | ||
403 | } | ||
404 | } | ||
diff --git a/tests/front/controller/visitor/OpenSearchControllerTest.php b/tests/front/controller/visitor/OpenSearchControllerTest.php new file mode 100644 index 00000000..42d876c3 --- /dev/null +++ b/tests/front/controller/visitor/OpenSearchControllerTest.php | |||
@@ -0,0 +1,44 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\TestCase; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | class OpenSearchControllerTest extends TestCase | ||
12 | { | ||
13 | use FrontControllerMockHelper; | ||
14 | |||
15 | /** @var OpenSearchController */ | ||
16 | protected $controller; | ||
17 | |||
18 | public function setUp(): void | ||
19 | { | ||
20 | $this->createContainer(); | ||
21 | |||
22 | $this->controller = new OpenSearchController($this->container); | ||
23 | } | ||
24 | |||
25 | public function testOpenSearchController(): void | ||
26 | { | ||
27 | $request = $this->createMock(Request::class); | ||
28 | $response = new Response(); | ||
29 | |||
30 | // Save RainTPL assigned variables | ||
31 | $assignedVariables = []; | ||
32 | $this->assignTemplateVars($assignedVariables); | ||
33 | |||
34 | $result = $this->controller->index($request, $response); | ||
35 | |||
36 | static::assertSame(200, $result->getStatusCode()); | ||
37 | static::assertStringContainsString( | ||
38 | 'application/opensearchdescription+xml', | ||
39 | $result->getHeader('Content-Type')[0] | ||
40 | ); | ||
41 | static::assertSame('opensearch', (string) $result->getBody()); | ||
42 | static::assertSame('http://shaarli/subfolder/', $assignedVariables['serverurl']); | ||
43 | } | ||
44 | } | ||
diff --git a/tests/front/controller/visitor/PictureWallControllerTest.php b/tests/front/controller/visitor/PictureWallControllerTest.php new file mode 100644 index 00000000..b868231d --- /dev/null +++ b/tests/front/controller/visitor/PictureWallControllerTest.php | |||
@@ -0,0 +1,123 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Front\Exception\ThumbnailsDisabledException; | ||
10 | use Shaarli\TestCase; | ||
11 | use Shaarli\Thumbnailer; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | class PictureWallControllerTest extends TestCase | ||
16 | { | ||
17 | use FrontControllerMockHelper; | ||
18 | |||
19 | /** @var PictureWallController */ | ||
20 | protected $controller; | ||
21 | |||
22 | public function setUp(): void | ||
23 | { | ||
24 | $this->createContainer(); | ||
25 | |||
26 | $this->controller = new PictureWallController($this->container); | ||
27 | } | ||
28 | |||
29 | public function testValidControllerInvokeDefault(): void | ||
30 | { | ||
31 | $request = $this->createMock(Request::class); | ||
32 | $request->expects(static::once())->method('getQueryParams')->willReturn([]); | ||
33 | $response = new Response(); | ||
34 | |||
35 | // ConfigManager: thumbnails are enabled | ||
36 | $this->container->conf = $this->createMock(ConfigManager::class); | ||
37 | $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) { | ||
38 | if ($parameter === 'thumbnails.mode') { | ||
39 | return Thumbnailer::MODE_COMMON; | ||
40 | } | ||
41 | |||
42 | return $default; | ||
43 | }); | ||
44 | |||
45 | // Save RainTPL assigned variables | ||
46 | $assignedVariables = []; | ||
47 | $this->assignTemplateVars($assignedVariables); | ||
48 | |||
49 | // Links dataset: 2 links with thumbnails | ||
50 | $this->container->bookmarkService | ||
51 | ->expects(static::once()) | ||
52 | ->method('search') | ||
53 | ->willReturnCallback(function (array $parameters, ?string $visibility): array { | ||
54 | // Visibility is set through the container, not the call | ||
55 | static::assertNull($visibility); | ||
56 | |||
57 | // No query parameters | ||
58 | if (count($parameters) === 0) { | ||
59 | return [ | ||
60 | (new Bookmark())->setId(1)->setUrl('http://url.tld')->setThumbnail('thumb1'), | ||
61 | (new Bookmark())->setId(2)->setUrl('http://url2.tld'), | ||
62 | (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setThumbnail('thumb2'), | ||
63 | ]; | ||
64 | } | ||
65 | }) | ||
66 | ; | ||
67 | |||
68 | // Make sure that PluginManager hook is triggered | ||
69 | $this->container->pluginManager | ||
70 | ->expects(static::atLeastOnce()) | ||
71 | ->method('executeHooks') | ||
72 | ->withConsecutive(['render_picwall']) | ||
73 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
74 | if ('render_picwall' === $hook) { | ||
75 | static::assertArrayHasKey('linksToDisplay', $data); | ||
76 | static::assertCount(2, $data['linksToDisplay']); | ||
77 | static::assertSame(1, $data['linksToDisplay'][0]['id']); | ||
78 | static::assertSame(3, $data['linksToDisplay'][1]['id']); | ||
79 | static::assertArrayHasKey('loggedin', $param); | ||
80 | } | ||
81 | |||
82 | return $data; | ||
83 | }); | ||
84 | |||
85 | $result = $this->controller->index($request, $response); | ||
86 | |||
87 | static::assertSame(200, $result->getStatusCode()); | ||
88 | static::assertSame('picwall', (string) $result->getBody()); | ||
89 | static::assertSame('Picture wall - Shaarli', $assignedVariables['pagetitle']); | ||
90 | static::assertCount(2, $assignedVariables['linksToDisplay']); | ||
91 | |||
92 | $link = $assignedVariables['linksToDisplay'][0]; | ||
93 | |||
94 | static::assertSame(1, $link['id']); | ||
95 | static::assertSame('http://url.tld', $link['url']); | ||
96 | static::assertSame('thumb1', $link['thumbnail']); | ||
97 | |||
98 | $link = $assignedVariables['linksToDisplay'][1]; | ||
99 | |||
100 | static::assertSame(3, $link['id']); | ||
101 | static::assertSame('http://url3.tld', $link['url']); | ||
102 | static::assertSame('thumb2', $link['thumbnail']); | ||
103 | } | ||
104 | |||
105 | public function testControllerWithThumbnailsDisabled(): void | ||
106 | { | ||
107 | $this->expectException(ThumbnailsDisabledException::class); | ||
108 | |||
109 | $request = $this->createMock(Request::class); | ||
110 | $response = new Response(); | ||
111 | |||
112 | // ConfigManager: thumbnails are disabled | ||
113 | $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) { | ||
114 | if ($parameter === 'thumbnails.mode') { | ||
115 | return Thumbnailer::MODE_NONE; | ||
116 | } | ||
117 | |||
118 | return $default; | ||
119 | }); | ||
120 | |||
121 | $this->controller->index($request, $response); | ||
122 | } | ||
123 | } | ||
diff --git a/tests/front/controller/visitor/PublicSessionFilterControllerTest.php b/tests/front/controller/visitor/PublicSessionFilterControllerTest.php new file mode 100644 index 00000000..7e3b00af --- /dev/null +++ b/tests/front/controller/visitor/PublicSessionFilterControllerTest.php | |||
@@ -0,0 +1,122 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Security\SessionManager; | ||
8 | use Shaarli\TestCase; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | class PublicSessionFilterControllerTest extends TestCase | ||
13 | { | ||
14 | use FrontControllerMockHelper; | ||
15 | |||
16 | /** @var PublicSessionFilterController */ | ||
17 | protected $controller; | ||
18 | |||
19 | public function setUp(): void | ||
20 | { | ||
21 | $this->createContainer(); | ||
22 | |||
23 | $this->controller = new PublicSessionFilterController($this->container); | ||
24 | } | ||
25 | |||
26 | /** | ||
27 | * Link per page - Default call with valid parameter and a referer. | ||
28 | */ | ||
29 | public function testLinksPerPage(): void | ||
30 | { | ||
31 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller/?searchtag=abc'; | ||
32 | |||
33 | $request = $this->createMock(Request::class); | ||
34 | $request->method('getParam')->with('nb')->willReturn('8'); | ||
35 | $response = new Response(); | ||
36 | |||
37 | $this->container->sessionManager | ||
38 | ->expects(static::once()) | ||
39 | ->method('setSessionParameter') | ||
40 | ->with(SessionManager::KEY_LINKS_PER_PAGE, 8) | ||
41 | ; | ||
42 | |||
43 | $result = $this->controller->linksPerPage($request, $response); | ||
44 | |||
45 | static::assertInstanceOf(Response::class, $result); | ||
46 | static::assertSame(302, $result->getStatusCode()); | ||
47 | static::assertSame(['/subfolder/controller/?searchtag=abc'], $result->getHeader('location')); | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * Link per page - Invalid value, should use default value (20) | ||
52 | */ | ||
53 | public function testLinksPerPageNotValid(): void | ||
54 | { | ||
55 | $request = $this->createMock(Request::class); | ||
56 | $request->method('getParam')->with('nb')->willReturn('test'); | ||
57 | $response = new Response(); | ||
58 | |||
59 | $this->container->sessionManager | ||
60 | ->expects(static::once()) | ||
61 | ->method('setSessionParameter') | ||
62 | ->with(SessionManager::KEY_LINKS_PER_PAGE, 20) | ||
63 | ; | ||
64 | |||
65 | $result = $this->controller->linksPerPage($request, $response); | ||
66 | |||
67 | static::assertInstanceOf(Response::class, $result); | ||
68 | static::assertSame(302, $result->getStatusCode()); | ||
69 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * Untagged only - valid call | ||
74 | */ | ||
75 | public function testUntaggedOnly(): void | ||
76 | { | ||
77 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller/?searchtag=abc'; | ||
78 | |||
79 | $request = $this->createMock(Request::class); | ||
80 | $response = new Response(); | ||
81 | |||
82 | $this->container->sessionManager | ||
83 | ->expects(static::once()) | ||
84 | ->method('setSessionParameter') | ||
85 | ->with(SessionManager::KEY_UNTAGGED_ONLY, true) | ||
86 | ; | ||
87 | |||
88 | $result = $this->controller->untaggedOnly($request, $response); | ||
89 | |||
90 | static::assertInstanceOf(Response::class, $result); | ||
91 | static::assertSame(302, $result->getStatusCode()); | ||
92 | static::assertSame(['/subfolder/controller/?searchtag=abc'], $result->getHeader('location')); | ||
93 | } | ||
94 | |||
95 | /** | ||
96 | * Untagged only - toggle off | ||
97 | */ | ||
98 | public function testUntaggedOnlyToggleOff(): void | ||
99 | { | ||
100 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller/?searchtag=abc'; | ||
101 | |||
102 | $request = $this->createMock(Request::class); | ||
103 | $response = new Response(); | ||
104 | |||
105 | $this->container->sessionManager | ||
106 | ->method('getSessionParameter') | ||
107 | ->with(SessionManager::KEY_UNTAGGED_ONLY) | ||
108 | ->willReturn(true) | ||
109 | ; | ||
110 | $this->container->sessionManager | ||
111 | ->expects(static::once()) | ||
112 | ->method('setSessionParameter') | ||
113 | ->with(SessionManager::KEY_UNTAGGED_ONLY, false) | ||
114 | ; | ||
115 | |||
116 | $result = $this->controller->untaggedOnly($request, $response); | ||
117 | |||
118 | static::assertInstanceOf(Response::class, $result); | ||
119 | static::assertSame(302, $result->getStatusCode()); | ||
120 | static::assertSame(['/subfolder/controller/?searchtag=abc'], $result->getHeader('location')); | ||
121 | } | ||
122 | } | ||
diff --git a/tests/front/controller/visitor/ShaarliVisitorControllerTest.php b/tests/front/controller/visitor/ShaarliVisitorControllerTest.php new file mode 100644 index 00000000..935ec24e --- /dev/null +++ b/tests/front/controller/visitor/ShaarliVisitorControllerTest.php | |||
@@ -0,0 +1,246 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkFilter; | ||
8 | use Shaarli\TestCase; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | /** | ||
13 | * Class ShaarliControllerTest | ||
14 | * | ||
15 | * This class is used to test default behavior of ShaarliVisitorController abstract class. | ||
16 | * It uses a dummy non abstract controller. | ||
17 | */ | ||
18 | class ShaarliVisitorControllerTest extends TestCase | ||
19 | { | ||
20 | use FrontControllerMockHelper; | ||
21 | |||
22 | /** @var LoginController */ | ||
23 | protected $controller; | ||
24 | |||
25 | /** @var mixed[] List of variable assigned to the template */ | ||
26 | protected $assignedValues; | ||
27 | |||
28 | /** @var Request */ | ||
29 | protected $request; | ||
30 | |||
31 | public function setUp(): void | ||
32 | { | ||
33 | $this->createContainer(); | ||
34 | |||
35 | $this->controller = new class($this->container) extends ShaarliVisitorController | ||
36 | { | ||
37 | public function assignView(string $key, $value): ShaarliVisitorController | ||
38 | { | ||
39 | return parent::assignView($key, $value); | ||
40 | } | ||
41 | |||
42 | public function render(string $template): string | ||
43 | { | ||
44 | return parent::render($template); | ||
45 | } | ||
46 | |||
47 | public function redirectFromReferer( | ||
48 | Request $request, | ||
49 | Response $response, | ||
50 | array $loopTerms = [], | ||
51 | array $clearParams = [], | ||
52 | string $anchor = null | ||
53 | ): Response { | ||
54 | return parent::redirectFromReferer($request, $response, $loopTerms, $clearParams, $anchor); | ||
55 | } | ||
56 | }; | ||
57 | $this->assignedValues = []; | ||
58 | |||
59 | $this->request = $this->createMock(Request::class); | ||
60 | } | ||
61 | |||
62 | public function testAssignView(): void | ||
63 | { | ||
64 | $this->assignTemplateVars($this->assignedValues); | ||
65 | |||
66 | $self = $this->controller->assignView('variableName', 'variableValue'); | ||
67 | |||
68 | static::assertInstanceOf(ShaarliVisitorController::class, $self); | ||
69 | static::assertSame('variableValue', $this->assignedValues['variableName']); | ||
70 | } | ||
71 | |||
72 | public function testRender(): void | ||
73 | { | ||
74 | $this->assignTemplateVars($this->assignedValues); | ||
75 | |||
76 | $this->container->bookmarkService | ||
77 | ->method('count') | ||
78 | ->willReturnCallback(function (string $visibility): int { | ||
79 | return $visibility === BookmarkFilter::$PRIVATE ? 5 : 10; | ||
80 | }) | ||
81 | ; | ||
82 | |||
83 | $this->container->pluginManager | ||
84 | ->method('executeHooks') | ||
85 | ->willReturnCallback(function (string $hook, array &$data, array $params): array { | ||
86 | return $data[$hook] = $params; | ||
87 | }); | ||
88 | $this->container->pluginManager->method('getErrors')->willReturn(['error']); | ||
89 | |||
90 | $this->container->loginManager->method('isLoggedIn')->willReturn(true); | ||
91 | |||
92 | $render = $this->controller->render('templateName'); | ||
93 | |||
94 | static::assertSame('templateName', $render); | ||
95 | |||
96 | static::assertSame(10, $this->assignedValues['linkcount']); | ||
97 | static::assertSame(5, $this->assignedValues['privateLinkcount']); | ||
98 | static::assertSame(['error'], $this->assignedValues['plugin_errors']); | ||
99 | |||
100 | static::assertSame('templateName', $this->assignedValues['plugins_includes']['render_includes']['target']); | ||
101 | static::assertTrue($this->assignedValues['plugins_includes']['render_includes']['loggedin']); | ||
102 | static::assertSame('templateName', $this->assignedValues['plugins_header']['render_header']['target']); | ||
103 | static::assertTrue($this->assignedValues['plugins_header']['render_header']['loggedin']); | ||
104 | static::assertSame('templateName', $this->assignedValues['plugins_footer']['render_footer']['target']); | ||
105 | static::assertTrue($this->assignedValues['plugins_footer']['render_footer']['loggedin']); | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * Test redirectFromReferer() - Default behaviour | ||
110 | */ | ||
111 | public function testRedirectFromRefererDefault(): void | ||
112 | { | ||
113 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller?query=param&other=2'; | ||
114 | |||
115 | $response = new Response(); | ||
116 | |||
117 | $result = $this->controller->redirectFromReferer($this->request, $response); | ||
118 | |||
119 | static::assertSame(302, $result->getStatusCode()); | ||
120 | static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); | ||
121 | } | ||
122 | |||
123 | /** | ||
124 | * Test redirectFromReferer() - With a loop term not matched in the referer | ||
125 | */ | ||
126 | public function testRedirectFromRefererWithUnmatchedLoopTerm(): void | ||
127 | { | ||
128 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller?query=param&other=2'; | ||
129 | |||
130 | $response = new Response(); | ||
131 | |||
132 | $result = $this->controller->redirectFromReferer($this->request, $response, ['nope']); | ||
133 | |||
134 | static::assertSame(302, $result->getStatusCode()); | ||
135 | static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Test redirectFromReferer() - With a loop term matching the referer in its path -> redirect to default | ||
140 | */ | ||
141 | public function testRedirectFromRefererWithMatchingLoopTermInPath(): void | ||
142 | { | ||
143 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller?query=param&other=2'; | ||
144 | |||
145 | $response = new Response(); | ||
146 | |||
147 | $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'controller']); | ||
148 | |||
149 | static::assertSame(302, $result->getStatusCode()); | ||
150 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * Test redirectFromReferer() - With a loop term matching the referer in its query parameters -> redirect to default | ||
155 | */ | ||
156 | public function testRedirectFromRefererWithMatchingLoopTermInQueryParam(): void | ||
157 | { | ||
158 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller?query=param&other=2'; | ||
159 | |||
160 | $response = new Response(); | ||
161 | |||
162 | $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'other']); | ||
163 | |||
164 | static::assertSame(302, $result->getStatusCode()); | ||
165 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
166 | } | ||
167 | |||
168 | /** | ||
169 | * Test redirectFromReferer() - With a loop term matching the referer in its query value | ||
170 | * -> we do not block redirection for query parameter values. | ||
171 | */ | ||
172 | public function testRedirectFromRefererWithMatchingLoopTermInQueryValue(): void | ||
173 | { | ||
174 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller?query=param&other=2'; | ||
175 | |||
176 | $response = new Response(); | ||
177 | |||
178 | $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'param']); | ||
179 | |||
180 | static::assertSame(302, $result->getStatusCode()); | ||
181 | static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); | ||
182 | } | ||
183 | |||
184 | /** | ||
185 | * Test redirectFromReferer() - With a loop term matching the referer in its domain name | ||
186 | * -> we do not block redirection for shaarli's hosts | ||
187 | */ | ||
188 | public function testRedirectFromRefererWithLoopTermInDomain(): void | ||
189 | { | ||
190 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller?query=param&other=2'; | ||
191 | |||
192 | $response = new Response(); | ||
193 | |||
194 | $result = $this->controller->redirectFromReferer($this->request, $response, ['shaarli']); | ||
195 | |||
196 | static::assertSame(302, $result->getStatusCode()); | ||
197 | static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); | ||
198 | } | ||
199 | |||
200 | /** | ||
201 | * Test redirectFromReferer() - With a loop term matching a query parameter AND clear this query param | ||
202 | * -> the param should be cleared before checking if it matches the redir loop terms | ||
203 | */ | ||
204 | public function testRedirectFromRefererWithMatchingClearedParam(): void | ||
205 | { | ||
206 | $this->container->environment['HTTP_REFERER'] = 'http://shaarli/subfolder/controller?query=param&other=2'; | ||
207 | |||
208 | $response = new Response(); | ||
209 | |||
210 | $result = $this->controller->redirectFromReferer($this->request, $response, ['query'], ['query']); | ||
211 | |||
212 | static::assertSame(302, $result->getStatusCode()); | ||
213 | static::assertSame(['/subfolder/controller?other=2'], $result->getHeader('location')); | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * Test redirectFromReferer() - From another domain -> we ignore the given referrer. | ||
218 | */ | ||
219 | public function testRedirectExternalReferer(): void | ||
220 | { | ||
221 | $this->container->environment['HTTP_REFERER'] = 'http://other.domain.tld/controller?query=param&other=2'; | ||
222 | |||
223 | $response = new Response(); | ||
224 | |||
225 | $result = $this->controller->redirectFromReferer($this->request, $response, ['query'], ['query']); | ||
226 | |||
227 | static::assertSame(302, $result->getStatusCode()); | ||
228 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
229 | } | ||
230 | |||
231 | /** | ||
232 | * Test redirectFromReferer() - From another domain -> we ignore the given referrer. | ||
233 | */ | ||
234 | public function testRedirectExternalRefererExplicitDomainName(): void | ||
235 | { | ||
236 | $this->container->environment['SERVER_NAME'] = 'my.shaarli.tld'; | ||
237 | $this->container->environment['HTTP_REFERER'] = 'http://your.shaarli.tld/controller?query=param&other=2'; | ||
238 | |||
239 | $response = new Response(); | ||
240 | |||
241 | $result = $this->controller->redirectFromReferer($this->request, $response, ['query'], ['query']); | ||
242 | |||
243 | static::assertSame(302, $result->getStatusCode()); | ||
244 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
245 | } | ||
246 | } | ||
diff --git a/tests/front/controller/visitor/TagCloudControllerTest.php b/tests/front/controller/visitor/TagCloudControllerTest.php new file mode 100644 index 00000000..9305612e --- /dev/null +++ b/tests/front/controller/visitor/TagCloudControllerTest.php | |||
@@ -0,0 +1,381 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\Bookmark\BookmarkFilter; | ||
8 | use Shaarli\TestCase; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | class TagCloudControllerTest extends TestCase | ||
13 | { | ||
14 | use FrontControllerMockHelper; | ||
15 | |||
16 | /** @var TagCloudController */ | ||
17 | protected $controller; | ||
18 | |||
19 | public function setUp(): void | ||
20 | { | ||
21 | $this->createContainer(); | ||
22 | |||
23 | $this->controller = new TagCloudController($this->container); | ||
24 | } | ||
25 | |||
26 | /** | ||
27 | * Tag Cloud - default parameters | ||
28 | */ | ||
29 | public function testValidCloudControllerInvokeDefault(): void | ||
30 | { | ||
31 | $allTags = [ | ||
32 | 'ghi' => 1, | ||
33 | 'abc' => 3, | ||
34 | 'def' => 12, | ||
35 | ]; | ||
36 | $expectedOrder = ['abc', 'def', 'ghi']; | ||
37 | |||
38 | $request = $this->createMock(Request::class); | ||
39 | $response = new Response(); | ||
40 | |||
41 | // Save RainTPL assigned variables | ||
42 | $assignedVariables = []; | ||
43 | $this->assignTemplateVars($assignedVariables); | ||
44 | |||
45 | $this->container->bookmarkService | ||
46 | ->expects(static::once()) | ||
47 | ->method('bookmarksCountPerTag') | ||
48 | ->with([], null) | ||
49 | ->willReturnCallback(function () use ($allTags): array { | ||
50 | return $allTags; | ||
51 | }) | ||
52 | ; | ||
53 | |||
54 | // Make sure that PluginManager hook is triggered | ||
55 | $this->container->pluginManager | ||
56 | ->expects(static::atLeastOnce()) | ||
57 | ->method('executeHooks') | ||
58 | ->withConsecutive(['render_tagcloud']) | ||
59 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
60 | if ('render_tagcloud' === $hook) { | ||
61 | static::assertSame('', $data['search_tags']); | ||
62 | static::assertCount(3, $data['tags']); | ||
63 | |||
64 | static::assertArrayHasKey('loggedin', $param); | ||
65 | } | ||
66 | |||
67 | return $data; | ||
68 | }) | ||
69 | ; | ||
70 | |||
71 | $result = $this->controller->cloud($request, $response); | ||
72 | |||
73 | static::assertSame(200, $result->getStatusCode()); | ||
74 | static::assertSame('tag.cloud', (string) $result->getBody()); | ||
75 | static::assertSame('Tag cloud - Shaarli', $assignedVariables['pagetitle']); | ||
76 | |||
77 | static::assertSame('', $assignedVariables['search_tags']); | ||
78 | static::assertCount(3, $assignedVariables['tags']); | ||
79 | static::assertSame($expectedOrder, array_keys($assignedVariables['tags'])); | ||
80 | |||
81 | foreach ($allTags as $tag => $count) { | ||
82 | static::assertArrayHasKey($tag, $assignedVariables['tags']); | ||
83 | static::assertSame($count, $assignedVariables['tags'][$tag]['count']); | ||
84 | static::assertGreaterThan(0, $assignedVariables['tags'][$tag]['size']); | ||
85 | static::assertLessThan(5, $assignedVariables['tags'][$tag]['size']); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Tag Cloud - Additional parameters: | ||
91 | * - logged in | ||
92 | * - visibility private | ||
93 | * - search tags: `ghi` and `def` (note that filtered tags are not displayed anymore) | ||
94 | */ | ||
95 | public function testValidCloudControllerInvokeWithParameters(): void | ||
96 | { | ||
97 | $request = $this->createMock(Request::class); | ||
98 | $request | ||
99 | ->method('getQueryParam') | ||
100 | ->with() | ||
101 | ->willReturnCallback(function (string $key): ?string { | ||
102 | if ('searchtags' === $key) { | ||
103 | return 'ghi def'; | ||
104 | } | ||
105 | |||
106 | return null; | ||
107 | }) | ||
108 | ; | ||
109 | $response = new Response(); | ||
110 | |||
111 | // Save RainTPL assigned variables | ||
112 | $assignedVariables = []; | ||
113 | $this->assignTemplateVars($assignedVariables); | ||
114 | |||
115 | $this->container->loginManager->method('isLoggedin')->willReturn(true); | ||
116 | $this->container->sessionManager->expects(static::once())->method('getSessionParameter')->willReturn('private'); | ||
117 | |||
118 | $this->container->bookmarkService | ||
119 | ->expects(static::once()) | ||
120 | ->method('bookmarksCountPerTag') | ||
121 | ->with(['ghi', 'def'], BookmarkFilter::$PRIVATE) | ||
122 | ->willReturnCallback(function (): array { | ||
123 | return ['abc' => 3]; | ||
124 | }) | ||
125 | ; | ||
126 | |||
127 | // Make sure that PluginManager hook is triggered | ||
128 | $this->container->pluginManager | ||
129 | ->expects(static::atLeastOnce()) | ||
130 | ->method('executeHooks') | ||
131 | ->withConsecutive(['render_tagcloud']) | ||
132 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
133 | if ('render_tagcloud' === $hook) { | ||
134 | static::assertSame('ghi def', $data['search_tags']); | ||
135 | static::assertCount(1, $data['tags']); | ||
136 | |||
137 | static::assertArrayHasKey('loggedin', $param); | ||
138 | } | ||
139 | |||
140 | return $data; | ||
141 | }) | ||
142 | ; | ||
143 | |||
144 | $result = $this->controller->cloud($request, $response); | ||
145 | |||
146 | static::assertSame(200, $result->getStatusCode()); | ||
147 | static::assertSame('tag.cloud', (string) $result->getBody()); | ||
148 | static::assertSame('ghi def - Tag cloud - Shaarli', $assignedVariables['pagetitle']); | ||
149 | |||
150 | static::assertSame('ghi def', $assignedVariables['search_tags']); | ||
151 | static::assertCount(1, $assignedVariables['tags']); | ||
152 | |||
153 | static::assertArrayHasKey('abc', $assignedVariables['tags']); | ||
154 | static::assertSame(3, $assignedVariables['tags']['abc']['count']); | ||
155 | static::assertGreaterThan(0, $assignedVariables['tags']['abc']['size']); | ||
156 | static::assertLessThan(5, $assignedVariables['tags']['abc']['size']); | ||
157 | } | ||
158 | |||
159 | /** | ||
160 | * Tag Cloud - empty | ||
161 | */ | ||
162 | public function testEmptyCloud(): void | ||
163 | { | ||
164 | $request = $this->createMock(Request::class); | ||
165 | $response = new Response(); | ||
166 | |||
167 | // Save RainTPL assigned variables | ||
168 | $assignedVariables = []; | ||
169 | $this->assignTemplateVars($assignedVariables); | ||
170 | |||
171 | $this->container->bookmarkService | ||
172 | ->expects(static::once()) | ||
173 | ->method('bookmarksCountPerTag') | ||
174 | ->with([], null) | ||
175 | ->willReturnCallback(function (array $parameters, ?string $visibility): array { | ||
176 | return []; | ||
177 | }) | ||
178 | ; | ||
179 | |||
180 | // Make sure that PluginManager hook is triggered | ||
181 | $this->container->pluginManager | ||
182 | ->expects(static::atLeastOnce()) | ||
183 | ->method('executeHooks') | ||
184 | ->withConsecutive(['render_tagcloud']) | ||
185 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
186 | if ('render_tagcloud' === $hook) { | ||
187 | static::assertSame('', $data['search_tags']); | ||
188 | static::assertCount(0, $data['tags']); | ||
189 | |||
190 | static::assertArrayHasKey('loggedin', $param); | ||
191 | } | ||
192 | |||
193 | return $data; | ||
194 | }) | ||
195 | ; | ||
196 | |||
197 | $result = $this->controller->cloud($request, $response); | ||
198 | |||
199 | static::assertSame(200, $result->getStatusCode()); | ||
200 | static::assertSame('tag.cloud', (string) $result->getBody()); | ||
201 | static::assertSame('Tag cloud - Shaarli', $assignedVariables['pagetitle']); | ||
202 | |||
203 | static::assertSame('', $assignedVariables['search_tags']); | ||
204 | static::assertCount(0, $assignedVariables['tags']); | ||
205 | } | ||
206 | |||
207 | /** | ||
208 | * Tag List - Default sort is by usage DESC | ||
209 | */ | ||
210 | public function testValidListControllerInvokeDefault(): void | ||
211 | { | ||
212 | $allTags = [ | ||
213 | 'def' => 12, | ||
214 | 'abc' => 3, | ||
215 | 'ghi' => 1, | ||
216 | ]; | ||
217 | |||
218 | $request = $this->createMock(Request::class); | ||
219 | $response = new Response(); | ||
220 | |||
221 | // Save RainTPL assigned variables | ||
222 | $assignedVariables = []; | ||
223 | $this->assignTemplateVars($assignedVariables); | ||
224 | |||
225 | $this->container->bookmarkService | ||
226 | ->expects(static::once()) | ||
227 | ->method('bookmarksCountPerTag') | ||
228 | ->with([], null) | ||
229 | ->willReturnCallback(function () use ($allTags): array { | ||
230 | return $allTags; | ||
231 | }) | ||
232 | ; | ||
233 | |||
234 | // Make sure that PluginManager hook is triggered | ||
235 | $this->container->pluginManager | ||
236 | ->expects(static::atLeastOnce()) | ||
237 | ->method('executeHooks') | ||
238 | ->withConsecutive(['render_taglist']) | ||
239 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
240 | if ('render_taglist' === $hook) { | ||
241 | static::assertSame('', $data['search_tags']); | ||
242 | static::assertCount(3, $data['tags']); | ||
243 | |||
244 | static::assertArrayHasKey('loggedin', $param); | ||
245 | } | ||
246 | |||
247 | return $data; | ||
248 | }) | ||
249 | ; | ||
250 | |||
251 | $result = $this->controller->list($request, $response); | ||
252 | |||
253 | static::assertSame(200, $result->getStatusCode()); | ||
254 | static::assertSame('tag.list', (string) $result->getBody()); | ||
255 | static::assertSame('Tag list - Shaarli', $assignedVariables['pagetitle']); | ||
256 | |||
257 | static::assertSame('', $assignedVariables['search_tags']); | ||
258 | static::assertCount(3, $assignedVariables['tags']); | ||
259 | |||
260 | foreach ($allTags as $tag => $count) { | ||
261 | static::assertSame($count, $assignedVariables['tags'][$tag]); | ||
262 | } | ||
263 | } | ||
264 | |||
265 | /** | ||
266 | * Tag List - Additional parameters: | ||
267 | * - logged in | ||
268 | * - visibility private | ||
269 | * - search tags: `ghi` and `def` (note that filtered tags are not displayed anymore) | ||
270 | * - sort alphabetically | ||
271 | */ | ||
272 | public function testValidListControllerInvokeWithParameters(): void | ||
273 | { | ||
274 | $request = $this->createMock(Request::class); | ||
275 | $request | ||
276 | ->method('getQueryParam') | ||
277 | ->with() | ||
278 | ->willReturnCallback(function (string $key): ?string { | ||
279 | if ('searchtags' === $key) { | ||
280 | return 'ghi def'; | ||
281 | } elseif ('sort' === $key) { | ||
282 | return 'alpha'; | ||
283 | } | ||
284 | |||
285 | return null; | ||
286 | }) | ||
287 | ; | ||
288 | $response = new Response(); | ||
289 | |||
290 | // Save RainTPL assigned variables | ||
291 | $assignedVariables = []; | ||
292 | $this->assignTemplateVars($assignedVariables); | ||
293 | |||
294 | $this->container->loginManager->method('isLoggedin')->willReturn(true); | ||
295 | $this->container->sessionManager->expects(static::once())->method('getSessionParameter')->willReturn('private'); | ||
296 | |||
297 | $this->container->bookmarkService | ||
298 | ->expects(static::once()) | ||
299 | ->method('bookmarksCountPerTag') | ||
300 | ->with(['ghi', 'def'], BookmarkFilter::$PRIVATE) | ||
301 | ->willReturnCallback(function (): array { | ||
302 | return ['abc' => 3]; | ||
303 | }) | ||
304 | ; | ||
305 | |||
306 | // Make sure that PluginManager hook is triggered | ||
307 | $this->container->pluginManager | ||
308 | ->expects(static::atLeastOnce()) | ||
309 | ->method('executeHooks') | ||
310 | ->withConsecutive(['render_taglist']) | ||
311 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
312 | if ('render_taglist' === $hook) { | ||
313 | static::assertSame('ghi def', $data['search_tags']); | ||
314 | static::assertCount(1, $data['tags']); | ||
315 | |||
316 | static::assertArrayHasKey('loggedin', $param); | ||
317 | } | ||
318 | |||
319 | return $data; | ||
320 | }) | ||
321 | ; | ||
322 | |||
323 | $result = $this->controller->list($request, $response); | ||
324 | |||
325 | static::assertSame(200, $result->getStatusCode()); | ||
326 | static::assertSame('tag.list', (string) $result->getBody()); | ||
327 | static::assertSame('ghi def - Tag list - Shaarli', $assignedVariables['pagetitle']); | ||
328 | |||
329 | static::assertSame('ghi def', $assignedVariables['search_tags']); | ||
330 | static::assertCount(1, $assignedVariables['tags']); | ||
331 | static::assertSame(3, $assignedVariables['tags']['abc']); | ||
332 | } | ||
333 | |||
334 | /** | ||
335 | * Tag List - empty | ||
336 | */ | ||
337 | public function testEmptyList(): void | ||
338 | { | ||
339 | $request = $this->createMock(Request::class); | ||
340 | $response = new Response(); | ||
341 | |||
342 | // Save RainTPL assigned variables | ||
343 | $assignedVariables = []; | ||
344 | $this->assignTemplateVars($assignedVariables); | ||
345 | |||
346 | $this->container->bookmarkService | ||
347 | ->expects(static::once()) | ||
348 | ->method('bookmarksCountPerTag') | ||
349 | ->with([], null) | ||
350 | ->willReturnCallback(function (array $parameters, ?string $visibility): array { | ||
351 | return []; | ||
352 | }) | ||
353 | ; | ||
354 | |||
355 | // Make sure that PluginManager hook is triggered | ||
356 | $this->container->pluginManager | ||
357 | ->expects(static::atLeastOnce()) | ||
358 | ->method('executeHooks') | ||
359 | ->withConsecutive(['render_taglist']) | ||
360 | ->willReturnCallback(function (string $hook, array $data, array $param): array { | ||
361 | if ('render_taglist' === $hook) { | ||
362 | static::assertSame('', $data['search_tags']); | ||
363 | static::assertCount(0, $data['tags']); | ||
364 | |||
365 | static::assertArrayHasKey('loggedin', $param); | ||
366 | } | ||
367 | |||
368 | return $data; | ||
369 | }) | ||
370 | ; | ||
371 | |||
372 | $result = $this->controller->list($request, $response); | ||
373 | |||
374 | static::assertSame(200, $result->getStatusCode()); | ||
375 | static::assertSame('tag.list', (string) $result->getBody()); | ||
376 | static::assertSame('Tag list - Shaarli', $assignedVariables['pagetitle']); | ||
377 | |||
378 | static::assertSame('', $assignedVariables['search_tags']); | ||
379 | static::assertCount(0, $assignedVariables['tags']); | ||
380 | } | ||
381 | } | ||
diff --git a/tests/front/controller/visitor/TagControllerTest.php b/tests/front/controller/visitor/TagControllerTest.php new file mode 100644 index 00000000..750ea02d --- /dev/null +++ b/tests/front/controller/visitor/TagControllerTest.php | |||
@@ -0,0 +1,215 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\TestCase; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | class TagControllerTest extends TestCase | ||
12 | { | ||
13 | use FrontControllerMockHelper; | ||
14 | |||
15 | /** @var TagController */ protected $controller; | ||
16 | |||
17 | public function setUp(): void | ||
18 | { | ||
19 | $this->createContainer(); | ||
20 | |||
21 | $this->controller = new TagController($this->container); | ||
22 | } | ||
23 | |||
24 | public function testAddTagWithReferer(): void | ||
25 | { | ||
26 | $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/']; | ||
27 | |||
28 | $request = $this->createMock(Request::class); | ||
29 | $response = new Response(); | ||
30 | |||
31 | $tags = ['newTag' => 'abc']; | ||
32 | |||
33 | $result = $this->controller->addTag($request, $response, $tags); | ||
34 | |||
35 | static::assertInstanceOf(Response::class, $result); | ||
36 | static::assertSame(302, $result->getStatusCode()); | ||
37 | static::assertSame(['/controller/?searchtags=abc'], $result->getHeader('location')); | ||
38 | } | ||
39 | |||
40 | public function testAddTagWithRefererAndExistingSearch(): void | ||
41 | { | ||
42 | $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def']; | ||
43 | |||
44 | $request = $this->createMock(Request::class); | ||
45 | $response = new Response(); | ||
46 | |||
47 | $tags = ['newTag' => 'abc']; | ||
48 | |||
49 | $result = $this->controller->addTag($request, $response, $tags); | ||
50 | |||
51 | static::assertInstanceOf(Response::class, $result); | ||
52 | static::assertSame(302, $result->getStatusCode()); | ||
53 | static::assertSame(['/controller/?searchtags=def+abc'], $result->getHeader('location')); | ||
54 | } | ||
55 | |||
56 | public function testAddTagWithoutRefererAndExistingSearch(): void | ||
57 | { | ||
58 | $request = $this->createMock(Request::class); | ||
59 | $response = new Response(); | ||
60 | |||
61 | $tags = ['newTag' => 'abc']; | ||
62 | |||
63 | $result = $this->controller->addTag($request, $response, $tags); | ||
64 | |||
65 | static::assertInstanceOf(Response::class, $result); | ||
66 | static::assertSame(302, $result->getStatusCode()); | ||
67 | static::assertSame(['/subfolder/?searchtags=abc'], $result->getHeader('location')); | ||
68 | } | ||
69 | |||
70 | public function testAddTagRemoveLegacyQueryParam(): void | ||
71 | { | ||
72 | $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def&addtag=abc']; | ||
73 | |||
74 | $request = $this->createMock(Request::class); | ||
75 | $response = new Response(); | ||
76 | |||
77 | $tags = ['newTag' => 'abc']; | ||
78 | |||
79 | $result = $this->controller->addTag($request, $response, $tags); | ||
80 | |||
81 | static::assertInstanceOf(Response::class, $result); | ||
82 | static::assertSame(302, $result->getStatusCode()); | ||
83 | static::assertSame(['/controller/?searchtags=def+abc'], $result->getHeader('location')); | ||
84 | } | ||
85 | |||
86 | public function testAddTagResetPagination(): void | ||
87 | { | ||
88 | $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def&page=12']; | ||
89 | |||
90 | $request = $this->createMock(Request::class); | ||
91 | $response = new Response(); | ||
92 | |||
93 | $tags = ['newTag' => 'abc']; | ||
94 | |||
95 | $result = $this->controller->addTag($request, $response, $tags); | ||
96 | |||
97 | static::assertInstanceOf(Response::class, $result); | ||
98 | static::assertSame(302, $result->getStatusCode()); | ||
99 | static::assertSame(['/controller/?searchtags=def+abc'], $result->getHeader('location')); | ||
100 | } | ||
101 | |||
102 | public function testAddTagWithRefererAndEmptySearch(): void | ||
103 | { | ||
104 | $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=']; | ||
105 | |||
106 | $request = $this->createMock(Request::class); | ||
107 | $response = new Response(); | ||
108 | |||
109 | $tags = ['newTag' => 'abc']; | ||
110 | |||
111 | $result = $this->controller->addTag($request, $response, $tags); | ||
112 | |||
113 | static::assertInstanceOf(Response::class, $result); | ||
114 | static::assertSame(302, $result->getStatusCode()); | ||
115 | static::assertSame(['/controller/?searchtags=abc'], $result->getHeader('location')); | ||
116 | } | ||
117 | |||
118 | public function testAddTagWithoutNewTagWithReferer(): void | ||
119 | { | ||
120 | $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def']; | ||
121 | |||
122 | $request = $this->createMock(Request::class); | ||
123 | $response = new Response(); | ||
124 | |||
125 | $result = $this->controller->addTag($request, $response, []); | ||
126 | |||
127 | static::assertInstanceOf(Response::class, $result); | ||
128 | static::assertSame(302, $result->getStatusCode()); | ||
129 | static::assertSame(['/controller/?searchtags=def'], $result->getHeader('location')); | ||
130 | } | ||
131 | |||
132 | public function testAddTagWithoutNewTagWithoutReferer(): void | ||
133 | { | ||
134 | $request = $this->createMock(Request::class); | ||
135 | $response = new Response(); | ||
136 | |||
137 | $result = $this->controller->addTag($request, $response, []); | ||
138 | |||
139 | static::assertInstanceOf(Response::class, $result); | ||
140 | static::assertSame(302, $result->getStatusCode()); | ||
141 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
142 | } | ||
143 | |||
144 | public function testRemoveTagWithoutMatchingTag(): void | ||
145 | { | ||
146 | $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def']; | ||
147 | |||
148 | $request = $this->createMock(Request::class); | ||
149 | $response = new Response(); | ||
150 | |||
151 | $tags = ['tag' => 'abc']; | ||
152 | |||
153 | $result = $this->controller->removeTag($request, $response, $tags); | ||
154 | |||
155 | static::assertInstanceOf(Response::class, $result); | ||
156 | static::assertSame(302, $result->getStatusCode()); | ||
157 | static::assertSame(['/controller/?searchtags=def'], $result->getHeader('location')); | ||
158 | } | ||
159 | |||
160 | public function testRemoveTagWithoutTagsearch(): void | ||
161 | { | ||
162 | $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/']; | ||
163 | |||
164 | $request = $this->createMock(Request::class); | ||
165 | $response = new Response(); | ||
166 | |||
167 | $tags = ['tag' => 'abc']; | ||
168 | |||
169 | $result = $this->controller->removeTag($request, $response, $tags); | ||
170 | |||
171 | static::assertInstanceOf(Response::class, $result); | ||
172 | static::assertSame(302, $result->getStatusCode()); | ||
173 | static::assertSame(['/controller/'], $result->getHeader('location')); | ||
174 | } | ||
175 | |||
176 | public function testRemoveTagWithoutReferer(): void | ||
177 | { | ||
178 | $request = $this->createMock(Request::class); | ||
179 | $response = new Response(); | ||
180 | |||
181 | $tags = ['tag' => 'abc']; | ||
182 | |||
183 | $result = $this->controller->removeTag($request, $response, $tags); | ||
184 | |||
185 | static::assertInstanceOf(Response::class, $result); | ||
186 | static::assertSame(302, $result->getStatusCode()); | ||
187 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
188 | } | ||
189 | |||
190 | public function testRemoveTagWithoutTag(): void | ||
191 | { | ||
192 | $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtag=abc']; | ||
193 | |||
194 | $request = $this->createMock(Request::class); | ||
195 | $response = new Response(); | ||
196 | |||
197 | $result = $this->controller->removeTag($request, $response, []); | ||
198 | |||
199 | static::assertInstanceOf(Response::class, $result); | ||
200 | static::assertSame(302, $result->getStatusCode()); | ||
201 | static::assertSame(['/controller/?searchtag=abc'], $result->getHeader('location')); | ||
202 | } | ||
203 | |||
204 | public function testRemoveTagWithoutTagWithoutReferer(): void | ||
205 | { | ||
206 | $request = $this->createMock(Request::class); | ||
207 | $response = new Response(); | ||
208 | |||
209 | $result = $this->controller->removeTag($request, $response, []); | ||
210 | |||
211 | static::assertInstanceOf(Response::class, $result); | ||
212 | static::assertSame(302, $result->getStatusCode()); | ||
213 | static::assertSame(['/subfolder/'], $result->getHeader('location')); | ||
214 | } | ||
215 | } | ||
diff --git a/tests/http/HttpUtils/ClientIpIdTest.php b/tests/http/HttpUtils/ClientIpIdTest.php index 982e57e0..3a0fcf30 100644 --- a/tests/http/HttpUtils/ClientIpIdTest.php +++ b/tests/http/HttpUtils/ClientIpIdTest.php | |||
@@ -10,7 +10,7 @@ require_once 'application/http/HttpUtils.php'; | |||
10 | /** | 10 | /** |
11 | * Unitary tests for client_ip_id() | 11 | * Unitary tests for client_ip_id() |
12 | */ | 12 | */ |
13 | class ClientIpIdTest extends \PHPUnit\Framework\TestCase | 13 | class ClientIpIdTest extends \Shaarli\TestCase |
14 | { | 14 | { |
15 | /** | 15 | /** |
16 | * Get a remote client ID based on its IP | 16 | * Get a remote client ID based on its IP |
diff --git a/tests/http/HttpUtils/GetHttpUrlTest.php b/tests/http/HttpUtils/GetHttpUrlTest.php index 3dc5bc9b..a868ac02 100644 --- a/tests/http/HttpUtils/GetHttpUrlTest.php +++ b/tests/http/HttpUtils/GetHttpUrlTest.php | |||
@@ -10,7 +10,7 @@ require_once 'application/http/HttpUtils.php'; | |||
10 | /** | 10 | /** |
11 | * Unitary tests for get_http_response() | 11 | * Unitary tests for get_http_response() |
12 | */ | 12 | */ |
13 | class GetHttpUrlTest extends \PHPUnit\Framework\TestCase | 13 | class GetHttpUrlTest extends \Shaarli\TestCase |
14 | { | 14 | { |
15 | /** | 15 | /** |
16 | * Get an invalid local URL | 16 | * Get an invalid local URL |
diff --git a/tests/http/HttpUtils/GetIpAdressFromProxyTest.php b/tests/http/HttpUtils/GetIpAdressFromProxyTest.php index fe3a639e..60cdb992 100644 --- a/tests/http/HttpUtils/GetIpAdressFromProxyTest.php +++ b/tests/http/HttpUtils/GetIpAdressFromProxyTest.php | |||
@@ -7,7 +7,7 @@ require_once 'application/http/HttpUtils.php'; | |||
7 | /** | 7 | /** |
8 | * Unitary tests for getIpAddressFromProxy() | 8 | * Unitary tests for getIpAddressFromProxy() |
9 | */ | 9 | */ |
10 | class GetIpAdressFromProxyTest extends \PHPUnit\Framework\TestCase | 10 | class GetIpAdressFromProxyTest extends \Shaarli\TestCase |
11 | { | 11 | { |
12 | 12 | ||
13 | /** | 13 | /** |
diff --git a/tests/http/HttpUtils/IndexUrlTest.php b/tests/http/HttpUtils/IndexUrlTest.php index bcbe59cb..f283d119 100644 --- a/tests/http/HttpUtils/IndexUrlTest.php +++ b/tests/http/HttpUtils/IndexUrlTest.php | |||
@@ -5,12 +5,14 @@ | |||
5 | 5 | ||
6 | namespace Shaarli\Http; | 6 | namespace Shaarli\Http; |
7 | 7 | ||
8 | use Shaarli\TestCase; | ||
9 | |||
8 | require_once 'application/http/HttpUtils.php'; | 10 | require_once 'application/http/HttpUtils.php'; |
9 | 11 | ||
10 | /** | 12 | /** |
11 | * Unitary tests for index_url() | 13 | * Unitary tests for index_url() |
12 | */ | 14 | */ |
13 | class IndexUrlTest extends \PHPUnit\Framework\TestCase | 15 | class IndexUrlTest extends TestCase |
14 | { | 16 | { |
15 | /** | 17 | /** |
16 | * If on the main page, remove "index.php" from the URL resource | 18 | * If on the main page, remove "index.php" from the URL resource |
@@ -71,4 +73,68 @@ class IndexUrlTest extends \PHPUnit\Framework\TestCase | |||
71 | ) | 73 | ) |
72 | ); | 74 | ); |
73 | } | 75 | } |
76 | |||
77 | /** | ||
78 | * The route is stored in REQUEST_URI | ||
79 | */ | ||
80 | public function testPageUrlWithRoute() | ||
81 | { | ||
82 | $this->assertEquals( | ||
83 | 'http://host.tld/picture-wall', | ||
84 | page_url( | ||
85 | array( | ||
86 | 'HTTPS' => 'Off', | ||
87 | 'SERVER_NAME' => 'host.tld', | ||
88 | 'SERVER_PORT' => '80', | ||
89 | 'SCRIPT_NAME' => '/index.php', | ||
90 | 'REQUEST_URI' => '/picture-wall', | ||
91 | ) | ||
92 | ) | ||
93 | ); | ||
94 | |||
95 | $this->assertEquals( | ||
96 | 'http://host.tld/admin/picture-wall', | ||
97 | page_url( | ||
98 | array( | ||
99 | 'HTTPS' => 'Off', | ||
100 | 'SERVER_NAME' => 'host.tld', | ||
101 | 'SERVER_PORT' => '80', | ||
102 | 'SCRIPT_NAME' => '/admin/index.php', | ||
103 | 'REQUEST_URI' => '/admin/picture-wall', | ||
104 | ) | ||
105 | ) | ||
106 | ); | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * The route is stored in REQUEST_URI and subfolder | ||
111 | */ | ||
112 | public function testPageUrlWithRouteUnderSubfolder() | ||
113 | { | ||
114 | $this->assertEquals( | ||
115 | 'http://host.tld/subfolder/picture-wall', | ||
116 | page_url( | ||
117 | array( | ||
118 | 'HTTPS' => 'Off', | ||
119 | 'SERVER_NAME' => 'host.tld', | ||
120 | 'SERVER_PORT' => '80', | ||
121 | 'SCRIPT_NAME' => '/subfolder/index.php', | ||
122 | 'REQUEST_URI' => '/subfolder/picture-wall', | ||
123 | ) | ||
124 | ) | ||
125 | ); | ||
126 | |||
127 | $this->assertEquals( | ||
128 | 'http://host.tld/subfolder/admin/picture-wall', | ||
129 | page_url( | ||
130 | array( | ||
131 | 'HTTPS' => 'Off', | ||
132 | 'SERVER_NAME' => 'host.tld', | ||
133 | 'SERVER_PORT' => '80', | ||
134 | 'SCRIPT_NAME' => '/subfolder/admin/index.php', | ||
135 | 'REQUEST_URI' => '/subfolder/admin/picture-wall', | ||
136 | ) | ||
137 | ) | ||
138 | ); | ||
139 | } | ||
74 | } | 140 | } |
diff --git a/tests/http/HttpUtils/IndexUrlTestWithConstant.php b/tests/http/HttpUtils/IndexUrlTestWithConstant.php new file mode 100644 index 00000000..ecaea724 --- /dev/null +++ b/tests/http/HttpUtils/IndexUrlTestWithConstant.php | |||
@@ -0,0 +1,51 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Http; | ||
6 | |||
7 | use Shaarli\TestCase; | ||
8 | |||
9 | /** | ||
10 | * Test index_url with SHAARLI_ROOT_URL defined to override automatic retrieval. | ||
11 | * This should stay in its dedicated class to make sure to not alter other tests of the suite. | ||
12 | */ | ||
13 | class IndexUrlTestWithConstant extends TestCase | ||
14 | { | ||
15 | public static function setUpBeforeClass(): void | ||
16 | { | ||
17 | define('SHAARLI_ROOT_URL', 'http://other-host.tld/subfolder/'); | ||
18 | } | ||
19 | |||
20 | /** | ||
21 | * The route is stored in REQUEST_URI and subfolder | ||
22 | */ | ||
23 | public function testIndexUrlWithConstantDefined() | ||
24 | { | ||
25 | $this->assertEquals( | ||
26 | 'http://other-host.tld/subfolder/', | ||
27 | index_url( | ||
28 | array( | ||
29 | 'HTTPS' => 'Off', | ||
30 | 'SERVER_NAME' => 'host.tld', | ||
31 | 'SERVER_PORT' => '80', | ||
32 | 'SCRIPT_NAME' => '/index.php', | ||
33 | 'REQUEST_URI' => '/picture-wall', | ||
34 | ) | ||
35 | ) | ||
36 | ); | ||
37 | |||
38 | $this->assertEquals( | ||
39 | 'http://other-host.tld/subfolder/', | ||
40 | index_url( | ||
41 | array( | ||
42 | 'HTTPS' => 'Off', | ||
43 | 'SERVER_NAME' => 'host.tld', | ||
44 | 'SERVER_PORT' => '80', | ||
45 | 'SCRIPT_NAME' => '/admin/index.php', | ||
46 | 'REQUEST_URI' => '/admin/picture-wall', | ||
47 | ) | ||
48 | ) | ||
49 | ); | ||
50 | } | ||
51 | } | ||
diff --git a/tests/http/HttpUtils/IsHttpsTest.php b/tests/http/HttpUtils/IsHttpsTest.php index 348956c6..8b3fd93d 100644 --- a/tests/http/HttpUtils/IsHttpsTest.php +++ b/tests/http/HttpUtils/IsHttpsTest.php | |||
@@ -9,7 +9,7 @@ require_once 'application/http/HttpUtils.php'; | |||
9 | * | 9 | * |
10 | * Test class for is_https() function. | 10 | * Test class for is_https() function. |
11 | */ | 11 | */ |
12 | class IsHttpsTest extends \PHPUnit\Framework\TestCase | 12 | class IsHttpsTest extends \Shaarli\TestCase |
13 | { | 13 | { |
14 | 14 | ||
15 | /** | 15 | /** |
diff --git a/tests/http/HttpUtils/PageUrlTest.php b/tests/http/HttpUtils/PageUrlTest.php index f1991716..ebb3e617 100644 --- a/tests/http/HttpUtils/PageUrlTest.php +++ b/tests/http/HttpUtils/PageUrlTest.php | |||
@@ -10,7 +10,7 @@ require_once 'application/http/HttpUtils.php'; | |||
10 | /** | 10 | /** |
11 | * Unitary tests for page_url() | 11 | * Unitary tests for page_url() |
12 | */ | 12 | */ |
13 | class PageUrlTest extends \PHPUnit\Framework\TestCase | 13 | class PageUrlTest extends \Shaarli\TestCase |
14 | { | 14 | { |
15 | /** | 15 | /** |
16 | * If on the main page, remove "index.php" from the URL resource | 16 | * If on the main page, remove "index.php" from the URL resource |
diff --git a/tests/http/HttpUtils/ServerUrlTest.php b/tests/http/HttpUtils/ServerUrlTest.php index 9caf1049..339664e1 100644 --- a/tests/http/HttpUtils/ServerUrlTest.php +++ b/tests/http/HttpUtils/ServerUrlTest.php | |||
@@ -10,7 +10,7 @@ require_once 'application/http/HttpUtils.php'; | |||
10 | /** | 10 | /** |
11 | * Unitary tests for server_url() | 11 | * Unitary tests for server_url() |
12 | */ | 12 | */ |
13 | class ServerUrlTest extends \PHPUnit\Framework\TestCase | 13 | class ServerUrlTest extends \Shaarli\TestCase |
14 | { | 14 | { |
15 | /** | 15 | /** |
16 | * Detect if the server uses SSL | 16 | * Detect if the server uses SSL |
diff --git a/tests/http/UrlTest.php b/tests/http/UrlTest.php index ae92f73a..c6b39c29 100644 --- a/tests/http/UrlTest.php +++ b/tests/http/UrlTest.php | |||
@@ -8,7 +8,7 @@ namespace Shaarli\Http; | |||
8 | /** | 8 | /** |
9 | * Unitary tests for URL utilities | 9 | * Unitary tests for URL utilities |
10 | */ | 10 | */ |
11 | class UrlTest extends \PHPUnit\Framework\TestCase | 11 | class UrlTest extends \Shaarli\TestCase |
12 | { | 12 | { |
13 | // base URL for tests | 13 | // base URL for tests |
14 | protected static $baseUrl = 'http://domain.tld:3000'; | 14 | protected static $baseUrl = 'http://domain.tld:3000'; |
diff --git a/tests/http/UrlUtils/CleanupUrlTest.php b/tests/http/UrlUtils/CleanupUrlTest.php index 6c4d124b..45690ecf 100644 --- a/tests/http/UrlUtils/CleanupUrlTest.php +++ b/tests/http/UrlUtils/CleanupUrlTest.php | |||
@@ -7,7 +7,7 @@ namespace Shaarli\Http; | |||
7 | 7 | ||
8 | require_once 'application/http/UrlUtils.php'; | 8 | require_once 'application/http/UrlUtils.php'; |
9 | 9 | ||
10 | class CleanupUrlTest extends \PHPUnit\Framework\TestCase | 10 | class CleanupUrlTest extends \Shaarli\TestCase |
11 | { | 11 | { |
12 | /** | 12 | /** |
13 | * @var string reference URL | 13 | * @var string reference URL |
diff --git a/tests/http/UrlUtils/GetUrlSchemeTest.php b/tests/http/UrlUtils/GetUrlSchemeTest.php index 2b97f7be..18a9a5e5 100644 --- a/tests/http/UrlUtils/GetUrlSchemeTest.php +++ b/tests/http/UrlUtils/GetUrlSchemeTest.php | |||
@@ -7,7 +7,7 @@ namespace Shaarli\Http; | |||
7 | 7 | ||
8 | require_once 'application/http/UrlUtils.php'; | 8 | require_once 'application/http/UrlUtils.php'; |
9 | 9 | ||
10 | class GetUrlSchemeTest extends \PHPUnit\Framework\TestCase | 10 | class GetUrlSchemeTest extends \Shaarli\TestCase |
11 | { | 11 | { |
12 | /** | 12 | /** |
13 | * Get empty scheme string for empty UrlUtils | 13 | * Get empty scheme string for empty UrlUtils |
diff --git a/tests/http/UrlUtils/UnparseUrlTest.php b/tests/http/UrlUtils/UnparseUrlTest.php index 040d8c54..5e6246cc 100644 --- a/tests/http/UrlUtils/UnparseUrlTest.php +++ b/tests/http/UrlUtils/UnparseUrlTest.php | |||
@@ -10,7 +10,7 @@ require_once 'application/http/UrlUtils.php'; | |||
10 | /** | 10 | /** |
11 | * Unitary tests for unparse_url() | 11 | * Unitary tests for unparse_url() |
12 | */ | 12 | */ |
13 | class UnparseUrlTest extends \PHPUnit\Framework\TestCase | 13 | class UnparseUrlTest extends \Shaarli\TestCase |
14 | { | 14 | { |
15 | /** | 15 | /** |
16 | * Thanks for building nothing | 16 | * Thanks for building nothing |
diff --git a/tests/http/UrlUtils/WhitelistProtocolsTest.php b/tests/http/UrlUtils/WhitelistProtocolsTest.php index 69512dbd..b8a6baaa 100644 --- a/tests/http/UrlUtils/WhitelistProtocolsTest.php +++ b/tests/http/UrlUtils/WhitelistProtocolsTest.php | |||
@@ -9,7 +9,7 @@ require_once 'application/http/UrlUtils.php'; | |||
9 | * | 9 | * |
10 | * Test whitelist_protocols() function of UrlUtils. | 10 | * Test whitelist_protocols() function of UrlUtils. |
11 | */ | 11 | */ |
12 | class WhitelistProtocolsTest extends \PHPUnit\Framework\TestCase | 12 | class WhitelistProtocolsTest extends \Shaarli\TestCase |
13 | { | 13 | { |
14 | /** | 14 | /** |
15 | * Test whitelist_protocols() on a note (relative URL). | 15 | * Test whitelist_protocols() on a note (relative URL). |
diff --git a/tests/languages/fr/LanguagesFrTest.php b/tests/languages/fr/LanguagesFrTest.php index b8b7ca3a..d84feed1 100644 --- a/tests/languages/fr/LanguagesFrTest.php +++ b/tests/languages/fr/LanguagesFrTest.php | |||
@@ -12,7 +12,7 @@ use Shaarli\Config\ConfigManager; | |||
12 | * | 12 | * |
13 | * @package Shaarli | 13 | * @package Shaarli |
14 | */ | 14 | */ |
15 | class LanguagesFrTest extends \PHPUnit\Framework\TestCase | 15 | class LanguagesFrTest extends \Shaarli\TestCase |
16 | { | 16 | { |
17 | /** | 17 | /** |
18 | * @var string Config file path (without extension). | 18 | * @var string Config file path (without extension). |
@@ -27,7 +27,7 @@ class LanguagesFrTest extends \PHPUnit\Framework\TestCase | |||
27 | /** | 27 | /** |
28 | * Init: force French | 28 | * Init: force French |
29 | */ | 29 | */ |
30 | public function setUp() | 30 | protected function setUp(): void |
31 | { | 31 | { |
32 | $this->conf = new ConfigManager(self::$configFile); | 32 | $this->conf = new ConfigManager(self::$configFile); |
33 | $this->conf->set('translation.language', 'fr'); | 33 | $this->conf->set('translation.language', 'fr'); |
@@ -36,7 +36,7 @@ class LanguagesFrTest extends \PHPUnit\Framework\TestCase | |||
36 | /** | 36 | /** |
37 | * Reset the locale since gettext seems to mess with it, making it too long | 37 | * Reset the locale since gettext seems to mess with it, making it too long |
38 | */ | 38 | */ |
39 | public static function tearDownAfterClass() | 39 | public static function tearDownAfterClass(): void |
40 | { | 40 | { |
41 | if (! empty(getenv('UT_LOCALE'))) { | 41 | if (! empty(getenv('UT_LOCALE'))) { |
42 | setlocale(LC_ALL, getenv('UT_LOCALE')); | 42 | setlocale(LC_ALL, getenv('UT_LOCALE')); |
diff --git a/tests/legacy/LegacyControllerTest.php b/tests/legacy/LegacyControllerTest.php new file mode 100644 index 00000000..1a2549a3 --- /dev/null +++ b/tests/legacy/LegacyControllerTest.php | |||
@@ -0,0 +1,101 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Legacy; | ||
6 | |||
7 | use Shaarli\Front\Controller\Visitor\FrontControllerMockHelper; | ||
8 | use Shaarli\TestCase; | ||
9 | use Slim\Http\Request; | ||
10 | use Slim\Http\Response; | ||
11 | |||
12 | class LegacyControllerTest extends TestCase | ||
13 | { | ||
14 | use FrontControllerMockHelper; | ||
15 | |||
16 | /** @var LegacyController */ | ||
17 | protected $controller; | ||
18 | |||
19 | public function setUp(): void | ||
20 | { | ||
21 | $this->createContainer(); | ||
22 | |||
23 | $this->controller = new LegacyController($this->container); | ||
24 | } | ||
25 | |||
26 | /** | ||
27 | * @dataProvider getProcessProvider | ||
28 | */ | ||
29 | public function testProcess(string $legacyRoute, array $queryParameters, string $slimRoute, bool $isLoggedIn): void | ||
30 | { | ||
31 | $request = $this->createMock(Request::class); | ||
32 | $request->method('getQueryParams')->willReturn($queryParameters); | ||
33 | $request | ||
34 | ->method('getParam') | ||
35 | ->willReturnCallback(function (string $key) use ($queryParameters): ?string { | ||
36 | return $queryParameters[$key] ?? null; | ||
37 | }) | ||
38 | ; | ||
39 | $response = new Response(); | ||
40 | |||
41 | $this->container->loginManager->method('isLoggedIn')->willReturn($isLoggedIn); | ||
42 | |||
43 | $result = $this->controller->process($request, $response, $legacyRoute); | ||
44 | |||
45 | static::assertSame('/subfolder' . $slimRoute, $result->getHeader('location')[0]); | ||
46 | } | ||
47 | |||
48 | public function testProcessNotFound(): void | ||
49 | { | ||
50 | $request = $this->createMock(Request::class); | ||
51 | $response = new Response(); | ||
52 | |||
53 | $this->expectException(UnknowLegacyRouteException::class); | ||
54 | |||
55 | $this->controller->process($request, $response, 'nope'); | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * @return array[] Parameters: | ||
60 | * - string legacyRoute | ||
61 | * - array queryParameters | ||
62 | * - string slimRoute | ||
63 | * - bool isLoggedIn | ||
64 | */ | ||
65 | public function getProcessProvider(): array | ||
66 | { | ||
67 | return [ | ||
68 | ['post', [], '/admin/shaare', true], | ||
69 | ['post', [], '/login?returnurl=/subfolder/admin/shaare', false], | ||
70 | ['post', ['title' => 'test'], '/admin/shaare?title=test', true], | ||
71 | ['post', ['title' => 'test'], '/login?returnurl=/subfolder/admin/shaare?title=test', false], | ||
72 | ['addlink', [], '/admin/add-shaare', true], | ||
73 | ['addlink', [], '/login?returnurl=/subfolder/admin/add-shaare', false], | ||
74 | ['login', [], '/login', true], | ||
75 | ['login', [], '/login', false], | ||
76 | ['logout', [], '/admin/logout', true], | ||
77 | ['logout', [], '/admin/logout', false], | ||
78 | ['picwall', [], '/picture-wall', false], | ||
79 | ['picwall', [], '/picture-wall', true], | ||
80 | ['tagcloud', [], '/tags/cloud', false], | ||
81 | ['tagcloud', [], '/tags/cloud', true], | ||
82 | ['taglist', [], '/tags/list', false], | ||
83 | ['taglist', [], '/tags/list', true], | ||
84 | ['daily', [], '/daily', false], | ||
85 | ['daily', [], '/daily', true], | ||
86 | ['daily', ['day' => '123456789', 'discard' => '1'], '/daily?day=123456789', false], | ||
87 | ['rss', [], '/feed/rss', false], | ||
88 | ['rss', [], '/feed/rss', true], | ||
89 | ['rss', ['search' => 'filter123', 'other' => 'param'], '/feed/rss?search=filter123&other=param', false], | ||
90 | ['atom', [], '/feed/atom', false], | ||
91 | ['atom', [], '/feed/atom', true], | ||
92 | ['atom', ['search' => 'filter123', 'other' => 'param'], '/feed/atom?search=filter123&other=param', false], | ||
93 | ['opensearch', [], '/open-search', false], | ||
94 | ['opensearch', [], '/open-search', true], | ||
95 | ['dailyrss', [], '/daily-rss', false], | ||
96 | ['dailyrss', [], '/daily-rss', true], | ||
97 | ['configure', [], '/login?returnurl=/subfolder/admin/configure', false], | ||
98 | ['configure', [], '/admin/configure', true], | ||
99 | ]; | ||
100 | } | ||
101 | } | ||
diff --git a/tests/legacy/LegacyDummyUpdater.php b/tests/legacy/LegacyDummyUpdater.php new file mode 100644 index 00000000..10e0a5b7 --- /dev/null +++ b/tests/legacy/LegacyDummyUpdater.php | |||
@@ -0,0 +1,74 @@ | |||
1 | <?php | ||
2 | namespace Shaarli\Updater; | ||
3 | |||
4 | use Exception; | ||
5 | use ReflectionClass; | ||
6 | use ReflectionMethod; | ||
7 | use Shaarli\Config\ConfigManager; | ||
8 | use Shaarli\Legacy\LegacyLinkDB; | ||
9 | use Shaarli\Legacy\LegacyUpdater; | ||
10 | |||
11 | /** | ||
12 | * Class LegacyDummyUpdater. | ||
13 | * Extends updater to add update method designed for unit tests. | ||
14 | */ | ||
15 | class LegacyDummyUpdater extends LegacyUpdater | ||
16 | { | ||
17 | /** | ||
18 | * Object constructor. | ||
19 | * | ||
20 | * @param array $doneUpdates Updates which are already done. | ||
21 | * @param LegacyLinkDB $linkDB LinkDB instance. | ||
22 | * @param ConfigManager $conf Configuration Manager instance. | ||
23 | * @param boolean $isLoggedIn True if the user is logged in. | ||
24 | */ | ||
25 | public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) | ||
26 | { | ||
27 | parent::__construct($doneUpdates, $linkDB, $conf, $isLoggedIn); | ||
28 | |||
29 | // Retrieve all update methods. | ||
30 | // For unit test, only retrieve final methods, | ||
31 | $class = new ReflectionClass($this); | ||
32 | $this->methods = $class->getMethods(ReflectionMethod::IS_FINAL); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Update method 1. | ||
37 | * | ||
38 | * @return bool true. | ||
39 | */ | ||
40 | final private function updateMethodDummy1() | ||
41 | { | ||
42 | return true; | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Update method 2. | ||
47 | * | ||
48 | * @return bool true. | ||
49 | */ | ||
50 | final private function updateMethodDummy2() | ||
51 | { | ||
52 | return true; | ||
53 | } | ||
54 | |||
55 | /** | ||
56 | * Update method 3. | ||
57 | * | ||
58 | * @return bool true. | ||
59 | */ | ||
60 | final private function updateMethodDummy3() | ||
61 | { | ||
62 | return true; | ||
63 | } | ||
64 | |||
65 | /** | ||
66 | * Update method 4, raise an exception. | ||
67 | * | ||
68 | * @throws Exception error. | ||
69 | */ | ||
70 | final private function updateMethodException() | ||
71 | { | ||
72 | throw new Exception('whatever'); | ||
73 | } | ||
74 | } | ||
diff --git a/tests/bookmark/LinkDBTest.php b/tests/legacy/LegacyLinkDBTest.php index 2990a6b5..df2cad62 100644 --- a/tests/bookmark/LinkDBTest.php +++ b/tests/legacy/LegacyLinkDBTest.php | |||
@@ -3,22 +3,22 @@ | |||
3 | * Link datastore tests | 3 | * Link datastore tests |
4 | */ | 4 | */ |
5 | 5 | ||
6 | namespace Shaarli\Bookmark; | 6 | namespace Shaarli\Legacy; |
7 | 7 | ||
8 | use DateTime; | 8 | use DateTime; |
9 | use ReferenceLinkDB; | 9 | use ReferenceLinkDB; |
10 | use ReflectionClass; | 10 | use ReflectionClass; |
11 | use Shaarli; | 11 | use Shaarli; |
12 | use Shaarli\Bookmark\Bookmark; | ||
12 | 13 | ||
13 | require_once 'application/feed/Cache.php'; | ||
14 | require_once 'application/Utils.php'; | 14 | require_once 'application/Utils.php'; |
15 | require_once 'tests/utils/ReferenceLinkDB.php'; | 15 | require_once 'tests/utils/ReferenceLinkDB.php'; |
16 | 16 | ||
17 | 17 | ||
18 | /** | 18 | /** |
19 | * Unitary tests for LinkDB | 19 | * Unitary tests for LegacyLinkDBTest |
20 | */ | 20 | */ |
21 | class LinkDBTest extends \PHPUnit\Framework\TestCase | 21 | class LegacyLinkDBTest extends \Shaarli\TestCase |
22 | { | 22 | { |
23 | // datastore to test write operations | 23 | // datastore to test write operations |
24 | protected static $testDatastore = 'sandbox/datastore.php'; | 24 | protected static $testDatastore = 'sandbox/datastore.php'; |
@@ -29,19 +29,19 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
29 | protected static $refDB = null; | 29 | protected static $refDB = null; |
30 | 30 | ||
31 | /** | 31 | /** |
32 | * @var LinkDB public LinkDB instance. | 32 | * @var LegacyLinkDB public LinkDB instance. |
33 | */ | 33 | */ |
34 | protected static $publicLinkDB = null; | 34 | protected static $publicLinkDB = null; |
35 | 35 | ||
36 | /** | 36 | /** |
37 | * @var LinkDB private LinkDB instance. | 37 | * @var LegacyLinkDB private LinkDB instance. |
38 | */ | 38 | */ |
39 | protected static $privateLinkDB = null; | 39 | protected static $privateLinkDB = null; |
40 | 40 | ||
41 | /** | 41 | /** |
42 | * Instantiates public and private LinkDBs with test data | 42 | * Instantiates public and private LinkDBs with test data |
43 | * | 43 | * |
44 | * The reference datastore contains public and private links that | 44 | * The reference datastore contains public and private bookmarks that |
45 | * will be used to test LinkDB's methods: | 45 | * will be used to test LinkDB's methods: |
46 | * - access filtering (public/private), | 46 | * - access filtering (public/private), |
47 | * - link searches: | 47 | * - link searches: |
@@ -49,24 +49,19 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
49 | * - by tag, | 49 | * - by tag, |
50 | * - by text, | 50 | * - by text, |
51 | * - etc. | 51 | * - etc. |
52 | */ | 52 | * |
53 | public static function setUpBeforeClass() | ||
54 | { | ||
55 | self::$refDB = new ReferenceLinkDB(); | ||
56 | self::$refDB->write(self::$testDatastore); | ||
57 | |||
58 | self::$publicLinkDB = new LinkDB(self::$testDatastore, false, false); | ||
59 | self::$privateLinkDB = new LinkDB(self::$testDatastore, true, false); | ||
60 | } | ||
61 | |||
62 | /** | ||
63 | * Resets test data for each test | 53 | * Resets test data for each test |
64 | */ | 54 | */ |
65 | protected function setUp() | 55 | protected function setUp(): void |
66 | { | 56 | { |
67 | if (file_exists(self::$testDatastore)) { | 57 | if (file_exists(self::$testDatastore)) { |
68 | unlink(self::$testDatastore); | 58 | unlink(self::$testDatastore); |
69 | } | 59 | } |
60 | |||
61 | self::$refDB = new ReferenceLinkDB(true); | ||
62 | self::$refDB->write(self::$testDatastore); | ||
63 | self::$publicLinkDB = new LegacyLinkDB(self::$testDatastore, false, false); | ||
64 | self::$privateLinkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
70 | } | 65 | } |
71 | 66 | ||
72 | /** | 67 | /** |
@@ -78,7 +73,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
78 | */ | 73 | */ |
79 | protected static function getMethod($name) | 74 | protected static function getMethod($name) |
80 | { | 75 | { |
81 | $class = new ReflectionClass('Shaarli\Bookmark\LinkDB'); | 76 | $class = new ReflectionClass('Shaarli\Legacy\LegacyLinkDB'); |
82 | $method = $class->getMethod($name); | 77 | $method = $class->getMethod($name); |
83 | $method->setAccessible(true); | 78 | $method->setAccessible(true); |
84 | return $method; | 79 | return $method; |
@@ -89,7 +84,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
89 | */ | 84 | */ |
90 | public function testConstructLoggedIn() | 85 | public function testConstructLoggedIn() |
91 | { | 86 | { |
92 | new LinkDB(self::$testDatastore, true, false); | 87 | new LegacyLinkDB(self::$testDatastore, true, false); |
93 | $this->assertFileExists(self::$testDatastore); | 88 | $this->assertFileExists(self::$testDatastore); |
94 | } | 89 | } |
95 | 90 | ||
@@ -98,19 +93,19 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
98 | */ | 93 | */ |
99 | public function testConstructLoggedOut() | 94 | public function testConstructLoggedOut() |
100 | { | 95 | { |
101 | new LinkDB(self::$testDatastore, false, false); | 96 | new LegacyLinkDB(self::$testDatastore, false, false); |
102 | $this->assertFileExists(self::$testDatastore); | 97 | $this->assertFileExists(self::$testDatastore); |
103 | } | 98 | } |
104 | 99 | ||
105 | /** | 100 | /** |
106 | * Attempt to instantiate a LinkDB whereas the datastore is not writable | 101 | * Attempt to instantiate a LinkDB whereas the datastore is not writable |
107 | * | ||
108 | * @expectedException Shaarli\Exceptions\IOException | ||
109 | * @expectedExceptionMessageRegExp /Error accessing "null"/ | ||
110 | */ | 102 | */ |
111 | public function testConstructDatastoreNotWriteable() | 103 | public function testConstructDatastoreNotWriteable() |
112 | { | 104 | { |
113 | new LinkDB('null/store.db', false, false); | 105 | $this->expectException(\Shaarli\Exceptions\IOException::class); |
106 | $this->expectExceptionMessageRegExp('/Error accessing "null"/'); | ||
107 | |||
108 | new LegacyLinkDB('null/store.db', false, false); | ||
114 | } | 109 | } |
115 | 110 | ||
116 | /** | 111 | /** |
@@ -118,7 +113,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
118 | */ | 113 | */ |
119 | public function testCheckDBNew() | 114 | public function testCheckDBNew() |
120 | { | 115 | { |
121 | $linkDB = new LinkDB(self::$testDatastore, false, false); | 116 | $linkDB = new LegacyLinkDB(self::$testDatastore, false, false); |
122 | unlink(self::$testDatastore); | 117 | unlink(self::$testDatastore); |
123 | $this->assertFileNotExists(self::$testDatastore); | 118 | $this->assertFileNotExists(self::$testDatastore); |
124 | 119 | ||
@@ -135,7 +130,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
135 | */ | 130 | */ |
136 | public function testCheckDBLoad() | 131 | public function testCheckDBLoad() |
137 | { | 132 | { |
138 | $linkDB = new LinkDB(self::$testDatastore, false, false); | 133 | $linkDB = new LegacyLinkDB(self::$testDatastore, false, false); |
139 | $datastoreSize = filesize(self::$testDatastore); | 134 | $datastoreSize = filesize(self::$testDatastore); |
140 | $this->assertGreaterThan(0, $datastoreSize); | 135 | $this->assertGreaterThan(0, $datastoreSize); |
141 | 136 | ||
@@ -155,13 +150,13 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
155 | public function testReadEmptyDB() | 150 | public function testReadEmptyDB() |
156 | { | 151 | { |
157 | file_put_contents(self::$testDatastore, '<?php /* S7QysKquBQA= */ ?>'); | 152 | file_put_contents(self::$testDatastore, '<?php /* S7QysKquBQA= */ ?>'); |
158 | $emptyDB = new LinkDB(self::$testDatastore, false, false); | 153 | $emptyDB = new LegacyLinkDB(self::$testDatastore, false, false); |
159 | $this->assertEquals(0, sizeof($emptyDB)); | 154 | $this->assertEquals(0, sizeof($emptyDB)); |
160 | $this->assertEquals(0, count($emptyDB)); | 155 | $this->assertEquals(0, count($emptyDB)); |
161 | } | 156 | } |
162 | 157 | ||
163 | /** | 158 | /** |
164 | * Load public links from the DB | 159 | * Load public bookmarks from the DB |
165 | */ | 160 | */ |
166 | public function testReadPublicDB() | 161 | public function testReadPublicDB() |
167 | { | 162 | { |
@@ -172,7 +167,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
172 | } | 167 | } |
173 | 168 | ||
174 | /** | 169 | /** |
175 | * Load public and private links from the DB | 170 | * Load public and private bookmarks from the DB |
176 | */ | 171 | */ |
177 | public function testReadPrivateDB() | 172 | public function testReadPrivateDB() |
178 | { | 173 | { |
@@ -183,31 +178,31 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
183 | } | 178 | } |
184 | 179 | ||
185 | /** | 180 | /** |
186 | * Save the links to the DB | 181 | * Save the bookmarks to the DB |
187 | */ | 182 | */ |
188 | public function testSave() | 183 | public function testSave() |
189 | { | 184 | { |
190 | $testDB = new LinkDB(self::$testDatastore, true, false); | 185 | $testDB = new LegacyLinkDB(self::$testDatastore, true, false); |
191 | $dbSize = sizeof($testDB); | 186 | $dbSize = sizeof($testDB); |
192 | 187 | ||
193 | $link = array( | 188 | $link = array( |
194 | 'id' => 42, | 189 | 'id' => 43, |
195 | 'title' => 'an additional link', | 190 | 'title' => 'an additional link', |
196 | 'url' => 'http://dum.my', | 191 | 'url' => 'http://dum.my', |
197 | 'description' => 'One more', | 192 | 'description' => 'One more', |
198 | 'private' => 0, | 193 | 'private' => 0, |
199 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'), | 194 | 'created' => DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150518_190000'), |
200 | 'tags' => 'unit test' | 195 | 'tags' => 'unit test' |
201 | ); | 196 | ); |
202 | $testDB[$link['id']] = $link; | 197 | $testDB[$link['id']] = $link; |
203 | $testDB->save('tests'); | 198 | $testDB->save('tests'); |
204 | 199 | ||
205 | $testDB = new LinkDB(self::$testDatastore, true, false); | 200 | $testDB = new LegacyLinkDB(self::$testDatastore, true, false); |
206 | $this->assertEquals($dbSize + 1, sizeof($testDB)); | 201 | $this->assertEquals($dbSize + 1, sizeof($testDB)); |
207 | } | 202 | } |
208 | 203 | ||
209 | /** | 204 | /** |
210 | * Count existing links | 205 | * Count existing bookmarks |
211 | */ | 206 | */ |
212 | public function testCount() | 207 | public function testCount() |
213 | { | 208 | { |
@@ -222,11 +217,11 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
222 | } | 217 | } |
223 | 218 | ||
224 | /** | 219 | /** |
225 | * Count existing links - public links hidden | 220 | * Count existing bookmarks - public bookmarks hidden |
226 | */ | 221 | */ |
227 | public function testCountHiddenPublic() | 222 | public function testCountHiddenPublic() |
228 | { | 223 | { |
229 | $linkDB = new LinkDB(self::$testDatastore, false, true); | 224 | $linkDB = new LegacyLinkDB(self::$testDatastore, false, true); |
230 | 225 | ||
231 | $this->assertEquals( | 226 | $this->assertEquals( |
232 | 0, | 227 | 0, |
@@ -239,7 +234,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
239 | } | 234 | } |
240 | 235 | ||
241 | /** | 236 | /** |
242 | * List the days for which links have been posted | 237 | * List the days for which bookmarks have been posted |
243 | */ | 238 | */ |
244 | public function testDays() | 239 | public function testDays() |
245 | { | 240 | { |
@@ -262,7 +257,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
262 | $link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/'); | 257 | $link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/'); |
263 | 258 | ||
264 | $this->assertNotEquals(false, $link); | 259 | $this->assertNotEquals(false, $link); |
265 | $this->assertContains( | 260 | $this->assertContainsPolyfill( |
266 | 'A free software media publishing platform', | 261 | 'A free software media publishing platform', |
267 | $link['description'] | 262 | $link['description'] |
268 | ); | 263 | ); |
@@ -425,22 +420,22 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
425 | 420 | ||
426 | /** | 421 | /** |
427 | * Test filterHash() with an invalid smallhash. | 422 | * Test filterHash() with an invalid smallhash. |
428 | * | ||
429 | * @expectedException \Shaarli\Bookmark\Exception\LinkNotFoundException | ||
430 | */ | 423 | */ |
431 | public function testFilterHashInValid1() | 424 | public function testFilterHashInValid1() |
432 | { | 425 | { |
426 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); | ||
427 | |||
433 | $request = 'blabla'; | 428 | $request = 'blabla'; |
434 | self::$publicLinkDB->filterHash($request); | 429 | self::$publicLinkDB->filterHash($request); |
435 | } | 430 | } |
436 | 431 | ||
437 | /** | 432 | /** |
438 | * Test filterHash() with an empty smallhash. | 433 | * Test filterHash() with an empty smallhash. |
439 | * | ||
440 | * @expectedException \Shaarli\Bookmark\Exception\LinkNotFoundException | ||
441 | */ | 434 | */ |
442 | public function testFilterHashInValid() | 435 | public function testFilterHashInValid() |
443 | { | 436 | { |
437 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); | ||
438 | |||
444 | self::$publicLinkDB->filterHash(''); | 439 | self::$publicLinkDB->filterHash(''); |
445 | } | 440 | } |
446 | 441 | ||
@@ -466,18 +461,18 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
466 | } | 461 | } |
467 | 462 | ||
468 | /** | 463 | /** |
469 | * Test rename tag with a valid value present in multiple links | 464 | * Test rename tag with a valid value present in multiple bookmarks |
470 | */ | 465 | */ |
471 | public function testRenameTagMultiple() | 466 | public function testRenameTagMultiple() |
472 | { | 467 | { |
473 | self::$refDB->write(self::$testDatastore); | 468 | self::$refDB->write(self::$testDatastore); |
474 | $linkDB = new LinkDB(self::$testDatastore, true, false); | 469 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); |
475 | 470 | ||
476 | $res = $linkDB->renameTag('cartoon', 'Taz'); | 471 | $res = $linkDB->renameTag('cartoon', 'Taz'); |
477 | $this->assertEquals(3, count($res)); | 472 | $this->assertEquals(3, count($res)); |
478 | $this->assertContains(' Taz ', $linkDB[4]['tags']); | 473 | $this->assertContainsPolyfill(' Taz ', $linkDB[4]['tags']); |
479 | $this->assertContains(' Taz ', $linkDB[1]['tags']); | 474 | $this->assertContainsPolyfill(' Taz ', $linkDB[1]['tags']); |
480 | $this->assertContains(' Taz ', $linkDB[0]['tags']); | 475 | $this->assertContainsPolyfill(' Taz ', $linkDB[0]['tags']); |
481 | } | 476 | } |
482 | 477 | ||
483 | /** | 478 | /** |
@@ -486,7 +481,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
486 | public function testRenameTagCaseSensitive() | 481 | public function testRenameTagCaseSensitive() |
487 | { | 482 | { |
488 | self::$refDB->write(self::$testDatastore); | 483 | self::$refDB->write(self::$testDatastore); |
489 | $linkDB = new LinkDB(self::$testDatastore, true, false); | 484 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); |
490 | 485 | ||
491 | $res = $linkDB->renameTag('sTuff', 'Taz'); | 486 | $res = $linkDB->renameTag('sTuff', 'Taz'); |
492 | $this->assertEquals(1, count($res)); | 487 | $this->assertEquals(1, count($res)); |
@@ -498,7 +493,7 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
498 | */ | 493 | */ |
499 | public function testRenameTagInvalid() | 494 | public function testRenameTagInvalid() |
500 | { | 495 | { |
501 | $linkDB = new LinkDB(self::$testDatastore, false, false); | 496 | $linkDB = new LegacyLinkDB(self::$testDatastore, false, false); |
502 | 497 | ||
503 | $this->assertFalse($linkDB->renameTag('', 'test')); | 498 | $this->assertFalse($linkDB->renameTag('', 'test')); |
504 | $this->assertFalse($linkDB->renameTag('', '')); | 499 | $this->assertFalse($linkDB->renameTag('', '')); |
@@ -513,11 +508,11 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
513 | public function testDeleteTag() | 508 | public function testDeleteTag() |
514 | { | 509 | { |
515 | self::$refDB->write(self::$testDatastore); | 510 | self::$refDB->write(self::$testDatastore); |
516 | $linkDB = new LinkDB(self::$testDatastore, true, false); | 511 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); |
517 | 512 | ||
518 | $res = $linkDB->renameTag('cartoon', null); | 513 | $res = $linkDB->renameTag('cartoon', null); |
519 | $this->assertEquals(3, count($res)); | 514 | $this->assertEquals(3, count($res)); |
520 | $this->assertNotContains('cartoon', $linkDB[4]['tags']); | 515 | $this->assertNotContainsPolyfill('cartoon', $linkDB[4]['tags']); |
521 | } | 516 | } |
522 | 517 | ||
523 | /** | 518 | /** |
@@ -619,4 +614,42 @@ class LinkDBTest extends \PHPUnit\Framework\TestCase | |||
619 | 614 | ||
620 | $this->assertEquals($expected, $tags, var_export($tags, true)); | 615 | $this->assertEquals($expected, $tags, var_export($tags, true)); |
621 | } | 616 | } |
617 | |||
618 | /** | ||
619 | * Make sure that bookmarks with the same timestamp have a consistent order: | ||
620 | * if their creation date is equal, bookmarks are sorted by ID DESC. | ||
621 | */ | ||
622 | public function testConsistentOrder() | ||
623 | { | ||
624 | $nextId = 43; | ||
625 | $creation = DateTime::createFromFormat('Ymd_His', '20190807_130444'); | ||
626 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
627 | for ($i = 0; $i < 4; ++$i) { | ||
628 | $linkDB[$nextId + $i] = [ | ||
629 | 'id' => $nextId + $i, | ||
630 | 'url' => 'http://'. $i, | ||
631 | 'created' => $creation, | ||
632 | 'title' => true, | ||
633 | 'description' => true, | ||
634 | 'tags' => true, | ||
635 | ]; | ||
636 | } | ||
637 | |||
638 | // Check 4 new links 4 times | ||
639 | for ($i = 0; $i < 4; ++$i) { | ||
640 | $linkDB->save('tests'); | ||
641 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
642 | $count = 3; | ||
643 | foreach ($linkDB as $link) { | ||
644 | if ($link['sticky'] === true) { | ||
645 | continue; | ||
646 | } | ||
647 | $this->assertEquals($nextId + $count, $link['id']); | ||
648 | $this->assertEquals('http://'. $count, $link['url']); | ||
649 | if (--$count < 0) { | ||
650 | break; | ||
651 | } | ||
652 | } | ||
653 | } | ||
654 | } | ||
622 | } | 655 | } |
diff --git a/tests/bookmark/LinkFilterTest.php b/tests/legacy/LegacyLinkFilterTest.php index 808f8122..45d7754d 100644 --- a/tests/bookmark/LinkFilterTest.php +++ b/tests/legacy/LegacyLinkFilterTest.php | |||
@@ -4,18 +4,20 @@ namespace Shaarli\Bookmark; | |||
4 | 4 | ||
5 | use Exception; | 5 | use Exception; |
6 | use ReferenceLinkDB; | 6 | use ReferenceLinkDB; |
7 | use Shaarli\Legacy\LegacyLinkDB; | ||
8 | use Shaarli\Legacy\LegacyLinkFilter; | ||
7 | 9 | ||
8 | /** | 10 | /** |
9 | * Class LinkFilterTest. | 11 | * Class LegacyLinkFilterTest. |
10 | */ | 12 | */ |
11 | class LinkFilterTest extends \PHPUnit\Framework\TestCase | 13 | class LegacyLinkFilterTest extends \Shaarli\TestCase |
12 | { | 14 | { |
13 | /** | 15 | /** |
14 | * @var string Test datastore path. | 16 | * @var string Test datastore path. |
15 | */ | 17 | */ |
16 | protected static $testDatastore = 'sandbox/datastore.php'; | 18 | protected static $testDatastore = 'sandbox/datastore.php'; |
17 | /** | 19 | /** |
18 | * @var LinkFilter instance. | 20 | * @var BookmarkFilter instance. |
19 | */ | 21 | */ |
20 | protected static $linkFilter; | 22 | protected static $linkFilter; |
21 | 23 | ||
@@ -25,19 +27,19 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
25 | protected static $refDB; | 27 | protected static $refDB; |
26 | 28 | ||
27 | /** | 29 | /** |
28 | * @var LinkDB instance | 30 | * @var LegacyLinkDB instance |
29 | */ | 31 | */ |
30 | protected static $linkDB; | 32 | protected static $linkDB; |
31 | 33 | ||
32 | /** | 34 | /** |
33 | * Instantiate linkFilter with ReferenceLinkDB data. | 35 | * Instantiate linkFilter with ReferenceLinkDB data. |
34 | */ | 36 | */ |
35 | public static function setUpBeforeClass() | 37 | public static function setUpBeforeClass(): void |
36 | { | 38 | { |
37 | self::$refDB = new ReferenceLinkDB(); | 39 | self::$refDB = new ReferenceLinkDB(true); |
38 | self::$refDB->write(self::$testDatastore); | 40 | self::$refDB->write(self::$testDatastore); |
39 | self::$linkDB = new LinkDB(self::$testDatastore, true, false); | 41 | self::$linkDB = new LegacyLinkDB(self::$testDatastore, true, false); |
40 | self::$linkFilter = new LinkFilter(self::$linkDB); | 42 | self::$linkFilter = new LegacyLinkFilter(self::$linkDB); |
41 | } | 43 | } |
42 | 44 | ||
43 | /** | 45 | /** |
@@ -74,14 +76,14 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
74 | 76 | ||
75 | $this->assertEquals( | 77 | $this->assertEquals( |
76 | ReferenceLinkDB::$NB_LINKS_TOTAL, | 78 | ReferenceLinkDB::$NB_LINKS_TOTAL, |
77 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '')) | 79 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, '')) |
78 | ); | 80 | ); |
79 | 81 | ||
80 | $this->assertEquals( | 82 | $this->assertEquals( |
81 | self::$refDB->countUntaggedLinks(), | 83 | self::$refDB->countUntaggedLinks(), |
82 | count( | 84 | count( |
83 | self::$linkFilter->filter( | 85 | self::$linkFilter->filter( |
84 | LinkFilter::$FILTER_TAG, | 86 | LegacyLinkFilter::$FILTER_TAG, |
85 | /*$request=*/ | 87 | /*$request=*/ |
86 | '', | 88 | '', |
87 | /*$casesensitive=*/ | 89 | /*$casesensitive=*/ |
@@ -96,89 +98,89 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
96 | 98 | ||
97 | $this->assertEquals( | 99 | $this->assertEquals( |
98 | ReferenceLinkDB::$NB_LINKS_TOTAL, | 100 | ReferenceLinkDB::$NB_LINKS_TOTAL, |
99 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '')) | 101 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, '')) |
100 | ); | 102 | ); |
101 | } | 103 | } |
102 | 104 | ||
103 | /** | 105 | /** |
104 | * Filter links using a tag | 106 | * Filter bookmarks using a tag |
105 | */ | 107 | */ |
106 | public function testFilterOneTag() | 108 | public function testFilterOneTag() |
107 | { | 109 | { |
108 | $this->assertEquals( | 110 | $this->assertEquals( |
109 | 4, | 111 | 4, |
110 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false)) | 112 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'web', false)) |
111 | ); | 113 | ); |
112 | 114 | ||
113 | $this->assertEquals( | 115 | $this->assertEquals( |
114 | 4, | 116 | 4, |
115 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false, 'all')) | 117 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'web', false, 'all')) |
116 | ); | 118 | ); |
117 | 119 | ||
118 | $this->assertEquals( | 120 | $this->assertEquals( |
119 | 4, | 121 | 4, |
120 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false, 'default-blabla')) | 122 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'web', false, 'default-blabla')) |
121 | ); | 123 | ); |
122 | 124 | ||
123 | // Private only. | 125 | // Private only. |
124 | $this->assertEquals( | 126 | $this->assertEquals( |
125 | 1, | 127 | 1, |
126 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false, 'private')) | 128 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'web', false, 'private')) |
127 | ); | 129 | ); |
128 | 130 | ||
129 | // Public only. | 131 | // Public only. |
130 | $this->assertEquals( | 132 | $this->assertEquals( |
131 | 3, | 133 | 3, |
132 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false, 'public')) | 134 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'web', false, 'public')) |
133 | ); | 135 | ); |
134 | } | 136 | } |
135 | 137 | ||
136 | /** | 138 | /** |
137 | * Filter links using a tag - case-sensitive | 139 | * Filter bookmarks using a tag - case-sensitive |
138 | */ | 140 | */ |
139 | public function testFilterCaseSensitiveTag() | 141 | public function testFilterCaseSensitiveTag() |
140 | { | 142 | { |
141 | $this->assertEquals( | 143 | $this->assertEquals( |
142 | 0, | 144 | 0, |
143 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'mercurial', true)) | 145 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'mercurial', true)) |
144 | ); | 146 | ); |
145 | 147 | ||
146 | $this->assertEquals( | 148 | $this->assertEquals( |
147 | 1, | 149 | 1, |
148 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'Mercurial', true)) | 150 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'Mercurial', true)) |
149 | ); | 151 | ); |
150 | } | 152 | } |
151 | 153 | ||
152 | /** | 154 | /** |
153 | * Filter links using a tag combination | 155 | * Filter bookmarks using a tag combination |
154 | */ | 156 | */ |
155 | public function testFilterMultipleTags() | 157 | public function testFilterMultipleTags() |
156 | { | 158 | { |
157 | $this->assertEquals( | 159 | $this->assertEquals( |
158 | 2, | 160 | 2, |
159 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'dev cartoon', false)) | 161 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'dev cartoon', false)) |
160 | ); | 162 | ); |
161 | } | 163 | } |
162 | 164 | ||
163 | /** | 165 | /** |
164 | * Filter links using a non-existent tag | 166 | * Filter bookmarks using a non-existent tag |
165 | */ | 167 | */ |
166 | public function testFilterUnknownTag() | 168 | public function testFilterUnknownTag() |
167 | { | 169 | { |
168 | $this->assertEquals( | 170 | $this->assertEquals( |
169 | 0, | 171 | 0, |
170 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'null', false)) | 172 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'null', false)) |
171 | ); | 173 | ); |
172 | } | 174 | } |
173 | 175 | ||
174 | /** | 176 | /** |
175 | * Return links for a given day | 177 | * Return bookmarks for a given day |
176 | */ | 178 | */ |
177 | public function testFilterDay() | 179 | public function testFilterDay() |
178 | { | 180 | { |
179 | $this->assertEquals( | 181 | $this->assertEquals( |
180 | 4, | 182 | 4, |
181 | count(self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '20121206')) | 183 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_DAY, '20121206')) |
182 | ); | 184 | ); |
183 | } | 185 | } |
184 | 186 | ||
@@ -189,28 +191,30 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
189 | { | 191 | { |
190 | $this->assertEquals( | 192 | $this->assertEquals( |
191 | 0, | 193 | 0, |
192 | count(self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '19700101')) | 194 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_DAY, '19700101')) |
193 | ); | 195 | ); |
194 | } | 196 | } |
195 | 197 | ||
196 | /** | 198 | /** |
197 | * Use an invalid date format | 199 | * Use an invalid date format |
198 | * @expectedException Exception | ||
199 | * @expectedExceptionMessageRegExp /Invalid date format/ | ||
200 | */ | 200 | */ |
201 | public function testFilterInvalidDayWithChars() | 201 | public function testFilterInvalidDayWithChars() |
202 | { | 202 | { |
203 | self::$linkFilter->filter(LinkFilter::$FILTER_DAY, 'Rainy day, dream away'); | 203 | $this->expectException(\Exception::class); |
204 | $this->expectExceptionMessageRegExp('/Invalid date format/'); | ||
205 | |||
206 | self::$linkFilter->filter(LegacyLinkFilter::$FILTER_DAY, 'Rainy day, dream away'); | ||
204 | } | 207 | } |
205 | 208 | ||
206 | /** | 209 | /** |
207 | * Use an invalid date format | 210 | * Use an invalid date format |
208 | * @expectedException Exception | ||
209 | * @expectedExceptionMessageRegExp /Invalid date format/ | ||
210 | */ | 211 | */ |
211 | public function testFilterInvalidDayDigits() | 212 | public function testFilterInvalidDayDigits() |
212 | { | 213 | { |
213 | self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '20'); | 214 | $this->expectException(\Exception::class); |
215 | $this->expectExceptionMessageRegExp('/Invalid date format/'); | ||
216 | |||
217 | self::$linkFilter->filter(LegacyLinkFilter::$FILTER_DAY, '20'); | ||
214 | } | 218 | } |
215 | 219 | ||
216 | /** | 220 | /** |
@@ -218,7 +222,7 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
218 | */ | 222 | */ |
219 | public function testFilterSmallHash() | 223 | public function testFilterSmallHash() |
220 | { | 224 | { |
221 | $links = self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'IuWvgA'); | 225 | $links = self::$linkFilter->filter(LegacyLinkFilter::$FILTER_HASH, 'IuWvgA'); |
222 | 226 | ||
223 | $this->assertEquals( | 227 | $this->assertEquals( |
224 | 1, | 228 | 1, |
@@ -233,12 +237,12 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
233 | 237 | ||
234 | /** | 238 | /** |
235 | * No link for this hash | 239 | * No link for this hash |
236 | * | ||
237 | * @expectedException \Shaarli\Bookmark\Exception\LinkNotFoundException | ||
238 | */ | 240 | */ |
239 | public function testFilterUnknownSmallHash() | 241 | public function testFilterUnknownSmallHash() |
240 | { | 242 | { |
241 | self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'Iblaah'); | 243 | $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class); |
244 | |||
245 | self::$linkFilter->filter(LegacyLinkFilter::$FILTER_HASH, 'Iblaah'); | ||
242 | } | 246 | } |
243 | 247 | ||
244 | /** | 248 | /** |
@@ -248,7 +252,7 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
248 | { | 252 | { |
249 | $this->assertEquals( | 253 | $this->assertEquals( |
250 | 0, | 254 | 0, |
251 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'azertyuiop')) | 255 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'azertyuiop')) |
252 | ); | 256 | ); |
253 | } | 257 | } |
254 | 258 | ||
@@ -259,12 +263,12 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
259 | { | 263 | { |
260 | $this->assertEquals( | 264 | $this->assertEquals( |
261 | 2, | 265 | 2, |
262 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'ars.userfriendly.org')) | 266 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'ars.userfriendly.org')) |
263 | ); | 267 | ); |
264 | 268 | ||
265 | $this->assertEquals( | 269 | $this->assertEquals( |
266 | 2, | 270 | 2, |
267 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'ars org')) | 271 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'ars org')) |
268 | ); | 272 | ); |
269 | } | 273 | } |
270 | 274 | ||
@@ -276,21 +280,21 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
276 | // use miscellaneous cases | 280 | // use miscellaneous cases |
277 | $this->assertEquals( | 281 | $this->assertEquals( |
278 | 2, | 282 | 2, |
279 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'userfriendly -')) | 283 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'userfriendly -')) |
280 | ); | 284 | ); |
281 | $this->assertEquals( | 285 | $this->assertEquals( |
282 | 2, | 286 | 2, |
283 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'UserFriendly -')) | 287 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'UserFriendly -')) |
284 | ); | 288 | ); |
285 | $this->assertEquals( | 289 | $this->assertEquals( |
286 | 2, | 290 | 2, |
287 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'uSeRFrIendlY -')) | 291 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'uSeRFrIendlY -')) |
288 | ); | 292 | ); |
289 | 293 | ||
290 | // use miscellaneous case and offset | 294 | // use miscellaneous case and offset |
291 | $this->assertEquals( | 295 | $this->assertEquals( |
292 | 2, | 296 | 2, |
293 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'RFrIendL')) | 297 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'RFrIendL')) |
294 | ); | 298 | ); |
295 | } | 299 | } |
296 | 300 | ||
@@ -301,17 +305,17 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
301 | { | 305 | { |
302 | $this->assertEquals( | 306 | $this->assertEquals( |
303 | 1, | 307 | 1, |
304 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'publishing media')) | 308 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'publishing media')) |
305 | ); | 309 | ); |
306 | 310 | ||
307 | $this->assertEquals( | 311 | $this->assertEquals( |
308 | 1, | 312 | 1, |
309 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'mercurial w3c')) | 313 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'mercurial w3c')) |
310 | ); | 314 | ); |
311 | 315 | ||
312 | $this->assertEquals( | 316 | $this->assertEquals( |
313 | 3, | 317 | 3, |
314 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '"free software"')) | 318 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, '"free software"')) |
315 | ); | 319 | ); |
316 | } | 320 | } |
317 | 321 | ||
@@ -322,29 +326,29 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
322 | { | 326 | { |
323 | $this->assertEquals( | 327 | $this->assertEquals( |
324 | 6, | 328 | 6, |
325 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web')) | 329 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'web')) |
326 | ); | 330 | ); |
327 | 331 | ||
328 | $this->assertEquals( | 332 | $this->assertEquals( |
329 | 6, | 333 | 6, |
330 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web', 'all')) | 334 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'web', 'all')) |
331 | ); | 335 | ); |
332 | 336 | ||
333 | $this->assertEquals( | 337 | $this->assertEquals( |
334 | 6, | 338 | 6, |
335 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web', 'bla')) | 339 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'web', 'bla')) |
336 | ); | 340 | ); |
337 | 341 | ||
338 | // Private only. | 342 | // Private only. |
339 | $this->assertEquals( | 343 | $this->assertEquals( |
340 | 1, | 344 | 1, |
341 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web', false, 'private')) | 345 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'web', false, 'private')) |
342 | ); | 346 | ); |
343 | 347 | ||
344 | // Public only. | 348 | // Public only. |
345 | $this->assertEquals( | 349 | $this->assertEquals( |
346 | 5, | 350 | 5, |
347 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web', false, 'public')) | 351 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'web', false, 'public')) |
348 | ); | 352 | ); |
349 | } | 353 | } |
350 | 354 | ||
@@ -355,7 +359,7 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
355 | { | 359 | { |
356 | $this->assertEquals( | 360 | $this->assertEquals( |
357 | 3, | 361 | 3, |
358 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'free software')) | 362 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'free software')) |
359 | ); | 363 | ); |
360 | } | 364 | } |
361 | 365 | ||
@@ -366,12 +370,12 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
366 | { | 370 | { |
367 | $this->assertEquals( | 371 | $this->assertEquals( |
368 | 1, | 372 | 1, |
369 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'free -gnu')) | 373 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, 'free -gnu')) |
370 | ); | 374 | ); |
371 | 375 | ||
372 | $this->assertEquals( | 376 | $this->assertEquals( |
373 | ReferenceLinkDB::$NB_LINKS_TOTAL - 1, | 377 | ReferenceLinkDB::$NB_LINKS_TOTAL - 1, |
374 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution')) | 378 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TEXT, '-revolution')) |
375 | ); | 379 | ); |
376 | } | 380 | } |
377 | 381 | ||
@@ -383,7 +387,7 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
383 | $this->assertEquals( | 387 | $this->assertEquals( |
384 | 2, | 388 | 2, |
385 | count(self::$linkFilter->filter( | 389 | count(self::$linkFilter->filter( |
386 | LinkFilter::$FILTER_TEXT, | 390 | LegacyLinkFilter::$FILTER_TEXT, |
387 | '"Free Software " stallman "read this" @website stuff' | 391 | '"Free Software " stallman "read this" @website stuff' |
388 | )) | 392 | )) |
389 | ); | 393 | ); |
@@ -391,7 +395,7 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
391 | $this->assertEquals( | 395 | $this->assertEquals( |
392 | 1, | 396 | 1, |
393 | count(self::$linkFilter->filter( | 397 | count(self::$linkFilter->filter( |
394 | LinkFilter::$FILTER_TEXT, | 398 | LegacyLinkFilter::$FILTER_TEXT, |
395 | '"free software " stallman "read this" -beard @website stuff' | 399 | '"free software " stallman "read this" -beard @website stuff' |
396 | )) | 400 | )) |
397 | ); | 401 | ); |
@@ -405,7 +409,7 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
405 | $this->assertEquals( | 409 | $this->assertEquals( |
406 | 0, | 410 | 0, |
407 | count(self::$linkFilter->filter( | 411 | count(self::$linkFilter->filter( |
408 | LinkFilter::$FILTER_TEXT, | 412 | LegacyLinkFilter::$FILTER_TEXT, |
409 | '"designer naming"' | 413 | '"designer naming"' |
410 | )) | 414 | )) |
411 | ); | 415 | ); |
@@ -413,7 +417,7 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
413 | $this->assertEquals( | 417 | $this->assertEquals( |
414 | 0, | 418 | 0, |
415 | count(self::$linkFilter->filter( | 419 | count(self::$linkFilter->filter( |
416 | LinkFilter::$FILTER_TEXT, | 420 | LegacyLinkFilter::$FILTER_TEXT, |
417 | '"designernaming"' | 421 | '"designernaming"' |
418 | )) | 422 | )) |
419 | ); | 423 | ); |
@@ -426,12 +430,12 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
426 | { | 430 | { |
427 | $this->assertEquals( | 431 | $this->assertEquals( |
428 | 1, | 432 | 1, |
429 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'gnu -free')) | 433 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, 'gnu -free')) |
430 | ); | 434 | ); |
431 | 435 | ||
432 | $this->assertEquals( | 436 | $this->assertEquals( |
433 | ReferenceLinkDB::$NB_LINKS_TOTAL - 1, | 437 | ReferenceLinkDB::$NB_LINKS_TOTAL - 1, |
434 | count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free')) | 438 | count(self::$linkFilter->filter(LegacyLinkFilter::$FILTER_TAG, '-free')) |
435 | ); | 439 | ); |
436 | } | 440 | } |
437 | 441 | ||
@@ -445,42 +449,42 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
445 | $this->assertEquals( | 449 | $this->assertEquals( |
446 | 1, | 450 | 1, |
447 | count(self::$linkFilter->filter( | 451 | count(self::$linkFilter->filter( |
448 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | 452 | LegacyLinkFilter::$FILTER_TAG | LegacyLinkFilter::$FILTER_TEXT, |
449 | array($tags, $terms) | 453 | array($tags, $terms) |
450 | )) | 454 | )) |
451 | ); | 455 | ); |
452 | $this->assertEquals( | 456 | $this->assertEquals( |
453 | 2, | 457 | 2, |
454 | count(self::$linkFilter->filter( | 458 | count(self::$linkFilter->filter( |
455 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | 459 | LegacyLinkFilter::$FILTER_TAG | LegacyLinkFilter::$FILTER_TEXT, |
456 | array('', $terms) | 460 | array('', $terms) |
457 | )) | 461 | )) |
458 | ); | 462 | ); |
459 | $this->assertEquals( | 463 | $this->assertEquals( |
460 | 1, | 464 | 1, |
461 | count(self::$linkFilter->filter( | 465 | count(self::$linkFilter->filter( |
462 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | 466 | LegacyLinkFilter::$FILTER_TAG | LegacyLinkFilter::$FILTER_TEXT, |
463 | array(false, 'PSR-2') | 467 | array(false, 'PSR-2') |
464 | )) | 468 | )) |
465 | ); | 469 | ); |
466 | $this->assertEquals( | 470 | $this->assertEquals( |
467 | 1, | 471 | 1, |
468 | count(self::$linkFilter->filter( | 472 | count(self::$linkFilter->filter( |
469 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | 473 | LegacyLinkFilter::$FILTER_TAG | LegacyLinkFilter::$FILTER_TEXT, |
470 | array($tags, '') | 474 | array($tags, '') |
471 | )) | 475 | )) |
472 | ); | 476 | ); |
473 | $this->assertEquals( | 477 | $this->assertEquals( |
474 | ReferenceLinkDB::$NB_LINKS_TOTAL, | 478 | ReferenceLinkDB::$NB_LINKS_TOTAL, |
475 | count(self::$linkFilter->filter( | 479 | count(self::$linkFilter->filter( |
476 | LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, | 480 | LegacyLinkFilter::$FILTER_TAG | LegacyLinkFilter::$FILTER_TEXT, |
477 | '' | 481 | '' |
478 | )) | 482 | )) |
479 | ); | 483 | ); |
480 | } | 484 | } |
481 | 485 | ||
482 | /** | 486 | /** |
483 | * Filter links by #hashtag. | 487 | * Filter bookmarks by #hashtag. |
484 | */ | 488 | */ |
485 | public function testFilterByHashtag() | 489 | public function testFilterByHashtag() |
486 | { | 490 | { |
@@ -488,7 +492,7 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
488 | $this->assertEquals( | 492 | $this->assertEquals( |
489 | 3, | 493 | 3, |
490 | count(self::$linkFilter->filter( | 494 | count(self::$linkFilter->filter( |
491 | LinkFilter::$FILTER_TAG, | 495 | LegacyLinkFilter::$FILTER_TAG, |
492 | $hashtag | 496 | $hashtag |
493 | )) | 497 | )) |
494 | ); | 498 | ); |
@@ -497,7 +501,7 @@ class LinkFilterTest extends \PHPUnit\Framework\TestCase | |||
497 | $this->assertEquals( | 501 | $this->assertEquals( |
498 | 1, | 502 | 1, |
499 | count(self::$linkFilter->filter( | 503 | count(self::$linkFilter->filter( |
500 | LinkFilter::$FILTER_TAG, | 504 | LegacyLinkFilter::$FILTER_TAG, |
501 | $hashtag, | 505 | $hashtag, |
502 | false, | 506 | false, |
503 | 'private' | 507 | 'private' |
diff --git a/tests/legacy/LegacyUpdaterTest.php b/tests/legacy/LegacyUpdaterTest.php new file mode 100644 index 00000000..f7391b86 --- /dev/null +++ b/tests/legacy/LegacyUpdaterTest.php | |||
@@ -0,0 +1,886 @@ | |||
1 | <?php | ||
2 | namespace Shaarli\Updater; | ||
3 | |||
4 | use DateTime; | ||
5 | use Exception; | ||
6 | use Shaarli\Bookmark\Bookmark; | ||
7 | use Shaarli\Config\ConfigJson; | ||
8 | use Shaarli\Config\ConfigManager; | ||
9 | use Shaarli\Config\ConfigPhp; | ||
10 | use Shaarli\Legacy\LegacyLinkDB; | ||
11 | use Shaarli\Legacy\LegacyUpdater; | ||
12 | use Shaarli\Thumbnailer; | ||
13 | |||
14 | require_once 'application/updater/UpdaterUtils.php'; | ||
15 | require_once 'tests/updater/DummyUpdater.php'; | ||
16 | require_once 'tests/utils/ReferenceLinkDB.php'; | ||
17 | require_once 'inc/rain.tpl.class.php'; | ||
18 | |||
19 | /** | ||
20 | * Class UpdaterTest. | ||
21 | * Runs unit tests against the updater class. | ||
22 | */ | ||
23 | class LegacyUpdaterTest extends \Shaarli\TestCase | ||
24 | { | ||
25 | /** | ||
26 | * @var string Path to test datastore. | ||
27 | */ | ||
28 | protected static $testDatastore = 'sandbox/datastore.php'; | ||
29 | |||
30 | /** | ||
31 | * @var string Config file path (without extension). | ||
32 | */ | ||
33 | protected static $configFile = 'sandbox/config'; | ||
34 | |||
35 | /** | ||
36 | * @var ConfigManager | ||
37 | */ | ||
38 | protected $conf; | ||
39 | |||
40 | /** | ||
41 | * Executed before each test. | ||
42 | */ | ||
43 | protected function setUp(): void | ||
44 | { | ||
45 | copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php'); | ||
46 | $this->conf = new ConfigManager(self::$configFile); | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * Test UpdaterUtils::read_updates_file with an empty/missing file. | ||
51 | */ | ||
52 | public function testReadEmptyUpdatesFile() | ||
53 | { | ||
54 | $this->assertEquals(array(), UpdaterUtils::read_updates_file('')); | ||
55 | $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; | ||
56 | touch($updatesFile); | ||
57 | $this->assertEquals(array(), UpdaterUtils::read_updates_file($updatesFile)); | ||
58 | unlink($updatesFile); | ||
59 | } | ||
60 | |||
61 | /** | ||
62 | * Test read/write updates file. | ||
63 | */ | ||
64 | public function testReadWriteUpdatesFile() | ||
65 | { | ||
66 | $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; | ||
67 | $updatesMethods = array('m1', 'm2', 'm3'); | ||
68 | |||
69 | UpdaterUtils::write_updates_file($updatesFile, $updatesMethods); | ||
70 | $readMethods = UpdaterUtils::read_updates_file($updatesFile); | ||
71 | $this->assertEquals($readMethods, $updatesMethods); | ||
72 | |||
73 | // Update | ||
74 | $updatesMethods[] = 'm4'; | ||
75 | UpdaterUtils::write_updates_file($updatesFile, $updatesMethods); | ||
76 | $readMethods = UpdaterUtils::read_updates_file($updatesFile); | ||
77 | $this->assertEquals($readMethods, $updatesMethods); | ||
78 | unlink($updatesFile); | ||
79 | } | ||
80 | |||
81 | /** | ||
82 | * Test errors in UpdaterUtils::write_updates_file(): empty updates file. | ||
83 | */ | ||
84 | public function testWriteEmptyUpdatesFile() | ||
85 | { | ||
86 | $this->expectException(\Exception::class); | ||
87 | $this->expectExceptionMessageRegExp('/Updates file path is not set(.*)/'); | ||
88 | |||
89 | UpdaterUtils::write_updates_file('', array('test')); | ||
90 | } | ||
91 | |||
92 | /** | ||
93 | * Test errors in UpdaterUtils::write_updates_file(): not writable updates file. | ||
94 | */ | ||
95 | public function testWriteUpdatesFileNotWritable() | ||
96 | { | ||
97 | $this->expectException(\Exception::class); | ||
98 | $this->expectExceptionMessageRegExp('/Unable to write(.*)/'); | ||
99 | |||
100 | $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; | ||
101 | touch($updatesFile); | ||
102 | chmod($updatesFile, 0444); | ||
103 | try { | ||
104 | @UpdaterUtils::write_updates_file($updatesFile, array('test')); | ||
105 | } catch (Exception $e) { | ||
106 | unlink($updatesFile); | ||
107 | throw $e; | ||
108 | } | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * Test the update() method, with no update to run. | ||
113 | * 1. Everything already run. | ||
114 | * 2. User is logged out. | ||
115 | */ | ||
116 | public function testNoUpdates() | ||
117 | { | ||
118 | $updates = array( | ||
119 | 'updateMethodDummy1', | ||
120 | 'updateMethodDummy2', | ||
121 | 'updateMethodDummy3', | ||
122 | 'updateMethodException', | ||
123 | ); | ||
124 | $updater = new DummyUpdater($updates, array(), $this->conf, true); | ||
125 | $this->assertEquals(array(), $updater->update()); | ||
126 | |||
127 | $updater = new DummyUpdater(array(), array(), $this->conf, false); | ||
128 | $this->assertEquals(array(), $updater->update()); | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * Test the update() method, with all updates to run (except the failing one). | ||
133 | */ | ||
134 | public function testUpdatesFirstTime() | ||
135 | { | ||
136 | $updates = array('updateMethodException',); | ||
137 | $expectedUpdates = array( | ||
138 | 'updateMethodDummy1', | ||
139 | 'updateMethodDummy2', | ||
140 | 'updateMethodDummy3', | ||
141 | ); | ||
142 | $updater = new DummyUpdater($updates, array(), $this->conf, true); | ||
143 | $this->assertEquals($expectedUpdates, $updater->update()); | ||
144 | } | ||
145 | |||
146 | /** | ||
147 | * Test the update() method, only one update to run. | ||
148 | */ | ||
149 | public function testOneUpdate() | ||
150 | { | ||
151 | $updates = array( | ||
152 | 'updateMethodDummy1', | ||
153 | 'updateMethodDummy3', | ||
154 | 'updateMethodException', | ||
155 | ); | ||
156 | $expectedUpdate = array('updateMethodDummy2'); | ||
157 | |||
158 | $updater = new DummyUpdater($updates, array(), $this->conf, true); | ||
159 | $this->assertEquals($expectedUpdate, $updater->update()); | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * Test Update failed. | ||
164 | */ | ||
165 | public function testUpdateFailed() | ||
166 | { | ||
167 | $this->expectException(\Exception::class); | ||
168 | |||
169 | $updates = array( | ||
170 | 'updateMethodDummy1', | ||
171 | 'updateMethodDummy2', | ||
172 | 'updateMethodDummy3', | ||
173 | ); | ||
174 | |||
175 | $updater = new DummyUpdater($updates, array(), $this->conf, true); | ||
176 | $updater->update(); | ||
177 | } | ||
178 | |||
179 | /** | ||
180 | * Test update mergeDeprecatedConfig: | ||
181 | * 1. init a config file. | ||
182 | * 2. init a options.php file with update value. | ||
183 | * 3. merge. | ||
184 | * 4. check updated value in config file. | ||
185 | */ | ||
186 | public function testUpdateMergeDeprecatedConfig() | ||
187 | { | ||
188 | $this->conf->setConfigFile('tests/utils/config/configPhp'); | ||
189 | $this->conf->reset(); | ||
190 | |||
191 | $optionsFile = 'tests/updater/options.php'; | ||
192 | $options = '<?php | ||
193 | $GLOBALS[\'privateLinkByDefault\'] = true;'; | ||
194 | file_put_contents($optionsFile, $options); | ||
195 | |||
196 | // tmp config file. | ||
197 | $this->conf->setConfigFile('tests/updater/config'); | ||
198 | |||
199 | // merge configs | ||
200 | $updater = new LegacyUpdater(array(), array(), $this->conf, true); | ||
201 | // This writes a new config file in tests/updater/config.php | ||
202 | $updater->updateMethodMergeDeprecatedConfigFile(); | ||
203 | |||
204 | // make sure updated field is changed | ||
205 | $this->conf->reload(); | ||
206 | $this->assertTrue($this->conf->get('privacy.default_private_links')); | ||
207 | $this->assertFalse(is_file($optionsFile)); | ||
208 | // Delete the generated file. | ||
209 | unlink($this->conf->getConfigFileExt()); | ||
210 | } | ||
211 | |||
212 | /** | ||
213 | * Test mergeDeprecatedConfig in without options file. | ||
214 | */ | ||
215 | public function testMergeDeprecatedConfigNoFile() | ||
216 | { | ||
217 | $updater = new LegacyUpdater(array(), array(), $this->conf, true); | ||
218 | $updater->updateMethodMergeDeprecatedConfigFile(); | ||
219 | |||
220 | $this->assertEquals('root', $this->conf->get('credentials.login')); | ||
221 | } | ||
222 | |||
223 | /** | ||
224 | * Test renameDashTags update method. | ||
225 | */ | ||
226 | public function testRenameDashTags() | ||
227 | { | ||
228 | $refDB = new \ReferenceLinkDB(true); | ||
229 | $refDB->write(self::$testDatastore); | ||
230 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
231 | |||
232 | $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); | ||
233 | $updater = new LegacyUpdater(array(), $linkDB, $this->conf, true); | ||
234 | $updater->updateMethodRenameDashTags(); | ||
235 | $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); | ||
236 | } | ||
237 | |||
238 | /** | ||
239 | * Convert old PHP config file to JSON config. | ||
240 | */ | ||
241 | public function testConfigToJson() | ||
242 | { | ||
243 | $configFile = 'tests/utils/config/configPhp'; | ||
244 | $this->conf->setConfigFile($configFile); | ||
245 | $this->conf->reset(); | ||
246 | |||
247 | // The ConfigIO is initialized with ConfigPhp. | ||
248 | $this->assertTrue($this->conf->getConfigIO() instanceof ConfigPhp); | ||
249 | |||
250 | $updater = new LegacyUpdater(array(), array(), $this->conf, false); | ||
251 | $done = $updater->updateMethodConfigToJson(); | ||
252 | $this->assertTrue($done); | ||
253 | |||
254 | // The ConfigIO has been updated to ConfigJson. | ||
255 | $this->assertTrue($this->conf->getConfigIO() instanceof ConfigJson); | ||
256 | $this->assertTrue(file_exists($this->conf->getConfigFileExt())); | ||
257 | |||
258 | // Check JSON config data. | ||
259 | $this->conf->reload(); | ||
260 | $this->assertEquals('root', $this->conf->get('credentials.login')); | ||
261 | $this->assertEquals('lala', $this->conf->get('redirector.url')); | ||
262 | $this->assertEquals('data/datastore.php', $this->conf->get('resource.datastore')); | ||
263 | $this->assertEquals('1', $this->conf->get('plugins.WALLABAG_VERSION')); | ||
264 | |||
265 | rename($configFile . '.save.php', $configFile . '.php'); | ||
266 | unlink($this->conf->getConfigFileExt()); | ||
267 | } | ||
268 | |||
269 | /** | ||
270 | * Launch config conversion update with an existing JSON file => nothing to do. | ||
271 | */ | ||
272 | public function testConfigToJsonNothingToDo() | ||
273 | { | ||
274 | $filetime = filemtime($this->conf->getConfigFileExt()); | ||
275 | $updater = new LegacyUpdater(array(), array(), $this->conf, false); | ||
276 | $done = $updater->updateMethodConfigToJson(); | ||
277 | $this->assertTrue($done); | ||
278 | $expected = filemtime($this->conf->getConfigFileExt()); | ||
279 | $this->assertEquals($expected, $filetime); | ||
280 | } | ||
281 | |||
282 | /** | ||
283 | * Test escapeUnescapedConfig with valid data. | ||
284 | */ | ||
285 | public function testEscapeConfig() | ||
286 | { | ||
287 | $sandbox = 'sandbox/config'; | ||
288 | copy(self::$configFile . '.json.php', $sandbox . '.json.php'); | ||
289 | $this->conf = new ConfigManager($sandbox); | ||
290 | $title = '<script>alert("title");</script>'; | ||
291 | $headerLink = '<script>alert("header_link");</script>'; | ||
292 | $this->conf->set('general.title', $title); | ||
293 | $this->conf->set('general.header_link', $headerLink); | ||
294 | $updater = new LegacyUpdater(array(), array(), $this->conf, true); | ||
295 | $done = $updater->updateMethodEscapeUnescapedConfig(); | ||
296 | $this->assertTrue($done); | ||
297 | $this->conf->reload(); | ||
298 | $this->assertEquals(escape($title), $this->conf->get('general.title')); | ||
299 | $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link')); | ||
300 | unlink($sandbox . '.json.php'); | ||
301 | } | ||
302 | |||
303 | /** | ||
304 | * Test updateMethodApiSettings(): create default settings for the API (enabled + secret). | ||
305 | */ | ||
306 | public function testUpdateApiSettings() | ||
307 | { | ||
308 | $confFile = 'sandbox/config'; | ||
309 | copy(self::$configFile .'.json.php', $confFile .'.json.php'); | ||
310 | $conf = new ConfigManager($confFile); | ||
311 | $updater = new LegacyUpdater(array(), array(), $conf, true); | ||
312 | |||
313 | $this->assertFalse($conf->exists('api.enabled')); | ||
314 | $this->assertFalse($conf->exists('api.secret')); | ||
315 | $updater->updateMethodApiSettings(); | ||
316 | $conf->reload(); | ||
317 | $this->assertTrue($conf->get('api.enabled')); | ||
318 | $this->assertTrue($conf->exists('api.secret')); | ||
319 | unlink($confFile .'.json.php'); | ||
320 | } | ||
321 | |||
322 | /** | ||
323 | * Test updateMethodApiSettings(): already set, do nothing. | ||
324 | */ | ||
325 | public function testUpdateApiSettingsNothingToDo() | ||
326 | { | ||
327 | $confFile = 'sandbox/config'; | ||
328 | copy(self::$configFile .'.json.php', $confFile .'.json.php'); | ||
329 | $conf = new ConfigManager($confFile); | ||
330 | $conf->set('api.enabled', false); | ||
331 | $conf->set('api.secret', ''); | ||
332 | $updater = new LegacyUpdater(array(), array(), $conf, true); | ||
333 | $updater->updateMethodApiSettings(); | ||
334 | $this->assertFalse($conf->get('api.enabled')); | ||
335 | $this->assertEmpty($conf->get('api.secret')); | ||
336 | unlink($confFile .'.json.php'); | ||
337 | } | ||
338 | |||
339 | /** | ||
340 | * Test updateMethodDatastoreIds(). | ||
341 | */ | ||
342 | public function testDatastoreIds() | ||
343 | { | ||
344 | $links = array( | ||
345 | '20121206_182539' => array( | ||
346 | 'linkdate' => '20121206_182539', | ||
347 | 'title' => 'Geek and Poke', | ||
348 | 'url' => 'http://geek-and-poke.com/', | ||
349 | 'description' => 'desc', | ||
350 | 'tags' => 'dev cartoon tag1 tag2 tag3 tag4 ', | ||
351 | 'updated' => '20121206_190301', | ||
352 | 'private' => false, | ||
353 | ), | ||
354 | '20121206_172539' => array( | ||
355 | 'linkdate' => '20121206_172539', | ||
356 | 'title' => 'UserFriendly - Samba', | ||
357 | 'url' => 'http://ars.userfriendly.org/cartoons/?id=20010306', | ||
358 | 'description' => '', | ||
359 | 'tags' => 'samba cartoon web', | ||
360 | 'private' => false, | ||
361 | ), | ||
362 | '20121206_142300' => array( | ||
363 | 'linkdate' => '20121206_142300', | ||
364 | 'title' => 'UserFriendly - Web Designer', | ||
365 | 'url' => 'http://ars.userfriendly.org/cartoons/?id=20121206', | ||
366 | 'description' => 'Naming conventions... #private', | ||
367 | 'tags' => 'samba cartoon web', | ||
368 | 'private' => true, | ||
369 | ), | ||
370 | ); | ||
371 | $refDB = new \ReferenceLinkDB(true); | ||
372 | $refDB->setLinks($links); | ||
373 | $refDB->write(self::$testDatastore); | ||
374 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
375 | |||
376 | $checksum = hash_file('sha1', self::$testDatastore); | ||
377 | |||
378 | $this->conf->set('resource.data_dir', 'sandbox'); | ||
379 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
380 | |||
381 | $updater = new LegacyUpdater(array(), $linkDB, $this->conf, true); | ||
382 | $this->assertTrue($updater->updateMethodDatastoreIds()); | ||
383 | |||
384 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
385 | |||
386 | $backupFiles = glob($this->conf->get('resource.data_dir') . '/datastore.'. date('YmdH') .'*.php'); | ||
387 | $backup = null; | ||
388 | foreach ($backupFiles as $backupFile) { | ||
389 | if (strpos($backupFile, '_1') === false) { | ||
390 | $backup = $backupFile; | ||
391 | } | ||
392 | } | ||
393 | $this->assertNotNull($backup); | ||
394 | $this->assertFileExists($backup); | ||
395 | $this->assertEquals($checksum, hash_file('sha1', $backup)); | ||
396 | unlink($backup); | ||
397 | |||
398 | $this->assertEquals(3, count($linkDB)); | ||
399 | $this->assertTrue(isset($linkDB[0])); | ||
400 | $this->assertFalse(isset($linkDB[0]['linkdate'])); | ||
401 | $this->assertEquals(0, $linkDB[0]['id']); | ||
402 | $this->assertEquals('UserFriendly - Web Designer', $linkDB[0]['title']); | ||
403 | $this->assertEquals('http://ars.userfriendly.org/cartoons/?id=20121206', $linkDB[0]['url']); | ||
404 | $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']); | ||
405 | $this->assertEquals('samba cartoon web', $linkDB[0]['tags']); | ||
406 | $this->assertTrue($linkDB[0]['private']); | ||
407 | $this->assertEquals( | ||
408 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121206_142300'), | ||
409 | $linkDB[0]['created'] | ||
410 | ); | ||
411 | |||
412 | $this->assertTrue(isset($linkDB[1])); | ||
413 | $this->assertFalse(isset($linkDB[1]['linkdate'])); | ||
414 | $this->assertEquals(1, $linkDB[1]['id']); | ||
415 | $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']); | ||
416 | $this->assertEquals( | ||
417 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121206_172539'), | ||
418 | $linkDB[1]['created'] | ||
419 | ); | ||
420 | |||
421 | $this->assertTrue(isset($linkDB[2])); | ||
422 | $this->assertFalse(isset($linkDB[2]['linkdate'])); | ||
423 | $this->assertEquals(2, $linkDB[2]['id']); | ||
424 | $this->assertEquals('Geek and Poke', $linkDB[2]['title']); | ||
425 | $this->assertEquals( | ||
426 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121206_182539'), | ||
427 | $linkDB[2]['created'] | ||
428 | ); | ||
429 | $this->assertEquals( | ||
430 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121206_190301'), | ||
431 | $linkDB[2]['updated'] | ||
432 | ); | ||
433 | } | ||
434 | |||
435 | /** | ||
436 | * Test updateMethodDatastoreIds() with the update already applied: nothing to do. | ||
437 | */ | ||
438 | public function testDatastoreIdsNothingToDo() | ||
439 | { | ||
440 | $refDB = new \ReferenceLinkDB(true); | ||
441 | $refDB->write(self::$testDatastore); | ||
442 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
443 | |||
444 | $this->conf->set('resource.data_dir', 'sandbox'); | ||
445 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
446 | |||
447 | $checksum = hash_file('sha1', self::$testDatastore); | ||
448 | $updater = new LegacyUpdater(array(), $linkDB, $this->conf, true); | ||
449 | $this->assertTrue($updater->updateMethodDatastoreIds()); | ||
450 | $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore)); | ||
451 | } | ||
452 | |||
453 | /** | ||
454 | * Test defaultTheme update with default settings: nothing to do. | ||
455 | */ | ||
456 | public function testDefaultThemeWithDefaultSettings() | ||
457 | { | ||
458 | $sandbox = 'sandbox/config'; | ||
459 | copy(self::$configFile . '.json.php', $sandbox . '.json.php'); | ||
460 | $this->conf = new ConfigManager($sandbox); | ||
461 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
462 | $this->assertTrue($updater->updateMethodDefaultTheme()); | ||
463 | |||
464 | $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); | ||
465 | $this->assertEquals('default', $this->conf->get('resource.theme')); | ||
466 | $this->conf = new ConfigManager($sandbox); | ||
467 | $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); | ||
468 | $this->assertEquals('default', $this->conf->get('resource.theme')); | ||
469 | unlink($sandbox . '.json.php'); | ||
470 | } | ||
471 | |||
472 | /** | ||
473 | * Test defaultTheme update with a custom theme in a subfolder | ||
474 | */ | ||
475 | public function testDefaultThemeWithCustomTheme() | ||
476 | { | ||
477 | $theme = 'iamanartist'; | ||
478 | $sandbox = 'sandbox/config'; | ||
479 | copy(self::$configFile . '.json.php', $sandbox . '.json.php'); | ||
480 | $this->conf = new ConfigManager($sandbox); | ||
481 | mkdir('sandbox/'. $theme); | ||
482 | touch('sandbox/'. $theme .'/linklist.html'); | ||
483 | $this->conf->set('resource.raintpl_tpl', 'sandbox/'. $theme .'/'); | ||
484 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
485 | $this->assertTrue($updater->updateMethodDefaultTheme()); | ||
486 | |||
487 | $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); | ||
488 | $this->assertEquals($theme, $this->conf->get('resource.theme')); | ||
489 | $this->conf = new ConfigManager($sandbox); | ||
490 | $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); | ||
491 | $this->assertEquals($theme, $this->conf->get('resource.theme')); | ||
492 | unlink($sandbox . '.json.php'); | ||
493 | unlink('sandbox/'. $theme .'/linklist.html'); | ||
494 | rmdir('sandbox/'. $theme); | ||
495 | } | ||
496 | |||
497 | /** | ||
498 | * Test updateMethodEscapeMarkdown with markdown plugin enabled | ||
499 | * => setting markdown_escape set to false. | ||
500 | */ | ||
501 | public function testEscapeMarkdownSettingToFalse() | ||
502 | { | ||
503 | $sandboxConf = 'sandbox/config'; | ||
504 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
505 | $this->conf = new ConfigManager($sandboxConf); | ||
506 | |||
507 | $this->conf->set('general.enabled_plugins', ['markdown']); | ||
508 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
509 | $this->assertTrue($updater->updateMethodEscapeMarkdown()); | ||
510 | $this->assertFalse($this->conf->get('security.markdown_escape')); | ||
511 | |||
512 | // reload from file | ||
513 | $this->conf = new ConfigManager($sandboxConf); | ||
514 | $this->assertFalse($this->conf->get('security.markdown_escape')); | ||
515 | } | ||
516 | |||
517 | |||
518 | /** | ||
519 | * Test updateMethodEscapeMarkdown with markdown plugin disabled | ||
520 | * => setting markdown_escape set to true. | ||
521 | */ | ||
522 | public function testEscapeMarkdownSettingToTrue() | ||
523 | { | ||
524 | $sandboxConf = 'sandbox/config'; | ||
525 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
526 | $this->conf = new ConfigManager($sandboxConf); | ||
527 | |||
528 | $this->conf->set('general.enabled_plugins', []); | ||
529 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
530 | $this->assertTrue($updater->updateMethodEscapeMarkdown()); | ||
531 | $this->assertTrue($this->conf->get('security.markdown_escape')); | ||
532 | |||
533 | // reload from file | ||
534 | $this->conf = new ConfigManager($sandboxConf); | ||
535 | $this->assertTrue($this->conf->get('security.markdown_escape')); | ||
536 | } | ||
537 | |||
538 | /** | ||
539 | * Test updateMethodEscapeMarkdown with nothing to do (setting already enabled) | ||
540 | */ | ||
541 | public function testEscapeMarkdownSettingNothingToDoEnabled() | ||
542 | { | ||
543 | $sandboxConf = 'sandbox/config'; | ||
544 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
545 | $this->conf = new ConfigManager($sandboxConf); | ||
546 | $this->conf->set('security.markdown_escape', true); | ||
547 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
548 | $this->assertTrue($updater->updateMethodEscapeMarkdown()); | ||
549 | $this->assertTrue($this->conf->get('security.markdown_escape')); | ||
550 | } | ||
551 | |||
552 | /** | ||
553 | * Test updateMethodEscapeMarkdown with nothing to do (setting already disabled) | ||
554 | */ | ||
555 | public function testEscapeMarkdownSettingNothingToDoDisabled() | ||
556 | { | ||
557 | $this->conf->set('security.markdown_escape', false); | ||
558 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
559 | $this->assertTrue($updater->updateMethodEscapeMarkdown()); | ||
560 | $this->assertFalse($this->conf->get('security.markdown_escape')); | ||
561 | } | ||
562 | |||
563 | /** | ||
564 | * Test updateMethodPiwikUrl with valid data | ||
565 | */ | ||
566 | public function testUpdatePiwikUrlValid() | ||
567 | { | ||
568 | $sandboxConf = 'sandbox/config'; | ||
569 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
570 | $this->conf = new ConfigManager($sandboxConf); | ||
571 | $url = 'mypiwik.tld'; | ||
572 | $this->conf->set('plugins.PIWIK_URL', $url); | ||
573 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
574 | $this->assertTrue($updater->updateMethodPiwikUrl()); | ||
575 | $this->assertEquals('http://'. $url, $this->conf->get('plugins.PIWIK_URL')); | ||
576 | |||
577 | // reload from file | ||
578 | $this->conf = new ConfigManager($sandboxConf); | ||
579 | $this->assertEquals('http://'. $url, $this->conf->get('plugins.PIWIK_URL')); | ||
580 | } | ||
581 | |||
582 | /** | ||
583 | * Test updateMethodPiwikUrl without setting | ||
584 | */ | ||
585 | public function testUpdatePiwikUrlEmpty() | ||
586 | { | ||
587 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
588 | $this->assertTrue($updater->updateMethodPiwikUrl()); | ||
589 | $this->assertEmpty($this->conf->get('plugins.PIWIK_URL')); | ||
590 | } | ||
591 | |||
592 | /** | ||
593 | * Test updateMethodPiwikUrl: valid URL, nothing to do | ||
594 | */ | ||
595 | public function testUpdatePiwikUrlNothingToDo() | ||
596 | { | ||
597 | $url = 'https://mypiwik.tld'; | ||
598 | $this->conf->set('plugins.PIWIK_URL', $url); | ||
599 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
600 | $this->assertTrue($updater->updateMethodPiwikUrl()); | ||
601 | $this->assertEquals($url, $this->conf->get('plugins.PIWIK_URL')); | ||
602 | } | ||
603 | |||
604 | /** | ||
605 | * Test updateMethodAtomDefault with show_atom set to false | ||
606 | * => update to true. | ||
607 | */ | ||
608 | public function testUpdateMethodAtomDefault() | ||
609 | { | ||
610 | $sandboxConf = 'sandbox/config'; | ||
611 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
612 | $this->conf = new ConfigManager($sandboxConf); | ||
613 | $this->conf->set('feed.show_atom', false); | ||
614 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
615 | $this->assertTrue($updater->updateMethodAtomDefault()); | ||
616 | $this->assertTrue($this->conf->get('feed.show_atom')); | ||
617 | // reload from file | ||
618 | $this->conf = new ConfigManager($sandboxConf); | ||
619 | $this->assertTrue($this->conf->get('feed.show_atom')); | ||
620 | } | ||
621 | /** | ||
622 | * Test updateMethodAtomDefault with show_atom not set. | ||
623 | * => nothing to do | ||
624 | */ | ||
625 | public function testUpdateMethodAtomDefaultNoExist() | ||
626 | { | ||
627 | $sandboxConf = 'sandbox/config'; | ||
628 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
629 | $this->conf = new ConfigManager($sandboxConf); | ||
630 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
631 | $this->assertTrue($updater->updateMethodAtomDefault()); | ||
632 | $this->assertTrue($this->conf->get('feed.show_atom')); | ||
633 | } | ||
634 | /** | ||
635 | * Test updateMethodAtomDefault with show_atom set to true. | ||
636 | * => nothing to do | ||
637 | */ | ||
638 | public function testUpdateMethodAtomDefaultAlreadyTrue() | ||
639 | { | ||
640 | $sandboxConf = 'sandbox/config'; | ||
641 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
642 | $this->conf = new ConfigManager($sandboxConf); | ||
643 | $this->conf->set('feed.show_atom', true); | ||
644 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
645 | $this->assertTrue($updater->updateMethodAtomDefault()); | ||
646 | $this->assertTrue($this->conf->get('feed.show_atom')); | ||
647 | } | ||
648 | |||
649 | /** | ||
650 | * Test updateMethodDownloadSizeAndTimeoutConf, it should be set if none is already defined. | ||
651 | */ | ||
652 | public function testUpdateMethodDownloadSizeAndTimeoutConf() | ||
653 | { | ||
654 | $sandboxConf = 'sandbox/config'; | ||
655 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
656 | $this->conf = new ConfigManager($sandboxConf); | ||
657 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
658 | $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); | ||
659 | $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); | ||
660 | $this->assertEquals(30, $this->conf->get('general.download_timeout')); | ||
661 | |||
662 | $this->conf = new ConfigManager($sandboxConf); | ||
663 | $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); | ||
664 | $this->assertEquals(30, $this->conf->get('general.download_timeout')); | ||
665 | } | ||
666 | |||
667 | /** | ||
668 | * Test updateMethodDownloadSizeAndTimeoutConf, it shouldn't be set if it is already defined. | ||
669 | */ | ||
670 | public function testUpdateMethodDownloadSizeAndTimeoutConfIgnore() | ||
671 | { | ||
672 | $sandboxConf = 'sandbox/config'; | ||
673 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
674 | $this->conf = new ConfigManager($sandboxConf); | ||
675 | $this->conf->set('general.download_max_size', 38); | ||
676 | $this->conf->set('general.download_timeout', 70); | ||
677 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
678 | $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); | ||
679 | $this->assertEquals(38, $this->conf->get('general.download_max_size')); | ||
680 | $this->assertEquals(70, $this->conf->get('general.download_timeout')); | ||
681 | } | ||
682 | |||
683 | /** | ||
684 | * Test updateMethodDownloadSizeAndTimeoutConf, only the maz size should be set here. | ||
685 | */ | ||
686 | public function testUpdateMethodDownloadSizeAndTimeoutConfOnlySize() | ||
687 | { | ||
688 | $sandboxConf = 'sandbox/config'; | ||
689 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
690 | $this->conf = new ConfigManager($sandboxConf); | ||
691 | $this->conf->set('general.download_max_size', 38); | ||
692 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
693 | $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); | ||
694 | $this->assertEquals(38, $this->conf->get('general.download_max_size')); | ||
695 | $this->assertEquals(30, $this->conf->get('general.download_timeout')); | ||
696 | } | ||
697 | |||
698 | /** | ||
699 | * Test updateMethodDownloadSizeAndTimeoutConf, only the time out should be set here. | ||
700 | */ | ||
701 | public function testUpdateMethodDownloadSizeAndTimeoutConfOnlyTimeout() | ||
702 | { | ||
703 | $sandboxConf = 'sandbox/config'; | ||
704 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
705 | $this->conf = new ConfigManager($sandboxConf); | ||
706 | $this->conf->set('general.download_timeout', 3); | ||
707 | $updater = new LegacyUpdater([], [], $this->conf, true); | ||
708 | $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); | ||
709 | $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); | ||
710 | $this->assertEquals(3, $this->conf->get('general.download_timeout')); | ||
711 | } | ||
712 | |||
713 | /** | ||
714 | * Test updateMethodWebThumbnailer with thumbnails enabled. | ||
715 | */ | ||
716 | public function testUpdateMethodWebThumbnailerEnabled() | ||
717 | { | ||
718 | $this->conf->remove('thumbnails'); | ||
719 | $this->conf->set('thumbnail.enable_thumbnails', true); | ||
720 | $updater = new LegacyUpdater([], [], $this->conf, true, $_SESSION); | ||
721 | $this->assertTrue($updater->updateMethodWebThumbnailer()); | ||
722 | $this->assertFalse($this->conf->exists('thumbnail')); | ||
723 | $this->assertEquals(\Shaarli\Thumbnailer::MODE_ALL, $this->conf->get('thumbnails.mode')); | ||
724 | $this->assertEquals(125, $this->conf->get('thumbnails.width')); | ||
725 | $this->assertEquals(90, $this->conf->get('thumbnails.height')); | ||
726 | $this->assertContainsPolyfill('You have enabled or changed thumbnails', $_SESSION['warnings'][0]); | ||
727 | } | ||
728 | |||
729 | /** | ||
730 | * Test updateMethodWebThumbnailer with thumbnails disabled. | ||
731 | */ | ||
732 | public function testUpdateMethodWebThumbnailerDisabled() | ||
733 | { | ||
734 | if (isset($_SESSION['warnings'])) { | ||
735 | unset($_SESSION['warnings']); | ||
736 | } | ||
737 | |||
738 | $this->conf->remove('thumbnails'); | ||
739 | $this->conf->set('thumbnail.enable_thumbnails', false); | ||
740 | $updater = new LegacyUpdater([], [], $this->conf, true, $_SESSION); | ||
741 | $this->assertTrue($updater->updateMethodWebThumbnailer()); | ||
742 | $this->assertFalse($this->conf->exists('thumbnail')); | ||
743 | $this->assertEquals(Thumbnailer::MODE_NONE, $this->conf->get('thumbnails.mode')); | ||
744 | $this->assertEquals(125, $this->conf->get('thumbnails.width')); | ||
745 | $this->assertEquals(90, $this->conf->get('thumbnails.height')); | ||
746 | $this->assertTrue(empty($_SESSION['warnings'])); | ||
747 | } | ||
748 | |||
749 | /** | ||
750 | * Test updateMethodWebThumbnailer with thumbnails disabled. | ||
751 | */ | ||
752 | public function testUpdateMethodWebThumbnailerNothingToDo() | ||
753 | { | ||
754 | if (isset($_SESSION['warnings'])) { | ||
755 | unset($_SESSION['warnings']); | ||
756 | } | ||
757 | |||
758 | $updater = new LegacyUpdater([], [], $this->conf, true, $_SESSION); | ||
759 | $this->assertTrue($updater->updateMethodWebThumbnailer()); | ||
760 | $this->assertFalse($this->conf->exists('thumbnail')); | ||
761 | $this->assertEquals(Thumbnailer::MODE_COMMON, $this->conf->get('thumbnails.mode')); | ||
762 | $this->assertEquals(90, $this->conf->get('thumbnails.width')); | ||
763 | $this->assertEquals(53, $this->conf->get('thumbnails.height')); | ||
764 | $this->assertTrue(empty($_SESSION['warnings'])); | ||
765 | } | ||
766 | |||
767 | /** | ||
768 | * Test updateMethodSetSticky(). | ||
769 | */ | ||
770 | public function testUpdateStickyValid() | ||
771 | { | ||
772 | $blank = [ | ||
773 | 'id' => 1, | ||
774 | 'url' => 'z', | ||
775 | 'title' => '', | ||
776 | 'description' => '', | ||
777 | 'tags' => '', | ||
778 | 'created' => new DateTime(), | ||
779 | ]; | ||
780 | $links = [ | ||
781 | 1 => ['id' => 1] + $blank, | ||
782 | 2 => ['id' => 2] + $blank, | ||
783 | ]; | ||
784 | $refDB = new \ReferenceLinkDB(true); | ||
785 | $refDB->setLinks($links); | ||
786 | $refDB->write(self::$testDatastore); | ||
787 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
788 | |||
789 | $updater = new LegacyUpdater(array(), $linkDB, $this->conf, true); | ||
790 | $this->assertTrue($updater->updateMethodSetSticky()); | ||
791 | |||
792 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
793 | foreach ($linkDB as $link) { | ||
794 | $this->assertFalse($link['sticky']); | ||
795 | } | ||
796 | } | ||
797 | |||
798 | /** | ||
799 | * Test updateMethodSetSticky(). | ||
800 | */ | ||
801 | public function testUpdateStickyNothingToDo() | ||
802 | { | ||
803 | $blank = [ | ||
804 | 'id' => 1, | ||
805 | 'url' => 'z', | ||
806 | 'title' => '', | ||
807 | 'description' => '', | ||
808 | 'tags' => '', | ||
809 | 'created' => new DateTime(), | ||
810 | ]; | ||
811 | $links = [ | ||
812 | 1 => ['id' => 1, 'sticky' => true] + $blank, | ||
813 | 2 => ['id' => 2] + $blank, | ||
814 | ]; | ||
815 | $refDB = new \ReferenceLinkDB(true); | ||
816 | $refDB->setLinks($links); | ||
817 | $refDB->write(self::$testDatastore); | ||
818 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
819 | |||
820 | $updater = new LegacyUpdater(array(), $linkDB, $this->conf, true); | ||
821 | $this->assertTrue($updater->updateMethodSetSticky()); | ||
822 | |||
823 | $linkDB = new LegacyLinkDB(self::$testDatastore, true, false); | ||
824 | $this->assertTrue($linkDB[1]['sticky']); | ||
825 | } | ||
826 | |||
827 | /** | ||
828 | * Test updateMethodRemoveRedirector(). | ||
829 | */ | ||
830 | public function testUpdateRemoveRedirector() | ||
831 | { | ||
832 | $sandboxConf = 'sandbox/config'; | ||
833 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
834 | $this->conf = new ConfigManager($sandboxConf); | ||
835 | $updater = new LegacyUpdater([], null, $this->conf, true); | ||
836 | $this->assertTrue($updater->updateMethodRemoveRedirector()); | ||
837 | $this->assertFalse($this->conf->exists('redirector')); | ||
838 | $this->conf = new ConfigManager($sandboxConf); | ||
839 | $this->assertFalse($this->conf->exists('redirector')); | ||
840 | } | ||
841 | |||
842 | /** | ||
843 | * Test updateMethodFormatterSetting() | ||
844 | */ | ||
845 | public function testUpdateMethodFormatterSettingDefault() | ||
846 | { | ||
847 | $sandboxConf = 'sandbox/config'; | ||
848 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
849 | $this->conf = new ConfigManager($sandboxConf); | ||
850 | $this->conf->set('formatter', 'default'); | ||
851 | $updater = new LegacyUpdater([], null, $this->conf, true); | ||
852 | $enabledPlugins = $this->conf->get('general.enabled_plugins'); | ||
853 | $this->assertFalse(in_array('markdown', $enabledPlugins)); | ||
854 | $this->assertTrue($updater->updateMethodFormatterSetting()); | ||
855 | $this->assertEquals('default', $this->conf->get('formatter')); | ||
856 | $this->assertEquals($enabledPlugins, $this->conf->get('general.enabled_plugins')); | ||
857 | |||
858 | $this->conf = new ConfigManager($sandboxConf); | ||
859 | $this->assertEquals('default', $this->conf->get('formatter')); | ||
860 | $this->assertEquals($enabledPlugins, $this->conf->get('general.enabled_plugins')); | ||
861 | } | ||
862 | |||
863 | /** | ||
864 | * Test updateMethodFormatterSetting() | ||
865 | */ | ||
866 | public function testUpdateMethodFormatterSettingMarkdown() | ||
867 | { | ||
868 | $sandboxConf = 'sandbox/config'; | ||
869 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
870 | $this->conf = new ConfigManager($sandboxConf); | ||
871 | $this->conf->set('formatter', 'default'); | ||
872 | $updater = new LegacyUpdater([], null, $this->conf, true); | ||
873 | $enabledPlugins = $this->conf->get('general.enabled_plugins'); | ||
874 | $enabledPlugins[] = 'markdown'; | ||
875 | $this->conf->set('general.enabled_plugins', $enabledPlugins); | ||
876 | |||
877 | $this->assertTrue(in_array('markdown', $this->conf->get('general.enabled_plugins'))); | ||
878 | $this->assertTrue($updater->updateMethodFormatterSetting()); | ||
879 | $this->assertEquals('markdown', $this->conf->get('formatter')); | ||
880 | $this->assertFalse(in_array('markdown', $this->conf->get('general.enabled_plugins'))); | ||
881 | |||
882 | $this->conf = new ConfigManager($sandboxConf); | ||
883 | $this->assertEquals('markdown', $this->conf->get('formatter')); | ||
884 | $this->assertFalse(in_array('markdown', $this->conf->get('general.enabled_plugins'))); | ||
885 | } | ||
886 | } | ||
diff --git a/tests/netscape/BookmarkExportTest.php b/tests/netscape/BookmarkExportTest.php index 6de9876d..9b95ccc9 100644 --- a/tests/netscape/BookmarkExportTest.php +++ b/tests/netscape/BookmarkExportTest.php | |||
@@ -1,14 +1,20 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
2 | namespace Shaarli\Netscape; | 3 | namespace Shaarli\Netscape; |
3 | 4 | ||
4 | use Shaarli\Bookmark\LinkDB; | 5 | use Shaarli\Bookmark\BookmarkFileService; |
6 | use Shaarli\Config\ConfigManager; | ||
7 | use Shaarli\Formatter\BookmarkFormatter; | ||
8 | use Shaarli\Formatter\FormatterFactory; | ||
9 | use Shaarli\History; | ||
10 | use Shaarli\TestCase; | ||
5 | 11 | ||
6 | require_once 'tests/utils/ReferenceLinkDB.php'; | 12 | require_once 'tests/utils/ReferenceLinkDB.php'; |
7 | 13 | ||
8 | /** | 14 | /** |
9 | * Netscape bookmark export | 15 | * Netscape bookmark export |
10 | */ | 16 | */ |
11 | class BookmarkExportTest extends \PHPUnit\Framework\TestCase | 17 | class BookmarkExportTest extends TestCase |
12 | { | 18 | { |
13 | /** | 19 | /** |
14 | * @var string datastore to test write operations | 20 | * @var string datastore to test write operations |
@@ -16,41 +22,86 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
16 | protected static $testDatastore = 'sandbox/datastore.php'; | 22 | protected static $testDatastore = 'sandbox/datastore.php'; |
17 | 23 | ||
18 | /** | 24 | /** |
25 | * @var ConfigManager instance. | ||
26 | */ | ||
27 | protected static $conf; | ||
28 | |||
29 | /** | ||
19 | * @var \ReferenceLinkDB instance. | 30 | * @var \ReferenceLinkDB instance. |
20 | */ | 31 | */ |
21 | protected static $refDb = null; | 32 | protected static $refDb = null; |
22 | 33 | ||
23 | /** | 34 | /** |
24 | * @var LinkDB private LinkDB instance. | 35 | * @var BookmarkFileService private instance. |
36 | */ | ||
37 | protected static $bookmarkService = null; | ||
38 | |||
39 | /** | ||
40 | * @var BookmarkFormatter instance | ||
41 | */ | ||
42 | protected static $formatter; | ||
43 | |||
44 | /** | ||
45 | * @var History instance | ||
46 | */ | ||
47 | protected static $history; | ||
48 | |||
49 | /** | ||
50 | * @var NetscapeBookmarkUtils | ||
25 | */ | 51 | */ |
26 | protected static $linkDb = null; | 52 | protected $netscapeBookmarkUtils; |
27 | 53 | ||
28 | /** | 54 | /** |
29 | * Instantiate reference data | 55 | * Instantiate reference data |
30 | */ | 56 | */ |
31 | public static function setUpBeforeClass() | 57 | public static function setUpBeforeClass(): void |
32 | { | 58 | { |
33 | self::$refDb = new \ReferenceLinkDB(); | 59 | static::$conf = new ConfigManager('tests/utils/config/configJson'); |
34 | self::$refDb->write(self::$testDatastore); | 60 | static::$conf->set('resource.datastore', static::$testDatastore); |
35 | self::$linkDb = new LinkDB(self::$testDatastore, true, false); | 61 | static::$refDb = new \ReferenceLinkDB(); |
62 | static::$refDb->write(static::$testDatastore); | ||
63 | static::$history = new History('sandbox/history.php'); | ||
64 | static::$bookmarkService = new BookmarkFileService(static::$conf, static::$history, true); | ||
65 | $factory = new FormatterFactory(static::$conf, true); | ||
66 | static::$formatter = $factory->getFormatter('raw'); | ||
67 | } | ||
68 | |||
69 | public function setUp(): void | ||
70 | { | ||
71 | $this->netscapeBookmarkUtils = new NetscapeBookmarkUtils( | ||
72 | static::$bookmarkService, | ||
73 | static::$conf, | ||
74 | static::$history | ||
75 | ); | ||
36 | } | 76 | } |
37 | 77 | ||
38 | /** | 78 | /** |
39 | * Attempt to export an invalid link selection | 79 | * Attempt to export an invalid link selection |
40 | * @expectedException Exception | ||
41 | * @expectedExceptionMessageRegExp /Invalid export selection/ | ||
42 | */ | 80 | */ |
43 | public function testFilterAndFormatInvalid() | 81 | public function testFilterAndFormatInvalid() |
44 | { | 82 | { |
45 | NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'derp', false, ''); | 83 | $this->expectException(\Exception::class); |
84 | $this->expectExceptionMessageRegExp('/Invalid export selection/'); | ||
85 | |||
86 | $this->netscapeBookmarkUtils->filterAndFormat( | ||
87 | self::$formatter, | ||
88 | 'derp', | ||
89 | false, | ||
90 | '' | ||
91 | ); | ||
46 | } | 92 | } |
47 | 93 | ||
48 | /** | 94 | /** |
49 | * Prepare all links for export | 95 | * Prepare all bookmarks for export |
50 | */ | 96 | */ |
51 | public function testFilterAndFormatAll() | 97 | public function testFilterAndFormatAll() |
52 | { | 98 | { |
53 | $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, ''); | 99 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
100 | self::$formatter, | ||
101 | 'all', | ||
102 | false, | ||
103 | '' | ||
104 | ); | ||
54 | $this->assertEquals(self::$refDb->countLinks(), sizeof($links)); | 105 | $this->assertEquals(self::$refDb->countLinks(), sizeof($links)); |
55 | foreach ($links as $link) { | 106 | foreach ($links as $link) { |
56 | $date = $link['created']; | 107 | $date = $link['created']; |
@@ -66,11 +117,16 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
66 | } | 117 | } |
67 | 118 | ||
68 | /** | 119 | /** |
69 | * Prepare private links for export | 120 | * Prepare private bookmarks for export |
70 | */ | 121 | */ |
71 | public function testFilterAndFormatPrivate() | 122 | public function testFilterAndFormatPrivate() |
72 | { | 123 | { |
73 | $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, ''); | 124 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
125 | self::$formatter, | ||
126 | 'private', | ||
127 | false, | ||
128 | '' | ||
129 | ); | ||
74 | $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links)); | 130 | $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links)); |
75 | foreach ($links as $link) { | 131 | foreach ($links as $link) { |
76 | $date = $link['created']; | 132 | $date = $link['created']; |
@@ -86,11 +142,16 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
86 | } | 142 | } |
87 | 143 | ||
88 | /** | 144 | /** |
89 | * Prepare public links for export | 145 | * Prepare public bookmarks for export |
90 | */ | 146 | */ |
91 | public function testFilterAndFormatPublic() | 147 | public function testFilterAndFormatPublic() |
92 | { | 148 | { |
93 | $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, ''); | 149 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
150 | self::$formatter, | ||
151 | 'public', | ||
152 | false, | ||
153 | '' | ||
154 | ); | ||
94 | $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links)); | 155 | $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links)); |
95 | foreach ($links as $link) { | 156 | foreach ($links as $link) { |
96 | $date = $link['created']; | 157 | $date = $link['created']; |
@@ -110,9 +171,14 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
110 | */ | 171 | */ |
111 | public function testFilterAndFormatDoNotPrependNoteUrl() | 172 | public function testFilterAndFormatDoNotPrependNoteUrl() |
112 | { | 173 | { |
113 | $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, ''); | 174 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
175 | self::$formatter, | ||
176 | 'public', | ||
177 | false, | ||
178 | '' | ||
179 | ); | ||
114 | $this->assertEquals( | 180 | $this->assertEquals( |
115 | '?WDWyig', | 181 | '/shaare/WDWyig', |
116 | $links[2]['url'] | 182 | $links[2]['url'] |
117 | ); | 183 | ); |
118 | } | 184 | } |
@@ -123,14 +189,14 @@ class BookmarkExportTest extends \PHPUnit\Framework\TestCase | |||
123 | public function testFilterAndFormatPrependNoteUrl() | 189 | public function testFilterAndFormatPrependNoteUrl() |
124 | { | 190 | { |
125 | $indexUrl = 'http://localhost:7469/shaarli/'; | 191 | $indexUrl = 'http://localhost:7469/shaarli/'; |
126 | $links = NetscapeBookmarkUtils::filterAndFormat( | 192 | $links = $this->netscapeBookmarkUtils->filterAndFormat( |
127 | self::$linkDb, | 193 | self::$formatter, |
128 | 'public', | 194 | 'public', |
129 | true, | 195 | true, |
130 | $indexUrl | 196 | $indexUrl |
131 | ); | 197 | ); |
132 | $this->assertEquals( | 198 | $this->assertEquals( |
133 | $indexUrl . '?WDWyig', | 199 | $indexUrl . 'shaare/WDWyig', |
134 | $links[2]['url'] | 200 | $links[2]['url'] |
135 | ); | 201 | ); |
136 | } | 202 | } |
diff --git a/tests/netscape/BookmarkImportTest.php b/tests/netscape/BookmarkImportTest.php index ccafc161..c1e49b5f 100644 --- a/tests/netscape/BookmarkImportTest.php +++ b/tests/netscape/BookmarkImportTest.php | |||
@@ -1,26 +1,31 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
2 | namespace Shaarli\Netscape; | 3 | namespace Shaarli\Netscape; |
3 | 4 | ||
4 | use DateTime; | 5 | use DateTime; |
5 | use Shaarli\Bookmark\LinkDB; | 6 | use Psr\Http\Message\UploadedFileInterface; |
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Bookmark\BookmarkFileService; | ||
9 | use Shaarli\Bookmark\BookmarkFilter; | ||
6 | use Shaarli\Config\ConfigManager; | 10 | use Shaarli\Config\ConfigManager; |
7 | use Shaarli\History; | 11 | use Shaarli\History; |
12 | use Shaarli\TestCase; | ||
13 | use Slim\Http\UploadedFile; | ||
8 | 14 | ||
9 | /** | 15 | /** |
10 | * Utility function to load a file's metadata in a $_FILES-like array | 16 | * Utility function to load a file's metadata in a $_FILES-like array |
11 | * | 17 | * |
12 | * @param string $filename Basename of the file | 18 | * @param string $filename Basename of the file |
13 | * | 19 | * |
14 | * @return array A $_FILES-like array | 20 | * @return UploadedFileInterface Upload file in PSR-7 compatible object |
15 | */ | 21 | */ |
16 | function file2array($filename) | 22 | function file2array($filename) |
17 | { | 23 | { |
18 | return array( | 24 | return new UploadedFile( |
19 | 'filetoupload' => array( | 25 | __DIR__ . '/input/' . $filename, |
20 | 'name' => $filename, | 26 | $filename, |
21 | 'tmp_name' => __DIR__ . '/input/' . $filename, | 27 | null, |
22 | 'size' => filesize(__DIR__ . '/input/' . $filename) | 28 | filesize(__DIR__ . '/input/' . $filename) |
23 | ) | ||
24 | ); | 29 | ); |
25 | } | 30 | } |
26 | 31 | ||
@@ -28,7 +33,7 @@ function file2array($filename) | |||
28 | /** | 33 | /** |
29 | * Netscape bookmark import | 34 | * Netscape bookmark import |
30 | */ | 35 | */ |
31 | class BookmarkImportTest extends \PHPUnit\Framework\TestCase | 36 | class BookmarkImportTest extends TestCase |
32 | { | 37 | { |
33 | /** | 38 | /** |
34 | * @var string datastore to test write operations | 39 | * @var string datastore to test write operations |
@@ -41,9 +46,9 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
41 | protected static $historyFilePath = 'sandbox/history.php'; | 46 | protected static $historyFilePath = 'sandbox/history.php'; |
42 | 47 | ||
43 | /** | 48 | /** |
44 | * @var LinkDB private LinkDB instance | 49 | * @var BookmarkFileService private LinkDB instance |
45 | */ | 50 | */ |
46 | protected $linkDb = null; | 51 | protected $bookmarkService = null; |
47 | 52 | ||
48 | /** | 53 | /** |
49 | * @var string Dummy page cache | 54 | * @var string Dummy page cache |
@@ -61,11 +66,16 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
61 | protected $history; | 66 | protected $history; |
62 | 67 | ||
63 | /** | 68 | /** |
69 | * @var NetscapeBookmarkUtils | ||
70 | */ | ||
71 | protected $netscapeBookmarkUtils; | ||
72 | |||
73 | /** | ||
64 | * @var string Save the current timezone. | 74 | * @var string Save the current timezone. |
65 | */ | 75 | */ |
66 | protected static $defaultTimeZone; | 76 | protected static $defaultTimeZone; |
67 | 77 | ||
68 | public static function setUpBeforeClass() | 78 | public static function setUpBeforeClass(): void |
69 | { | 79 | { |
70 | self::$defaultTimeZone = date_default_timezone_get(); | 80 | self::$defaultTimeZone = date_default_timezone_get(); |
71 | // Timezone without DST for test consistency | 81 | // Timezone without DST for test consistency |
@@ -75,28 +85,31 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
75 | /** | 85 | /** |
76 | * Resets test data before each test | 86 | * Resets test data before each test |
77 | */ | 87 | */ |
78 | protected function setUp() | 88 | protected function setUp(): void |
79 | { | 89 | { |
80 | if (file_exists(self::$testDatastore)) { | 90 | if (file_exists(self::$testDatastore)) { |
81 | unlink(self::$testDatastore); | 91 | unlink(self::$testDatastore); |
82 | } | 92 | } |
83 | // start with an empty datastore | 93 | // start with an empty datastore |
84 | file_put_contents(self::$testDatastore, '<?php /* S7QysKquBQA= */ ?>'); | 94 | file_put_contents(self::$testDatastore, '<?php /* S7QysKquBQA= */ ?>'); |
85 | $this->linkDb = new LinkDB(self::$testDatastore, true, false); | 95 | |
86 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | 96 | $this->conf = new ConfigManager('tests/utils/config/configJson'); |
87 | $this->conf->set('resource.page_cache', $this->pagecache); | 97 | $this->conf->set('resource.page_cache', $this->pagecache); |
98 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
88 | $this->history = new History(self::$historyFilePath); | 99 | $this->history = new History(self::$historyFilePath); |
100 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->history, true); | ||
101 | $this->netscapeBookmarkUtils = new NetscapeBookmarkUtils($this->bookmarkService, $this->conf, $this->history); | ||
89 | } | 102 | } |
90 | 103 | ||
91 | /** | 104 | /** |
92 | * Delete history file. | 105 | * Delete history file. |
93 | */ | 106 | */ |
94 | public function tearDown() | 107 | protected function tearDown(): void |
95 | { | 108 | { |
96 | @unlink(self::$historyFilePath); | 109 | @unlink(self::$historyFilePath); |
97 | } | 110 | } |
98 | 111 | ||
99 | public static function tearDownAfterClass() | 112 | public static function tearDownAfterClass(): void |
100 | { | 113 | { |
101 | date_default_timezone_set(self::$defaultTimeZone); | 114 | date_default_timezone_set(self::$defaultTimeZone); |
102 | } | 115 | } |
@@ -110,9 +123,9 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
110 | $this->assertEquals( | 123 | $this->assertEquals( |
111 | 'File empty.htm (0 bytes) has an unknown file format.' | 124 | 'File empty.htm (0 bytes) has an unknown file format.' |
112 | .' Nothing was imported.', | 125 | .' Nothing was imported.', |
113 | NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history) | 126 | $this->netscapeBookmarkUtils->import(null, $files) |
114 | ); | 127 | ); |
115 | $this->assertEquals(0, count($this->linkDb)); | 128 | $this->assertEquals(0, $this->bookmarkService->count()); |
116 | } | 129 | } |
117 | 130 | ||
118 | /** | 131 | /** |
@@ -123,9 +136,9 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
123 | $files = file2array('no_doctype.htm'); | 136 | $files = file2array('no_doctype.htm'); |
124 | $this->assertEquals( | 137 | $this->assertEquals( |
125 | 'File no_doctype.htm (350 bytes) has an unknown file format. Nothing was imported.', | 138 | 'File no_doctype.htm (350 bytes) has an unknown file format. Nothing was imported.', |
126 | NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history) | 139 | $this->netscapeBookmarkUtils->import(null, $files) |
127 | ); | 140 | ); |
128 | $this->assertEquals(0, count($this->linkDb)); | 141 | $this->assertEquals(0, $this->bookmarkService->count()); |
129 | } | 142 | } |
130 | 143 | ||
131 | /** | 144 | /** |
@@ -136,10 +149,10 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
136 | $files = file2array('lowercase_doctype.htm'); | 149 | $files = file2array('lowercase_doctype.htm'); |
137 | $this->assertStringMatchesFormat( | 150 | $this->assertStringMatchesFormat( |
138 | 'File lowercase_doctype.htm (386 bytes) was successfully processed in %d seconds:' | 151 | 'File lowercase_doctype.htm (386 bytes) was successfully processed in %d seconds:' |
139 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 152 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
140 | NetscapeBookmarkUtils::import(null, $files, $this->linkDb, $this->conf, $this->history) | 153 | $this->netscapeBookmarkUtils->import(null, $files) |
141 | ); | 154 | ); |
142 | $this->assertEquals(2, count($this->linkDb)); | 155 | $this->assertEquals(2, $this->bookmarkService->count()); |
143 | } | 156 | } |
144 | 157 | ||
145 | 158 | ||
@@ -151,25 +164,24 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
151 | $files = file2array('internet_explorer_encoding.htm'); | 164 | $files = file2array('internet_explorer_encoding.htm'); |
152 | $this->assertStringMatchesFormat( | 165 | $this->assertStringMatchesFormat( |
153 | 'File internet_explorer_encoding.htm (356 bytes) was successfully processed in %d seconds:' | 166 | 'File internet_explorer_encoding.htm (356 bytes) was successfully processed in %d seconds:' |
154 | .' 1 links imported, 0 links overwritten, 0 links skipped.', | 167 | .' 1 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
155 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) | 168 | $this->netscapeBookmarkUtils->import([], $files) |
156 | ); | 169 | ); |
157 | $this->assertEquals(1, count($this->linkDb)); | 170 | $this->assertEquals(1, $this->bookmarkService->count()); |
158 | $this->assertEquals(0, count_private($this->linkDb)); | 171 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
159 | 172 | ||
173 | $bookmark = $this->bookmarkService->findByUrl('http://hginit.com/'); | ||
174 | $this->assertEquals(0, $bookmark->getId()); | ||
160 | $this->assertEquals( | 175 | $this->assertEquals( |
161 | array( | 176 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160618_203944'), |
162 | 'id' => 0, | 177 | $bookmark->getCreated() |
163 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160618_203944'), | ||
164 | 'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky', | ||
165 | 'url' => 'http://hginit.com/', | ||
166 | 'description' => '', | ||
167 | 'private' => 0, | ||
168 | 'tags' => '', | ||
169 | 'shorturl' => 'La37cg', | ||
170 | ), | ||
171 | $this->linkDb->getLinkFromUrl('http://hginit.com/') | ||
172 | ); | 178 | ); |
179 | $this->assertEquals('Hg Init a Mercurial tutorial by Joel Spolsky', $bookmark->getTitle()); | ||
180 | $this->assertEquals('http://hginit.com/', $bookmark->getUrl()); | ||
181 | $this->assertEquals('', $bookmark->getDescription()); | ||
182 | $this->assertFalse($bookmark->isPrivate()); | ||
183 | $this->assertEquals('', $bookmark->getTagsString()); | ||
184 | $this->assertEquals('La37cg', $bookmark->getShortUrl()); | ||
173 | } | 185 | } |
174 | 186 | ||
175 | /** | 187 | /** |
@@ -180,116 +192,115 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
180 | $files = file2array('netscape_nested.htm'); | 192 | $files = file2array('netscape_nested.htm'); |
181 | $this->assertStringMatchesFormat( | 193 | $this->assertStringMatchesFormat( |
182 | 'File netscape_nested.htm (1337 bytes) was successfully processed in %d seconds:' | 194 | 'File netscape_nested.htm (1337 bytes) was successfully processed in %d seconds:' |
183 | .' 8 links imported, 0 links overwritten, 0 links skipped.', | 195 | .' 8 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
184 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) | 196 | $this->netscapeBookmarkUtils->import([], $files) |
185 | ); | ||
186 | $this->assertEquals(8, count($this->linkDb)); | ||
187 | $this->assertEquals(2, count_private($this->linkDb)); | ||
188 | |||
189 | $this->assertEquals( | ||
190 | array( | ||
191 | 'id' => 0, | ||
192 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235541'), | ||
193 | 'title' => 'Nested 1', | ||
194 | 'url' => 'http://nest.ed/1', | ||
195 | 'description' => '', | ||
196 | 'private' => 0, | ||
197 | 'tags' => 'tag1 tag2', | ||
198 | 'shorturl' => 'KyDNKA', | ||
199 | ), | ||
200 | $this->linkDb->getLinkFromUrl('http://nest.ed/1') | ||
201 | ); | ||
202 | $this->assertEquals( | ||
203 | array( | ||
204 | 'id' => 1, | ||
205 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235542'), | ||
206 | 'title' => 'Nested 1-1', | ||
207 | 'url' => 'http://nest.ed/1-1', | ||
208 | 'description' => '', | ||
209 | 'private' => 0, | ||
210 | 'tags' => 'folder1 tag1 tag2', | ||
211 | 'shorturl' => 'T2LnXg', | ||
212 | ), | ||
213 | $this->linkDb->getLinkFromUrl('http://nest.ed/1-1') | ||
214 | ); | ||
215 | $this->assertEquals( | ||
216 | array( | ||
217 | 'id' => 2, | ||
218 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235547'), | ||
219 | 'title' => 'Nested 1-2', | ||
220 | 'url' => 'http://nest.ed/1-2', | ||
221 | 'description' => '', | ||
222 | 'private' => 0, | ||
223 | 'tags' => 'folder1 tag3 tag4', | ||
224 | 'shorturl' => '46SZxA', | ||
225 | ), | ||
226 | $this->linkDb->getLinkFromUrl('http://nest.ed/1-2') | ||
227 | ); | ||
228 | $this->assertEquals( | ||
229 | array( | ||
230 | 'id' => 3, | ||
231 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'), | ||
232 | 'title' => 'Nested 2-1', | ||
233 | 'url' => 'http://nest.ed/2-1', | ||
234 | 'description' => 'First link of the second section', | ||
235 | 'private' => 1, | ||
236 | 'tags' => 'folder2', | ||
237 | 'shorturl' => '4UHOSw', | ||
238 | ), | ||
239 | $this->linkDb->getLinkFromUrl('http://nest.ed/2-1') | ||
240 | ); | ||
241 | $this->assertEquals( | ||
242 | array( | ||
243 | 'id' => 4, | ||
244 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'), | ||
245 | 'title' => 'Nested 2-2', | ||
246 | 'url' => 'http://nest.ed/2-2', | ||
247 | 'description' => 'Second link of the second section', | ||
248 | 'private' => 1, | ||
249 | 'tags' => 'folder2', | ||
250 | 'shorturl' => 'yfzwbw', | ||
251 | ), | ||
252 | $this->linkDb->getLinkFromUrl('http://nest.ed/2-2') | ||
253 | ); | ||
254 | $this->assertEquals( | ||
255 | array( | ||
256 | 'id' => 5, | ||
257 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'), | ||
258 | 'title' => 'Nested 3-1', | ||
259 | 'url' => 'http://nest.ed/3-1', | ||
260 | 'description' => '', | ||
261 | 'private' => 0, | ||
262 | 'tags' => 'folder3 folder3-1 tag3', | ||
263 | 'shorturl' => 'UwxIUQ', | ||
264 | ), | ||
265 | $this->linkDb->getLinkFromUrl('http://nest.ed/3-1') | ||
266 | ); | ||
267 | $this->assertEquals( | ||
268 | array( | ||
269 | 'id' => 6, | ||
270 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'), | ||
271 | 'title' => 'Nested 3-2', | ||
272 | 'url' => 'http://nest.ed/3-2', | ||
273 | 'description' => '', | ||
274 | 'private' => 0, | ||
275 | 'tags' => 'folder3 folder3-1', | ||
276 | 'shorturl' => 'p8dyZg', | ||
277 | ), | ||
278 | $this->linkDb->getLinkFromUrl('http://nest.ed/3-2') | ||
279 | ); | ||
280 | $this->assertEquals( | ||
281 | array( | ||
282 | 'id' => 7, | ||
283 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160229_111541'), | ||
284 | 'title' => 'Nested 2', | ||
285 | 'url' => 'http://nest.ed/2', | ||
286 | 'description' => '', | ||
287 | 'private' => 0, | ||
288 | 'tags' => 'tag4', | ||
289 | 'shorturl' => 'Gt3Uug', | ||
290 | ), | ||
291 | $this->linkDb->getLinkFromUrl('http://nest.ed/2') | ||
292 | ); | 197 | ); |
198 | $this->assertEquals(8, $this->bookmarkService->count()); | ||
199 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
200 | |||
201 | $bookmark = $this->bookmarkService->findByUrl('http://nest.ed/1'); | ||
202 | $this->assertEquals(0, $bookmark->getId()); | ||
203 | $this->assertEquals( | ||
204 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160225_235541'), | ||
205 | $bookmark->getCreated() | ||
206 | ); | ||
207 | $this->assertEquals('Nested 1', $bookmark->getTitle()); | ||
208 | $this->assertEquals('http://nest.ed/1', $bookmark->getUrl()); | ||
209 | $this->assertEquals('', $bookmark->getDescription()); | ||
210 | $this->assertFalse($bookmark->isPrivate()); | ||
211 | $this->assertEquals('tag1 tag2', $bookmark->getTagsString()); | ||
212 | $this->assertEquals('KyDNKA', $bookmark->getShortUrl()); | ||
213 | |||
214 | $bookmark = $this->bookmarkService->findByUrl('http://nest.ed/1-1'); | ||
215 | $this->assertEquals(1, $bookmark->getId()); | ||
216 | $this->assertEquals( | ||
217 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160225_235542'), | ||
218 | $bookmark->getCreated() | ||
219 | ); | ||
220 | $this->assertEquals('Nested 1-1', $bookmark->getTitle()); | ||
221 | $this->assertEquals('http://nest.ed/1-1', $bookmark->getUrl()); | ||
222 | $this->assertEquals('', $bookmark->getDescription()); | ||
223 | $this->assertFalse($bookmark->isPrivate()); | ||
224 | $this->assertEquals('folder1 tag1 tag2', $bookmark->getTagsString()); | ||
225 | $this->assertEquals('T2LnXg', $bookmark->getShortUrl()); | ||
226 | |||
227 | $bookmark = $this->bookmarkService->findByUrl('http://nest.ed/1-2'); | ||
228 | $this->assertEquals(2, $bookmark->getId()); | ||
229 | $this->assertEquals( | ||
230 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160225_235547'), | ||
231 | $bookmark->getCreated() | ||
232 | ); | ||
233 | $this->assertEquals('Nested 1-2', $bookmark->getTitle()); | ||
234 | $this->assertEquals('http://nest.ed/1-2', $bookmark->getUrl()); | ||
235 | $this->assertEquals('', $bookmark->getDescription()); | ||
236 | $this->assertFalse($bookmark->isPrivate()); | ||
237 | $this->assertEquals('folder1 tag3 tag4', $bookmark->getTagsString()); | ||
238 | $this->assertEquals('46SZxA', $bookmark->getShortUrl()); | ||
239 | |||
240 | $bookmark = $this->bookmarkService->findByUrl('http://nest.ed/2-1'); | ||
241 | $this->assertEquals(3, $bookmark->getId()); | ||
242 | $this->assertEquals( | ||
243 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160202_202222'), | ||
244 | $bookmark->getCreated() | ||
245 | ); | ||
246 | $this->assertEquals('Nested 2-1', $bookmark->getTitle()); | ||
247 | $this->assertEquals('http://nest.ed/2-1', $bookmark->getUrl()); | ||
248 | $this->assertEquals('First link of the second section', $bookmark->getDescription()); | ||
249 | $this->assertTrue($bookmark->isPrivate()); | ||
250 | $this->assertEquals('folder2', $bookmark->getTagsString()); | ||
251 | $this->assertEquals('4UHOSw', $bookmark->getShortUrl()); | ||
252 | |||
253 | $bookmark = $this->bookmarkService->findByUrl('http://nest.ed/2-2'); | ||
254 | $this->assertEquals(4, $bookmark->getId()); | ||
255 | $this->assertEquals( | ||
256 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160119_230227'), | ||
257 | $bookmark->getCreated() | ||
258 | ); | ||
259 | $this->assertEquals('Nested 2-2', $bookmark->getTitle()); | ||
260 | $this->assertEquals('http://nest.ed/2-2', $bookmark->getUrl()); | ||
261 | $this->assertEquals('Second link of the second section', $bookmark->getDescription()); | ||
262 | $this->assertTrue($bookmark->isPrivate()); | ||
263 | $this->assertEquals('folder2', $bookmark->getTagsString()); | ||
264 | $this->assertEquals('yfzwbw', $bookmark->getShortUrl()); | ||
265 | |||
266 | $bookmark = $this->bookmarkService->findByUrl('http://nest.ed/3-1'); | ||
267 | $this->assertEquals(5, $bookmark->getId()); | ||
268 | $this->assertEquals( | ||
269 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160202_202222'), | ||
270 | $bookmark->getCreated() | ||
271 | ); | ||
272 | $this->assertEquals('Nested 3-1', $bookmark->getTitle()); | ||
273 | $this->assertEquals('http://nest.ed/3-1', $bookmark->getUrl()); | ||
274 | $this->assertEquals('', $bookmark->getDescription()); | ||
275 | $this->assertFalse($bookmark->isPrivate()); | ||
276 | $this->assertEquals('folder3 folder3-1 tag3', $bookmark->getTagsString()); | ||
277 | $this->assertEquals('UwxIUQ', $bookmark->getShortUrl()); | ||
278 | |||
279 | $bookmark = $this->bookmarkService->findByUrl('http://nest.ed/3-2'); | ||
280 | $this->assertEquals(6, $bookmark->getId()); | ||
281 | $this->assertEquals( | ||
282 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160119_230227'), | ||
283 | $bookmark->getCreated() | ||
284 | ); | ||
285 | $this->assertEquals('Nested 3-2', $bookmark->getTitle()); | ||
286 | $this->assertEquals('http://nest.ed/3-2', $bookmark->getUrl()); | ||
287 | $this->assertEquals('', $bookmark->getDescription()); | ||
288 | $this->assertFalse($bookmark->isPrivate()); | ||
289 | $this->assertEquals('folder3 folder3-1', $bookmark->getTagsString()); | ||
290 | $this->assertEquals('p8dyZg', $bookmark->getShortUrl()); | ||
291 | |||
292 | $bookmark = $this->bookmarkService->findByUrl('http://nest.ed/2'); | ||
293 | $this->assertEquals(7, $bookmark->getId()); | ||
294 | $this->assertEquals( | ||
295 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160229_111541'), | ||
296 | $bookmark->getCreated() | ||
297 | ); | ||
298 | $this->assertEquals('Nested 2', $bookmark->getTitle()); | ||
299 | $this->assertEquals('http://nest.ed/2', $bookmark->getUrl()); | ||
300 | $this->assertEquals('', $bookmark->getDescription()); | ||
301 | $this->assertFalse($bookmark->isPrivate()); | ||
302 | $this->assertEquals('tag4', $bookmark->getTagsString()); | ||
303 | $this->assertEquals('Gt3Uug', $bookmark->getShortUrl()); | ||
293 | } | 304 | } |
294 | 305 | ||
295 | /** | 306 | /** |
@@ -302,40 +313,38 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
302 | $files = file2array('netscape_basic.htm'); | 313 | $files = file2array('netscape_basic.htm'); |
303 | $this->assertStringMatchesFormat( | 314 | $this->assertStringMatchesFormat( |
304 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 315 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
305 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 316 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
306 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) | 317 | $this->netscapeBookmarkUtils->import([], $files) |
307 | ); | 318 | ); |
308 | 319 | ||
309 | $this->assertEquals(2, count($this->linkDb)); | 320 | $this->assertEquals(2, $this->bookmarkService->count()); |
310 | $this->assertEquals(1, count_private($this->linkDb)); | 321 | $this->assertEquals(1, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
311 | 322 | ||
323 | $bookmark = $this->bookmarkService->findByUrl('https://private.tld'); | ||
324 | $this->assertEquals(0, $bookmark->getId()); | ||
312 | $this->assertEquals( | 325 | $this->assertEquals( |
313 | array( | 326 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20001010_135536'), |
314 | 'id' => 0, | 327 | $bookmark->getCreated() |
315 | // Old link - UTC+4 (note that TZ in the import file is ignored). | ||
316 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'), | ||
317 | 'title' => 'Secret stuff', | ||
318 | 'url' => 'https://private.tld', | ||
319 | 'description' => "Super-secret stuff you're not supposed to know about", | ||
320 | 'private' => 1, | ||
321 | 'tags' => 'private secret', | ||
322 | 'shorturl' => 'EokDtA', | ||
323 | ), | ||
324 | $this->linkDb->getLinkFromUrl('https://private.tld') | ||
325 | ); | 328 | ); |
329 | $this->assertEquals('Secret stuff', $bookmark->getTitle()); | ||
330 | $this->assertEquals('https://private.tld', $bookmark->getUrl()); | ||
331 | $this->assertEquals('Super-secret stuff you\'re not supposed to know about', $bookmark->getDescription()); | ||
332 | $this->assertTrue($bookmark->isPrivate()); | ||
333 | $this->assertEquals('private secret', $bookmark->getTagsString()); | ||
334 | $this->assertEquals('EokDtA', $bookmark->getShortUrl()); | ||
335 | |||
336 | $bookmark = $this->bookmarkService->findByUrl('http://public.tld'); | ||
337 | $this->assertEquals(1, $bookmark->getId()); | ||
326 | $this->assertEquals( | 338 | $this->assertEquals( |
327 | array( | 339 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160225_235548'), |
328 | 'id' => 1, | 340 | $bookmark->getCreated() |
329 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'), | ||
330 | 'title' => 'Public stuff', | ||
331 | 'url' => 'http://public.tld', | ||
332 | 'description' => '', | ||
333 | 'private' => 0, | ||
334 | 'tags' => 'public hello world', | ||
335 | 'shorturl' => 'Er9ddA', | ||
336 | ), | ||
337 | $this->linkDb->getLinkFromUrl('http://public.tld') | ||
338 | ); | 341 | ); |
342 | $this->assertEquals('Public stuff', $bookmark->getTitle()); | ||
343 | $this->assertEquals('http://public.tld', $bookmark->getUrl()); | ||
344 | $this->assertEquals('', $bookmark->getDescription()); | ||
345 | $this->assertFalse($bookmark->isPrivate()); | ||
346 | $this->assertEquals('public hello world', $bookmark->getTagsString()); | ||
347 | $this->assertEquals('Er9ddA', $bookmark->getShortUrl()); | ||
339 | } | 348 | } |
340 | 349 | ||
341 | /** | 350 | /** |
@@ -347,43 +356,42 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
347 | $files = file2array('netscape_basic.htm'); | 356 | $files = file2array('netscape_basic.htm'); |
348 | $this->assertStringMatchesFormat( | 357 | $this->assertStringMatchesFormat( |
349 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 358 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
350 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 359 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
351 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 360 | $this->netscapeBookmarkUtils->import($post, $files) |
352 | ); | 361 | ); |
353 | $this->assertEquals(2, count($this->linkDb)); | ||
354 | $this->assertEquals(1, count_private($this->linkDb)); | ||
355 | 362 | ||
363 | $this->assertEquals(2, $this->bookmarkService->count()); | ||
364 | $this->assertEquals(1, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
365 | |||
366 | $bookmark = $this->bookmarkService->findByUrl('https://private.tld'); | ||
367 | $this->assertEquals(0, $bookmark->getId()); | ||
356 | $this->assertEquals( | 368 | $this->assertEquals( |
357 | array( | 369 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20001010_135536'), |
358 | 'id' => 0, | 370 | $bookmark->getCreated() |
359 | // Note that TZ in the import file is ignored. | ||
360 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'), | ||
361 | 'title' => 'Secret stuff', | ||
362 | 'url' => 'https://private.tld', | ||
363 | 'description' => "Super-secret stuff you're not supposed to know about", | ||
364 | 'private' => 1, | ||
365 | 'tags' => 'private secret', | ||
366 | 'shorturl' => 'EokDtA', | ||
367 | ), | ||
368 | $this->linkDb->getLinkFromUrl('https://private.tld') | ||
369 | ); | 371 | ); |
372 | $this->assertEquals('Secret stuff', $bookmark->getTitle()); | ||
373 | $this->assertEquals('https://private.tld', $bookmark->getUrl()); | ||
374 | $this->assertEquals('Super-secret stuff you\'re not supposed to know about', $bookmark->getDescription()); | ||
375 | $this->assertTrue($bookmark->isPrivate()); | ||
376 | $this->assertEquals('private secret', $bookmark->getTagsString()); | ||
377 | $this->assertEquals('EokDtA', $bookmark->getShortUrl()); | ||
378 | |||
379 | $bookmark = $this->bookmarkService->findByUrl('http://public.tld'); | ||
380 | $this->assertEquals(1, $bookmark->getId()); | ||
370 | $this->assertEquals( | 381 | $this->assertEquals( |
371 | array( | 382 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160225_235548'), |
372 | 'id' => 1, | 383 | $bookmark->getCreated() |
373 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'), | ||
374 | 'title' => 'Public stuff', | ||
375 | 'url' => 'http://public.tld', | ||
376 | 'description' => '', | ||
377 | 'private' => 0, | ||
378 | 'tags' => 'public hello world', | ||
379 | 'shorturl' => 'Er9ddA', | ||
380 | ), | ||
381 | $this->linkDb->getLinkFromUrl('http://public.tld') | ||
382 | ); | 384 | ); |
385 | $this->assertEquals('Public stuff', $bookmark->getTitle()); | ||
386 | $this->assertEquals('http://public.tld', $bookmark->getUrl()); | ||
387 | $this->assertEquals('', $bookmark->getDescription()); | ||
388 | $this->assertFalse($bookmark->isPrivate()); | ||
389 | $this->assertEquals('public hello world', $bookmark->getTagsString()); | ||
390 | $this->assertEquals('Er9ddA', $bookmark->getShortUrl()); | ||
383 | } | 391 | } |
384 | 392 | ||
385 | /** | 393 | /** |
386 | * Import links as public | 394 | * Import bookmarks as public |
387 | */ | 395 | */ |
388 | public function testImportAsPublic() | 396 | public function testImportAsPublic() |
389 | { | 397 | { |
@@ -391,23 +399,17 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
391 | $files = file2array('netscape_basic.htm'); | 399 | $files = file2array('netscape_basic.htm'); |
392 | $this->assertStringMatchesFormat( | 400 | $this->assertStringMatchesFormat( |
393 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 401 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
394 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 402 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
395 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 403 | $this->netscapeBookmarkUtils->import($post, $files) |
396 | ); | ||
397 | $this->assertEquals(2, count($this->linkDb)); | ||
398 | $this->assertEquals(0, count_private($this->linkDb)); | ||
399 | $this->assertEquals( | ||
400 | 0, | ||
401 | $this->linkDb[0]['private'] | ||
402 | ); | ||
403 | $this->assertEquals( | ||
404 | 0, | ||
405 | $this->linkDb[1]['private'] | ||
406 | ); | 404 | ); |
405 | $this->assertEquals(2, $this->bookmarkService->count()); | ||
406 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
407 | $this->assertFalse($this->bookmarkService->get(0)->isPrivate()); | ||
408 | $this->assertFalse($this->bookmarkService->get(1)->isPrivate()); | ||
407 | } | 409 | } |
408 | 410 | ||
409 | /** | 411 | /** |
410 | * Import links as private | 412 | * Import bookmarks as private |
411 | */ | 413 | */ |
412 | public function testImportAsPrivate() | 414 | public function testImportAsPrivate() |
413 | { | 415 | { |
@@ -415,45 +417,34 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
415 | $files = file2array('netscape_basic.htm'); | 417 | $files = file2array('netscape_basic.htm'); |
416 | $this->assertStringMatchesFormat( | 418 | $this->assertStringMatchesFormat( |
417 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 419 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
418 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 420 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
419 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 421 | $this->netscapeBookmarkUtils->import($post, $files) |
420 | ); | ||
421 | $this->assertEquals(2, count($this->linkDb)); | ||
422 | $this->assertEquals(2, count_private($this->linkDb)); | ||
423 | $this->assertEquals( | ||
424 | 1, | ||
425 | $this->linkDb['0']['private'] | ||
426 | ); | ||
427 | $this->assertEquals( | ||
428 | 1, | ||
429 | $this->linkDb['1']['private'] | ||
430 | ); | 422 | ); |
423 | $this->assertEquals(2, $this->bookmarkService->count()); | ||
424 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
425 | $this->assertTrue($this->bookmarkService->get(0)->isPrivate()); | ||
426 | $this->assertTrue($this->bookmarkService->get(1)->isPrivate()); | ||
431 | } | 427 | } |
432 | 428 | ||
433 | /** | 429 | /** |
434 | * Overwrite private links so they become public | 430 | * Overwrite private bookmarks so they become public |
435 | */ | 431 | */ |
436 | public function testOverwriteAsPublic() | 432 | public function testOverwriteAsPublic() |
437 | { | 433 | { |
438 | $files = file2array('netscape_basic.htm'); | 434 | $files = file2array('netscape_basic.htm'); |
439 | 435 | ||
440 | // import links as private | 436 | // import bookmarks as private |
441 | $post = array('privacy' => 'private'); | 437 | $post = array('privacy' => 'private'); |
442 | $this->assertStringMatchesFormat( | 438 | $this->assertStringMatchesFormat( |
443 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 439 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
444 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 440 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
445 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 441 | $this->netscapeBookmarkUtils->import($post, $files) |
446 | ); | ||
447 | $this->assertEquals(2, count($this->linkDb)); | ||
448 | $this->assertEquals(2, count_private($this->linkDb)); | ||
449 | $this->assertEquals( | ||
450 | 1, | ||
451 | $this->linkDb[0]['private'] | ||
452 | ); | ||
453 | $this->assertEquals( | ||
454 | 1, | ||
455 | $this->linkDb[1]['private'] | ||
456 | ); | 442 | ); |
443 | $this->assertEquals(2, $this->bookmarkService->count()); | ||
444 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
445 | $this->assertTrue($this->bookmarkService->get(0)->isPrivate()); | ||
446 | $this->assertTrue($this->bookmarkService->get(1)->isPrivate()); | ||
447 | |||
457 | // re-import as public, enable overwriting | 448 | // re-import as public, enable overwriting |
458 | $post = array( | 449 | $post = array( |
459 | 'privacy' => 'public', | 450 | 'privacy' => 'public', |
@@ -461,45 +452,33 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
461 | ); | 452 | ); |
462 | $this->assertStringMatchesFormat( | 453 | $this->assertStringMatchesFormat( |
463 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 454 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
464 | .' 2 links imported, 2 links overwritten, 0 links skipped.', | 455 | .' 2 bookmarks imported, 2 bookmarks overwritten, 0 bookmarks skipped.', |
465 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 456 | $this->netscapeBookmarkUtils->import($post, $files) |
466 | ); | ||
467 | $this->assertEquals(2, count($this->linkDb)); | ||
468 | $this->assertEquals(0, count_private($this->linkDb)); | ||
469 | $this->assertEquals( | ||
470 | 0, | ||
471 | $this->linkDb[0]['private'] | ||
472 | ); | ||
473 | $this->assertEquals( | ||
474 | 0, | ||
475 | $this->linkDb[1]['private'] | ||
476 | ); | 457 | ); |
458 | $this->assertEquals(2, $this->bookmarkService->count()); | ||
459 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
460 | $this->assertFalse($this->bookmarkService->get(0)->isPrivate()); | ||
461 | $this->assertFalse($this->bookmarkService->get(1)->isPrivate()); | ||
477 | } | 462 | } |
478 | 463 | ||
479 | /** | 464 | /** |
480 | * Overwrite public links so they become private | 465 | * Overwrite public bookmarks so they become private |
481 | */ | 466 | */ |
482 | public function testOverwriteAsPrivate() | 467 | public function testOverwriteAsPrivate() |
483 | { | 468 | { |
484 | $files = file2array('netscape_basic.htm'); | 469 | $files = file2array('netscape_basic.htm'); |
485 | 470 | ||
486 | // import links as public | 471 | // import bookmarks as public |
487 | $post = array('privacy' => 'public'); | 472 | $post = array('privacy' => 'public'); |
488 | $this->assertStringMatchesFormat( | 473 | $this->assertStringMatchesFormat( |
489 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 474 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
490 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 475 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
491 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 476 | $this->netscapeBookmarkUtils->import($post, $files) |
492 | ); | ||
493 | $this->assertEquals(2, count($this->linkDb)); | ||
494 | $this->assertEquals(0, count_private($this->linkDb)); | ||
495 | $this->assertEquals( | ||
496 | 0, | ||
497 | $this->linkDb['0']['private'] | ||
498 | ); | ||
499 | $this->assertEquals( | ||
500 | 0, | ||
501 | $this->linkDb['1']['private'] | ||
502 | ); | 477 | ); |
478 | $this->assertEquals(2, $this->bookmarkService->count()); | ||
479 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
480 | $this->assertFalse($this->bookmarkService->get(0)->isPrivate()); | ||
481 | $this->assertFalse($this->bookmarkService->get(1)->isPrivate()); | ||
503 | 482 | ||
504 | // re-import as private, enable overwriting | 483 | // re-import as private, enable overwriting |
505 | $post = array( | 484 | $post = array( |
@@ -508,23 +487,17 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
508 | ); | 487 | ); |
509 | $this->assertStringMatchesFormat( | 488 | $this->assertStringMatchesFormat( |
510 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 489 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
511 | .' 2 links imported, 2 links overwritten, 0 links skipped.', | 490 | .' 2 bookmarks imported, 2 bookmarks overwritten, 0 bookmarks skipped.', |
512 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 491 | $this->netscapeBookmarkUtils->import($post, $files) |
513 | ); | ||
514 | $this->assertEquals(2, count($this->linkDb)); | ||
515 | $this->assertEquals(2, count_private($this->linkDb)); | ||
516 | $this->assertEquals( | ||
517 | 1, | ||
518 | $this->linkDb['0']['private'] | ||
519 | ); | ||
520 | $this->assertEquals( | ||
521 | 1, | ||
522 | $this->linkDb['1']['private'] | ||
523 | ); | 492 | ); |
493 | $this->assertEquals(2, $this->bookmarkService->count()); | ||
494 | $this->assertEquals(2, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
495 | $this->assertTrue($this->bookmarkService->get(0)->isPrivate()); | ||
496 | $this->assertTrue($this->bookmarkService->get(1)->isPrivate()); | ||
524 | } | 497 | } |
525 | 498 | ||
526 | /** | 499 | /** |
527 | * Attept to import the same links twice without enabling overwriting | 500 | * Attept to import the same bookmarks twice without enabling overwriting |
528 | */ | 501 | */ |
529 | public function testSkipOverwrite() | 502 | public function testSkipOverwrite() |
530 | { | 503 | { |
@@ -532,21 +505,21 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
532 | $files = file2array('netscape_basic.htm'); | 505 | $files = file2array('netscape_basic.htm'); |
533 | $this->assertStringMatchesFormat( | 506 | $this->assertStringMatchesFormat( |
534 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 507 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
535 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 508 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
536 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 509 | $this->netscapeBookmarkUtils->import($post, $files) |
537 | ); | 510 | ); |
538 | $this->assertEquals(2, count($this->linkDb)); | 511 | $this->assertEquals(2, $this->bookmarkService->count()); |
539 | $this->assertEquals(0, count_private($this->linkDb)); | 512 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
540 | 513 | ||
541 | // re-import as private, DO NOT enable overwriting | 514 | // re-import as private, DO NOT enable overwriting |
542 | $post = array('privacy' => 'private'); | 515 | $post = array('privacy' => 'private'); |
543 | $this->assertStringMatchesFormat( | 516 | $this->assertStringMatchesFormat( |
544 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 517 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
545 | .' 0 links imported, 0 links overwritten, 2 links skipped.', | 518 | .' 0 bookmarks imported, 0 bookmarks overwritten, 2 bookmarks skipped.', |
546 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 519 | $this->netscapeBookmarkUtils->import($post, $files) |
547 | ); | 520 | ); |
548 | $this->assertEquals(2, count($this->linkDb)); | 521 | $this->assertEquals(2, $this->bookmarkService->count()); |
549 | $this->assertEquals(0, count_private($this->linkDb)); | 522 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
550 | } | 523 | } |
551 | 524 | ||
552 | /** | 525 | /** |
@@ -561,19 +534,13 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
561 | $files = file2array('netscape_basic.htm'); | 534 | $files = file2array('netscape_basic.htm'); |
562 | $this->assertStringMatchesFormat( | 535 | $this->assertStringMatchesFormat( |
563 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 536 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
564 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 537 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
565 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 538 | $this->netscapeBookmarkUtils->import($post, $files) |
566 | ); | ||
567 | $this->assertEquals(2, count($this->linkDb)); | ||
568 | $this->assertEquals(0, count_private($this->linkDb)); | ||
569 | $this->assertEquals( | ||
570 | 'tag1 tag2 tag3 private secret', | ||
571 | $this->linkDb['0']['tags'] | ||
572 | ); | ||
573 | $this->assertEquals( | ||
574 | 'tag1 tag2 tag3 public hello world', | ||
575 | $this->linkDb['1']['tags'] | ||
576 | ); | 539 | ); |
540 | $this->assertEquals(2, $this->bookmarkService->count()); | ||
541 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); | ||
542 | $this->assertEquals('tag1 tag2 tag3 private secret', $this->bookmarkService->get(0)->getTagsString()); | ||
543 | $this->assertEquals('tag1 tag2 tag3 public hello world', $this->bookmarkService->get(1)->getTagsString()); | ||
577 | } | 544 | } |
578 | 545 | ||
579 | /** | 546 | /** |
@@ -588,18 +555,18 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
588 | $files = file2array('netscape_basic.htm'); | 555 | $files = file2array('netscape_basic.htm'); |
589 | $this->assertStringMatchesFormat( | 556 | $this->assertStringMatchesFormat( |
590 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' | 557 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
591 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 558 | .' 2 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
592 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 559 | $this->netscapeBookmarkUtils->import($post, $files) |
593 | ); | 560 | ); |
594 | $this->assertEquals(2, count($this->linkDb)); | 561 | $this->assertEquals(2, $this->bookmarkService->count()); |
595 | $this->assertEquals(0, count_private($this->linkDb)); | 562 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
596 | $this->assertEquals( | 563 | $this->assertEquals( |
597 | 'tag1& tag2 "tag3" private secret', | 564 | 'tag1& tag2 "tag3" private secret', |
598 | $this->linkDb['0']['tags'] | 565 | $this->bookmarkService->get(0)->getTagsString() |
599 | ); | 566 | ); |
600 | $this->assertEquals( | 567 | $this->assertEquals( |
601 | 'tag1& tag2 "tag3" public hello world', | 568 | 'tag1& tag2 "tag3" public hello world', |
602 | $this->linkDb['1']['tags'] | 569 | $this->bookmarkService->get(1)->getTagsString() |
603 | ); | 570 | ); |
604 | } | 571 | } |
605 | 572 | ||
@@ -613,23 +580,14 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
613 | $files = file2array('same_date.htm'); | 580 | $files = file2array('same_date.htm'); |
614 | $this->assertStringMatchesFormat( | 581 | $this->assertStringMatchesFormat( |
615 | 'File same_date.htm (453 bytes) was successfully processed in %d seconds:' | 582 | 'File same_date.htm (453 bytes) was successfully processed in %d seconds:' |
616 | .' 3 links imported, 0 links overwritten, 0 links skipped.', | 583 | .' 3 bookmarks imported, 0 bookmarks overwritten, 0 bookmarks skipped.', |
617 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history) | 584 | $this->netscapeBookmarkUtils->import(array(), $files) |
618 | ); | 585 | ); |
619 | $this->assertEquals(3, count($this->linkDb)); | 586 | $this->assertEquals(3, $this->bookmarkService->count()); |
620 | $this->assertEquals(0, count_private($this->linkDb)); | 587 | $this->assertEquals(0, $this->bookmarkService->count(BookmarkFilter::$PRIVATE)); |
621 | $this->assertEquals( | 588 | $this->assertEquals(0, $this->bookmarkService->get(0)->getId()); |
622 | 0, | 589 | $this->assertEquals(1, $this->bookmarkService->get(1)->getId()); |
623 | $this->linkDb[0]['id'] | 590 | $this->assertEquals(2, $this->bookmarkService->get(2)->getId()); |
624 | ); | ||
625 | $this->assertEquals( | ||
626 | 1, | ||
627 | $this->linkDb[1]['id'] | ||
628 | ); | ||
629 | $this->assertEquals( | ||
630 | 2, | ||
631 | $this->linkDb[2]['id'] | ||
632 | ); | ||
633 | } | 591 | } |
634 | 592 | ||
635 | public function testImportCreateUpdateHistory() | 593 | public function testImportCreateUpdateHistory() |
@@ -639,14 +597,14 @@ class BookmarkImportTest extends \PHPUnit\Framework\TestCase | |||
639 | 'overwrite' => 'true', | 597 | 'overwrite' => 'true', |
640 | ]; | 598 | ]; |
641 | $files = file2array('netscape_basic.htm'); | 599 | $files = file2array('netscape_basic.htm'); |
642 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); | 600 | $this->netscapeBookmarkUtils->import($post, $files); |
643 | $history = $this->history->getHistory(); | 601 | $history = $this->history->getHistory(); |
644 | $this->assertEquals(1, count($history)); | 602 | $this->assertEquals(1, count($history)); |
645 | $this->assertEquals(History::IMPORT, $history[0]['event']); | 603 | $this->assertEquals(History::IMPORT, $history[0]['event']); |
646 | $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']); | 604 | $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']); |
647 | 605 | ||
648 | // re-import as private, enable overwriting | 606 | // re-import as private, enable overwriting |
649 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); | 607 | $this->netscapeBookmarkUtils->import($post, $files); |
650 | $history = $this->history->getHistory(); | 608 | $history = $this->history->getHistory(); |
651 | $this->assertEquals(2, count($history)); | 609 | $this->assertEquals(2, count($history)); |
652 | $this->assertEquals(History::IMPORT, $history[0]['event']); | 610 | $this->assertEquals(History::IMPORT, $history[0]['event']); |
diff --git a/tests/plugins/PluginAddlinkTest.php b/tests/plugins/PluginAddlinkTest.php index d052f8b9..a3ec9fc9 100644 --- a/tests/plugins/PluginAddlinkTest.php +++ b/tests/plugins/PluginAddlinkTest.php | |||
@@ -2,19 +2,19 @@ | |||
2 | namespace Shaarli\Plugin\Addlink; | 2 | namespace Shaarli\Plugin\Addlink; |
3 | 3 | ||
4 | use Shaarli\Plugin\PluginManager; | 4 | use Shaarli\Plugin\PluginManager; |
5 | use Shaarli\Router; | 5 | use Shaarli\Render\TemplatePage; |
6 | 6 | ||
7 | require_once 'plugins/addlink_toolbar/addlink_toolbar.php'; | 7 | require_once 'plugins/addlink_toolbar/addlink_toolbar.php'; |
8 | 8 | ||
9 | /** | 9 | /** |
10 | * Unit test for the Addlink toolbar plugin | 10 | * Unit test for the Addlink toolbar plugin |
11 | */ | 11 | */ |
12 | class PluginAddlinkTest extends \PHPUnit\Framework\TestCase | 12 | class PluginAddlinkTest extends \Shaarli\TestCase |
13 | { | 13 | { |
14 | /** | 14 | /** |
15 | * Reset plugin path. | 15 | * Reset plugin path. |
16 | */ | 16 | */ |
17 | public function setUp() | 17 | protected function setUp(): void |
18 | { | 18 | { |
19 | PluginManager::$PLUGINS_PATH = 'plugins'; | 19 | PluginManager::$PLUGINS_PATH = 'plugins'; |
20 | } | 20 | } |
@@ -26,8 +26,9 @@ class PluginAddlinkTest extends \PHPUnit\Framework\TestCase | |||
26 | { | 26 | { |
27 | $str = 'stuff'; | 27 | $str = 'stuff'; |
28 | $data = array($str => $str); | 28 | $data = array($str => $str); |
29 | $data['_PAGE_'] = Router::$PAGE_LINKLIST; | 29 | $data['_PAGE_'] = TemplatePage::LINKLIST; |
30 | $data['_LOGGEDIN_'] = true; | 30 | $data['_LOGGEDIN_'] = true; |
31 | $data['_BASE_PATH_'] = '/subfolder'; | ||
31 | 32 | ||
32 | $data = hook_addlink_toolbar_render_header($data); | 33 | $data = hook_addlink_toolbar_render_header($data); |
33 | $this->assertEquals($str, $data[$str]); | 34 | $this->assertEquals($str, $data[$str]); |
@@ -36,6 +37,8 @@ class PluginAddlinkTest extends \PHPUnit\Framework\TestCase | |||
36 | $data = array($str => $str); | 37 | $data = array($str => $str); |
37 | $data['_PAGE_'] = $str; | 38 | $data['_PAGE_'] = $str; |
38 | $data['_LOGGEDIN_'] = true; | 39 | $data['_LOGGEDIN_'] = true; |
40 | $data['_BASE_PATH_'] = '/subfolder'; | ||
41 | |||
39 | $data = hook_addlink_toolbar_render_header($data); | 42 | $data = hook_addlink_toolbar_render_header($data); |
40 | $this->assertEquals($str, $data[$str]); | 43 | $this->assertEquals($str, $data[$str]); |
41 | $this->assertArrayNotHasKey('fields_toolbar', $data); | 44 | $this->assertArrayNotHasKey('fields_toolbar', $data); |
@@ -48,8 +51,9 @@ class PluginAddlinkTest extends \PHPUnit\Framework\TestCase | |||
48 | { | 51 | { |
49 | $str = 'stuff'; | 52 | $str = 'stuff'; |
50 | $data = array($str => $str); | 53 | $data = array($str => $str); |
51 | $data['_PAGE_'] = Router::$PAGE_LINKLIST; | 54 | $data['_PAGE_'] = TemplatePage::LINKLIST; |
52 | $data['_LOGGEDIN_'] = false; | 55 | $data['_LOGGEDIN_'] = false; |
56 | $data['_BASE_PATH_'] = '/subfolder'; | ||
53 | 57 | ||
54 | $data = hook_addlink_toolbar_render_header($data); | 58 | $data = hook_addlink_toolbar_render_header($data); |
55 | $this->assertEquals($str, $data[$str]); | 59 | $this->assertEquals($str, $data[$str]); |
diff --git a/tests/plugins/PluginArchiveorgTest.php b/tests/plugins/PluginArchiveorgTest.php index 510288bb..467dc3d0 100644 --- a/tests/plugins/PluginArchiveorgTest.php +++ b/tests/plugins/PluginArchiveorgTest.php | |||
@@ -1,4 +1,5 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
2 | namespace Shaarli\Plugin\Archiveorg; | 3 | namespace Shaarli\Plugin\Archiveorg; |
3 | 4 | ||
4 | /** | 5 | /** |
@@ -6,6 +7,7 @@ namespace Shaarli\Plugin\Archiveorg; | |||
6 | */ | 7 | */ |
7 | 8 | ||
8 | use Shaarli\Plugin\PluginManager; | 9 | use Shaarli\Plugin\PluginManager; |
10 | use Shaarli\TestCase; | ||
9 | 11 | ||
10 | require_once 'plugins/archiveorg/archiveorg.php'; | 12 | require_once 'plugins/archiveorg/archiveorg.php'; |
11 | 13 | ||
@@ -13,20 +15,35 @@ require_once 'plugins/archiveorg/archiveorg.php'; | |||
13 | * Class PluginArchiveorgTest | 15 | * Class PluginArchiveorgTest |
14 | * Unit test for the archiveorg plugin | 16 | * Unit test for the archiveorg plugin |
15 | */ | 17 | */ |
16 | class PluginArchiveorgTest extends \PHPUnit\Framework\TestCase | 18 | class PluginArchiveorgTest extends TestCase |
17 | { | 19 | { |
20 | protected $savedScriptName; | ||
21 | |||
18 | /** | 22 | /** |
19 | * Reset plugin path | 23 | * Reset plugin path |
20 | */ | 24 | */ |
21 | public function setUp() | 25 | public function setUp(): void |
22 | { | 26 | { |
23 | PluginManager::$PLUGINS_PATH = 'plugins'; | 27 | PluginManager::$PLUGINS_PATH = 'plugins'; |
28 | |||
29 | // plugins manipulate global vars | ||
30 | $_SERVER['SERVER_PORT'] = '80'; | ||
31 | $_SERVER['SERVER_NAME'] = 'shaarli.shaarli'; | ||
32 | $this->savedScriptName = $_SERVER['SCRIPT_NAME'] ?? null; | ||
33 | $_SERVER['SCRIPT_NAME'] = '/index.php'; | ||
34 | } | ||
35 | |||
36 | public function tearDown(): void | ||
37 | { | ||
38 | unset($_SERVER['SERVER_PORT']); | ||
39 | unset($_SERVER['SERVER_NAME']); | ||
40 | $_SERVER['SCRIPT_NAME'] = $this->savedScriptName; | ||
24 | } | 41 | } |
25 | 42 | ||
26 | /** | 43 | /** |
27 | * Test render_linklist hook on external links. | 44 | * Test render_linklist hook on external bookmarks. |
28 | */ | 45 | */ |
29 | public function testArchiveorgLinklistOnExternalLinks() | 46 | public function testArchiveorgLinklistOnExternalLinks(): void |
30 | { | 47 | { |
31 | $str = 'http://randomstr.com/test'; | 48 | $str = 'http://randomstr.com/test'; |
32 | 49 | ||
@@ -54,18 +71,18 @@ class PluginArchiveorgTest extends \PHPUnit\Framework\TestCase | |||
54 | } | 71 | } |
55 | 72 | ||
56 | /** | 73 | /** |
57 | * Test render_linklist hook on internal links. | 74 | * Test render_linklist hook on internal bookmarks. |
58 | */ | 75 | */ |
59 | public function testArchiveorgLinklistOnInternalLinks() | 76 | public function testArchiveorgLinklistOnInternalLinks(): void |
60 | { | 77 | { |
61 | $internalLink1 = 'http://shaarli.shaarli/?qvMAqg'; | 78 | $internalLink1 = 'http://shaarli.shaarli/shaare/qvMAqg'; |
62 | $internalLinkRealURL1 = '?qvMAqg'; | 79 | $internalLinkRealURL1 = '/shaare/qvMAqg'; |
63 | 80 | ||
64 | $internalLink2 = 'http://shaarli.shaarli/?2_7zww'; | 81 | $internalLink2 = 'http://shaarli.shaarli/shaare/2_7zww'; |
65 | $internalLinkRealURL2 = '?2_7zww'; | 82 | $internalLinkRealURL2 = '/shaare/2_7zww'; |
66 | 83 | ||
67 | $internalLink3 = 'http://shaarli.shaarli/?z7u-_Q'; | 84 | $internalLink3 = 'http://shaarli.shaarli/shaare/z7u-_Q'; |
68 | $internalLinkRealURL3 = '?z7u-_Q'; | 85 | $internalLinkRealURL3 = '/shaare/z7u-_Q'; |
69 | 86 | ||
70 | $data = array( | 87 | $data = array( |
71 | 'title' => $internalLink1, | 88 | 'title' => $internalLink1, |
diff --git a/tests/plugins/PluginDefaultColorsTest.php b/tests/plugins/PluginDefaultColorsTest.php index b9951cca..cc844c60 100644 --- a/tests/plugins/PluginDefaultColorsTest.php +++ b/tests/plugins/PluginDefaultColorsTest.php | |||
@@ -2,11 +2,10 @@ | |||
2 | 2 | ||
3 | namespace Shaarli\Plugin\DefaultColors; | 3 | namespace Shaarli\Plugin\DefaultColors; |
4 | 4 | ||
5 | use DateTime; | ||
6 | use PHPUnit\Framework\TestCase; | ||
7 | use Shaarli\Bookmark\LinkDB; | 5 | use Shaarli\Bookmark\LinkDB; |
8 | use Shaarli\Config\ConfigManager; | 6 | use Shaarli\Config\ConfigManager; |
9 | use Shaarli\Plugin\PluginManager; | 7 | use Shaarli\Plugin\PluginManager; |
8 | use Shaarli\TestCase; | ||
10 | 9 | ||
11 | require_once 'plugins/default_colors/default_colors.php'; | 10 | require_once 'plugins/default_colors/default_colors.php'; |
12 | 11 | ||
@@ -20,7 +19,7 @@ class PluginDefaultColorsTest extends TestCase | |||
20 | /** | 19 | /** |
21 | * Reset plugin path | 20 | * Reset plugin path |
22 | */ | 21 | */ |
23 | public function setUp() | 22 | protected function setUp(): void |
24 | { | 23 | { |
25 | PluginManager::$PLUGINS_PATH = 'sandbox'; | 24 | PluginManager::$PLUGINS_PATH = 'sandbox'; |
26 | mkdir(PluginManager::$PLUGINS_PATH . '/default_colors/'); | 25 | mkdir(PluginManager::$PLUGINS_PATH . '/default_colors/'); |
@@ -33,7 +32,7 @@ class PluginDefaultColorsTest extends TestCase | |||
33 | /** | 32 | /** |
34 | * Remove sandbox files and folder | 33 | * Remove sandbox files and folder |
35 | */ | 34 | */ |
36 | public function tearDown() | 35 | protected function tearDown(): void |
37 | { | 36 | { |
38 | if (file_exists('sandbox/default_colors/default_colors.css.template')) { | 37 | if (file_exists('sandbox/default_colors/default_colors.css.template')) { |
39 | unlink('sandbox/default_colors/default_colors.css.template'); | 38 | unlink('sandbox/default_colors/default_colors.css.template'); |
@@ -57,6 +56,8 @@ class PluginDefaultColorsTest extends TestCase | |||
57 | $conf->set('plugins.DEFAULT_COLORS_BACKGROUND', 'value'); | 56 | $conf->set('plugins.DEFAULT_COLORS_BACKGROUND', 'value'); |
58 | $errors = default_colors_init($conf); | 57 | $errors = default_colors_init($conf); |
59 | $this->assertEmpty($errors); | 58 | $this->assertEmpty($errors); |
59 | |||
60 | $this->assertFileExists($file = 'sandbox/default_colors/default_colors.css'); | ||
60 | } | 61 | } |
61 | 62 | ||
62 | /** | 63 | /** |
@@ -72,9 +73,9 @@ class PluginDefaultColorsTest extends TestCase | |||
72 | /** | 73 | /** |
73 | * Test the save plugin parameters hook with all colors specified. | 74 | * Test the save plugin parameters hook with all colors specified. |
74 | */ | 75 | */ |
75 | public function testSavePluginParametersAll() | 76 | public function testGenerateCssFile() |
76 | { | 77 | { |
77 | $post = [ | 78 | $params = [ |
78 | 'other1' => true, | 79 | 'other1' => true, |
79 | 'DEFAULT_COLORS_MAIN' => 'blue', | 80 | 'DEFAULT_COLORS_MAIN' => 'blue', |
80 | 'DEFAULT_COLORS_BACKGROUND' => 'pink', | 81 | 'DEFAULT_COLORS_BACKGROUND' => 'pink', |
@@ -82,7 +83,7 @@ class PluginDefaultColorsTest extends TestCase | |||
82 | 'DEFAULT_COLORS_DARK_MAIN' => 'green', | 83 | 'DEFAULT_COLORS_DARK_MAIN' => 'green', |
83 | ]; | 84 | ]; |
84 | 85 | ||
85 | hook_default_colors_save_plugin_parameters($post); | 86 | default_colors_generate_css_file($params); |
86 | $this->assertFileExists($file = 'sandbox/default_colors/default_colors.css'); | 87 | $this->assertFileExists($file = 'sandbox/default_colors/default_colors.css'); |
87 | $content = file_get_contents($file); | 88 | $content = file_get_contents($file); |
88 | $expected = ':root { | 89 | $expected = ':root { |
@@ -98,16 +99,16 @@ class PluginDefaultColorsTest extends TestCase | |||
98 | /** | 99 | /** |
99 | * Test the save plugin parameters hook with only one color specified. | 100 | * Test the save plugin parameters hook with only one color specified. |
100 | */ | 101 | */ |
101 | public function testSavePluginParametersSingle() | 102 | public function testGenerateCssFileSingle() |
102 | { | 103 | { |
103 | $post = [ | 104 | $params = [ |
104 | 'other1' => true, | 105 | 'other1' => true, |
105 | 'DEFAULT_COLORS_BACKGROUND' => 'pink', | 106 | 'DEFAULT_COLORS_BACKGROUND' => 'pink', |
106 | 'other2' => ['yep'], | 107 | 'other2' => ['yep'], |
107 | 'DEFAULT_COLORS_DARK_MAIN' => '', | 108 | 'DEFAULT_COLORS_DARK_MAIN' => '', |
108 | ]; | 109 | ]; |
109 | 110 | ||
110 | hook_default_colors_save_plugin_parameters($post); | 111 | default_colors_generate_css_file($params); |
111 | $this->assertFileExists($file = 'sandbox/default_colors/default_colors.css'); | 112 | $this->assertFileExists($file = 'sandbox/default_colors/default_colors.css'); |
112 | $content = file_get_contents($file); | 113 | $content = file_get_contents($file); |
113 | $expected = ':root { | 114 | $expected = ':root { |
@@ -121,9 +122,9 @@ class PluginDefaultColorsTest extends TestCase | |||
121 | /** | 122 | /** |
122 | * Test the save plugin parameters hook with no color specified. | 123 | * Test the save plugin parameters hook with no color specified. |
123 | */ | 124 | */ |
124 | public function testSavePluginParametersNone() | 125 | public function testGenerateCssFileNone() |
125 | { | 126 | { |
126 | hook_default_colors_save_plugin_parameters([]); | 127 | default_colors_generate_css_file([]); |
127 | $this->assertFileNotExists($file = 'sandbox/default_colors/default_colors.css'); | 128 | $this->assertFileNotExists($file = 'sandbox/default_colors/default_colors.css'); |
128 | } | 129 | } |
129 | 130 | ||
diff --git a/tests/plugins/PluginIssoTest.php b/tests/plugins/PluginIssoTest.php index bdfab439..16ecf357 100644 --- a/tests/plugins/PluginIssoTest.php +++ b/tests/plugins/PluginIssoTest.php | |||
@@ -2,9 +2,10 @@ | |||
2 | namespace Shaarli\Plugin\Isso; | 2 | namespace Shaarli\Plugin\Isso; |
3 | 3 | ||
4 | use DateTime; | 4 | use DateTime; |
5 | use Shaarli\Bookmark\LinkDB; | 5 | use Shaarli\Bookmark\Bookmark; |
6 | use Shaarli\Config\ConfigManager; | 6 | use Shaarli\Config\ConfigManager; |
7 | use Shaarli\Plugin\PluginManager; | 7 | use Shaarli\Plugin\PluginManager; |
8 | use Shaarli\TestCase; | ||
8 | 9 | ||
9 | require_once 'plugins/isso/isso.php'; | 10 | require_once 'plugins/isso/isso.php'; |
10 | 11 | ||
@@ -13,12 +14,12 @@ require_once 'plugins/isso/isso.php'; | |||
13 | * | 14 | * |
14 | * Test the Isso plugin (comment system). | 15 | * Test the Isso plugin (comment system). |
15 | */ | 16 | */ |
16 | class PluginIssoTest extends \PHPUnit\Framework\TestCase | 17 | class PluginIssoTest extends TestCase |
17 | { | 18 | { |
18 | /** | 19 | /** |
19 | * Reset plugin path | 20 | * Reset plugin path |
20 | */ | 21 | */ |
21 | public function setUp() | 22 | public function setUp(): void |
22 | { | 23 | { |
23 | PluginManager::$PLUGINS_PATH = 'plugins'; | 24 | PluginManager::$PLUGINS_PATH = 'plugins'; |
24 | } | 25 | } |
@@ -26,7 +27,7 @@ class PluginIssoTest extends \PHPUnit\Framework\TestCase | |||
26 | /** | 27 | /** |
27 | * Test Isso init without errors. | 28 | * Test Isso init without errors. |
28 | */ | 29 | */ |
29 | public function testIssoInitNoError() | 30 | public function testIssoInitNoError(): void |
30 | { | 31 | { |
31 | $conf = new ConfigManager(''); | 32 | $conf = new ConfigManager(''); |
32 | $conf->set('plugins.ISSO_SERVER', 'value'); | 33 | $conf->set('plugins.ISSO_SERVER', 'value'); |
@@ -37,7 +38,7 @@ class PluginIssoTest extends \PHPUnit\Framework\TestCase | |||
37 | /** | 38 | /** |
38 | * Test Isso init with errors. | 39 | * Test Isso init with errors. |
39 | */ | 40 | */ |
40 | public function testIssoInitError() | 41 | public function testIssoInitError(): void |
41 | { | 42 | { |
42 | $conf = new ConfigManager(''); | 43 | $conf = new ConfigManager(''); |
43 | $errors = isso_init($conf); | 44 | $errors = isso_init($conf); |
@@ -47,7 +48,7 @@ class PluginIssoTest extends \PHPUnit\Framework\TestCase | |||
47 | /** | 48 | /** |
48 | * Test render_linklist hook with valid settings to display the comment form. | 49 | * Test render_linklist hook with valid settings to display the comment form. |
49 | */ | 50 | */ |
50 | public function testIssoDisplayed() | 51 | public function testIssoDisplayed(): void |
51 | { | 52 | { |
52 | $conf = new ConfigManager(''); | 53 | $conf = new ConfigManager(''); |
53 | $conf->set('plugins.ISSO_SERVER', 'value'); | 54 | $conf->set('plugins.ISSO_SERVER', 'value'); |
@@ -60,7 +61,7 @@ class PluginIssoTest extends \PHPUnit\Framework\TestCase | |||
60 | array( | 61 | array( |
61 | 'id' => 12, | 62 | 'id' => 12, |
62 | 'url' => $str, | 63 | 'url' => $str, |
63 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date), | 64 | 'created' => DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $date), |
64 | ) | 65 | ) |
65 | ) | 66 | ) |
66 | ); | 67 | ); |
@@ -85,9 +86,9 @@ class PluginIssoTest extends \PHPUnit\Framework\TestCase | |||
85 | } | 86 | } |
86 | 87 | ||
87 | /** | 88 | /** |
88 | * Test isso plugin when multiple links are displayed (shouldn't be displayed). | 89 | * Test isso plugin when multiple bookmarks are displayed (shouldn't be displayed). |
89 | */ | 90 | */ |
90 | public function testIssoMultipleLinks() | 91 | public function testIssoMultipleLinks(): void |
91 | { | 92 | { |
92 | $conf = new ConfigManager(''); | 93 | $conf = new ConfigManager(''); |
93 | $conf->set('plugins.ISSO_SERVER', 'value'); | 94 | $conf->set('plugins.ISSO_SERVER', 'value'); |
@@ -102,27 +103,27 @@ class PluginIssoTest extends \PHPUnit\Framework\TestCase | |||
102 | 'id' => 12, | 103 | 'id' => 12, |
103 | 'url' => $str, | 104 | 'url' => $str, |
104 | 'shorturl' => $short1 = 'abcd', | 105 | 'shorturl' => $short1 = 'abcd', |
105 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date1), | 106 | 'created' => DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $date1), |
106 | ), | 107 | ), |
107 | array( | 108 | array( |
108 | 'id' => 13, | 109 | 'id' => 13, |
109 | 'url' => $str . '2', | 110 | 'url' => $str . '2', |
110 | 'shorturl' => $short2 = 'efgh', | 111 | 'shorturl' => $short2 = 'efgh', |
111 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date2), | 112 | 'created' => DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $date2), |
112 | ), | 113 | ), |
113 | ) | 114 | ) |
114 | ); | 115 | ); |
115 | 116 | ||
116 | $processed = hook_isso_render_linklist($data, $conf); | 117 | $processed = hook_isso_render_linklist($data, $conf); |
117 | // link_plugin should be added for the icon | 118 | // link_plugin should be added for the icon |
118 | $this->assertContains('<a href="?'. $short1 .'#isso-thread">', $processed['links'][0]['link_plugin'][0]); | 119 | $this->assertContainsPolyfill('<a href="/shaare/'. $short1 .'#isso-thread">', $processed['links'][0]['link_plugin'][0]); |
119 | $this->assertContains('<a href="?'. $short2 .'#isso-thread">', $processed['links'][1]['link_plugin'][0]); | 120 | $this->assertContainsPolyfill('<a href="/shaare/'. $short2 .'#isso-thread">', $processed['links'][1]['link_plugin'][0]); |
120 | } | 121 | } |
121 | 122 | ||
122 | /** | 123 | /** |
123 | * Test isso plugin when using search (shouldn't be displayed). | 124 | * Test isso plugin when using search (shouldn't be displayed). |
124 | */ | 125 | */ |
125 | public function testIssoNotDisplayedWhenSearch() | 126 | public function testIssoNotDisplayedWhenSearch(): void |
126 | { | 127 | { |
127 | $conf = new ConfigManager(''); | 128 | $conf = new ConfigManager(''); |
128 | $conf->set('plugins.ISSO_SERVER', 'value'); | 129 | $conf->set('plugins.ISSO_SERVER', 'value'); |
@@ -136,7 +137,7 @@ class PluginIssoTest extends \PHPUnit\Framework\TestCase | |||
136 | 'id' => 12, | 137 | 'id' => 12, |
137 | 'url' => $str, | 138 | 'url' => $str, |
138 | 'shorturl' => $short1 = 'abcd', | 139 | 'shorturl' => $short1 = 'abcd', |
139 | 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date), | 140 | 'created' => DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $date), |
140 | ) | 141 | ) |
141 | ), | 142 | ), |
142 | 'search_term' => $str | 143 | 'search_term' => $str |
@@ -145,13 +146,13 @@ class PluginIssoTest extends \PHPUnit\Framework\TestCase | |||
145 | $processed = hook_isso_render_linklist($data, $conf); | 146 | $processed = hook_isso_render_linklist($data, $conf); |
146 | 147 | ||
147 | // link_plugin should be added for the icon | 148 | // link_plugin should be added for the icon |
148 | $this->assertContains('<a href="?'. $short1 .'#isso-thread">', $processed['links'][0]['link_plugin'][0]); | 149 | $this->assertContainsPolyfill('<a href="/shaare/'. $short1 .'#isso-thread">', $processed['links'][0]['link_plugin'][0]); |
149 | } | 150 | } |
150 | 151 | ||
151 | /** | 152 | /** |
152 | * Test isso plugin without server configuration (shouldn't be displayed). | 153 | * Test isso plugin without server configuration (shouldn't be displayed). |
153 | */ | 154 | */ |
154 | public function testIssoWithoutConf() | 155 | public function testIssoWithoutConf(): void |
155 | { | 156 | { |
156 | $data = 'abc'; | 157 | $data = 'abc'; |
157 | $conf = new ConfigManager(''); | 158 | $conf = new ConfigManager(''); |
diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php deleted file mode 100644 index 9ddbc558..00000000 --- a/tests/plugins/PluginMarkdownTest.php +++ /dev/null | |||
@@ -1,306 +0,0 @@ | |||
1 | <?php | ||
2 | namespace Shaarli\Plugin\Markdown; | ||
3 | |||
4 | use Shaarli\Config\ConfigManager; | ||
5 | use Shaarli\Plugin\PluginManager; | ||
6 | |||
7 | /** | ||
8 | * PluginMarkdownTest.php | ||
9 | */ | ||
10 | |||
11 | require_once 'application/bookmark/LinkUtils.php'; | ||
12 | require_once 'application/Utils.php'; | ||
13 | require_once 'plugins/markdown/markdown.php'; | ||
14 | |||
15 | /** | ||
16 | * Class PluginMarkdownTest | ||
17 | * Unit test for the Markdown plugin | ||
18 | */ | ||
19 | class PluginMarkdownTest extends \PHPUnit\Framework\TestCase | ||
20 | { | ||
21 | /** | ||
22 | * @var ConfigManager instance. | ||
23 | */ | ||
24 | protected $conf; | ||
25 | |||
26 | /** | ||
27 | * Reset plugin path | ||
28 | */ | ||
29 | public function setUp() | ||
30 | { | ||
31 | PluginManager::$PLUGINS_PATH = 'plugins'; | ||
32 | $this->conf = new ConfigManager('tests/utils/config/configJson'); | ||
33 | $this->conf->set('security.allowed_protocols', ['ftp', 'magnet']); | ||
34 | } | ||
35 | |||
36 | /** | ||
37 | * Test render_linklist hook. | ||
38 | * Only check that there is basic markdown rendering. | ||
39 | */ | ||
40 | public function testMarkdownLinklist() | ||
41 | { | ||
42 | $markdown = '# My title' . PHP_EOL . 'Very interesting content.'; | ||
43 | $data = array( | ||
44 | 'links' => array( | ||
45 | 0 => array( | ||
46 | 'description' => $markdown, | ||
47 | ), | ||
48 | ), | ||
49 | ); | ||
50 | |||
51 | $data = hook_markdown_render_linklist($data, $this->conf); | ||
52 | $this->assertNotFalse(strpos($data['links'][0]['description'], '<h1>')); | ||
53 | $this->assertNotFalse(strpos($data['links'][0]['description'], '<p>')); | ||
54 | |||
55 | $this->assertEquals($markdown, $data['links'][0]['description_src']); | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * Test render_feed hook. | ||
60 | */ | ||
61 | public function testMarkdownFeed() | ||
62 | { | ||
63 | $markdown = '# My title' . PHP_EOL . 'Very interesting content.'; | ||
64 | $markdown .= '— <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>'; | ||
65 | $data = array( | ||
66 | 'links' => array( | ||
67 | 0 => array( | ||
68 | 'description' => $markdown, | ||
69 | ), | ||
70 | ), | ||
71 | ); | ||
72 | |||
73 | $data = hook_markdown_render_feed($data, $this->conf); | ||
74 | $this->assertNotFalse(strpos($data['links'][0]['description'], '<h1>')); | ||
75 | $this->assertNotFalse(strpos($data['links'][0]['description'], '<p>')); | ||
76 | $this->assertStringEndsWith( | ||
77 | '— <a href="http://domain.tld/?0oc_VQ">Permalien</a></p></div>', | ||
78 | $data['links'][0]['description'] | ||
79 | ); | ||
80 | } | ||
81 | |||
82 | /** | ||
83 | * Test render_daily hook. | ||
84 | * Only check that there is basic markdown rendering. | ||
85 | */ | ||
86 | public function testMarkdownDaily() | ||
87 | { | ||
88 | $markdown = '# My title' . PHP_EOL . 'Very interesting content.'; | ||
89 | $data = array( | ||
90 | // Columns data | ||
91 | 'linksToDisplay' => array( | ||
92 | // nth link | ||
93 | 0 => array( | ||
94 | 'formatedDescription' => $markdown, | ||
95 | ), | ||
96 | ), | ||
97 | ); | ||
98 | |||
99 | $data = hook_markdown_render_daily($data, $this->conf); | ||
100 | $this->assertNotFalse(strpos($data['linksToDisplay'][0]['formatedDescription'], '<h1>')); | ||
101 | $this->assertNotFalse(strpos($data['linksToDisplay'][0]['formatedDescription'], '<p>')); | ||
102 | } | ||
103 | |||
104 | /** | ||
105 | * Test reverse_text2clickable(). | ||
106 | */ | ||
107 | public function testReverseText2clickable() | ||
108 | { | ||
109 | $text = 'stuff http://hello.there/is=someone#here otherstuff'; | ||
110 | $clickableText = text2clickable($text); | ||
111 | $reversedText = reverse_text2clickable($clickableText); | ||
112 | $this->assertEquals($text, $reversedText); | ||
113 | } | ||
114 | |||
115 | /** | ||
116 | * Test reverse_text2clickable(). | ||
117 | */ | ||
118 | public function testReverseText2clickableHashtags() | ||
119 | { | ||
120 | $text = file_get_contents('tests/plugins/resources/hashtags.raw'); | ||
121 | $md = file_get_contents('tests/plugins/resources/hashtags.md'); | ||
122 | $clickableText = hashtag_autolink($text); | ||
123 | $reversedText = reverse_text2clickable($clickableText); | ||
124 | $this->assertEquals($md, $reversedText); | ||
125 | } | ||
126 | |||
127 | /** | ||
128 | * Test reverse_nl2br(). | ||
129 | */ | ||
130 | public function testReverseNl2br() | ||
131 | { | ||
132 | $text = 'stuff' . PHP_EOL . 'otherstuff'; | ||
133 | $processedText = nl2br($text); | ||
134 | $reversedText = reverse_nl2br($processedText); | ||
135 | $this->assertEquals($text, $reversedText); | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Test reverse_space2nbsp(). | ||
140 | */ | ||
141 | public function testReverseSpace2nbsp() | ||
142 | { | ||
143 | $text = ' stuff' . PHP_EOL . ' otherstuff and another'; | ||
144 | $processedText = space2nbsp($text); | ||
145 | $reversedText = reverse_space2nbsp($processedText); | ||
146 | $this->assertEquals($text, $reversedText); | ||
147 | } | ||
148 | |||
149 | public function testReverseFeedPermalink() | ||
150 | { | ||
151 | $text = 'Description... '; | ||
152 | $text .= '— <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>'; | ||
153 | $expected = 'Description... — [Permalien](http://domain.tld/?0oc_VQ)'; | ||
154 | $processedText = reverse_feed_permalink($text); | ||
155 | |||
156 | $this->assertEquals($expected, $processedText); | ||
157 | } | ||
158 | |||
159 | public function testReverseLastFeedPermalink() | ||
160 | { | ||
161 | $text = 'Description... '; | ||
162 | $text .= '<br>— <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>'; | ||
163 | $expected = $text; | ||
164 | $text .= '<br>— <a href="http://domain.tld/?0oc_VQ" title="Permalien">Permalien</a>'; | ||
165 | $expected .= '<br>— [Permalien](http://domain.tld/?0oc_VQ)'; | ||
166 | $processedText = reverse_feed_permalink($text); | ||
167 | |||
168 | $this->assertEquals($expected, $processedText); | ||
169 | } | ||
170 | |||
171 | public function testReverseNoFeedPermalink() | ||
172 | { | ||
173 | $text = 'Hello! Where are you from?'; | ||
174 | $expected = $text; | ||
175 | $processedText = reverse_feed_permalink($text); | ||
176 | |||
177 | $this->assertEquals($expected, $processedText); | ||
178 | } | ||
179 | |||
180 | /** | ||
181 | * Test sanitize_html(). | ||
182 | */ | ||
183 | public function testSanitizeHtml() | ||
184 | { | ||
185 | $input = '< script src="js.js"/>'; | ||
186 | $input .= '< script attr>alert(\'xss\');</script>'; | ||
187 | $input .= '<style> * { display: none }</style>'; | ||
188 | $output = escape($input); | ||
189 | $input .= '<a href="#" onmouseHover="alert(\'xss\');" attr="tt">link</a>'; | ||
190 | $output .= '<a href="#" attr="tt">link</a>'; | ||
191 | $input .= '<a href="#" onmouseHover=alert(\'xss\'); attr="tt">link</a>'; | ||
192 | $output .= '<a href="#" attr="tt">link</a>'; | ||
193 | $this->assertEquals($output, sanitize_html($input)); | ||
194 | // Do not touch escaped HTML. | ||
195 | $input = escape($input); | ||
196 | $this->assertEquals($input, sanitize_html($input)); | ||
197 | } | ||
198 | |||
199 | /** | ||
200 | * Test the no markdown tag. | ||
201 | */ | ||
202 | public function testNoMarkdownTag() | ||
203 | { | ||
204 | $str = 'All _work_ and `no play` makes Jack a *dull* boy.'; | ||
205 | $data = array( | ||
206 | 'links' => array(array( | ||
207 | 'description' => $str, | ||
208 | 'tags' => NO_MD_TAG, | ||
209 | 'taglist' => array(NO_MD_TAG), | ||
210 | )) | ||
211 | ); | ||
212 | |||
213 | $processed = hook_markdown_render_linklist($data, $this->conf); | ||
214 | $this->assertEquals($str, $processed['links'][0]['description']); | ||
215 | |||
216 | $processed = hook_markdown_render_feed($data, $this->conf); | ||
217 | $this->assertEquals($str, $processed['links'][0]['description']); | ||
218 | |||
219 | $data = array( | ||
220 | // Columns data | ||
221 | 'linksToDisplay' => array( | ||
222 | // nth link | ||
223 | 0 => array( | ||
224 | 'formatedDescription' => $str, | ||
225 | 'tags' => NO_MD_TAG, | ||
226 | 'taglist' => array(), | ||
227 | ), | ||
228 | ), | ||
229 | ); | ||
230 | |||
231 | $data = hook_markdown_render_daily($data, $this->conf); | ||
232 | $this->assertEquals($str, $data['linksToDisplay'][0]['formatedDescription']); | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * Test that a close value to nomarkdown is not understand as nomarkdown (previous value `.nomarkdown`). | ||
237 | */ | ||
238 | public function testNoMarkdownNotExcactlyMatching() | ||
239 | { | ||
240 | $str = 'All _work_ and `no play` makes Jack a *dull* boy.'; | ||
241 | $data = array( | ||
242 | 'links' => array(array( | ||
243 | 'description' => $str, | ||
244 | 'tags' => '.' . NO_MD_TAG, | ||
245 | 'taglist' => array('.'. NO_MD_TAG), | ||
246 | )) | ||
247 | ); | ||
248 | |||
249 | $data = hook_markdown_render_feed($data, $this->conf); | ||
250 | $this->assertContains('<em>', $data['links'][0]['description']); | ||
251 | } | ||
252 | |||
253 | /** | ||
254 | * Make sure that the generated HTML match the reference HTML file. | ||
255 | */ | ||
256 | public function testMarkdownGlobalProcessDescription() | ||
257 | { | ||
258 | $md = file_get_contents('tests/plugins/resources/markdown.md'); | ||
259 | $md = format_description($md); | ||
260 | $html = file_get_contents('tests/plugins/resources/markdown.html'); | ||
261 | |||
262 | $data = process_markdown( | ||
263 | $md, | ||
264 | $this->conf->get('security.markdown_escape', true), | ||
265 | $this->conf->get('security.allowed_protocols') | ||
266 | ); | ||
267 | $this->assertEquals($html, $data . PHP_EOL); | ||
268 | } | ||
269 | |||
270 | /** | ||
271 | * Make sure that the HTML tags are escaped. | ||
272 | */ | ||
273 | public function testMarkdownWithHtmlEscape() | ||
274 | { | ||
275 | $md = '**strong** <strong>strong</strong>'; | ||
276 | $html = '<div class="markdown"><p><strong>strong</strong> <strong>strong</strong></p></div>'; | ||
277 | $data = array( | ||
278 | 'links' => array( | ||
279 | 0 => array( | ||
280 | 'description' => $md, | ||
281 | ), | ||
282 | ), | ||
283 | ); | ||
284 | $data = hook_markdown_render_linklist($data, $this->conf); | ||
285 | $this->assertEquals($html, $data['links'][0]['description']); | ||
286 | } | ||
287 | |||
288 | /** | ||
289 | * Make sure that the HTML tags aren't escaped with the setting set to false. | ||
290 | */ | ||
291 | public function testMarkdownWithHtmlNoEscape() | ||
292 | { | ||
293 | $this->conf->set('security.markdown_escape', false); | ||
294 | $md = '**strong** <strong>strong</strong>'; | ||
295 | $html = '<div class="markdown"><p><strong>strong</strong> <strong>strong</strong></p></div>'; | ||
296 | $data = array( | ||
297 | 'links' => array( | ||
298 | 0 => array( | ||
299 | 'description' => $md, | ||
300 | ), | ||
301 | ), | ||
302 | ); | ||
303 | $data = hook_markdown_render_linklist($data, $this->conf); | ||
304 | $this->assertEquals($html, $data['links'][0]['description']); | ||
305 | } | ||
306 | } | ||
diff --git a/tests/plugins/PluginPlayvideosTest.php b/tests/plugins/PluginPlayvideosTest.php index 51472617..338d2e35 100644 --- a/tests/plugins/PluginPlayvideosTest.php +++ b/tests/plugins/PluginPlayvideosTest.php | |||
@@ -6,7 +6,7 @@ namespace Shaarli\Plugin\Playvideos; | |||
6 | */ | 6 | */ |
7 | 7 | ||
8 | use Shaarli\Plugin\PluginManager; | 8 | use Shaarli\Plugin\PluginManager; |
9 | use Shaarli\Router; | 9 | use Shaarli\Render\TemplatePage; |
10 | 10 | ||
11 | require_once 'plugins/playvideos/playvideos.php'; | 11 | require_once 'plugins/playvideos/playvideos.php'; |
12 | 12 | ||
@@ -14,12 +14,12 @@ require_once 'plugins/playvideos/playvideos.php'; | |||
14 | * Class PluginPlayvideosTest | 14 | * Class PluginPlayvideosTest |
15 | * Unit test for the PlayVideos plugin | 15 | * Unit test for the PlayVideos plugin |
16 | */ | 16 | */ |
17 | class PluginPlayvideosTest extends \PHPUnit\Framework\TestCase | 17 | class PluginPlayvideosTest extends \Shaarli\TestCase |
18 | { | 18 | { |
19 | /** | 19 | /** |
20 | * Reset plugin path | 20 | * Reset plugin path |
21 | */ | 21 | */ |
22 | public function setUp() | 22 | protected function setUp(): void |
23 | { | 23 | { |
24 | PluginManager::$PLUGINS_PATH = 'plugins'; | 24 | PluginManager::$PLUGINS_PATH = 'plugins'; |
25 | } | 25 | } |
@@ -31,7 +31,7 @@ class PluginPlayvideosTest extends \PHPUnit\Framework\TestCase | |||
31 | { | 31 | { |
32 | $str = 'stuff'; | 32 | $str = 'stuff'; |
33 | $data = array($str => $str); | 33 | $data = array($str => $str); |
34 | $data['_PAGE_'] = Router::$PAGE_LINKLIST; | 34 | $data['_PAGE_'] = TemplatePage::LINKLIST; |
35 | 35 | ||
36 | $data = hook_playvideos_render_header($data); | 36 | $data = hook_playvideos_render_header($data); |
37 | $this->assertEquals($str, $data[$str]); | 37 | $this->assertEquals($str, $data[$str]); |
@@ -50,7 +50,7 @@ class PluginPlayvideosTest extends \PHPUnit\Framework\TestCase | |||
50 | { | 50 | { |
51 | $str = 'stuff'; | 51 | $str = 'stuff'; |
52 | $data = array($str => $str); | 52 | $data = array($str => $str); |
53 | $data['_PAGE_'] = Router::$PAGE_LINKLIST; | 53 | $data['_PAGE_'] = TemplatePage::LINKLIST; |
54 | 54 | ||
55 | $data = hook_playvideos_render_footer($data); | 55 | $data = hook_playvideos_render_footer($data); |
56 | $this->assertEquals($str, $data[$str]); | 56 | $this->assertEquals($str, $data[$str]); |
diff --git a/tests/plugins/PluginPubsubhubbubTest.php b/tests/plugins/PluginPubsubhubbubTest.php index a7bd8fc9..d3f7b439 100644 --- a/tests/plugins/PluginPubsubhubbubTest.php +++ b/tests/plugins/PluginPubsubhubbubTest.php | |||
@@ -3,7 +3,7 @@ namespace Shaarli\Plugin\Pubsubhubbub; | |||
3 | 3 | ||
4 | use Shaarli\Config\ConfigManager; | 4 | use Shaarli\Config\ConfigManager; |
5 | use Shaarli\Plugin\PluginManager; | 5 | use Shaarli\Plugin\PluginManager; |
6 | use Shaarli\Router; | 6 | use Shaarli\Render\TemplatePage; |
7 | 7 | ||
8 | require_once 'plugins/pubsubhubbub/pubsubhubbub.php'; | 8 | require_once 'plugins/pubsubhubbub/pubsubhubbub.php'; |
9 | 9 | ||
@@ -11,7 +11,7 @@ require_once 'plugins/pubsubhubbub/pubsubhubbub.php'; | |||
11 | * Class PluginPubsubhubbubTest | 11 | * Class PluginPubsubhubbubTest |
12 | * Unit test for the pubsubhubbub plugin | 12 | * Unit test for the pubsubhubbub plugin |
13 | */ | 13 | */ |
14 | class PluginPubsubhubbubTest extends \PHPUnit\Framework\TestCase | 14 | class PluginPubsubhubbubTest extends \Shaarli\TestCase |
15 | { | 15 | { |
16 | /** | 16 | /** |
17 | * @var string Config file path (without extension). | 17 | * @var string Config file path (without extension). |
@@ -21,7 +21,7 @@ class PluginPubsubhubbubTest extends \PHPUnit\Framework\TestCase | |||
21 | /** | 21 | /** |
22 | * Reset plugin path | 22 | * Reset plugin path |
23 | */ | 23 | */ |
24 | public function setUp() | 24 | protected function setUp(): void |
25 | { | 25 | { |
26 | PluginManager::$PLUGINS_PATH = 'plugins'; | 26 | PluginManager::$PLUGINS_PATH = 'plugins'; |
27 | } | 27 | } |
@@ -34,7 +34,7 @@ class PluginPubsubhubbubTest extends \PHPUnit\Framework\TestCase | |||
34 | $hub = 'http://domain.hub'; | 34 | $hub = 'http://domain.hub'; |
35 | $conf = new ConfigManager(self::$configFile); | 35 | $conf = new ConfigManager(self::$configFile); |
36 | $conf->set('plugins.PUBSUBHUB_URL', $hub); | 36 | $conf->set('plugins.PUBSUBHUB_URL', $hub); |
37 | $data['_PAGE_'] = Router::$PAGE_FEED_RSS; | 37 | $data['_PAGE_'] = TemplatePage::FEED_RSS; |
38 | 38 | ||
39 | $data = hook_pubsubhubbub_render_feed($data, $conf); | 39 | $data = hook_pubsubhubbub_render_feed($data, $conf); |
40 | $expected = '<atom:link rel="hub" href="'. $hub .'" />'; | 40 | $expected = '<atom:link rel="hub" href="'. $hub .'" />'; |
@@ -49,7 +49,7 @@ class PluginPubsubhubbubTest extends \PHPUnit\Framework\TestCase | |||
49 | $hub = 'http://domain.hub'; | 49 | $hub = 'http://domain.hub'; |
50 | $conf = new ConfigManager(self::$configFile); | 50 | $conf = new ConfigManager(self::$configFile); |
51 | $conf->set('plugins.PUBSUBHUB_URL', $hub); | 51 | $conf->set('plugins.PUBSUBHUB_URL', $hub); |
52 | $data['_PAGE_'] = Router::$PAGE_FEED_ATOM; | 52 | $data['_PAGE_'] = TemplatePage::FEED_ATOM; |
53 | 53 | ||
54 | $data = hook_pubsubhubbub_render_feed($data, $conf); | 54 | $data = hook_pubsubhubbub_render_feed($data, $conf); |
55 | $expected = '<link rel="hub" href="'. $hub .'" />'; | 55 | $expected = '<link rel="hub" href="'. $hub .'" />'; |
diff --git a/tests/plugins/PluginQrcodeTest.php b/tests/plugins/PluginQrcodeTest.php index 0c61e14a..1d85fba6 100644 --- a/tests/plugins/PluginQrcodeTest.php +++ b/tests/plugins/PluginQrcodeTest.php | |||
@@ -6,7 +6,7 @@ namespace Shaarli\Plugin\Qrcode; | |||
6 | */ | 6 | */ |
7 | 7 | ||
8 | use Shaarli\Plugin\PluginManager; | 8 | use Shaarli\Plugin\PluginManager; |
9 | use Shaarli\Router; | 9 | use Shaarli\Render\TemplatePage; |
10 | 10 | ||
11 | require_once 'plugins/qrcode/qrcode.php'; | 11 | require_once 'plugins/qrcode/qrcode.php'; |
12 | 12 | ||
@@ -14,12 +14,12 @@ require_once 'plugins/qrcode/qrcode.php'; | |||
14 | * Class PluginQrcodeTest | 14 | * Class PluginQrcodeTest |
15 | * Unit test for the QR-Code plugin | 15 | * Unit test for the QR-Code plugin |
16 | */ | 16 | */ |
17 | class PluginQrcodeTest extends \PHPUnit\Framework\TestCase | 17 | class PluginQrcodeTest extends \Shaarli\TestCase |
18 | { | 18 | { |
19 | /** | 19 | /** |
20 | * Reset plugin path | 20 | * Reset plugin path |
21 | */ | 21 | */ |
22 | public function setUp() | 22 | protected function setUp(): void |
23 | { | 23 | { |
24 | PluginManager::$PLUGINS_PATH = 'plugins'; | 24 | PluginManager::$PLUGINS_PATH = 'plugins'; |
25 | } | 25 | } |
@@ -57,7 +57,7 @@ class PluginQrcodeTest extends \PHPUnit\Framework\TestCase | |||
57 | { | 57 | { |
58 | $str = 'stuff'; | 58 | $str = 'stuff'; |
59 | $data = array($str => $str); | 59 | $data = array($str => $str); |
60 | $data['_PAGE_'] = Router::$PAGE_LINKLIST; | 60 | $data['_PAGE_'] = TemplatePage::LINKLIST; |
61 | 61 | ||
62 | $data = hook_qrcode_render_footer($data); | 62 | $data = hook_qrcode_render_footer($data); |
63 | $this->assertEquals($str, $data[$str]); | 63 | $this->assertEquals($str, $data[$str]); |
diff --git a/tests/plugins/PluginWallabagTest.php b/tests/plugins/PluginWallabagTest.php index 79751921..36317215 100644 --- a/tests/plugins/PluginWallabagTest.php +++ b/tests/plugins/PluginWallabagTest.php | |||
@@ -10,12 +10,12 @@ require_once 'plugins/wallabag/wallabag.php'; | |||
10 | * Class PluginWallabagTest | 10 | * Class PluginWallabagTest |
11 | * Unit test for the Wallabag plugin | 11 | * Unit test for the Wallabag plugin |
12 | */ | 12 | */ |
13 | class PluginWallabagTest extends \PHPUnit\Framework\TestCase | 13 | class PluginWallabagTest extends \Shaarli\TestCase |
14 | { | 14 | { |
15 | /** | 15 | /** |
16 | * Reset plugin path | 16 | * Reset plugin path |
17 | */ | 17 | */ |
18 | public function setUp() | 18 | protected function setUp(): void |
19 | { | 19 | { |
20 | PluginManager::$PLUGINS_PATH = 'plugins'; | 20 | PluginManager::$PLUGINS_PATH = 'plugins'; |
21 | } | 21 | } |
diff --git a/tests/plugins/WallabagInstanceTest.php b/tests/plugins/WallabagInstanceTest.php index a3cd9076..5ef3de1a 100644 --- a/tests/plugins/WallabagInstanceTest.php +++ b/tests/plugins/WallabagInstanceTest.php | |||
@@ -4,7 +4,7 @@ namespace Shaarli\Plugin\Wallabag; | |||
4 | /** | 4 | /** |
5 | * Class WallabagInstanceTest | 5 | * Class WallabagInstanceTest |
6 | */ | 6 | */ |
7 | class WallabagInstanceTest extends \PHPUnit\Framework\TestCase | 7 | class WallabagInstanceTest extends \Shaarli\TestCase |
8 | { | 8 | { |
9 | /** | 9 | /** |
10 | * @var string wallabag url. | 10 | * @var string wallabag url. |
@@ -14,7 +14,7 @@ class WallabagInstanceTest extends \PHPUnit\Framework\TestCase | |||
14 | /** | 14 | /** |
15 | * Reset plugin path | 15 | * Reset plugin path |
16 | */ | 16 | */ |
17 | public function setUp() | 17 | protected function setUp(): void |
18 | { | 18 | { |
19 | $this->instance = 'http://some.url'; | 19 | $this->instance = 'http://some.url'; |
20 | } | 20 | } |
diff --git a/tests/plugins/resources/hashtags.md b/tests/plugins/resources/hashtags.md deleted file mode 100644 index 46326de3..00000000 --- a/tests/plugins/resources/hashtags.md +++ /dev/null | |||
@@ -1,10 +0,0 @@ | |||
1 | [#lol](?addtag=lol) | ||
2 | |||
3 | #test | ||
4 | |||
5 | `#test2` | ||
6 | |||
7 | ``` | ||
8 | bla #bli blo | ||
9 | #bla | ||
10 | ``` | ||
diff --git a/tests/plugins/resources/hashtags.raw b/tests/plugins/resources/hashtags.raw deleted file mode 100644 index 9d2dc98a..00000000 --- a/tests/plugins/resources/hashtags.raw +++ /dev/null | |||
@@ -1,10 +0,0 @@ | |||
1 | #lol | ||
2 | |||
3 | #test | ||
4 | |||
5 | `#test2` | ||
6 | |||
7 | ``` | ||
8 | bla #bli blo | ||
9 | #bla | ||
10 | ``` | ||
diff --git a/tests/plugins/resources/markdown.html b/tests/plugins/resources/markdown.html deleted file mode 100644 index c3460bf7..00000000 --- a/tests/plugins/resources/markdown.html +++ /dev/null | |||
@@ -1,33 +0,0 @@ | |||
1 | <div class="markdown"><ul> | ||
2 | <li>test: | ||
3 | <ul> | ||
4 | <li><a href="http://link.tld">zero</a></li> | ||
5 | <li><a href="http://link.tld">two</a></li> | ||
6 | <li><a href="http://link.tld">three</a></li> | ||
7 | </ul></li> | ||
8 | </ul> | ||
9 | <ol> | ||
10 | <li><a href="http://link.tld">zero</a> | ||
11 | <ol start="2"> | ||
12 | <li><a href="http://link.tld">two</a></li> | ||
13 | <li><a href="http://link.tld">three</a></li> | ||
14 | <li><a href="http://link.tld">four</a></li> | ||
15 | <li>foo <a href="?addtag=foobar">#foobar</a></li> | ||
16 | </ol></li> | ||
17 | </ol> | ||
18 | <p><a href="?addtag=foobar">#foobar</a> foo <code>lol #foo</code> <a href="?addtag=bar">#bar</a></p> | ||
19 | <p>fsdfs <a href="http://link.tld">http://link.tld</a> <a href="?addtag=foobar">#foobar</a> <code>http://link.tld</code></p> | ||
20 | <pre><code>http://link.tld #foobar | ||
21 | next #foo</code></pre> | ||
22 | <p>Block:</p> | ||
23 | <pre><code>lorem ipsum #foobar http://link.tld | ||
24 | #foobar http://link.tld</code></pre> | ||
25 | <p><a href="?123456">link</a><br /> | ||
26 | <img src="/img/train.png" alt="link" /><br /> | ||
27 | <a href="http://test.tld/path/?query=value#hash">link</a><br /> | ||
28 | <a href="http://test.tld/path/?query=value#hash">link</a><br /> | ||
29 | <a href="https://test.tld/path/?query=value#hash">link</a><br /> | ||
30 | <a href="ftp://test.tld/path/?query=value#hash">link</a><br /> | ||
31 | <a href="magnet:test.tld/path/?query=value#hash">link</a><br /> | ||
32 | <a href="http://alert('xss')">link</a><br /> | ||
33 | <a href="http://test.tld/path/?query=value#hash">link</a></p></div> | ||
diff --git a/tests/plugins/resources/markdown.md b/tests/plugins/resources/markdown.md deleted file mode 100644 index 9350a8c7..00000000 --- a/tests/plugins/resources/markdown.md +++ /dev/null | |||
@@ -1,34 +0,0 @@ | |||
1 | * test: | ||
2 | * [zero](http://link.tld) | ||
3 | + [two](http://link.tld) | ||
4 | - [three](http://link.tld) | ||
5 | |||
6 | 1. [zero](http://link.tld) | ||
7 | 2. [two](http://link.tld) | ||
8 | 3. [three](http://link.tld) | ||
9 | 4. [four](http://link.tld) | ||
10 | 5. foo #foobar | ||
11 | |||
12 | #foobar foo `lol #foo` #bar | ||
13 | |||
14 | fsdfs http://link.tld #foobar `http://link.tld` | ||
15 | |||
16 | http://link.tld #foobar | ||
17 | next #foo | ||
18 | |||
19 | Block: | ||
20 | |||
21 | ``` | ||
22 | lorem ipsum #foobar http://link.tld | ||
23 | #foobar http://link.tld | ||
24 | ``` | ||
25 | |||
26 | [link](?123456) | ||
27 |  | ||
28 | [link](test.tld/path/?query=value#hash) | ||
29 | [link](http://test.tld/path/?query=value#hash) | ||
30 | [link](https://test.tld/path/?query=value#hash) | ||
31 | [link](ftp://test.tld/path/?query=value#hash) | ||
32 | [link](magnet:test.tld/path/?query=value#hash) | ||
33 | [link](javascript:alert('xss')) | ||
34 | [link](other://test.tld/path/?query=value#hash) | ||
diff --git a/tests/plugins/test/test.php b/tests/plugins/test/test.php index 2aaf5122..03be4f4e 100644 --- a/tests/plugins/test/test.php +++ b/tests/plugins/test/test.php | |||
@@ -13,9 +13,17 @@ function hook_test_random($data) | |||
13 | $data[1] = 'page test'; | 13 | $data[1] = 'page test'; |
14 | } elseif (isset($data['_LOGGEDIN_']) && $data['_LOGGEDIN_'] === true) { | 14 | } elseif (isset($data['_LOGGEDIN_']) && $data['_LOGGEDIN_'] === true) { |
15 | $data[1] = 'loggedin'; | 15 | $data[1] = 'loggedin'; |
16 | } elseif (array_key_exists('_LOGGEDIN_', $data)) { | ||
17 | $data[1] = 'loggedin'; | ||
18 | $data[2] = $data['_LOGGEDIN_']; | ||
16 | } else { | 19 | } else { |
17 | $data[1] = $data[0]; | 20 | $data[1] = $data[0]; |
18 | } | 21 | } |
19 | 22 | ||
20 | return $data; | 23 | return $data; |
21 | } | 24 | } |
25 | |||
26 | function hook_test_error() | ||
27 | { | ||
28 | new Unknown(); | ||
29 | } | ||
diff --git a/tests/feed/CacheTest.php b/tests/render/PageCacheManagerTest.php index c0a9f26f..08d4e5ea 100644 --- a/tests/feed/CacheTest.php +++ b/tests/render/PageCacheManagerTest.php | |||
@@ -1,18 +1,18 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
2 | /** | 3 | /** |
3 | * Cache tests | 4 | * Cache tests |
4 | */ | 5 | */ |
5 | namespace Shaarli\Feed; | ||
6 | 6 | ||
7 | // required to access $_SESSION array | 7 | namespace Shaarli\Render; |
8 | session_start(); | ||
9 | 8 | ||
10 | require_once 'application/feed/Cache.php'; | 9 | use Shaarli\Security\SessionManager; |
10 | use Shaarli\TestCase; | ||
11 | 11 | ||
12 | /** | 12 | /** |
13 | * Unitary tests for cached pages | 13 | * Unitary tests for cached pages |
14 | */ | 14 | */ |
15 | class CacheTest extends \PHPUnit\Framework\TestCase | 15 | class PageCacheManagerTest extends TestCase |
16 | { | 16 | { |
17 | // test cache directory | 17 | // test cache directory |
18 | protected static $testCacheDir = 'sandbox/dummycache'; | 18 | protected static $testCacheDir = 'sandbox/dummycache'; |
@@ -20,12 +20,19 @@ class CacheTest extends \PHPUnit\Framework\TestCase | |||
20 | // dummy cached file names / content | 20 | // dummy cached file names / content |
21 | protected static $pages = array('a', 'toto', 'd7b59c'); | 21 | protected static $pages = array('a', 'toto', 'd7b59c'); |
22 | 22 | ||
23 | /** @var PageCacheManager */ | ||
24 | protected $cacheManager; | ||
25 | |||
26 | /** @var SessionManager */ | ||
27 | protected $sessionManager; | ||
23 | 28 | ||
24 | /** | 29 | /** |
25 | * Populate the cache with dummy files | 30 | * Populate the cache with dummy files |
26 | */ | 31 | */ |
27 | public function setUp() | 32 | protected function setUp(): void |
28 | { | 33 | { |
34 | $this->cacheManager = new PageCacheManager(static::$testCacheDir, true); | ||
35 | |||
29 | if (!is_dir(self::$testCacheDir)) { | 36 | if (!is_dir(self::$testCacheDir)) { |
30 | mkdir(self::$testCacheDir); | 37 | mkdir(self::$testCacheDir); |
31 | } else { | 38 | } else { |
@@ -41,7 +48,7 @@ class CacheTest extends \PHPUnit\Framework\TestCase | |||
41 | /** | 48 | /** |
42 | * Remove dummycache folder after each tests. | 49 | * Remove dummycache folder after each tests. |
43 | */ | 50 | */ |
44 | public function tearDown() | 51 | protected function tearDown(): void |
45 | { | 52 | { |
46 | array_map('unlink', glob(self::$testCacheDir . '/*')); | 53 | array_map('unlink', glob(self::$testCacheDir . '/*')); |
47 | rmdir(self::$testCacheDir); | 54 | rmdir(self::$testCacheDir); |
@@ -52,7 +59,7 @@ class CacheTest extends \PHPUnit\Framework\TestCase | |||
52 | */ | 59 | */ |
53 | public function testPurgeCachedPages() | 60 | public function testPurgeCachedPages() |
54 | { | 61 | { |
55 | purgeCachedPages(self::$testCacheDir); | 62 | $this->cacheManager->purgeCachedPages(); |
56 | foreach (self::$pages as $page) { | 63 | foreach (self::$pages as $page) { |
57 | $this->assertFileNotExists(self::$testCacheDir . '/' . $page . '.cache'); | 64 | $this->assertFileNotExists(self::$testCacheDir . '/' . $page . '.cache'); |
58 | } | 65 | } |
@@ -65,28 +72,14 @@ class CacheTest extends \PHPUnit\Framework\TestCase | |||
65 | */ | 72 | */ |
66 | public function testPurgeCachedPagesMissingDir() | 73 | public function testPurgeCachedPagesMissingDir() |
67 | { | 74 | { |
75 | $this->cacheManager = new PageCacheManager(self::$testCacheDir . '_missing', true); | ||
76 | |||
68 | $oldlog = ini_get('error_log'); | 77 | $oldlog = ini_get('error_log'); |
69 | ini_set('error_log', '/dev/null'); | 78 | ini_set('error_log', '/dev/null'); |
70 | $this->assertEquals( | 79 | $this->assertEquals( |
71 | 'Cannot purge sandbox/dummycache_missing: no directory', | 80 | 'Cannot purge sandbox/dummycache_missing: no directory', |
72 | purgeCachedPages(self::$testCacheDir . '_missing') | 81 | $this->cacheManager->purgeCachedPages() |
73 | ); | 82 | ); |
74 | ini_set('error_log', $oldlog); | 83 | ini_set('error_log', $oldlog); |
75 | } | 84 | } |
76 | |||
77 | /** | ||
78 | * Purge cached pages and session cache | ||
79 | */ | ||
80 | public function testInvalidateCaches() | ||
81 | { | ||
82 | $this->assertArrayNotHasKey('tags', $_SESSION); | ||
83 | $_SESSION['tags'] = array('goodbye', 'cruel', 'world'); | ||
84 | |||
85 | invalidateCaches(self::$testCacheDir); | ||
86 | foreach (self::$pages as $page) { | ||
87 | $this->assertFileNotExists(self::$testCacheDir . '/' . $page . '.cache'); | ||
88 | } | ||
89 | |||
90 | $this->assertArrayNotHasKey('tags', $_SESSION); | ||
91 | } | ||
92 | } | 85 | } |
diff --git a/tests/render/ThemeUtilsTest.php b/tests/render/ThemeUtilsTest.php index 58e3426b..7d841e4d 100644 --- a/tests/render/ThemeUtilsTest.php +++ b/tests/render/ThemeUtilsTest.php | |||
@@ -7,7 +7,7 @@ namespace Shaarli\Render; | |||
7 | * | 7 | * |
8 | * @package Shaarli | 8 | * @package Shaarli |
9 | */ | 9 | */ |
10 | class ThemeUtilsTest extends \PHPUnit\Framework\TestCase | 10 | class ThemeUtilsTest extends \Shaarli\TestCase |
11 | { | 11 | { |
12 | /** | 12 | /** |
13 | * Test getThemes() with existing theme directories. | 13 | * Test getThemes() with existing theme directories. |
diff --git a/tests/security/BanManagerTest.php b/tests/security/BanManagerTest.php index bba7c8ad..698d3d10 100644 --- a/tests/security/BanManagerTest.php +++ b/tests/security/BanManagerTest.php | |||
@@ -3,8 +3,8 @@ | |||
3 | 3 | ||
4 | namespace Shaarli\Security; | 4 | namespace Shaarli\Security; |
5 | 5 | ||
6 | use PHPUnit\Framework\TestCase; | ||
7 | use Shaarli\FileUtils; | 6 | use Shaarli\FileUtils; |
7 | use Shaarli\TestCase; | ||
8 | 8 | ||
9 | /** | 9 | /** |
10 | * Test coverage for BanManager | 10 | * Test coverage for BanManager |
@@ -32,7 +32,7 @@ class BanManagerTest extends TestCase | |||
32 | /** | 32 | /** |
33 | * Prepare or reset test resources | 33 | * Prepare or reset test resources |
34 | */ | 34 | */ |
35 | public function setUp() | 35 | protected function setUp(): void |
36 | { | 36 | { |
37 | if (file_exists($this->banFile)) { | 37 | if (file_exists($this->banFile)) { |
38 | unlink($this->banFile); | 38 | unlink($this->banFile); |
diff --git a/tests/security/LoginManagerTest.php b/tests/security/LoginManagerTest.php index eef0f22a..d302983d 100644 --- a/tests/security/LoginManagerTest.php +++ b/tests/security/LoginManagerTest.php | |||
@@ -1,9 +1,8 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli\Security; | ||
3 | 2 | ||
4 | require_once 'tests/utils/FakeConfigManager.php'; | 3 | namespace Shaarli\Security; |
5 | 4 | ||
6 | use PHPUnit\Framework\TestCase; | 5 | use Shaarli\TestCase; |
7 | 6 | ||
8 | /** | 7 | /** |
9 | * Test coverage for LoginManager | 8 | * Test coverage for LoginManager |
@@ -58,10 +57,13 @@ class LoginManagerTest extends TestCase | |||
58 | /** @var string Salt used by hash functions */ | 57 | /** @var string Salt used by hash functions */ |
59 | protected $salt = '669e24fa9c5a59a613f98e8e38327384504a4af2'; | 58 | protected $salt = '669e24fa9c5a59a613f98e8e38327384504a4af2'; |
60 | 59 | ||
60 | /** @var CookieManager */ | ||
61 | protected $cookieManager; | ||
62 | |||
61 | /** | 63 | /** |
62 | * Prepare or reset test resources | 64 | * Prepare or reset test resources |
63 | */ | 65 | */ |
64 | public function setUp() | 66 | protected function setUp(): void |
65 | { | 67 | { |
66 | if (file_exists($this->banFile)) { | 68 | if (file_exists($this->banFile)) { |
67 | unlink($this->banFile); | 69 | unlink($this->banFile); |
@@ -78,13 +80,18 @@ class LoginManagerTest extends TestCase | |||
78 | 'security.ban_after' => 2, | 80 | 'security.ban_after' => 2, |
79 | 'security.ban_duration' => 3600, | 81 | 'security.ban_duration' => 3600, |
80 | 'security.trusted_proxies' => [$this->trustedProxy], | 82 | 'security.trusted_proxies' => [$this->trustedProxy], |
83 | 'ldap.host' => '', | ||
81 | ]); | 84 | ]); |
82 | 85 | ||
83 | $this->cookie = []; | 86 | $this->cookie = []; |
84 | $this->session = []; | 87 | $this->session = []; |
85 | 88 | ||
86 | $this->sessionManager = new SessionManager($this->session, $this->configManager); | 89 | $this->cookieManager = $this->createMock(CookieManager::class); |
87 | $this->loginManager = new LoginManager($this->configManager, $this->sessionManager); | 90 | $this->cookieManager->method('getCookieParameter')->willReturnCallback(function (string $key) { |
91 | return $this->cookie[$key] ?? null; | ||
92 | }); | ||
93 | $this->sessionManager = new SessionManager($this->session, $this->configManager, 'session_path'); | ||
94 | $this->loginManager = new LoginManager($this->configManager, $this->sessionManager, $this->cookieManager); | ||
88 | $this->server['REMOTE_ADDR'] = $this->ipAddr; | 95 | $this->server['REMOTE_ADDR'] = $this->ipAddr; |
89 | } | 96 | } |
90 | 97 | ||
@@ -192,8 +199,8 @@ class LoginManagerTest extends TestCase | |||
192 | $configManager = new \FakeConfigManager([ | 199 | $configManager = new \FakeConfigManager([ |
193 | 'resource.ban_file' => $this->banFile, | 200 | 'resource.ban_file' => $this->banFile, |
194 | ]); | 201 | ]); |
195 | $loginManager = new LoginManager($configManager, null); | 202 | $loginManager = new LoginManager($configManager, null, $this->cookieManager); |
196 | $loginManager->checkLoginState([], ''); | 203 | $loginManager->checkLoginState(''); |
197 | 204 | ||
198 | $this->assertFalse($loginManager->isLoggedIn()); | 205 | $this->assertFalse($loginManager->isLoggedIn()); |
199 | } | 206 | } |
@@ -209,9 +216,9 @@ class LoginManagerTest extends TestCase | |||
209 | 'expires_on' => time() + 100, | 216 | 'expires_on' => time() + 100, |
210 | ]; | 217 | ]; |
211 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); | 218 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); |
212 | $this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = 'nope'; | 219 | $this->cookie[CookieManager::STAY_SIGNED_IN] = 'nope'; |
213 | 220 | ||
214 | $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress); | 221 | $this->loginManager->checkLoginState($this->clientIpAddress); |
215 | 222 | ||
216 | $this->assertTrue($this->loginManager->isLoggedIn()); | 223 | $this->assertTrue($this->loginManager->isLoggedIn()); |
217 | $this->assertTrue(empty($this->session['username'])); | 224 | $this->assertTrue(empty($this->session['username'])); |
@@ -223,9 +230,9 @@ class LoginManagerTest extends TestCase | |||
223 | public function testCheckLoginStateStaySignedInWithValidToken() | 230 | public function testCheckLoginStateStaySignedInWithValidToken() |
224 | { | 231 | { |
225 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); | 232 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); |
226 | $this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = $this->loginManager->getStaySignedInToken(); | 233 | $this->cookie[CookieManager::STAY_SIGNED_IN] = $this->loginManager->getStaySignedInToken(); |
227 | 234 | ||
228 | $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress); | 235 | $this->loginManager->checkLoginState($this->clientIpAddress); |
229 | 236 | ||
230 | $this->assertTrue($this->loginManager->isLoggedIn()); | 237 | $this->assertTrue($this->loginManager->isLoggedIn()); |
231 | $this->assertEquals($this->login, $this->session['username']); | 238 | $this->assertEquals($this->login, $this->session['username']); |
@@ -240,7 +247,7 @@ class LoginManagerTest extends TestCase | |||
240 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); | 247 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); |
241 | $this->session['expires_on'] = time() - 100; | 248 | $this->session['expires_on'] = time() - 100; |
242 | 249 | ||
243 | $this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress); | 250 | $this->loginManager->checkLoginState($this->clientIpAddress); |
244 | 251 | ||
245 | $this->assertFalse($this->loginManager->isLoggedIn()); | 252 | $this->assertFalse($this->loginManager->isLoggedIn()); |
246 | } | 253 | } |
@@ -252,7 +259,7 @@ class LoginManagerTest extends TestCase | |||
252 | { | 259 | { |
253 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); | 260 | $this->loginManager->generateStaySignedInToken($this->clientIpAddress); |
254 | 261 | ||
255 | $this->loginManager->checkLoginState($this->cookie, '10.7.157.98'); | 262 | $this->loginManager->checkLoginState('10.7.157.98'); |
256 | 263 | ||
257 | $this->assertFalse($this->loginManager->isLoggedIn()); | 264 | $this->assertFalse($this->loginManager->isLoggedIn()); |
258 | } | 265 | } |
@@ -296,4 +303,37 @@ class LoginManagerTest extends TestCase | |||
296 | $this->loginManager->checkCredentials('', '', $this->login, $this->password) | 303 | $this->loginManager->checkCredentials('', '', $this->login, $this->password) |
297 | ); | 304 | ); |
298 | } | 305 | } |
306 | |||
307 | /** | ||
308 | * Check user credentials through LDAP - server unreachable | ||
309 | */ | ||
310 | public function testCheckCredentialsFromUnreachableLdap() | ||
311 | { | ||
312 | $this->configManager->set('ldap.host', 'dummy'); | ||
313 | $this->assertFalse( | ||
314 | $this->loginManager->checkCredentials('', '', $this->login, $this->password) | ||
315 | ); | ||
316 | } | ||
317 | |||
318 | /** | ||
319 | * Check user credentials through LDAP - wrong login and password supplied | ||
320 | */ | ||
321 | public function testCheckCredentialsFromLdapWrongLoginAndPassword() | ||
322 | { | ||
323 | $this->configManager->set('ldap.host', 'dummy'); | ||
324 | $this->assertFalse( | ||
325 | $this->loginManager->checkCredentialsFromLdap($this->login, $this->password, function() { return null; }, function() { return false; }) | ||
326 | ); | ||
327 | } | ||
328 | |||
329 | /** | ||
330 | * Check user credentials through LDAP - correct login and password supplied | ||
331 | */ | ||
332 | public function testCheckCredentialsFromLdapGoodLoginAndPassword() | ||
333 | { | ||
334 | $this->configManager->set('ldap.host', 'dummy'); | ||
335 | $this->assertTrue( | ||
336 | $this->loginManager->checkCredentialsFromLdap($this->login, $this->password, function() { return null; }, function() { return true; }) | ||
337 | ); | ||
338 | } | ||
299 | } | 339 | } |
diff --git a/tests/security/SessionManagerTest.php b/tests/security/SessionManagerTest.php index f264505e..3f9c3ef5 100644 --- a/tests/security/SessionManagerTest.php +++ b/tests/security/SessionManagerTest.php | |||
@@ -1,12 +1,8 @@ | |||
1 | <?php | 1 | <?php |
2 | require_once 'tests/utils/FakeConfigManager.php'; | ||
3 | 2 | ||
4 | // Initialize reference data _before_ PHPUnit starts a session | 3 | namespace Shaarli\Security; |
5 | require_once 'tests/utils/ReferenceSessionIdHashes.php'; | ||
6 | ReferenceSessionIdHashes::genAllHashes(); | ||
7 | 4 | ||
8 | use PHPUnit\Framework\TestCase; | 5 | use Shaarli\TestCase; |
9 | use Shaarli\Security\SessionManager; | ||
10 | 6 | ||
11 | /** | 7 | /** |
12 | * Test coverage for SessionManager | 8 | * Test coverage for SessionManager |
@@ -28,23 +24,23 @@ class SessionManagerTest extends TestCase | |||
28 | /** | 24 | /** |
29 | * Assign reference data | 25 | * Assign reference data |
30 | */ | 26 | */ |
31 | public static function setUpBeforeClass() | 27 | public static function setUpBeforeClass(): void |
32 | { | 28 | { |
33 | self::$sidHashes = ReferenceSessionIdHashes::getHashes(); | 29 | self::$sidHashes = \ReferenceSessionIdHashes::getHashes(); |
34 | } | 30 | } |
35 | 31 | ||
36 | /** | 32 | /** |
37 | * Initialize or reset test resources | 33 | * Initialize or reset test resources |
38 | */ | 34 | */ |
39 | public function setUp() | 35 | protected function setUp(): void |
40 | { | 36 | { |
41 | $this->conf = new FakeConfigManager([ | 37 | $this->conf = new \FakeConfigManager([ |
42 | 'credentials.login' => 'johndoe', | 38 | 'credentials.login' => 'johndoe', |
43 | 'credentials.salt' => 'salt', | 39 | 'credentials.salt' => 'salt', |
44 | 'security.session_protection_disabled' => false, | 40 | 'security.session_protection_disabled' => false, |
45 | ]); | 41 | ]); |
46 | $this->session = []; | 42 | $this->session = []; |
47 | $this->sessionManager = new SessionManager($this->session, $this->conf); | 43 | $this->sessionManager = new SessionManager($this->session, $this->conf, 'session_path'); |
48 | } | 44 | } |
49 | 45 | ||
50 | /** | 46 | /** |
@@ -69,7 +65,7 @@ class SessionManagerTest extends TestCase | |||
69 | $token => 1, | 65 | $token => 1, |
70 | ], | 66 | ], |
71 | ]; | 67 | ]; |
72 | $sessionManager = new SessionManager($session, $this->conf); | 68 | $sessionManager = new SessionManager($session, $this->conf, 'session_path'); |
73 | 69 | ||
74 | // check and destroy the token | 70 | // check and destroy the token |
75 | $this->assertTrue($sessionManager->checkToken($token)); | 71 | $this->assertTrue($sessionManager->checkToken($token)); |
@@ -211,15 +207,16 @@ class SessionManagerTest extends TestCase | |||
211 | 'expires_on' => time() + 1000, | 207 | 'expires_on' => time() + 1000, |
212 | 'username' => 'johndoe', | 208 | 'username' => 'johndoe', |
213 | 'visibility' => 'public', | 209 | 'visibility' => 'public', |
214 | 'untaggedonly' => false, | 210 | 'untaggedonly' => true, |
215 | ]; | 211 | ]; |
216 | $this->sessionManager->logout(); | 212 | $this->sessionManager->logout(); |
217 | 213 | ||
218 | $this->assertFalse(isset($this->session['ip'])); | 214 | $this->assertArrayNotHasKey('ip', $this->session); |
219 | $this->assertFalse(isset($this->session['expires_on'])); | 215 | $this->assertArrayNotHasKey('expires_on', $this->session); |
220 | $this->assertFalse(isset($this->session['username'])); | 216 | $this->assertArrayNotHasKey('username', $this->session); |
221 | $this->assertFalse(isset($this->session['visibility'])); | 217 | $this->assertArrayNotHasKey('visibility', $this->session); |
222 | $this->assertFalse(isset($this->session['untaggedonly'])); | 218 | $this->assertArrayHasKey('untaggedonly', $this->session); |
219 | $this->assertTrue($this->session['untaggedonly']); | ||
223 | } | 220 | } |
224 | 221 | ||
225 | /** | 222 | /** |
@@ -269,4 +266,61 @@ class SessionManagerTest extends TestCase | |||
269 | $this->session['ip'] = 'ip_id_one'; | 266 | $this->session['ip'] = 'ip_id_one'; |
270 | $this->assertTrue($this->sessionManager->hasClientIpChanged('ip_id_two')); | 267 | $this->assertTrue($this->sessionManager->hasClientIpChanged('ip_id_two')); |
271 | } | 268 | } |
269 | |||
270 | /** | ||
271 | * Test creating an entry in the session array | ||
272 | */ | ||
273 | public function testSetSessionParameterCreate(): void | ||
274 | { | ||
275 | $this->sessionManager->setSessionParameter('abc', 'def'); | ||
276 | |||
277 | static::assertSame('def', $this->session['abc']); | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * Test updating an entry in the session array | ||
282 | */ | ||
283 | public function testSetSessionParameterUpdate(): void | ||
284 | { | ||
285 | $this->session['abc'] = 'ghi'; | ||
286 | |||
287 | $this->sessionManager->setSessionParameter('abc', 'def'); | ||
288 | |||
289 | static::assertSame('def', $this->session['abc']); | ||
290 | } | ||
291 | |||
292 | /** | ||
293 | * Test updating an entry in the session array with null value | ||
294 | */ | ||
295 | public function testSetSessionParameterUpdateNull(): void | ||
296 | { | ||
297 | $this->session['abc'] = 'ghi'; | ||
298 | |||
299 | $this->sessionManager->setSessionParameter('abc', null); | ||
300 | |||
301 | static::assertArrayHasKey('abc', $this->session); | ||
302 | static::assertNull($this->session['abc']); | ||
303 | } | ||
304 | |||
305 | /** | ||
306 | * Test deleting an existing entry in the session array | ||
307 | */ | ||
308 | public function testDeleteSessionParameter(): void | ||
309 | { | ||
310 | $this->session['abc'] = 'def'; | ||
311 | |||
312 | $this->sessionManager->deleteSessionParameter('abc'); | ||
313 | |||
314 | static::assertArrayNotHasKey('abc', $this->session); | ||
315 | } | ||
316 | |||
317 | /** | ||
318 | * Test deleting a non existent entry in the session array | ||
319 | */ | ||
320 | public function testDeleteSessionParameterNotExisting(): void | ||
321 | { | ||
322 | $this->sessionManager->deleteSessionParameter('abc'); | ||
323 | |||
324 | static::assertArrayNotHasKey('abc', $this->session); | ||
325 | } | ||
272 | } | 326 | } |
diff --git a/tests/updater/DummyUpdater.php b/tests/updater/DummyUpdater.php index 9e866f1f..3403233f 100644 --- a/tests/updater/DummyUpdater.php +++ b/tests/updater/DummyUpdater.php | |||
@@ -4,6 +4,7 @@ namespace Shaarli\Updater; | |||
4 | use Exception; | 4 | use Exception; |
5 | use ReflectionClass; | 5 | use ReflectionClass; |
6 | use ReflectionMethod; | 6 | use ReflectionMethod; |
7 | use Shaarli\Bookmark\BookmarkFileService; | ||
7 | use Shaarli\Bookmark\LinkDB; | 8 | use Shaarli\Bookmark\LinkDB; |
8 | use Shaarli\Config\ConfigManager; | 9 | use Shaarli\Config\ConfigManager; |
9 | 10 | ||
@@ -16,14 +17,14 @@ class DummyUpdater extends Updater | |||
16 | /** | 17 | /** |
17 | * Object constructor. | 18 | * Object constructor. |
18 | * | 19 | * |
19 | * @param array $doneUpdates Updates which are already done. | 20 | * @param array $doneUpdates Updates which are already done. |
20 | * @param LinkDB $linkDB LinkDB instance. | 21 | * @param BookmarkFileService $bookmarkService LinkDB instance. |
21 | * @param ConfigManager $conf Configuration Manager instance. | 22 | * @param ConfigManager $conf Configuration Manager instance. |
22 | * @param boolean $isLoggedIn True if the user is logged in. | 23 | * @param boolean $isLoggedIn True if the user is logged in. |
23 | */ | 24 | */ |
24 | public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) | 25 | public function __construct($doneUpdates, $bookmarkService, $conf, $isLoggedIn) |
25 | { | 26 | { |
26 | parent::__construct($doneUpdates, $linkDB, $conf, $isLoggedIn); | 27 | parent::__construct($doneUpdates, $bookmarkService, $conf, $isLoggedIn); |
27 | 28 | ||
28 | // Retrieve all update methods. | 29 | // Retrieve all update methods. |
29 | // For unit test, only retrieve final methods, | 30 | // For unit test, only retrieve final methods, |
@@ -36,7 +37,7 @@ class DummyUpdater extends Updater | |||
36 | * | 37 | * |
37 | * @return bool true. | 38 | * @return bool true. |
38 | */ | 39 | */ |
39 | final private function updateMethodDummy1() | 40 | final protected function updateMethodDummy1() |
40 | { | 41 | { |
41 | return true; | 42 | return true; |
42 | } | 43 | } |
@@ -46,7 +47,7 @@ class DummyUpdater extends Updater | |||
46 | * | 47 | * |
47 | * @return bool true. | 48 | * @return bool true. |
48 | */ | 49 | */ |
49 | final private function updateMethodDummy2() | 50 | final protected function updateMethodDummy2() |
50 | { | 51 | { |
51 | return true; | 52 | return true; |
52 | } | 53 | } |
@@ -56,7 +57,7 @@ class DummyUpdater extends Updater | |||
56 | * | 57 | * |
57 | * @return bool true. | 58 | * @return bool true. |
58 | */ | 59 | */ |
59 | final private function updateMethodDummy3() | 60 | final protected function updateMethodDummy3() |
60 | { | 61 | { |
61 | return true; | 62 | return true; |
62 | } | 63 | } |
@@ -66,7 +67,7 @@ class DummyUpdater extends Updater | |||
66 | * | 67 | * |
67 | * @throws Exception error. | 68 | * @throws Exception error. |
68 | */ | 69 | */ |
69 | final private function updateMethodException() | 70 | final protected function updateMethodException() |
70 | { | 71 | { |
71 | throw new Exception('whatever'); | 72 | throw new Exception('whatever'); |
72 | } | 73 | } |
diff --git a/tests/updater/UpdaterTest.php b/tests/updater/UpdaterTest.php index 93bc86c1..a6280b8c 100644 --- a/tests/updater/UpdaterTest.php +++ b/tests/updater/UpdaterTest.php | |||
@@ -1,24 +1,19 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli\Updater; | 2 | namespace Shaarli\Updater; |
3 | 3 | ||
4 | use DateTime; | ||
5 | use Exception; | 4 | use Exception; |
6 | use Shaarli\Bookmark\LinkDB; | 5 | use Shaarli\Bookmark\BookmarkFileService; |
7 | use Shaarli\Config\ConfigJson; | 6 | use Shaarli\Bookmark\BookmarkServiceInterface; |
8 | use Shaarli\Config\ConfigManager; | 7 | use Shaarli\Config\ConfigManager; |
9 | use Shaarli\Config\ConfigPhp; | 8 | use Shaarli\History; |
10 | use Shaarli\Thumbnailer; | 9 | use Shaarli\TestCase; |
11 | 10 | ||
12 | require_once 'application/updater/UpdaterUtils.php'; | ||
13 | require_once 'tests/updater/DummyUpdater.php'; | ||
14 | require_once 'tests/utils/ReferenceLinkDB.php'; | ||
15 | require_once 'inc/rain.tpl.class.php'; | ||
16 | 11 | ||
17 | /** | 12 | /** |
18 | * Class UpdaterTest. | 13 | * Class UpdaterTest. |
19 | * Runs unit tests against the updater class. | 14 | * Runs unit tests against the updater class. |
20 | */ | 15 | */ |
21 | class UpdaterTest extends \PHPUnit\Framework\TestCase | 16 | class UpdaterTest extends TestCase |
22 | { | 17 | { |
23 | /** | 18 | /** |
24 | * @var string Path to test datastore. | 19 | * @var string Path to test datastore. |
@@ -35,24 +30,38 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase | |||
35 | */ | 30 | */ |
36 | protected $conf; | 31 | protected $conf; |
37 | 32 | ||
33 | /** @var BookmarkServiceInterface */ | ||
34 | protected $bookmarkService; | ||
35 | |||
36 | /** @var \ReferenceLinkDB */ | ||
37 | protected $refDB; | ||
38 | |||
39 | /** @var Updater */ | ||
40 | protected $updater; | ||
41 | |||
38 | /** | 42 | /** |
39 | * Executed before each test. | 43 | * Executed before each test. |
40 | */ | 44 | */ |
41 | public function setUp() | 45 | protected function setUp(): void |
42 | { | 46 | { |
47 | $this->refDB = new \ReferenceLinkDB(); | ||
48 | $this->refDB->write(self::$testDatastore); | ||
49 | |||
43 | copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php'); | 50 | copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php'); |
44 | $this->conf = new ConfigManager(self::$configFile); | 51 | $this->conf = new ConfigManager(self::$configFile); |
52 | $this->bookmarkService = new BookmarkFileService($this->conf, $this->createMock(History::class), true); | ||
53 | $this->updater = new Updater([], $this->bookmarkService, $this->conf, true); | ||
45 | } | 54 | } |
46 | 55 | ||
47 | /** | 56 | /** |
48 | * Test read_updates_file with an empty/missing file. | 57 | * Test UpdaterUtils::read_updates_file with an empty/missing file. |
49 | */ | 58 | */ |
50 | public function testReadEmptyUpdatesFile() | 59 | public function testReadEmptyUpdatesFile() |
51 | { | 60 | { |
52 | $this->assertEquals(array(), read_updates_file('')); | 61 | $this->assertEquals(array(), UpdaterUtils::read_updates_file('')); |
53 | $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; | 62 | $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; |
54 | touch($updatesFile); | 63 | touch($updatesFile); |
55 | $this->assertEquals(array(), read_updates_file($updatesFile)); | 64 | $this->assertEquals(array(), UpdaterUtils::read_updates_file($updatesFile)); |
56 | unlink($updatesFile); | 65 | unlink($updatesFile); |
57 | } | 66 | } |
58 | 67 | ||
@@ -64,42 +73,42 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase | |||
64 | $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; | 73 | $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; |
65 | $updatesMethods = array('m1', 'm2', 'm3'); | 74 | $updatesMethods = array('m1', 'm2', 'm3'); |
66 | 75 | ||
67 | write_updates_file($updatesFile, $updatesMethods); | 76 | UpdaterUtils::write_updates_file($updatesFile, $updatesMethods); |
68 | $readMethods = read_updates_file($updatesFile); | 77 | $readMethods = UpdaterUtils::read_updates_file($updatesFile); |
69 | $this->assertEquals($readMethods, $updatesMethods); | 78 | $this->assertEquals($readMethods, $updatesMethods); |
70 | 79 | ||
71 | // Update | 80 | // Update |
72 | $updatesMethods[] = 'm4'; | 81 | $updatesMethods[] = 'm4'; |
73 | write_updates_file($updatesFile, $updatesMethods); | 82 | UpdaterUtils::write_updates_file($updatesFile, $updatesMethods); |
74 | $readMethods = read_updates_file($updatesFile); | 83 | $readMethods = UpdaterUtils::read_updates_file($updatesFile); |
75 | $this->assertEquals($readMethods, $updatesMethods); | 84 | $this->assertEquals($readMethods, $updatesMethods); |
76 | unlink($updatesFile); | 85 | unlink($updatesFile); |
77 | } | 86 | } |
78 | 87 | ||
79 | /** | 88 | /** |
80 | * Test errors in write_updates_file(): empty updates file. | 89 | * Test errors in UpdaterUtils::write_updates_file(): empty updates file. |
81 | * | ||
82 | * @expectedException Exception | ||
83 | * @expectedExceptionMessageRegExp /Updates file path is not set(.*)/ | ||
84 | */ | 90 | */ |
85 | public function testWriteEmptyUpdatesFile() | 91 | public function testWriteEmptyUpdatesFile() |
86 | { | 92 | { |
87 | write_updates_file('', array('test')); | 93 | $this->expectException(\Exception::class); |
94 | $this->expectExceptionMessageRegExp('/Updates file path is not set(.*)/'); | ||
95 | |||
96 | UpdaterUtils::write_updates_file('', array('test')); | ||
88 | } | 97 | } |
89 | 98 | ||
90 | /** | 99 | /** |
91 | * Test errors in write_updates_file(): not writable updates file. | 100 | * Test errors in UpdaterUtils::write_updates_file(): not writable updates file. |
92 | * | ||
93 | * @expectedException Exception | ||
94 | * @expectedExceptionMessageRegExp /Unable to write(.*)/ | ||
95 | */ | 101 | */ |
96 | public function testWriteUpdatesFileNotWritable() | 102 | public function testWriteUpdatesFileNotWritable() |
97 | { | 103 | { |
104 | $this->expectException(\Exception::class); | ||
105 | $this->expectExceptionMessageRegExp('/Unable to write(.*)/'); | ||
106 | |||
98 | $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; | 107 | $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt'; |
99 | touch($updatesFile); | 108 | touch($updatesFile); |
100 | chmod($updatesFile, 0444); | 109 | chmod($updatesFile, 0444); |
101 | try { | 110 | try { |
102 | @write_updates_file($updatesFile, array('test')); | 111 | @UpdaterUtils::write_updates_file($updatesFile, array('test')); |
103 | } catch (Exception $e) { | 112 | } catch (Exception $e) { |
104 | unlink($updatesFile); | 113 | unlink($updatesFile); |
105 | throw $e; | 114 | throw $e; |
@@ -159,11 +168,11 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase | |||
159 | 168 | ||
160 | /** | 169 | /** |
161 | * Test Update failed. | 170 | * Test Update failed. |
162 | * | ||
163 | * @expectedException \Exception | ||
164 | */ | 171 | */ |
165 | public function testUpdateFailed() | 172 | public function testUpdateFailed() |
166 | { | 173 | { |
174 | $this->expectException(\Exception::class); | ||
175 | |||
167 | $updates = array( | 176 | $updates = array( |
168 | 'updateMethodDummy1', | 177 | 'updateMethodDummy1', |
169 | 'updateMethodDummy2', | 178 | 'updateMethodDummy2', |
@@ -174,653 +183,39 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase | |||
174 | $updater->update(); | 183 | $updater->update(); |
175 | } | 184 | } |
176 | 185 | ||
177 | /** | 186 | public function testUpdateMethodRelativeHomeLinkRename(): void |
178 | * Test update mergeDeprecatedConfig: | ||
179 | * 1. init a config file. | ||
180 | * 2. init a options.php file with update value. | ||
181 | * 3. merge. | ||
182 | * 4. check updated value in config file. | ||
183 | */ | ||
184 | public function testUpdateMergeDeprecatedConfig() | ||
185 | { | ||
186 | $this->conf->setConfigFile('tests/utils/config/configPhp'); | ||
187 | $this->conf->reset(); | ||
188 | |||
189 | $optionsFile = 'tests/updater/options.php'; | ||
190 | $options = '<?php | ||
191 | $GLOBALS[\'privateLinkByDefault\'] = true;'; | ||
192 | file_put_contents($optionsFile, $options); | ||
193 | |||
194 | // tmp config file. | ||
195 | $this->conf->setConfigFile('tests/updater/config'); | ||
196 | |||
197 | // merge configs | ||
198 | $updater = new Updater(array(), array(), $this->conf, true); | ||
199 | // This writes a new config file in tests/updater/config.php | ||
200 | $updater->updateMethodMergeDeprecatedConfigFile(); | ||
201 | |||
202 | // make sure updated field is changed | ||
203 | $this->conf->reload(); | ||
204 | $this->assertTrue($this->conf->get('privacy.default_private_links')); | ||
205 | $this->assertFalse(is_file($optionsFile)); | ||
206 | // Delete the generated file. | ||
207 | unlink($this->conf->getConfigFileExt()); | ||
208 | } | ||
209 | |||
210 | /** | ||
211 | * Test mergeDeprecatedConfig in without options file. | ||
212 | */ | ||
213 | public function testMergeDeprecatedConfigNoFile() | ||
214 | { | ||
215 | $updater = new Updater(array(), array(), $this->conf, true); | ||
216 | $updater->updateMethodMergeDeprecatedConfigFile(); | ||
217 | |||
218 | $this->assertEquals('root', $this->conf->get('credentials.login')); | ||
219 | } | ||
220 | |||
221 | /** | ||
222 | * Test renameDashTags update method. | ||
223 | */ | ||
224 | public function testRenameDashTags() | ||
225 | { | ||
226 | $refDB = new \ReferenceLinkDB(); | ||
227 | $refDB->write(self::$testDatastore); | ||
228 | $linkDB = new LinkDB(self::$testDatastore, true, false); | ||
229 | |||
230 | $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); | ||
231 | $updater = new Updater(array(), $linkDB, $this->conf, true); | ||
232 | $updater->updateMethodRenameDashTags(); | ||
233 | $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); | ||
234 | } | ||
235 | |||
236 | /** | ||
237 | * Convert old PHP config file to JSON config. | ||
238 | */ | ||
239 | public function testConfigToJson() | ||
240 | { | ||
241 | $configFile = 'tests/utils/config/configPhp'; | ||
242 | $this->conf->setConfigFile($configFile); | ||
243 | $this->conf->reset(); | ||
244 | |||
245 | // The ConfigIO is initialized with ConfigPhp. | ||
246 | $this->assertTrue($this->conf->getConfigIO() instanceof ConfigPhp); | ||
247 | |||
248 | $updater = new Updater(array(), array(), $this->conf, false); | ||
249 | $done = $updater->updateMethodConfigToJson(); | ||
250 | $this->assertTrue($done); | ||
251 | |||
252 | // The ConfigIO has been updated to ConfigJson. | ||
253 | $this->assertTrue($this->conf->getConfigIO() instanceof ConfigJson); | ||
254 | $this->assertTrue(file_exists($this->conf->getConfigFileExt())); | ||
255 | |||
256 | // Check JSON config data. | ||
257 | $this->conf->reload(); | ||
258 | $this->assertEquals('root', $this->conf->get('credentials.login')); | ||
259 | $this->assertEquals('lala', $this->conf->get('redirector.url')); | ||
260 | $this->assertEquals('data/datastore.php', $this->conf->get('resource.datastore')); | ||
261 | $this->assertEquals('1', $this->conf->get('plugins.WALLABAG_VERSION')); | ||
262 | |||
263 | rename($configFile . '.save.php', $configFile . '.php'); | ||
264 | unlink($this->conf->getConfigFileExt()); | ||
265 | } | ||
266 | |||
267 | /** | ||
268 | * Launch config conversion update with an existing JSON file => nothing to do. | ||
269 | */ | ||
270 | public function testConfigToJsonNothingToDo() | ||
271 | { | ||
272 | $filetime = filemtime($this->conf->getConfigFileExt()); | ||
273 | $updater = new Updater(array(), array(), $this->conf, false); | ||
274 | $done = $updater->updateMethodConfigToJson(); | ||
275 | $this->assertTrue($done); | ||
276 | $expected = filemtime($this->conf->getConfigFileExt()); | ||
277 | $this->assertEquals($expected, $filetime); | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * Test escapeUnescapedConfig with valid data. | ||
282 | */ | ||
283 | public function testEscapeConfig() | ||
284 | { | ||
285 | $sandbox = 'sandbox/config'; | ||
286 | copy(self::$configFile . '.json.php', $sandbox . '.json.php'); | ||
287 | $this->conf = new ConfigManager($sandbox); | ||
288 | $title = '<script>alert("title");</script>'; | ||
289 | $headerLink = '<script>alert("header_link");</script>'; | ||
290 | $this->conf->set('general.title', $title); | ||
291 | $this->conf->set('general.header_link', $headerLink); | ||
292 | $updater = new Updater(array(), array(), $this->conf, true); | ||
293 | $done = $updater->updateMethodEscapeUnescapedConfig(); | ||
294 | $this->assertTrue($done); | ||
295 | $this->conf->reload(); | ||
296 | $this->assertEquals(escape($title), $this->conf->get('general.title')); | ||
297 | $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link')); | ||
298 | unlink($sandbox . '.json.php'); | ||
299 | } | ||
300 | |||
301 | /** | ||
302 | * Test updateMethodApiSettings(): create default settings for the API (enabled + secret). | ||
303 | */ | ||
304 | public function testUpdateApiSettings() | ||
305 | { | ||
306 | $confFile = 'sandbox/config'; | ||
307 | copy(self::$configFile .'.json.php', $confFile .'.json.php'); | ||
308 | $conf = new ConfigManager($confFile); | ||
309 | $updater = new Updater(array(), array(), $conf, true); | ||
310 | |||
311 | $this->assertFalse($conf->exists('api.enabled')); | ||
312 | $this->assertFalse($conf->exists('api.secret')); | ||
313 | $updater->updateMethodApiSettings(); | ||
314 | $conf->reload(); | ||
315 | $this->assertTrue($conf->get('api.enabled')); | ||
316 | $this->assertTrue($conf->exists('api.secret')); | ||
317 | unlink($confFile .'.json.php'); | ||
318 | } | ||
319 | |||
320 | /** | ||
321 | * Test updateMethodApiSettings(): already set, do nothing. | ||
322 | */ | ||
323 | public function testUpdateApiSettingsNothingToDo() | ||
324 | { | 187 | { |
325 | $confFile = 'sandbox/config'; | 188 | $this->updater->setBasePath('/subfolder'); |
326 | copy(self::$configFile .'.json.php', $confFile .'.json.php'); | 189 | $this->conf->set('general.header_link', '?'); |
327 | $conf = new ConfigManager($confFile); | ||
328 | $conf->set('api.enabled', false); | ||
329 | $conf->set('api.secret', ''); | ||
330 | $updater = new Updater(array(), array(), $conf, true); | ||
331 | $updater->updateMethodApiSettings(); | ||
332 | $this->assertFalse($conf->get('api.enabled')); | ||
333 | $this->assertEmpty($conf->get('api.secret')); | ||
334 | unlink($confFile .'.json.php'); | ||
335 | } | ||
336 | 190 | ||
337 | /** | 191 | $this->updater->updateMethodRelativeHomeLink(); |
338 | * Test updateMethodDatastoreIds(). | ||
339 | */ | ||
340 | public function testDatastoreIds() | ||
341 | { | ||
342 | $links = array( | ||
343 | '20121206_182539' => array( | ||
344 | 'linkdate' => '20121206_182539', | ||
345 | 'title' => 'Geek and Poke', | ||
346 | 'url' => 'http://geek-and-poke.com/', | ||
347 | 'description' => 'desc', | ||
348 | 'tags' => 'dev cartoon tag1 tag2 tag3 tag4 ', | ||
349 | 'updated' => '20121206_190301', | ||
350 | 'private' => false, | ||
351 | ), | ||
352 | '20121206_172539' => array( | ||
353 | 'linkdate' => '20121206_172539', | ||
354 | 'title' => 'UserFriendly - Samba', | ||
355 | 'url' => 'http://ars.userfriendly.org/cartoons/?id=20010306', | ||
356 | 'description' => '', | ||
357 | 'tags' => 'samba cartoon web', | ||
358 | 'private' => false, | ||
359 | ), | ||
360 | '20121206_142300' => array( | ||
361 | 'linkdate' => '20121206_142300', | ||
362 | 'title' => 'UserFriendly - Web Designer', | ||
363 | 'url' => 'http://ars.userfriendly.org/cartoons/?id=20121206', | ||
364 | 'description' => 'Naming conventions... #private', | ||
365 | 'tags' => 'samba cartoon web', | ||
366 | 'private' => true, | ||
367 | ), | ||
368 | ); | ||
369 | $refDB = new \ReferenceLinkDB(); | ||
370 | $refDB->setLinks($links); | ||
371 | $refDB->write(self::$testDatastore); | ||
372 | $linkDB = new LinkDB(self::$testDatastore, true, false); | ||
373 | |||
374 | $checksum = hash_file('sha1', self::$testDatastore); | ||
375 | |||
376 | $this->conf->set('resource.data_dir', 'sandbox'); | ||
377 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
378 | |||
379 | $updater = new Updater(array(), $linkDB, $this->conf, true); | ||
380 | $this->assertTrue($updater->updateMethodDatastoreIds()); | ||
381 | |||
382 | $linkDB = new LinkDB(self::$testDatastore, true, false); | ||
383 | |||
384 | $backup = glob($this->conf->get('resource.data_dir') . '/datastore.'. date('YmdH') .'*.php'); | ||
385 | $backup = $backup[0]; | ||
386 | |||
387 | $this->assertFileExists($backup); | ||
388 | $this->assertEquals($checksum, hash_file('sha1', $backup)); | ||
389 | unlink($backup); | ||
390 | |||
391 | $this->assertEquals(3, count($linkDB)); | ||
392 | $this->assertTrue(isset($linkDB[0])); | ||
393 | $this->assertFalse(isset($linkDB[0]['linkdate'])); | ||
394 | $this->assertEquals(0, $linkDB[0]['id']); | ||
395 | $this->assertEquals('UserFriendly - Web Designer', $linkDB[0]['title']); | ||
396 | $this->assertEquals('http://ars.userfriendly.org/cartoons/?id=20121206', $linkDB[0]['url']); | ||
397 | $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']); | ||
398 | $this->assertEquals('samba cartoon web', $linkDB[0]['tags']); | ||
399 | $this->assertTrue($linkDB[0]['private']); | ||
400 | $this->assertEquals( | ||
401 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), | ||
402 | $linkDB[0]['created'] | ||
403 | ); | ||
404 | 192 | ||
405 | $this->assertTrue(isset($linkDB[1])); | 193 | static::assertSame('/subfolder/', $this->conf->get('general.header_link')); |
406 | $this->assertFalse(isset($linkDB[1]['linkdate'])); | ||
407 | $this->assertEquals(1, $linkDB[1]['id']); | ||
408 | $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']); | ||
409 | $this->assertEquals( | ||
410 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), | ||
411 | $linkDB[1]['created'] | ||
412 | ); | ||
413 | |||
414 | $this->assertTrue(isset($linkDB[2])); | ||
415 | $this->assertFalse(isset($linkDB[2]['linkdate'])); | ||
416 | $this->assertEquals(2, $linkDB[2]['id']); | ||
417 | $this->assertEquals('Geek and Poke', $linkDB[2]['title']); | ||
418 | $this->assertEquals( | ||
419 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), | ||
420 | $linkDB[2]['created'] | ||
421 | ); | ||
422 | $this->assertEquals( | ||
423 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'), | ||
424 | $linkDB[2]['updated'] | ||
425 | ); | ||
426 | } | 194 | } |
427 | 195 | ||
428 | /** | 196 | public function testUpdateMethodRelativeHomeLinkDoNotRename(): void |
429 | * Test updateMethodDatastoreIds() with the update already applied: nothing to do. | ||
430 | */ | ||
431 | public function testDatastoreIdsNothingToDo() | ||
432 | { | 197 | { |
433 | $refDB = new \ReferenceLinkDB(); | 198 | $this->conf->set('general.header_link', '~/my-blog'); |
434 | $refDB->write(self::$testDatastore); | ||
435 | $linkDB = new LinkDB(self::$testDatastore, true, false); | ||
436 | 199 | ||
437 | $this->conf->set('resource.data_dir', 'sandbox'); | 200 | $this->updater->updateMethodRelativeHomeLink(); |
438 | $this->conf->set('resource.datastore', self::$testDatastore); | ||
439 | 201 | ||
440 | $checksum = hash_file('sha1', self::$testDatastore); | 202 | static::assertSame('~/my-blog', $this->conf->get('general.header_link')); |
441 | $updater = new Updater(array(), $linkDB, $this->conf, true); | ||
442 | $this->assertTrue($updater->updateMethodDatastoreIds()); | ||
443 | $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore)); | ||
444 | } | 203 | } |
445 | 204 | ||
446 | /** | 205 | public function testUpdateMethodMigrateExistingNotesUrl(): void |
447 | * Test defaultTheme update with default settings: nothing to do. | ||
448 | */ | ||
449 | public function testDefaultThemeWithDefaultSettings() | ||
450 | { | 206 | { |
451 | $sandbox = 'sandbox/config'; | 207 | $this->updater->updateMethodMigrateExistingNotesUrl(); |
452 | copy(self::$configFile . '.json.php', $sandbox . '.json.php'); | ||
453 | $this->conf = new ConfigManager($sandbox); | ||
454 | $updater = new Updater([], [], $this->conf, true); | ||
455 | $this->assertTrue($updater->updateMethodDefaultTheme()); | ||
456 | |||
457 | $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); | ||
458 | $this->assertEquals('default', $this->conf->get('resource.theme')); | ||
459 | $this->conf = new ConfigManager($sandbox); | ||
460 | $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); | ||
461 | $this->assertEquals('default', $this->conf->get('resource.theme')); | ||
462 | unlink($sandbox . '.json.php'); | ||
463 | } | ||
464 | 208 | ||
465 | /** | 209 | static::assertSame($this->refDB->getLinks()[0]->getUrl(), $this->bookmarkService->get(0)->getUrl()); |
466 | * Test defaultTheme update with a custom theme in a subfolder | 210 | static::assertSame($this->refDB->getLinks()[1]->getUrl(), $this->bookmarkService->get(1)->getUrl()); |
467 | */ | 211 | static::assertSame($this->refDB->getLinks()[4]->getUrl(), $this->bookmarkService->get(4)->getUrl()); |
468 | public function testDefaultThemeWithCustomTheme() | 212 | static::assertSame($this->refDB->getLinks()[6]->getUrl(), $this->bookmarkService->get(6)->getUrl()); |
469 | { | 213 | static::assertSame($this->refDB->getLinks()[7]->getUrl(), $this->bookmarkService->get(7)->getUrl()); |
470 | $theme = 'iamanartist'; | 214 | static::assertSame($this->refDB->getLinks()[8]->getUrl(), $this->bookmarkService->get(8)->getUrl()); |
471 | $sandbox = 'sandbox/config'; | 215 | static::assertSame($this->refDB->getLinks()[9]->getUrl(), $this->bookmarkService->get(9)->getUrl()); |
472 | copy(self::$configFile . '.json.php', $sandbox . '.json.php'); | 216 | static::assertSame('/shaare/WDWyig', $this->bookmarkService->get(42)->getUrl()); |
473 | $this->conf = new ConfigManager($sandbox); | 217 | static::assertSame('/shaare/WDWyig', $this->bookmarkService->get(41)->getUrl()); |
474 | mkdir('sandbox/'. $theme); | 218 | static::assertSame('/shaare/0gCTjQ', $this->bookmarkService->get(10)->getUrl()); |
475 | touch('sandbox/'. $theme .'/linklist.html'); | 219 | static::assertSame('/shaare/PCRizQ', $this->bookmarkService->get(11)->getUrl()); |
476 | $this->conf->set('resource.raintpl_tpl', 'sandbox/'. $theme .'/'); | ||
477 | $updater = new Updater([], [], $this->conf, true); | ||
478 | $this->assertTrue($updater->updateMethodDefaultTheme()); | ||
479 | |||
480 | $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); | ||
481 | $this->assertEquals($theme, $this->conf->get('resource.theme')); | ||
482 | $this->conf = new ConfigManager($sandbox); | ||
483 | $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); | ||
484 | $this->assertEquals($theme, $this->conf->get('resource.theme')); | ||
485 | unlink($sandbox . '.json.php'); | ||
486 | unlink('sandbox/'. $theme .'/linklist.html'); | ||
487 | rmdir('sandbox/'. $theme); | ||
488 | } | ||
489 | |||
490 | /** | ||
491 | * Test updateMethodEscapeMarkdown with markdown plugin enabled | ||
492 | * => setting markdown_escape set to false. | ||
493 | */ | ||
494 | public function testEscapeMarkdownSettingToFalse() | ||
495 | { | ||
496 | $sandboxConf = 'sandbox/config'; | ||
497 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
498 | $this->conf = new ConfigManager($sandboxConf); | ||
499 | |||
500 | $this->conf->set('general.enabled_plugins', ['markdown']); | ||
501 | $updater = new Updater([], [], $this->conf, true); | ||
502 | $this->assertTrue($updater->updateMethodEscapeMarkdown()); | ||
503 | $this->assertFalse($this->conf->get('security.markdown_escape')); | ||
504 | |||
505 | // reload from file | ||
506 | $this->conf = new ConfigManager($sandboxConf); | ||
507 | $this->assertFalse($this->conf->get('security.markdown_escape')); | ||
508 | } | ||
509 | |||
510 | |||
511 | /** | ||
512 | * Test updateMethodEscapeMarkdown with markdown plugin disabled | ||
513 | * => setting markdown_escape set to true. | ||
514 | */ | ||
515 | public function testEscapeMarkdownSettingToTrue() | ||
516 | { | ||
517 | $sandboxConf = 'sandbox/config'; | ||
518 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
519 | $this->conf = new ConfigManager($sandboxConf); | ||
520 | |||
521 | $this->conf->set('general.enabled_plugins', []); | ||
522 | $updater = new Updater([], [], $this->conf, true); | ||
523 | $this->assertTrue($updater->updateMethodEscapeMarkdown()); | ||
524 | $this->assertTrue($this->conf->get('security.markdown_escape')); | ||
525 | |||
526 | // reload from file | ||
527 | $this->conf = new ConfigManager($sandboxConf); | ||
528 | $this->assertTrue($this->conf->get('security.markdown_escape')); | ||
529 | } | ||
530 | |||
531 | /** | ||
532 | * Test updateMethodEscapeMarkdown with nothing to do (setting already enabled) | ||
533 | */ | ||
534 | public function testEscapeMarkdownSettingNothingToDoEnabled() | ||
535 | { | ||
536 | $sandboxConf = 'sandbox/config'; | ||
537 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
538 | $this->conf = new ConfigManager($sandboxConf); | ||
539 | $this->conf->set('security.markdown_escape', true); | ||
540 | $updater = new Updater([], [], $this->conf, true); | ||
541 | $this->assertTrue($updater->updateMethodEscapeMarkdown()); | ||
542 | $this->assertTrue($this->conf->get('security.markdown_escape')); | ||
543 | } | ||
544 | |||
545 | /** | ||
546 | * Test updateMethodEscapeMarkdown with nothing to do (setting already disabled) | ||
547 | */ | ||
548 | public function testEscapeMarkdownSettingNothingToDoDisabled() | ||
549 | { | ||
550 | $this->conf->set('security.markdown_escape', false); | ||
551 | $updater = new Updater([], [], $this->conf, true); | ||
552 | $this->assertTrue($updater->updateMethodEscapeMarkdown()); | ||
553 | $this->assertFalse($this->conf->get('security.markdown_escape')); | ||
554 | } | ||
555 | |||
556 | /** | ||
557 | * Test updateMethodPiwikUrl with valid data | ||
558 | */ | ||
559 | public function testUpdatePiwikUrlValid() | ||
560 | { | ||
561 | $sandboxConf = 'sandbox/config'; | ||
562 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
563 | $this->conf = new ConfigManager($sandboxConf); | ||
564 | $url = 'mypiwik.tld'; | ||
565 | $this->conf->set('plugins.PIWIK_URL', $url); | ||
566 | $updater = new Updater([], [], $this->conf, true); | ||
567 | $this->assertTrue($updater->updateMethodPiwikUrl()); | ||
568 | $this->assertEquals('http://'. $url, $this->conf->get('plugins.PIWIK_URL')); | ||
569 | |||
570 | // reload from file | ||
571 | $this->conf = new ConfigManager($sandboxConf); | ||
572 | $this->assertEquals('http://'. $url, $this->conf->get('plugins.PIWIK_URL')); | ||
573 | } | ||
574 | |||
575 | /** | ||
576 | * Test updateMethodPiwikUrl without setting | ||
577 | */ | ||
578 | public function testUpdatePiwikUrlEmpty() | ||
579 | { | ||
580 | $updater = new Updater([], [], $this->conf, true); | ||
581 | $this->assertTrue($updater->updateMethodPiwikUrl()); | ||
582 | $this->assertEmpty($this->conf->get('plugins.PIWIK_URL')); | ||
583 | } | ||
584 | |||
585 | /** | ||
586 | * Test updateMethodPiwikUrl: valid URL, nothing to do | ||
587 | */ | ||
588 | public function testUpdatePiwikUrlNothingToDo() | ||
589 | { | ||
590 | $url = 'https://mypiwik.tld'; | ||
591 | $this->conf->set('plugins.PIWIK_URL', $url); | ||
592 | $updater = new Updater([], [], $this->conf, true); | ||
593 | $this->assertTrue($updater->updateMethodPiwikUrl()); | ||
594 | $this->assertEquals($url, $this->conf->get('plugins.PIWIK_URL')); | ||
595 | } | ||
596 | |||
597 | /** | ||
598 | * Test updateMethodAtomDefault with show_atom set to false | ||
599 | * => update to true. | ||
600 | */ | ||
601 | public function testUpdateMethodAtomDefault() | ||
602 | { | ||
603 | $sandboxConf = 'sandbox/config'; | ||
604 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
605 | $this->conf = new ConfigManager($sandboxConf); | ||
606 | $this->conf->set('feed.show_atom', false); | ||
607 | $updater = new Updater([], [], $this->conf, true); | ||
608 | $this->assertTrue($updater->updateMethodAtomDefault()); | ||
609 | $this->assertTrue($this->conf->get('feed.show_atom')); | ||
610 | // reload from file | ||
611 | $this->conf = new ConfigManager($sandboxConf); | ||
612 | $this->assertTrue($this->conf->get('feed.show_atom')); | ||
613 | } | ||
614 | /** | ||
615 | * Test updateMethodAtomDefault with show_atom not set. | ||
616 | * => nothing to do | ||
617 | */ | ||
618 | public function testUpdateMethodAtomDefaultNoExist() | ||
619 | { | ||
620 | $sandboxConf = 'sandbox/config'; | ||
621 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
622 | $this->conf = new ConfigManager($sandboxConf); | ||
623 | $updater = new Updater([], [], $this->conf, true); | ||
624 | $this->assertTrue($updater->updateMethodAtomDefault()); | ||
625 | $this->assertTrue($this->conf->get('feed.show_atom')); | ||
626 | } | ||
627 | /** | ||
628 | * Test updateMethodAtomDefault with show_atom set to true. | ||
629 | * => nothing to do | ||
630 | */ | ||
631 | public function testUpdateMethodAtomDefaultAlreadyTrue() | ||
632 | { | ||
633 | $sandboxConf = 'sandbox/config'; | ||
634 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
635 | $this->conf = new ConfigManager($sandboxConf); | ||
636 | $this->conf->set('feed.show_atom', true); | ||
637 | $updater = new Updater([], [], $this->conf, true); | ||
638 | $this->assertTrue($updater->updateMethodAtomDefault()); | ||
639 | $this->assertTrue($this->conf->get('feed.show_atom')); | ||
640 | } | ||
641 | |||
642 | /** | ||
643 | * Test updateMethodDownloadSizeAndTimeoutConf, it should be set if none is already defined. | ||
644 | */ | ||
645 | public function testUpdateMethodDownloadSizeAndTimeoutConf() | ||
646 | { | ||
647 | $sandboxConf = 'sandbox/config'; | ||
648 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
649 | $this->conf = new ConfigManager($sandboxConf); | ||
650 | $updater = new Updater([], [], $this->conf, true); | ||
651 | $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); | ||
652 | $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); | ||
653 | $this->assertEquals(30, $this->conf->get('general.download_timeout')); | ||
654 | |||
655 | $this->conf = new ConfigManager($sandboxConf); | ||
656 | $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); | ||
657 | $this->assertEquals(30, $this->conf->get('general.download_timeout')); | ||
658 | } | ||
659 | |||
660 | /** | ||
661 | * Test updateMethodDownloadSizeAndTimeoutConf, it shouldn't be set if it is already defined. | ||
662 | */ | ||
663 | public function testUpdateMethodDownloadSizeAndTimeoutConfIgnore() | ||
664 | { | ||
665 | $sandboxConf = 'sandbox/config'; | ||
666 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
667 | $this->conf = new ConfigManager($sandboxConf); | ||
668 | $this->conf->set('general.download_max_size', 38); | ||
669 | $this->conf->set('general.download_timeout', 70); | ||
670 | $updater = new Updater([], [], $this->conf, true); | ||
671 | $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); | ||
672 | $this->assertEquals(38, $this->conf->get('general.download_max_size')); | ||
673 | $this->assertEquals(70, $this->conf->get('general.download_timeout')); | ||
674 | } | ||
675 | |||
676 | /** | ||
677 | * Test updateMethodDownloadSizeAndTimeoutConf, only the maz size should be set here. | ||
678 | */ | ||
679 | public function testUpdateMethodDownloadSizeAndTimeoutConfOnlySize() | ||
680 | { | ||
681 | $sandboxConf = 'sandbox/config'; | ||
682 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
683 | $this->conf = new ConfigManager($sandboxConf); | ||
684 | $this->conf->set('general.download_max_size', 38); | ||
685 | $updater = new Updater([], [], $this->conf, true); | ||
686 | $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); | ||
687 | $this->assertEquals(38, $this->conf->get('general.download_max_size')); | ||
688 | $this->assertEquals(30, $this->conf->get('general.download_timeout')); | ||
689 | } | ||
690 | |||
691 | /** | ||
692 | * Test updateMethodDownloadSizeAndTimeoutConf, only the time out should be set here. | ||
693 | */ | ||
694 | public function testUpdateMethodDownloadSizeAndTimeoutConfOnlyTimeout() | ||
695 | { | ||
696 | $sandboxConf = 'sandbox/config'; | ||
697 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
698 | $this->conf = new ConfigManager($sandboxConf); | ||
699 | $this->conf->set('general.download_timeout', 3); | ||
700 | $updater = new Updater([], [], $this->conf, true); | ||
701 | $this->assertTrue($updater->updateMethodDownloadSizeAndTimeoutConf()); | ||
702 | $this->assertEquals(4194304, $this->conf->get('general.download_max_size')); | ||
703 | $this->assertEquals(3, $this->conf->get('general.download_timeout')); | ||
704 | } | ||
705 | |||
706 | /** | ||
707 | * Test updateMethodWebThumbnailer with thumbnails enabled. | ||
708 | */ | ||
709 | public function testUpdateMethodWebThumbnailerEnabled() | ||
710 | { | ||
711 | $this->conf->remove('thumbnails'); | ||
712 | $this->conf->set('thumbnail.enable_thumbnails', true); | ||
713 | $updater = new Updater([], [], $this->conf, true, $_SESSION); | ||
714 | $this->assertTrue($updater->updateMethodWebThumbnailer()); | ||
715 | $this->assertFalse($this->conf->exists('thumbnail')); | ||
716 | $this->assertEquals(\Shaarli\Thumbnailer::MODE_ALL, $this->conf->get('thumbnails.mode')); | ||
717 | $this->assertEquals(125, $this->conf->get('thumbnails.width')); | ||
718 | $this->assertEquals(90, $this->conf->get('thumbnails.height')); | ||
719 | $this->assertContains('You have enabled or changed thumbnails', $_SESSION['warnings'][0]); | ||
720 | } | ||
721 | |||
722 | /** | ||
723 | * Test updateMethodWebThumbnailer with thumbnails disabled. | ||
724 | */ | ||
725 | public function testUpdateMethodWebThumbnailerDisabled() | ||
726 | { | ||
727 | $this->conf->remove('thumbnails'); | ||
728 | $this->conf->set('thumbnail.enable_thumbnails', false); | ||
729 | $updater = new Updater([], [], $this->conf, true, $_SESSION); | ||
730 | $this->assertTrue($updater->updateMethodWebThumbnailer()); | ||
731 | $this->assertFalse($this->conf->exists('thumbnail')); | ||
732 | $this->assertEquals(Thumbnailer::MODE_NONE, $this->conf->get('thumbnails.mode')); | ||
733 | $this->assertEquals(125, $this->conf->get('thumbnails.width')); | ||
734 | $this->assertEquals(90, $this->conf->get('thumbnails.height')); | ||
735 | $this->assertTrue(empty($_SESSION['warnings'])); | ||
736 | } | ||
737 | |||
738 | /** | ||
739 | * Test updateMethodWebThumbnailer with thumbnails disabled. | ||
740 | */ | ||
741 | public function testUpdateMethodWebThumbnailerNothingToDo() | ||
742 | { | ||
743 | $updater = new Updater([], [], $this->conf, true, $_SESSION); | ||
744 | $this->assertTrue($updater->updateMethodWebThumbnailer()); | ||
745 | $this->assertFalse($this->conf->exists('thumbnail')); | ||
746 | $this->assertEquals(Thumbnailer::MODE_COMMON, $this->conf->get('thumbnails.mode')); | ||
747 | $this->assertEquals(90, $this->conf->get('thumbnails.width')); | ||
748 | $this->assertEquals(53, $this->conf->get('thumbnails.height')); | ||
749 | $this->assertTrue(empty($_SESSION['warnings'])); | ||
750 | } | ||
751 | |||
752 | /** | ||
753 | * Test updateMethodSetSticky(). | ||
754 | */ | ||
755 | public function testUpdateStickyValid() | ||
756 | { | ||
757 | $blank = [ | ||
758 | 'id' => 1, | ||
759 | 'url' => 'z', | ||
760 | 'title' => '', | ||
761 | 'description' => '', | ||
762 | 'tags' => '', | ||
763 | 'created' => new DateTime(), | ||
764 | ]; | ||
765 | $links = [ | ||
766 | 1 => ['id' => 1] + $blank, | ||
767 | 2 => ['id' => 2] + $blank, | ||
768 | ]; | ||
769 | $refDB = new \ReferenceLinkDB(); | ||
770 | $refDB->setLinks($links); | ||
771 | $refDB->write(self::$testDatastore); | ||
772 | $linkDB = new LinkDB(self::$testDatastore, true, false); | ||
773 | |||
774 | $updater = new Updater(array(), $linkDB, $this->conf, true); | ||
775 | $this->assertTrue($updater->updateMethodSetSticky()); | ||
776 | |||
777 | $linkDB = new LinkDB(self::$testDatastore, true, false); | ||
778 | foreach ($linkDB as $link) { | ||
779 | $this->assertFalse($link['sticky']); | ||
780 | } | ||
781 | } | ||
782 | |||
783 | /** | ||
784 | * Test updateMethodSetSticky(). | ||
785 | */ | ||
786 | public function testUpdateStickyNothingToDo() | ||
787 | { | ||
788 | $blank = [ | ||
789 | 'id' => 1, | ||
790 | 'url' => 'z', | ||
791 | 'title' => '', | ||
792 | 'description' => '', | ||
793 | 'tags' => '', | ||
794 | 'created' => new DateTime(), | ||
795 | ]; | ||
796 | $links = [ | ||
797 | 1 => ['id' => 1, 'sticky' => true] + $blank, | ||
798 | 2 => ['id' => 2] + $blank, | ||
799 | ]; | ||
800 | $refDB = new \ReferenceLinkDB(); | ||
801 | $refDB->setLinks($links); | ||
802 | $refDB->write(self::$testDatastore); | ||
803 | $linkDB = new LinkDB(self::$testDatastore, true, false); | ||
804 | |||
805 | $updater = new Updater(array(), $linkDB, $this->conf, true); | ||
806 | $this->assertTrue($updater->updateMethodSetSticky()); | ||
807 | |||
808 | $linkDB = new LinkDB(self::$testDatastore, true, false); | ||
809 | $this->assertTrue($linkDB[1]['sticky']); | ||
810 | } | ||
811 | |||
812 | /** | ||
813 | * Test updateMethodRemoveRedirector(). | ||
814 | */ | ||
815 | public function testUpdateRemoveRedirector() | ||
816 | { | ||
817 | $sandboxConf = 'sandbox/config'; | ||
818 | copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); | ||
819 | $this->conf = new ConfigManager($sandboxConf); | ||
820 | $updater = new Updater([], null, $this->conf, true); | ||
821 | $this->assertTrue($updater->updateMethodRemoveRedirector()); | ||
822 | $this->assertFalse($this->conf->exists('redirector')); | ||
823 | $this->conf = new ConfigManager($sandboxConf); | ||
824 | $this->assertFalse($this->conf->exists('redirector')); | ||
825 | } | 220 | } |
826 | } | 221 | } |
diff --git a/tests/utils/FakeBookmarkService.php b/tests/utils/FakeBookmarkService.php new file mode 100644 index 00000000..1ec5bc3d --- /dev/null +++ b/tests/utils/FakeBookmarkService.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | use Shaarli\Bookmark\BookmarkArray; | ||
5 | use Shaarli\Bookmark\BookmarkFilter; | ||
6 | use Shaarli\Bookmark\BookmarkIO; | ||
7 | use Shaarli\Bookmark\BookmarkFileService; | ||
8 | use Shaarli\Bookmark\Exception\EmptyDataStoreException; | ||
9 | use Shaarli\Config\ConfigManager; | ||
10 | use Shaarli\History; | ||
11 | |||
12 | class FakeBookmarkService extends BookmarkFileService | ||
13 | { | ||
14 | public function getBookmarks() | ||
15 | { | ||
16 | return $this->bookmarks; | ||
17 | } | ||
18 | } | ||
diff --git a/tests/utils/ReferenceHistory.php b/tests/utils/ReferenceHistory.php index e411c417..516c9f51 100644 --- a/tests/utils/ReferenceHistory.php +++ b/tests/utils/ReferenceHistory.php | |||
@@ -76,7 +76,7 @@ class ReferenceHistory | |||
76 | } | 76 | } |
77 | 77 | ||
78 | /** | 78 | /** |
79 | * Returns the number of links in the reference data | 79 | * Returns the number of bookmarks in the reference data |
80 | */ | 80 | */ |
81 | public function count() | 81 | public function count() |
82 | { | 82 | { |
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php index c12bcb67..fc3cb109 100644 --- a/tests/utils/ReferenceLinkDB.php +++ b/tests/utils/ReferenceLinkDB.php | |||
@@ -1,30 +1,39 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | use Shaarli\Bookmark\LinkDB; | 3 | use Shaarli\Bookmark\Bookmark; |
4 | use Shaarli\Bookmark\BookmarkArray; | ||
4 | 5 | ||
5 | /** | 6 | /** |
6 | * Populates a reference datastore to test LinkDB | 7 | * Populates a reference datastore to test Bookmark |
7 | */ | 8 | */ |
8 | class ReferenceLinkDB | 9 | class ReferenceLinkDB |
9 | { | 10 | { |
10 | public static $NB_LINKS_TOTAL = 11; | 11 | public static $NB_LINKS_TOTAL = 11; |
11 | 12 | ||
12 | private $_links = array(); | 13 | private $bookmarks = array(); |
13 | private $_publicCount = 0; | 14 | private $_publicCount = 0; |
14 | private $_privateCount = 0; | 15 | private $_privateCount = 0; |
15 | 16 | ||
17 | private $isLegacy; | ||
18 | |||
16 | /** | 19 | /** |
17 | * Populates the test DB with reference data | 20 | * Populates the test DB with reference data |
21 | * | ||
22 | * @param bool $isLegacy Use links as array instead of Bookmark object | ||
18 | */ | 23 | */ |
19 | public function __construct() | 24 | public function __construct($isLegacy = false) |
20 | { | 25 | { |
26 | $this->isLegacy = $isLegacy; | ||
27 | if (! $this->isLegacy) { | ||
28 | $this->bookmarks = new BookmarkArray(); | ||
29 | } | ||
21 | $this->addLink( | 30 | $this->addLink( |
22 | 11, | 31 | 11, |
23 | 'Pined older', | 32 | 'Pined older', |
24 | '?PCRizQ', | 33 | '/shaare/PCRizQ', |
25 | 'This is an older pinned link', | 34 | 'This is an older pinned link', |
26 | 0, | 35 | 0, |
27 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20100309_101010'), | 36 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20100309_101010'), |
28 | '', | 37 | '', |
29 | null, | 38 | null, |
30 | 'PCRizQ', | 39 | 'PCRizQ', |
@@ -34,10 +43,10 @@ class ReferenceLinkDB | |||
34 | $this->addLink( | 43 | $this->addLink( |
35 | 10, | 44 | 10, |
36 | 'Pined', | 45 | 'Pined', |
37 | '?0gCTjQ', | 46 | '/shaare/0gCTjQ', |
38 | 'This is a pinned link', | 47 | 'This is a pinned link', |
39 | 0, | 48 | 0, |
40 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121207_152312'), | 49 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121207_152312'), |
41 | '', | 50 | '', |
42 | null, | 51 | null, |
43 | '0gCTjQ', | 52 | '0gCTjQ', |
@@ -47,10 +56,10 @@ class ReferenceLinkDB | |||
47 | $this->addLink( | 56 | $this->addLink( |
48 | 41, | 57 | 41, |
49 | 'Link title: @website', | 58 | 'Link title: @website', |
50 | '?WDWyig', | 59 | '/shaare/WDWyig', |
51 | 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag', | 60 | 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag', |
52 | 0, | 61 | 0, |
53 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), | 62 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114651'), |
54 | 'sTuff', | 63 | 'sTuff', |
55 | null, | 64 | null, |
56 | 'WDWyig' | 65 | 'WDWyig' |
@@ -59,10 +68,10 @@ class ReferenceLinkDB | |||
59 | $this->addLink( | 68 | $this->addLink( |
60 | 42, | 69 | 42, |
61 | 'Note: I have a big ID but an old date', | 70 | 'Note: I have a big ID but an old date', |
62 | '?WDWyig', | 71 | '/shaare/WDWyig', |
63 | 'Used to test links reordering.', | 72 | 'Used to test bookmarks reordering.', |
64 | 0, | 73 | 0, |
65 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20100310_101010'), | 74 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20100310_101010'), |
66 | 'ut' | 75 | 'ut' |
67 | ); | 76 | ); |
68 | 77 | ||
@@ -72,7 +81,7 @@ class ReferenceLinkDB | |||
72 | 'http://www.php-fig.org/psr/psr-2/', | 81 | 'http://www.php-fig.org/psr/psr-2/', |
73 | 'This guide extends and expands on PSR-1, the basic coding standard.', | 82 | 'This guide extends and expands on PSR-1, the basic coding standard.', |
74 | 0, | 83 | 0, |
75 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_152312'), | 84 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121206_152312'), |
76 | '' | 85 | '' |
77 | ); | 86 | ); |
78 | 87 | ||
@@ -82,9 +91,9 @@ class ReferenceLinkDB | |||
82 | 'https://static.fsf.org/nosvn/faif-2.0.pdf', | 91 | 'https://static.fsf.org/nosvn/faif-2.0.pdf', |
83 | 'Richard Stallman and the Free Software Revolution. Read this. #hashtag', | 92 | 'Richard Stallman and the Free Software Revolution. Read this. #hashtag', |
84 | 0, | 93 | 0, |
85 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'), | 94 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20150310_114633'), |
86 | 'free gnu software stallman -exclude stuff hashtag', | 95 | 'free gnu software stallman -exclude stuff hashtag', |
87 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160803_093033') | 96 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20160803_093033') |
88 | ); | 97 | ); |
89 | 98 | ||
90 | $this->addLink( | 99 | $this->addLink( |
@@ -93,9 +102,9 @@ class ReferenceLinkDB | |||
93 | 'http://mediagoblin.org/', | 102 | 'http://mediagoblin.org/', |
94 | 'A free software media publishing platform #hashtagOther', | 103 | 'A free software media publishing platform #hashtagOther', |
95 | 0, | 104 | 0, |
96 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'), | 105 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20130614_184135'), |
97 | 'gnu media web .hidden hashtag', | 106 | 'gnu media web .hidden hashtag', |
98 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130615_184230'), | 107 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20130615_184230'), |
99 | 'IuWvgA' | 108 | 'IuWvgA' |
100 | ); | 109 | ); |
101 | 110 | ||
@@ -105,7 +114,7 @@ class ReferenceLinkDB | |||
105 | 'https://dvcs.w3.org/hg/markup-validator/summary', | 114 | 'https://dvcs.w3.org/hg/markup-validator/summary', |
106 | 'Mercurial repository for the W3C Validator #private', | 115 | 'Mercurial repository for the W3C Validator #private', |
107 | 1, | 116 | 1, |
108 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20141125_084734'), | 117 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20141125_084734'), |
109 | 'css html w3c web Mercurial' | 118 | 'css html w3c web Mercurial' |
110 | ); | 119 | ); |
111 | 120 | ||
@@ -115,7 +124,7 @@ class ReferenceLinkDB | |||
115 | 'http://ars.userfriendly.org/cartoons/?id=20121206', | 124 | 'http://ars.userfriendly.org/cartoons/?id=20121206', |
116 | 'Naming conventions... #private', | 125 | 'Naming conventions... #private', |
117 | 0, | 126 | 0, |
118 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), | 127 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121206_142300'), |
119 | 'dev cartoon web' | 128 | 'dev cartoon web' |
120 | ); | 129 | ); |
121 | 130 | ||
@@ -125,7 +134,7 @@ class ReferenceLinkDB | |||
125 | 'http://ars.userfriendly.org/cartoons/?id=20010306', | 134 | 'http://ars.userfriendly.org/cartoons/?id=20010306', |
126 | 'Tropical printing', | 135 | 'Tropical printing', |
127 | 0, | 136 | 0, |
128 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), | 137 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121206_172539'), |
129 | 'samba cartoon web' | 138 | 'samba cartoon web' |
130 | ); | 139 | ); |
131 | 140 | ||
@@ -135,7 +144,7 @@ class ReferenceLinkDB | |||
135 | 'http://geek-and-poke.com/', | 144 | 'http://geek-and-poke.com/', |
136 | '', | 145 | '', |
137 | 1, | 146 | 1, |
138 | DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), | 147 | DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, '20121206_182539'), |
139 | 'dev cartoon tag1 tag2 tag3 tag4 ' | 148 | 'dev cartoon tag1 tag2 tag3 tag4 ' |
140 | ); | 149 | ); |
141 | } | 150 | } |
@@ -164,10 +173,15 @@ class ReferenceLinkDB | |||
164 | 'tags' => $tags, | 173 | 'tags' => $tags, |
165 | 'created' => $date, | 174 | 'created' => $date, |
166 | 'updated' => $updated, | 175 | 'updated' => $updated, |
167 | 'shorturl' => $shorturl ? $shorturl : smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id), | 176 | 'shorturl' => $shorturl ? $shorturl : smallHash($date->format(Bookmark::LINK_DATE_FORMAT) . $id), |
168 | 'sticky' => $pinned | 177 | 'sticky' => $pinned |
169 | ); | 178 | ); |
170 | $this->_links[$id] = $link; | 179 | if (! $this->isLegacy) { |
180 | $bookmark = new Bookmark(); | ||
181 | $this->bookmarks[$id] = $bookmark->fromArray($link); | ||
182 | } else { | ||
183 | $this->bookmarks[$id] = $link; | ||
184 | } | ||
171 | 185 | ||
172 | if ($private) { | 186 | if ($private) { |
173 | $this->_privateCount++; | 187 | $this->_privateCount++; |
@@ -184,37 +198,38 @@ class ReferenceLinkDB | |||
184 | $this->reorder(); | 198 | $this->reorder(); |
185 | file_put_contents( | 199 | file_put_contents( |
186 | $filename, | 200 | $filename, |
187 | '<?php /* '.base64_encode(gzdeflate(serialize($this->_links))).' */ ?>' | 201 | '<?php /* '.base64_encode(gzdeflate(serialize($this->bookmarks))).' */ ?>' |
188 | ); | 202 | ); |
189 | } | 203 | } |
190 | 204 | ||
191 | /** | 205 | /** |
192 | * Reorder links by creation date (newest first). | 206 | * Reorder links by creation date (newest first). |
193 | * | 207 | * |
194 | * Also update the urls and ids mapping arrays. | ||
195 | * | ||
196 | * @param string $order ASC|DESC | 208 | * @param string $order ASC|DESC |
197 | */ | 209 | */ |
198 | public function reorder($order = 'DESC') | 210 | public function reorder($order = 'DESC') |
199 | { | 211 | { |
200 | // backward compatibility: ignore reorder if the the `created` field doesn't exist | 212 | if (! $this->isLegacy) { |
201 | if (! isset(array_values($this->_links)[0]['created'])) { | 213 | $this->bookmarks->reorder($order); |
202 | return; | 214 | } else { |
203 | } | 215 | $order = $order === 'ASC' ? -1 : 1; |
204 | 216 | // backward compatibility: ignore reorder if the the `created` field doesn't exist | |
205 | $order = $order === 'ASC' ? -1 : 1; | 217 | if (! isset(array_values($this->bookmarks)[0]['created'])) { |
206 | // Reorder array by dates. | 218 | return; |
207 | usort($this->_links, function ($a, $b) use ($order) { | ||
208 | if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) { | ||
209 | return $a['sticky'] ? -1 : 1; | ||
210 | } | 219 | } |
211 | 220 | ||
212 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; | 221 | usort($this->bookmarks, function ($a, $b) use ($order) { |
213 | }); | 222 | if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) { |
223 | return $a['sticky'] ? -1 : 1; | ||
224 | } | ||
225 | |||
226 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; | ||
227 | }); | ||
228 | } | ||
214 | } | 229 | } |
215 | 230 | ||
216 | /** | 231 | /** |
217 | * Returns the number of links in the reference data | 232 | * Returns the number of bookmarks in the reference data |
218 | */ | 233 | */ |
219 | public function countLinks() | 234 | public function countLinks() |
220 | { | 235 | { |
@@ -222,7 +237,7 @@ class ReferenceLinkDB | |||
222 | } | 237 | } |
223 | 238 | ||
224 | /** | 239 | /** |
225 | * Returns the number of public links in the reference data | 240 | * Returns the number of public bookmarks in the reference data |
226 | */ | 241 | */ |
227 | public function countPublicLinks() | 242 | public function countPublicLinks() |
228 | { | 243 | { |
@@ -230,7 +245,7 @@ class ReferenceLinkDB | |||
230 | } | 245 | } |
231 | 246 | ||
232 | /** | 247 | /** |
233 | * Returns the number of private links in the reference data | 248 | * Returns the number of private bookmarks in the reference data |
234 | */ | 249 | */ |
235 | public function countPrivateLinks() | 250 | public function countPrivateLinks() |
236 | { | 251 | { |
@@ -238,14 +253,20 @@ class ReferenceLinkDB | |||
238 | } | 253 | } |
239 | 254 | ||
240 | /** | 255 | /** |
241 | * Returns the number of links without tag | 256 | * Returns the number of bookmarks without tag |
242 | */ | 257 | */ |
243 | public function countUntaggedLinks() | 258 | public function countUntaggedLinks() |
244 | { | 259 | { |
245 | $cpt = 0; | 260 | $cpt = 0; |
246 | foreach ($this->_links as $link) { | 261 | foreach ($this->bookmarks as $link) { |
247 | if (empty($link['tags'])) { | 262 | if (! $this->isLegacy) { |
248 | ++$cpt; | 263 | if (empty($link->getTags())) { |
264 | ++$cpt; | ||
265 | } | ||
266 | } else { | ||
267 | if (empty($link['tags'])) { | ||
268 | ++$cpt; | ||
269 | } | ||
249 | } | 270 | } |
250 | } | 271 | } |
251 | return $cpt; | 272 | return $cpt; |
@@ -254,16 +275,16 @@ class ReferenceLinkDB | |||
254 | public function getLinks() | 275 | public function getLinks() |
255 | { | 276 | { |
256 | $this->reorder(); | 277 | $this->reorder(); |
257 | return $this->_links; | 278 | return $this->bookmarks; |
258 | } | 279 | } |
259 | 280 | ||
260 | /** | 281 | /** |
261 | * Setter to override link creation. | 282 | * Setter to override link creation. |
262 | * | 283 | * |
263 | * @param array $links List of links. | 284 | * @param array $links List of bookmarks. |
264 | */ | 285 | */ |
265 | public function setLinks($links) | 286 | public function setLinks($links) |
266 | { | 287 | { |
267 | $this->_links = $links; | 288 | $this->bookmarks = $links; |
268 | } | 289 | } |
269 | } | 290 | } |
diff --git a/tests/utils/config/configJson.json.php b/tests/utils/config/configJson.json.php index 1549ddfc..b04dc303 100644 --- a/tests/utils/config/configJson.json.php +++ b/tests/utils/config/configJson.json.php | |||
@@ -41,12 +41,12 @@ | |||
41 | "foo": "bar" | 41 | "foo": "bar" |
42 | }, | 42 | }, |
43 | "resource": { | 43 | "resource": { |
44 | "datastore": "tests\/utils\/config\/datastore.php", | 44 | "datastore": "sandbox/datastore.php", |
45 | "data_dir": "sandbox\/", | 45 | "data_dir": "sandbox\/", |
46 | "raintpl_tpl": "tpl\/", | 46 | "raintpl_tpl": "tpl\/", |
47 | "config": "data\/config.php", | 47 | "config": "data\/config.php", |
48 | "ban_file": "data\/ipbans.php", | 48 | "ban_file": "data\/ipbans.php", |
49 | "updates": "data\/updates.txt", | 49 | "updates": "sandbox/updates.txt", |
50 | "log": "data\/log.txt", | 50 | "log": "data\/log.txt", |
51 | "update_check": "data\/lastupdatecheck.txt", | 51 | "update_check": "data\/lastupdatecheck.txt", |
52 | "history": "data\/history.php", | 52 | "history": "data\/history.php", |
@@ -59,7 +59,7 @@ | |||
59 | "WALLABAG_VERSION": 1 | 59 | "WALLABAG_VERSION": 1 |
60 | }, | 60 | }, |
61 | "dev": { | 61 | "dev": { |
62 | "debug": true | 62 | "debug": false |
63 | }, | 63 | }, |
64 | "updates": { | 64 | "updates": { |
65 | "check_updates": false, | 65 | "check_updates": false, |
diff --git a/tpl/default/404.html b/tpl/default/404.html index 472566a6..7b696e4c 100644 --- a/tpl/default/404.html +++ b/tpl/default/404.html | |||
@@ -6,9 +6,9 @@ | |||
6 | <body> | 6 | <body> |
7 | <div id="pageheader"> | 7 | <div id="pageheader"> |
8 | {include="page.header"} | 8 | {include="page.header"} |
9 | <div class="center" id="page404" class="page404-container"> | 9 | <div id="pageError" class="page-error-container center"> |
10 | <h2>{'Sorry, nothing to see here.'|t}</h2> | 10 | <h2>{'Sorry, nothing to see here.'|t}</h2> |
11 | <img src="img/sad_star.png" alt=""> | 11 | <img src="{$asset_path}/img/sad_star.png#" alt=""> |
12 | <p>{$error_message}</p> | 12 | <p>{$error_message}</p> |
13 | </div> | 13 | </div> |
14 | {include="page.footer"} | 14 | {include="page.footer"} |
diff --git a/tpl/default/addlink.html b/tpl/default/addlink.html index b4b4a0ec..67d3ebd1 100644 --- a/tpl/default/addlink.html +++ b/tpl/default/addlink.html | |||
@@ -9,7 +9,7 @@ | |||
9 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> | 9 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> |
10 | <div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24"> | 10 | <div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24"> |
11 | <h2 class="window-title">{"Shaare a new link"|t}</h2> | 11 | <h2 class="window-title">{"Shaare a new link"|t}</h2> |
12 | <form method="GET" action="#" name="addform" class="addform"> | 12 | <form method="GET" action="{$base_path}/admin/shaare" name="addform" class="addform"> |
13 | <div> | 13 | <div> |
14 | <label for="shaare">{'URL or leave empty to post a note'|t}</label> | 14 | <label for="shaare">{'URL or leave empty to post a note'|t}</label> |
15 | <input type="text" name="post" id="shaare" class="autofocus"> | 15 | <input type="text" name="post" id="shaare" class="autofocus"> |
diff --git a/tpl/default/changepassword.html b/tpl/default/changepassword.html index ab579433..736774f3 100644 --- a/tpl/default/changepassword.html +++ b/tpl/default/changepassword.html | |||
@@ -9,7 +9,7 @@ | |||
9 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> | 9 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> |
10 | <div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24"> | 10 | <div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24"> |
11 | <h2 class="window-title">{"Change password"|t}</h2> | 11 | <h2 class="window-title">{"Change password"|t}</h2> |
12 | <form method="POST" action="#" name="changepasswordform" id="changepasswordform"> | 12 | <form method="POST" action="{$base_path}/admin/password" name="changepasswordform" id="changepasswordform"> |
13 | <div> | 13 | <div> |
14 | <input type="password" name="oldpassword" aria-label="{'Current password'|t}" placeholder="{'Current password'|t}" class="autofocus"> | 14 | <input type="password" name="oldpassword" aria-label="{'Current password'|t}" placeholder="{'Current password'|t}" class="autofocus"> |
15 | </div> | 15 | </div> |
diff --git a/tpl/default/changetag.html b/tpl/default/changetag.html index ec6e0b46..16c55896 100644 --- a/tpl/default/changetag.html +++ b/tpl/default/changetag.html | |||
@@ -9,7 +9,7 @@ | |||
9 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> | 9 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> |
10 | <div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24"> | 10 | <div id="addlink-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24"> |
11 | <h2 class="window-title">{"Manage tags"|t}</h2> | 11 | <h2 class="window-title">{"Manage tags"|t}</h2> |
12 | <form method="POST" action="#" name="changetag" id="changetag"> | 12 | <form method="POST" action="{$base_path}/admin/tags" name="changetag" id="changetag"> |
13 | <div> | 13 | <div> |
14 | <input type="text" name="fromtag" aria-label="{'Tag'|t}" placeholder="{'Tag'|t}" value="{$fromtag}" | 14 | <input type="text" name="fromtag" aria-label="{'Tag'|t}" placeholder="{'Tag'|t}" value="{$fromtag}" |
15 | list="tagsList" autocomplete="off" class="awesomplete autofocus" data-minChars="1"> | 15 | list="tagsList" autocomplete="off" class="awesomplete autofocus" data-minChars="1"> |
@@ -32,7 +32,7 @@ | |||
32 | </div> | 32 | </div> |
33 | </form> | 33 | </form> |
34 | 34 | ||
35 | <p>{'You can also edit tags in the'|t} <a href="?do=taglist&sort=usage">{'tag list'|t}</a>.</p> | 35 | <p>{'You can also edit tags in the'|t} <a href="{$base_path}/tags/list?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 c1a6a6bc..bb2564af 100644 --- a/tpl/default/configure.html +++ b/tpl/default/configure.html | |||
@@ -11,7 +11,7 @@ | |||
11 | {$ratioInput='7-12'} | 11 | {$ratioInput='7-12'} |
12 | {$ratioInputMobile='1-8'} | 12 | {$ratioInputMobile='1-8'} |
13 | 13 | ||
14 | <form method="POST" action="#" name="configform" id="configform"> | 14 | <form method="POST" action="{$base_path}/admin/configure" name="configform" id="configform"> |
15 | <div class="pure-g"> | 15 | <div class="pure-g"> |
16 | <div class="pure-u-lg-1-8 pure-u-1-24"></div> | 16 | <div class="pure-u-lg-1-8 pure-u-1-24"></div> |
17 | <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete"> | 17 | <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete"> |
@@ -35,7 +35,7 @@ | |||
35 | <div class="form-label"> | 35 | <div class="form-label"> |
36 | <label for="titleLink"> | 36 | <label for="titleLink"> |
37 | <span class="label-name">{'Home link'|t}</span><br> | 37 | <span class="label-name">{'Home link'|t}</span><br> |
38 | <span class="label-desc">{'Default value'|t}: ?</span> | 38 | <span class="label-desc">{'Default value'|t}: {$base_path}/</span> |
39 | </label> | 39 | </label> |
40 | </div> | 40 | </div> |
41 | </div> | 41 | </div> |
@@ -68,6 +68,28 @@ | |||
68 | </select> | 68 | </select> |
69 | </div> | 69 | </div> |
70 | </div> | 70 | </div> |
71 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> | ||
72 | <div class="form-label"> | ||
73 | <label for="formatter"> | ||
74 | <span class="label-name">{'Description formatter'|t}</span> | ||
75 | </label> | ||
76 | </div> | ||
77 | </div> | ||
78 | <div class="pure-u-lg-{$ratioInput} pure-u-1"> | ||
79 | <div class="form-input"> | ||
80 | <select name="formatter" id="formatter" class="align"> | ||
81 | {loop="$formatter_available"} | ||
82 | <option value="{$value}" | ||
83 | {if="$value===$formatter"} | ||
84 | selected="selected" | ||
85 | {/if} | ||
86 | > | ||
87 | {$value|ucfirst} | ||
88 | </option> | ||
89 | {/loop} | ||
90 | </select> | ||
91 | </div> | ||
92 | </div> | ||
71 | </div> | 93 | </div> |
72 | <div class="pure-g"> | 94 | <div class="pure-g"> |
73 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> | 95 | <div class="pure-u-lg-{$ratioLabel} pure-u-1"> |
@@ -267,7 +289,7 @@ | |||
267 | {if="! $gd_enabled"} | 289 | {if="! $gd_enabled"} |
268 | {'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t} | 290 | {'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t} |
269 | {elseif="$thumbnails_enabled"} | 291 | {elseif="$thumbnails_enabled"} |
270 | <a href="?do=thumbs_update">{'Synchronize thumbnails'|t}</a> | 292 | <a href="{$base_path}/admin/thumbnails">{'Synchronize thumbnails'|t}</a> |
271 | {/if} | 293 | {/if} |
272 | </span> | 294 | </span> |
273 | </label> | 295 | </label> |
diff --git a/tpl/default/daily.html b/tpl/default/daily.html index 6b5103a4..3ab8053f 100644 --- a/tpl/default/daily.html +++ b/tpl/default/daily.html | |||
@@ -11,7 +11,7 @@ | |||
11 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor" id="daily"> | 11 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor" id="daily"> |
12 | <h2 class="window-title"> | 12 | <h2 class="window-title"> |
13 | {'The Daily Shaarli'|t} | 13 | {'The Daily Shaarli'|t} |
14 | <a href="?do=dailyrss" title="{'1 RSS entry per day'|t}"><i class="fa fa-rss"></i></a> | 14 | <a href="{$base_path}/daily-rss" title="{'1 RSS entry per day'|t}"><i class="fa fa-rss"></i></a> |
15 | </h2> | 15 | </h2> |
16 | 16 | ||
17 | <div id="plugin_zone_start_daily" class="plugin_zone"> | 17 | <div id="plugin_zone_start_daily" class="plugin_zone"> |
@@ -25,7 +25,7 @@ | |||
25 | <div class="pure-g"> | 25 | <div class="pure-g"> |
26 | <div class="pure-u-lg-1-3 pure-u-1 center"> | 26 | <div class="pure-u-lg-1-3 pure-u-1 center"> |
27 | {if="$previousday"} | 27 | {if="$previousday"} |
28 | <a href="?do=daily&day={$previousday}"> | 28 | <a href="{$base_path}/daily?day={$previousday}"> |
29 | <i class="fa fa-arrow-left"></i> | 29 | <i class="fa fa-arrow-left"></i> |
30 | {'Previous day'|t} | 30 | {'Previous day'|t} |
31 | </a> | 31 | </a> |
@@ -36,7 +36,7 @@ | |||
36 | </div> | 36 | </div> |
37 | <div class="pure-u-lg-1-3 pure-u-1 center"> | 37 | <div class="pure-u-lg-1-3 pure-u-1 center"> |
38 | {if="$nextday"} | 38 | {if="$nextday"} |
39 | <a href="?do=daily&day={$nextday}"> | 39 | <a href="{$base_path}/daily?day={$nextday}"> |
40 | {'Next day'|t} | 40 | {'Next day'|t} |
41 | <i class="fa fa-arrow-right"></i> | 41 | <i class="fa fa-arrow-right"></i> |
42 | </a> | 42 | </a> |
@@ -69,7 +69,7 @@ | |||
69 | {$link=$value} | 69 | {$link=$value} |
70 | <div class="daily-entry"> | 70 | <div class="daily-entry"> |
71 | <div class="daily-entry-title center"> | 71 | <div class="daily-entry-title center"> |
72 | <a href="?{$link.shorturl}" title="{'Permalink'|t}"> | 72 | <a href="{$base_path}/?{$link.shorturl}" title="{'Permalink'|t}"> |
73 | <i class="fa fa-link"></i> | 73 | <i class="fa fa-link"></i> |
74 | </a> | 74 | </a> |
75 | <a href="{$link.real_url}">{$link.title}</a> | 75 | <a href="{$link.real_url}">{$link.title}</a> |
@@ -85,7 +85,7 @@ | |||
85 | {if="$link.tags"} | 85 | {if="$link.tags"} |
86 | <div class="daily-entry-tags center"> | 86 | <div class="daily-entry-tags center"> |
87 | {loop="link.taglist"} | 87 | {loop="link.taglist"} |
88 | <span class="label label-tag" title="Add tag"> | 88 | <span class="label label-tag"> |
89 | {$value} | 89 | {$value} |
90 | </span> | 90 | </span> |
91 | {/loop} | 91 | {/loop} |
@@ -116,7 +116,7 @@ | |||
116 | </div> | 116 | </div> |
117 | </div> | 117 | </div> |
118 | {include="page.footer"} | 118 | {include="page.footer"} |
119 | <script src="js/thumbnails.min.js?v={$version_hash}"></script> | 119 | <script src="{$asset_path}/js/thumbnails.min.js?v={$version_hash}#"></script> |
120 | </body> | 120 | </body> |
121 | </html> | 121 | </html> |
122 | 122 | ||
diff --git a/tpl/default/dailyrss.html b/tpl/default/dailyrss.html index f589b06e..d40d9496 100644 --- a/tpl/default/dailyrss.html +++ b/tpl/default/dailyrss.html | |||
@@ -1,16 +1,32 @@ | |||
1 | <item> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <title>{$title} - {function="strftime('%A %e %B %Y', $daydate)"}</title> | 2 | <rss version="2.0"> |
3 | <guid>{$absurl}</guid> | 3 | <channel> |
4 | <link>{$absurl}</link> | 4 | <title>Daily - {$title}</title> |
5 | <pubDate>{$rssdate}</pubDate> | 5 | <link>{$index_url}</link> |
6 | <description><![CDATA[ | 6 | <description>Daily shaared bookmarks</description> |
7 | {loop="links"} | 7 | <language>{$language}</language> |
8 | <h3><a href="{$value.url}">{$value.title}</a></h3> | 8 | <copyright>{$index_url}</copyright> |
9 | <small>{if="!$hide_timestamps"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> | 9 | <generator>Shaarli</generator> |
10 | {$value.url}</small><br> | 10 | |
11 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> | 11 | {loop="$days"} |
12 | {if="$value.description"}{$value.formatedDescription}{/if} | 12 | <item> |
13 | <br><br><hr> | 13 | <title>{$value.date_human} - {$title}</title> |
14 | {/loop} | 14 | <guid>{$value.absolute_url}</guid> |
15 | ]]></description> | 15 | <link>{$value.absolute_url}</link> |
16 | </item> | 16 | <pubDate>{$value.date_rss}</pubDate> |
17 | <description><![CDATA[ | ||
18 | {loop="$value.links"} | ||
19 | <h3><a href="{$value.url}">{$value.title}</a></h3> | ||
20 | <small> | ||
21 | {if="!$hide_timestamps"}{$value.created|format_date} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> | ||
22 | {$value.url} | ||
23 | </small><br> | ||
24 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> | ||
25 | {if="$value.description"}{$value.description}{/if} | ||
26 | <br><br><hr> | ||
27 | {/loop} | ||
28 | ]]></description> | ||
29 | </item> | ||
30 | {/loop} | ||
31 | </channel> | ||
32 | </rss><!-- Cached version of {$page_url} --> | ||
diff --git a/tpl/default/editlink.html b/tpl/default/editlink.html index df14535d..568545bd 100644 --- a/tpl/default/editlink.html +++ b/tpl/default/editlink.html | |||
@@ -7,11 +7,14 @@ | |||
7 | {include="page.header"} | 7 | {include="page.header"} |
8 | <div id="editlinkform" class="edit-link-container" class="pure-g"> | 8 | <div id="editlinkform" class="edit-link-container" class="pure-g"> |
9 | <div class="pure-u-lg-1-5 pure-u-1-24"></div> | 9 | <div class="pure-u-lg-1-5 pure-u-1-24"></div> |
10 | <form method="post" name="linkform" class="page-form pure-u-lg-3-5 pure-u-22-24 page-form page-form-light"> | 10 | <form method="post" |
11 | name="linkform" | ||
12 | action="{$base_path}/admin/shaare" | ||
13 | class="page-form pure-u-lg-3-5 pure-u-22-24 page-form page-form-light" | ||
14 | > | ||
11 | <h2 class="window-title"> | 15 | <h2 class="window-title"> |
12 | {if="!$link_is_new"}{'Edit Shaare'|t}{else}{'New Shaare'|t}{/if} | 16 | {if="!$link_is_new"}{'Edit Shaare'|t}{else}{'New Shaare'|t}{/if} |
13 | </h2> | 17 | </h2> |
14 | <input type="hidden" name="lf_linkdate" value="{$link.linkdate}"> | ||
15 | {if="isset($link.id)"} | 18 | {if="isset($link.id)"} |
16 | <input type="hidden" name="lf_id" value="{$link.id}"> | 19 | <input type="hidden" name="lf_id" value="{$link.id}"> |
17 | {/if} | 20 | {/if} |
@@ -20,7 +23,7 @@ | |||
20 | <label for="lf_url">{'URL'|t}</label> | 23 | <label for="lf_url">{'URL'|t}</label> |
21 | </div> | 24 | </div> |
22 | <div> | 25 | <div> |
23 | <input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input autofocus"> | 26 | <input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input"> |
24 | </div> | 27 | </div> |
25 | <div> | 28 | <div> |
26 | <label for="lf_title">{'Title'|t}</label> | 29 | <label for="lf_title">{'Title'|t}</label> |
@@ -50,6 +53,15 @@ | |||
50 | <label for="lf_private">{'Private'|t}</label> | 53 | <label for="lf_private">{'Private'|t}</label> |
51 | </div> | 54 | </div> |
52 | 55 | ||
56 | {if="$formatter==='markdown'"} | ||
57 | <div class="md_help"> | ||
58 | {'Description will be rendered with'|t} | ||
59 | <a href="http://daringfireball.net/projects/markdown/syntax" title="{'Markdown syntax documentation'|t}"> | ||
60 | {'Markdown syntax'|t} | ||
61 | </a>. | ||
62 | </div> | ||
63 | {/if} | ||
64 | |||
53 | <div id="editlink-plugins"> | 65 | <div id="editlink-plugins"> |
54 | {loop="$edit_link_plugin"} | 66 | {loop="$edit_link_plugin"} |
55 | {$value} | 67 | {$value} |
@@ -61,7 +73,7 @@ | |||
61 | <input type="submit" name="save_edit" class="" id="button-save-edit" | 73 | <input type="submit" name="save_edit" class="" id="button-save-edit" |
62 | value="{if="$link_is_new"}{'Save'|t}{else}{'Apply Changes'|t}{/if}"> | 74 | value="{if="$link_is_new"}{'Save'|t}{else}{'Apply Changes'|t}{/if}"> |
63 | {if="!$link_is_new"} | 75 | {if="!$link_is_new"} |
64 | <a href="?delete_link&lf_linkdate={$link.id}&token={$token}" | 76 | <a href="{$base_path}/admin/shaare/delete?id={$link.id}&token={$token}" |
65 | title="" name="delete_link" class="button button-red confirm-delete"> | 77 | title="" name="delete_link" class="button button-red confirm-delete"> |
66 | {'Delete'|t} | 78 | {'Delete'|t} |
67 | </a> | 79 | </a> |
@@ -69,6 +81,7 @@ | |||
69 | </div> | 81 | </div> |
70 | 82 | ||
71 | <input type="hidden" name="token" value="{$token}"> | 83 | <input type="hidden" name="token" value="{$token}"> |
84 | <input type="hidden" name="source" value="{$source}"> | ||
72 | {if="$http_referer"} | 85 | {if="$http_referer"} |
73 | <input type="hidden" name="returnurl" value="{$http_referer}"> | 86 | <input type="hidden" name="returnurl" value="{$http_referer}"> |
74 | {/if} | 87 | {/if} |
diff --git a/tpl/default/error.html b/tpl/default/error.html new file mode 100644 index 00000000..c3e0c3c1 --- /dev/null +++ b/tpl/default/error.html | |||
@@ -0,0 +1,22 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html{if="$language !== 'auto'"} lang="{$language}"{/if}> | ||
3 | <head> | ||
4 | {include="includes"} | ||
5 | </head> | ||
6 | <body> | ||
7 | <div id="pageheader"> | ||
8 | {include="page.header"} | ||
9 | <div id="pageError" class="page-error-container center"> | ||
10 | <h2>{$message}</h2> | ||
11 | |||
12 | {if="!empty($stacktrace)"} | ||
13 | <pre> | ||
14 | {$stacktrace} | ||
15 | </pre> | ||
16 | {/if} | ||
17 | |||
18 | <img src="{$asset_path}/img/sad_star.png#" alt=""> | ||
19 | </div> | ||
20 | {include="page.footer"} | ||
21 | </body> | ||
22 | </html> | ||
diff --git a/tpl/default/export.html b/tpl/default/export.html index 99c01b11..c9c92943 100644 --- a/tpl/default/export.html +++ b/tpl/default/export.html | |||
@@ -6,14 +6,13 @@ | |||
6 | <body> | 6 | <body> |
7 | {include="page.header"} | 7 | {include="page.header"} |
8 | 8 | ||
9 | <form method="GET" action="#" name="exportform" id="exportform"> | 9 | <form method="POST" action="{$base_path}/admin/export" name="exportform" id="exportform"> |
10 | <div class="pure-g"> | 10 | <div class="pure-g"> |
11 | <div class="pure-u-lg-1-4 pure-u-1-24"></div> | 11 | <div class="pure-u-lg-1-4 pure-u-1-24"></div> |
12 | <div class="pure-u-lg-1-2 pure-u-22-24 page-form page-form-complete"> | 12 | <div class="pure-u-lg-1-2 pure-u-22-24 page-form page-form-complete"> |
13 | <div> | 13 | <div> |
14 | <h2 class="window-title">{"Export Database"|t}</h2> | 14 | <h2 class="window-title">{"Export Database"|t}</h2> |
15 | </div> | 15 | </div> |
16 | <input type="hidden" name="do" value="export"> | ||
17 | <input type="hidden" name="token" value="{$token}"> | 16 | <input type="hidden" name="token" value="{$token}"> |
18 | 17 | ||
19 | <div class="pure-g"> | 18 | <div class="pure-g"> |
diff --git a/tpl/default/feed.atom.html b/tpl/default/feed.atom.html index 29187505..dd58bd1e 100644 --- a/tpl/default/feed.atom.html +++ b/tpl/default/feed.atom.html | |||
@@ -6,11 +6,13 @@ | |||
6 | <updated>{$last_update}</updated> | 6 | <updated>{$last_update}</updated> |
7 | {/if} | 7 | {/if} |
8 | <link rel="self" href="{$self_link}#" /> | 8 | <link rel="self" href="{$self_link}#" /> |
9 | <link rel="search" type="application/opensearchdescription+xml" href="{$index_url}open-search#" | ||
10 | title="Shaarli search - {$shaarlititle}" /> | ||
9 | {loop="$plugins_feed_header"} | 11 | {loop="$plugins_feed_header"} |
10 | {$value} | 12 | {$value} |
11 | {/loop} | 13 | {/loop} |
12 | <author> | 14 | <author> |
13 | <name>{$index_url}</name> | 15 | <name>{$pagetitle}</name> |
14 | <uri>{$index_url}</uri> | 16 | <uri>{$index_url}</uri> |
15 | </author> | 17 | </author> |
16 | <id>{$index_url}</id> | 18 | <id>{$index_url}</id> |
diff --git a/tpl/default/feed.rss.html b/tpl/default/feed.rss.html index 66d9a869..85cec7f3 100644 --- a/tpl/default/feed.rss.html +++ b/tpl/default/feed.rss.html | |||
@@ -7,7 +7,9 @@ | |||
7 | <language>{$language}</language> | 7 | <language>{$language}</language> |
8 | <copyright>{$index_url}</copyright> | 8 | <copyright>{$index_url}</copyright> |
9 | <generator>Shaarli</generator> | 9 | <generator>Shaarli</generator> |
10 | <atom:link rel="self" href="{$self_link}" /> | 10 | <atom:link rel="self" href="{$self_link}" /> |
11 | <atom:link rel="search" type="application/opensearchdescription+xml" href="{$index_url}open-search#" | ||
12 | title="Shaarli search - {$shaarlititle}" /> | ||
11 | {loop="$plugins_feed_header"} | 13 | {loop="$plugins_feed_header"} |
12 | {$value} | 14 | {$value} |
13 | {/loop} | 15 | {/loop} |
diff --git a/tpl/default/import.html b/tpl/default/import.html index c41afcdb..156de71f 100644 --- a/tpl/default/import.html +++ b/tpl/default/import.html | |||
@@ -6,7 +6,7 @@ | |||
6 | <body> | 6 | <body> |
7 | {include="page.header"} | 7 | {include="page.header"} |
8 | 8 | ||
9 | <form method="POST" action="?do=import" enctype="multipart/form-data" name="uploadform" id="uploadform"> | 9 | <form method="POST" action="{$base_path}/admin/import" enctype="multipart/form-data" name="uploadform" id="uploadform"> |
10 | <div class="pure-g"> | 10 | <div class="pure-g"> |
11 | <div class="pure-u-lg-1-4 pure-u-1-24"></div> | 11 | <div class="pure-u-lg-1-4 pure-u-1-24"></div> |
12 | <div class="pure-u-lg-1-2 pure-u-22-24 page-form page-form-complete"> | 12 | <div class="pure-u-lg-1-2 pure-u-22-24 page-form page-form-complete"> |
diff --git a/tpl/default/includes.html b/tpl/default/includes.html index 6c30d1bf..227f9b52 100644 --- a/tpl/default/includes.html +++ b/tpl/default/includes.html | |||
@@ -3,18 +3,22 @@ | |||
3 | <meta name="format-detection" content="telephone=no" /> | 3 | <meta name="format-detection" content="telephone=no" /> |
4 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 4 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
5 | <meta name="referrer" content="same-origin"> | 5 | <meta name="referrer" content="same-origin"> |
6 | <link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> | 6 | <link rel="alternate" type="application/atom+xml" href="{$feedurl}feed/atom?{$searchcrits}#" title="ATOM Feed" /> |
7 | <link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" /> | 7 | <link rel="alternate" type="application/rss+xml" href="{$feedurl}feed/rss?{$searchcrits}#" title="RSS Feed" /> |
8 | <link href="img/favicon.png" rel="shortcut icon" type="image/png" /> | 8 | <link href="{$asset_path}/img/favicon.png#" rel="shortcut icon" type="image/png" /> |
9 | <link href="img/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180" /> | 9 | <link href="{$asset_path}/img/apple-touch-icon.png#" rel="apple-touch-icon" sizes="180x180" /> |
10 | <link type="text/css" rel="stylesheet" href="css/shaarli.min.css?v={$version_hash}" /> | 10 | <link type="text/css" rel="stylesheet" href="{$asset_path}/css/shaarli.min.css?v={$version_hash}#" /> |
11 | {if="$formatter==='markdown'"} | ||
12 | <link type="text/css" rel="stylesheet" href="{$asset_path}/css/markdown.min.css?v={$version_hash}#" /> | ||
13 | {/if} | ||
11 | {loop="$plugins_includes.css_files"} | 14 | {loop="$plugins_includes.css_files"} |
12 | <link type="text/css" rel="stylesheet" href="{$value}?v={$version_hash}#"/> | 15 | <link type="text/css" rel="stylesheet" href="{$base_path}/{$value}?v={$version_hash}#"/> |
13 | {/loop} | 16 | {/loop} |
14 | {if="is_file('data/user.css')"} | 17 | {if="is_file('data/user.css')"} |
15 | <link type="text/css" rel="stylesheet" href="data/user.css#" /> | 18 | <link type="text/css" rel="stylesheet" href="{$base_path}/data/user.css#" /> |
16 | {/if} | 19 | {/if} |
17 | <link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/> | 20 | <link rel="search" type="application/opensearchdescription+xml" href="{$base_path}/open-search#" |
21 | title="Shaarli search - {$shaarlititle}" /> | ||
18 | {if="! empty($links) && count($links) === 1"} | 22 | {if="! empty($links) && count($links) === 1"} |
19 | {$link=reset($links)} | 23 | {$link=reset($links)} |
20 | <meta property="og:title" content="{$link.title}" /> | 24 | <meta property="og:title" content="{$link.title}" /> |
@@ -22,12 +26,12 @@ | |||
22 | <meta property="og:url" content="{$index_url}?{$link.shorturl}" /> | 26 | <meta property="og:url" content="{$index_url}?{$link.shorturl}" /> |
23 | {$ogDescription=isset($link.description_src) ? $link.description_src : $link.description} | 27 | {$ogDescription=isset($link.description_src) ? $link.description_src : $link.description} |
24 | <meta property="og:description" content="{function="substr(strip_tags($ogDescription), 0, 300)"}" /> | 28 | <meta property="og:description" content="{function="substr(strip_tags($ogDescription), 0, 300)"}" /> |
25 | {if="$link.thumbnail"} | 29 | {if="!empty($link.thumbnail)"} |
26 | <meta property="og:image" content="{$index_url}{$link.thumbnail}" /> | 30 | <meta property="og:image" content="{$index_url}{$link.thumbnail}" /> |
27 | {/if} | 31 | {/if} |
28 | {if="!$hide_timestamps || $is_logged_in"} | 32 | {if="!$hide_timestamps || $is_logged_in"} |
29 | <meta property="article:published_time" content="{$link.created->format(DateTime::ATOM)}" /> | 33 | <meta property="article:published_time" content="{$link.created->format(DateTime::ATOM)}" /> |
30 | {if="$link.updated"} | 34 | {if="!empty($link.updated)"} |
31 | <meta property="article:modified_time" content="{$link.updated->format(DateTime::ATOM)}" /> | 35 | <meta property="article:modified_time" content="{$link.updated->format(DateTime::ATOM)}" /> |
32 | {/if} | 36 | {/if} |
33 | {/if} | 37 | {/if} |
diff --git a/tpl/default/install.html b/tpl/default/install.html index c6f501f0..a506a2eb 100644 --- a/tpl/default/install.html +++ b/tpl/default/install.html | |||
@@ -10,7 +10,7 @@ | |||
10 | {$ratioLabelMobile='7-8'} | 10 | {$ratioLabelMobile='7-8'} |
11 | {$ratioInputMobile='1-8'} | 11 | {$ratioInputMobile='1-8'} |
12 | 12 | ||
13 | <form method="POST" action="#" name="installform" id="installform"> | 13 | <form method="POST" action="{$base_path}/install" name="installform" id="installform"> |
14 | <div class="pure-g"> | 14 | <div class="pure-g"> |
15 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> | 15 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> |
16 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-form-complete"> | 16 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-form-complete"> |
diff --git a/tpl/default/linklist.html b/tpl/default/linklist.html index ffc236c7..b08773d8 100644 --- a/tpl/default/linklist.html +++ b/tpl/default/linklist.html | |||
@@ -94,7 +94,9 @@ | |||
94 | {'tagged'|t} | 94 | {'tagged'|t} |
95 | {loop="$exploded_tags"} | 95 | {loop="$exploded_tags"} |
96 | <span class="label label-tag" title="{'Remove tag'|t}"> | 96 | <span class="label label-tag" title="{'Remove tag'|t}"> |
97 | <a href="?removetag={function="urlencode($value)"}" aria-label="{'Remove tag'|t}">{$value}<span class="remove"><i class="fa fa-times" aria-hidden="true"></i></span></a> | 97 | <a href="{$base_path}/remove-tag/{function="$search_tags_url.$key1"}" aria-label="{'Remove tag'|t}"> |
98 | {$value}<span class="remove"><i class="fa fa-times" aria-hidden="true"></i></span> | ||
99 | </a> | ||
98 | </span> | 100 | </span> |
99 | {/loop} | 101 | {/loop} |
100 | {/if} | 102 | {/if} |
@@ -138,7 +140,7 @@ | |||
138 | <div class="thumbnail"> | 140 | <div class="thumbnail"> |
139 | {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} | 141 | {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} |
140 | <a href="{$value.real_url}" aria-hidden="true" tabindex="-1"> | 142 | <a href="{$value.real_url}" aria-hidden="true" tabindex="-1"> |
141 | <img data-src="{$value.thumbnail}#" class="b-lazy" | 143 | <img data-src="{$base_path}/{$value.thumbnail}#" class="b-lazy" |
142 | src="" | 144 | src="" |
143 | alt="" width="{$thumbnails_width}" height="{$thumbnails_height}" /> | 145 | alt="" width="{$thumbnails_width}" height="{$thumbnails_height}" /> |
144 | </a> | 146 | </a> |
@@ -181,7 +183,7 @@ | |||
181 | {$tag_counter=count($value.taglist)} | 183 | {$tag_counter=count($value.taglist)} |
182 | {loop="value.taglist"} | 184 | {loop="value.taglist"} |
183 | <span class="label label-tag" title="{$strAddTag}"> | 185 | <span class="label label-tag" title="{$strAddTag}"> |
184 | <a href="?addtag={$value|urlencode}">{$value}</a> | 186 | <a href="{$base_path}/add-tag/{$value1.urlencoded_taglist.$key2}">{$value}</a> |
185 | </span> | 187 | </span> |
186 | {if="$tag_counter - 1 != $counter"}·{/if} | 188 | {if="$tag_counter - 1 != $counter"}·{/if} |
187 | {/loop} | 189 | {/loop} |
@@ -196,16 +198,16 @@ | |||
196 | <input type="checkbox" class="link-checkbox" value="{$value.id}"> | 198 | <input type="checkbox" class="link-checkbox" value="{$value.id}"> |
197 | </span> | 199 | </span> |
198 | <span class="linklist-item-infos-controls-item ctrl-edit"> | 200 | <span class="linklist-item-infos-controls-item ctrl-edit"> |
199 | <a href="?edit_link={$value.id}" aria-label="{$strEdit}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link" aria-hidden="true"></i></a> | 201 | <a href="{$base_path}/admin/shaare/{$value.id}" aria-label="{$strEdit}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link" aria-hidden="true"></i></a> |
200 | </span> | 202 | </span> |
201 | <span class="linklist-item-infos-controls-item ctrl-delete"> | 203 | <span class="linklist-item-infos-controls-item ctrl-delete"> |
202 | <a href="?delete_link&lf_linkdate={$value.id}&token={$token}" aria-label="{$strDelete}" | 204 | <a href="{$base_path}/admin/shaare/delete?id={$value.id}&token={$token}" aria-label="{$strDelete}" |
203 | title="{$strDelete}" class="delete-link pure-u-0 pure-u-lg-visible confirm-delete"> | 205 | title="{$strDelete}" class="delete-link pure-u-0 pure-u-lg-visible confirm-delete"> |
204 | <i class="fa fa-trash" aria-hidden="true"></i> | 206 | <i class="fa fa-trash" aria-hidden="true"></i> |
205 | </a> | 207 | </a> |
206 | </span> | 208 | </span> |
207 | <span class="linklist-item-infos-controls-item ctrl-pin"> | 209 | <span class="linklist-item-infos-controls-item ctrl-pin"> |
208 | <a href="?do=pin&id={$value.id}&token={$token}" | 210 | <a href="{$base_path}/admin/shaare/{$value.id}/pin?token={$token}" |
209 | title="{$strToggleSticky}" aria-label="{$strToggleSticky}" class="pin-link {if="$value.sticky"}pinned-link{/if} pure-u-0 pure-u-lg-visible"> | 211 | title="{$strToggleSticky}" aria-label="{$strToggleSticky}" class="pin-link {if="$value.sticky"}pinned-link{/if} pure-u-0 pure-u-lg-visible"> |
210 | <i class="fa fa-thumb-tack" aria-hidden="true"></i> | 212 | <i class="fa fa-thumb-tack" aria-hidden="true"></i> |
211 | </a> | 213 | </a> |
@@ -222,7 +224,7 @@ | |||
222 | </div> | 224 | </div> |
223 | {/if} | 225 | {/if} |
224 | {/if} | 226 | {/if} |
225 | <a href="?{$value.shorturl}" title="{$strPermalink}"> | 227 | <a href="{$base_path}/shaare/{$value.shorturl}" title="{$strPermalink}"> |
226 | {if="!$hide_timestamps || $is_logged_in"} | 228 | {if="!$hide_timestamps || $is_logged_in"} |
227 | {$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink} | 229 | {$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink} |
228 | <span class="linkdate" title="{$updated}"> | 230 | <span class="linkdate" title="{$updated}"> |
@@ -265,12 +267,22 @@ | |||
265 | {/if} | 267 | {/if} |
266 | {if="$is_logged_in"} | 268 | {if="$is_logged_in"} |
267 | · | 269 | · |
268 | <a href="?delete_link&lf_linkdate={$value.id}&token={$token}" aria-label="{$strDelete}" | 270 | <a href="{$base_path}/admin/shaare/delete?id={$value.id}&token={$token}" aria-label="{$strDelete}" |
269 | title="{$strDelete}" class="delete-link confirm-delete"> | 271 | title="{$strDelete}" class="delete-link confirm-delete"> |
270 | <i class="fa fa-trash" aria-hidden="true"></i> | 272 | <i class="fa fa-trash" aria-hidden="true"></i> |
271 | </a> | 273 | </a> |
272 | · | 274 | · |
273 | <a href="?edit_link={$value.id}" aria-label="{$strEdit}" title="{$strEdit}"><i class="fa fa-pencil-square-o edit-link" aria-hidden="true"></i></a> | 275 | <a href="{$base_path}/admin/shaare/{$value.id}" aria-label="{$strEdit}" title="{$strEdit}"> |
276 | <i class="fa fa-pencil-square-o edit-link" aria-hidden="true"></i> | ||
277 | </a> | ||
278 | · | ||
279 | <a href="{$base_path}/admin/shaare/{$value.id}/pin?token={$token}" | ||
280 | aria-label="{$strToggleSticky}" | ||
281 | title="{$strToggleSticky}" | ||
282 | class="pin-link {if="$value.sticky"}pinned-link{/if}" | ||
283 | > | ||
284 | <i class="fa fa-thumb-tack" aria-hidden="true"></i> | ||
285 | </a> | ||
274 | {/if} | 286 | {/if} |
275 | </div> | 287 | </div> |
276 | </div> | 288 | </div> |
@@ -295,6 +307,6 @@ | |||
295 | </div> | 307 | </div> |
296 | 308 | ||
297 | {include="page.footer"} | 309 | {include="page.footer"} |
298 | <script src="js/thumbnails.min.js?v={$version_hash}"></script> | 310 | <script src="{$asset_path}/js/thumbnails.min.js?v={$version_hash}#"></script> |
299 | </body> | 311 | </body> |
300 | </html> | 312 | </html> |
diff --git a/tpl/default/linklist.paging.html b/tpl/default/linklist.paging.html index 68947f92..aa637868 100644 --- a/tpl/default/linklist.paging.html +++ b/tpl/default/linklist.paging.html | |||
@@ -1,27 +1,29 @@ | |||
1 | <div class="linklist-paging"> | 1 | <div class="linklist-paging"> |
2 | <div class="paging pure-g"> | 2 | <div class="paging pure-g"> |
3 | <div class="linklist-filters pure-u-1-3"> | 3 | <div class="linklist-filters pure-u-1-3"> |
4 | {if="$is_logged_in or !empty($action_plugin)"} | 4 | <span class="linklist-filters-text pure-u-0 pure-u-lg-visible"> |
5 | <span class="linklist-filters-text pure-u-0 pure-u-lg-visible"> | 5 | {'Filters'|t} |
6 | {'Filters'|t} | 6 | </span> |
7 | </span> | 7 | {if="$is_logged_in"} |
8 | {if="$is_logged_in"} | 8 | <a href="{$base_path}/admin/visibility/private" aria-label="{'Only display private links'|t}" title="{'Only display private links'|t}" |
9 | <a href="?visibility=private" aria-label="{'Only display private links'|t}" title="{'Only display private links'|t}" | 9 | class="{if="$visibility==='private'"}filter-on{else}filter-off{/if}" |
10 | class="{if="$visibility==='private'"}filter-on{else}filter-off{/if}" | 10 | ><i class="fa fa-user-secret" aria-hidden="true"></i></a> |
11 | ><i class="fa fa-user-secret" aria-hidden="true"></i></a> | 11 | <a href="{$base_path}/admin/visibility/public" aria-label="{'Only display public links'|t}" title="{'Only display public links'|t}" |
12 | <a href="?visibility=public" aria-label="{'Only display public links'|t}" title="{'Only display public links'|t}" | 12 | class="{if="$visibility==='public'"}filter-on{else}filter-off{/if}" |
13 | class="{if="$visibility==='public'"}filter-on{else}filter-off{/if}" | 13 | ><i class="fa fa-globe" aria-hidden="true"></i></a> |
14 | ><i class="fa fa-globe" aria-hidden="true"></i></a> | 14 | {/if} |
15 | {/if} | 15 | <a href="{$base_path}/untagged-only" aria-label="{'Filter untagged links'|t}" title="{'Filter untagged links'|t}" |
16 | <a href="?untaggedonly" aria-label="{'Filter untagged links'|t}" title="{'Filter untagged links'|t}" | 16 | class={if="$untaggedonly"}"filter-on"{else}"filter-off"{/if} |
17 | class={if="$untaggedonly"}"filter-on"{else}"filter-off"{/if} | 17 | ><i class="fa fa-tag" aria-hidden="true"></i></a> |
18 | ><i class="fa fa-tag" aria-hidden="true"></i></a> | 18 | {if="$is_logged_in"} |
19 | <a href="#" aria-label="{'Select all'|t}" title="{'Select all'|t}" | 19 | <a href="#" aria-label="{'Select all'|t}" title="{'Select all'|t}" |
20 | class="filter-off select-all-button pure-u-0 pure-u-lg-visible" | 20 | class="filter-off select-all-button pure-u-0 pure-u-lg-visible" |
21 | ><i class="fa fa-check-square-o" aria-hidden="true"></i></a> | 21 | ><i class="fa fa-check-square-o" aria-hidden="true"></i></a> |
22 | <a href="#" class="filter-off fold-all pure-u-lg-0" aria-label="{'Fold all'|t}" title="{'Fold all'|t}"> | 22 | {/if} |
23 | <i class="fa fa-chevron-up" aria-hidden="true"></i> | 23 | <a href="#" class="filter-off fold-all pure-u-lg-0" aria-label="{'Fold all'|t}" title="{'Fold all'|t}"> |
24 | </a> | 24 | <i class="fa fa-chevron-up" aria-hidden="true"></i> |
25 | </a> | ||
26 | {if="!empty($action_plugin)"} | ||
25 | {loop="$action_plugin"} | 27 | {loop="$action_plugin"} |
26 | {$value.attr.class=isset($value.attr.class) ? $value.attr.class : ''} | 28 | {$value.attr.class=isset($value.attr.class) ? $value.attr.class : ''} |
27 | {$value.attr.class=!empty($value.on) ? $value.attr.class .' filter-on' : $value.attr.class .' filter-off'} | 29 | {$value.attr.class=!empty($value.on) ? $value.attr.class .' filter-on' : $value.attr.class .' filter-off'} |
@@ -53,11 +55,16 @@ | |||
53 | 55 | ||
54 | <div class="linksperpage pure-u-1-3"> | 56 | <div class="linksperpage pure-u-1-3"> |
55 | <div class="pure-u-0 pure-u-lg-visible">{'Links per page'|t}</div> | 57 | <div class="pure-u-0 pure-u-lg-visible">{'Links per page'|t}</div> |
56 | <a href="?linksperpage=20">20</a> | 58 | <a href="{$base_path}/links-per-page?nb=20" |
57 | <a href="?linksperpage=50">50</a> | 59 | {if="$links_per_page == 20"}class="selected"{/if}>20</a> |
58 | <a href="?linksperpage=100">100</a> | 60 | <a href="{$base_path}/links-per-page?nb=50" |
59 | <form method="GET" class="pure-u-0 pure-u-lg-visible"> | 61 | {if="$links_per_page == 50"}class="selected"{/if}>50</a> |
60 | <input type="text" name="linksperpage" placeholder="133"> | 62 | <a href="{$base_path}/links-per-page?nb=100" |
63 | {if="$links_per_page == 100"}class="selected"{/if}>100</a> | ||
64 | <form method="GET" class="pure-u-0 pure-u-lg-visible" action="{$base_path}/links-per-page"> | ||
65 | <input type="text" name="nb" placeholder="133" | ||
66 | {if="$links_per_page != 20 && $links_per_page != 50 && $links_per_page != 100"} | ||
67 | value="{$links_per_page}"{/if}> | ||
61 | </form> | 68 | </form> |
62 | <a href="#" class="filter-off fold-all pure-u-0 pure-u-lg-visible" aria-label="{'Fold all'|t}" title="{'Fold all'|t}"> | 69 | <a href="#" class="filter-off fold-all pure-u-0 pure-u-lg-visible" aria-label="{'Fold all'|t}" title="{'Fold all'|t}"> |
63 | <i class="fa fa-chevron-up" aria-hidden="true"></i> | 70 | <i class="fa fa-chevron-up" aria-hidden="true"></i> |
diff --git a/tpl/default/loginform.html b/tpl/default/loginform.html index 761aec0c..90c2b2b6 100644 --- a/tpl/default/loginform.html +++ b/tpl/default/loginform.html | |||
@@ -5,44 +5,32 @@ | |||
5 | </head> | 5 | </head> |
6 | <body> | 6 | <body> |
7 | {include="page.header"} | 7 | {include="page.header"} |
8 | {if="!$user_can_login"} | 8 | <div class="pure-g"> |
9 | <div class="pure-g pure-alert pure-alert-error pure-alert-closable center"> | 9 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> |
10 | <div class="pure-u-2-24"></div> | 10 | <div id="login-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24 login-form-container"> |
11 | <div class="pure-u-20-24"> | 11 | <form method="post" name="loginform"> |
12 | <p>{'You have been banned after too many failed login attempts. Try again later.'|t}</p> | 12 | <h2 class="window-title">{'Login'|t}</h2> |
13 | </div> | 13 | <div> |
14 | <div class="pure-u-2-24"> | 14 | <input type="text" name="login" aria-label="{'Username'|t}" placeholder="{'Username'|t}" |
15 | <i class="fa fa-times pure-alert-close"></i> | 15 | {if="!empty($username)"}value="{$username}"{/if} class="autofocus"> |
16 | </div> | ||
17 | <div> | ||
18 | <input type="password" name="password" aria-label="{'Password'|t}" placeholder="{'Password'|t}" class="autofocus"> | ||
19 | </div> | ||
20 | <div class="remember-me"> | ||
21 | <input type="checkbox" name="longlastingsession" id="longlastingsessionform" | ||
22 | {if="$remember_user_default"}checked="checked"{/if}> | ||
23 | <label for="longlastingsessionform">{'Remember me'|t}</label> | ||
24 | </div> | ||
25 | <div> | ||
26 | <input type="submit" value="{'Login'|t}" class="bigbutton"> | ||
27 | </div> | ||
28 | <input type="hidden" name="token" value="{$token}"> | ||
29 | {if="$returnurl"}<input type="hidden" name="returnurl" value="{$returnurl}">{/if} | ||
30 | </form> | ||
16 | </div> | 31 | </div> |
32 | <div class="pure-u-lg-1-3 pure-u-1-8"></div> | ||
17 | </div> | 33 | </div> |
18 | {else} | ||
19 | <div class="pure-g"> | ||
20 | <div class="pure-u-lg-1-3 pure-u-1-24"></div> | ||
21 | <div id="login-form" class="page-form page-form-light pure-u-lg-1-3 pure-u-22-24 login-form-container"> | ||
22 | <form method="post" name="loginform"> | ||
23 | <h2 class="window-title">{'Login'|t}</h2> | ||
24 | <div> | ||
25 | <input type="text" name="login" aria-label="{'Username'|t}" placeholder="{'Username'|t}" | ||
26 | {if="!empty($username)"}value="{$username}"{/if} class="autofocus"> | ||
27 | </div> | ||
28 | <div> | ||
29 | <input type="password" name="password" aria-label="{'Password'|t}" placeholder="{'Password'|t}" class="autofocus"> | ||
30 | </div> | ||
31 | <div class="remember-me"> | ||
32 | <input type="checkbox" name="longlastingsession" id="longlastingsessionform" | ||
33 | {if="$remember_user_default"}checked="checked"{/if}> | ||
34 | <label for="longlastingsessionform">{'Remember me'|t}</label> | ||
35 | </div> | ||
36 | <div> | ||
37 | <input type="submit" value="{'Login'|t}" class="bigbutton"> | ||
38 | </div> | ||
39 | <input type="hidden" name="token" value="{$token}"> | ||
40 | {if="$returnurl"}<input type="hidden" name="returnurl" value="{$returnurl}">{/if} | ||
41 | </form> | ||
42 | </div> | ||
43 | <div class="pure-u-lg-1-3 pure-u-1-8"></div> | ||
44 | </div> | ||
45 | {/if} | ||
46 | 34 | ||
47 | {include="page.footer"} | 35 | {include="page.footer"} |
48 | </body> | 36 | </body> |
diff --git a/tpl/default/opensearch.html b/tpl/default/opensearch.html index 3fcc30b7..1c7f279b 100644 --- a/tpl/default/opensearch.html +++ b/tpl/default/opensearch.html | |||
@@ -3,8 +3,8 @@ | |||
3 | <ShortName>Shaarli search - {$pagetitle}</ShortName> | 3 | <ShortName>Shaarli search - {$pagetitle}</ShortName> |
4 | <Description>Shaarli search - {$pagetitle}</Description> | 4 | <Description>Shaarli search - {$pagetitle}</Description> |
5 | <Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" /> | 5 | <Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" /> |
6 | <Url type="application/atom+xml" template="{$serverurl}?do=atom&searchterm={searchTerms}"/> | 6 | <Url type="application/atom+xml" template="{$serverurl}feed/atom?searchterm={searchTerms}"/> |
7 | <Url type="application/rss+xml" template="{$serverurl}?do=rss&searchterm={searchTerms}"/> | 7 | <Url type="application/rss+xml" template="{$serverurl}feed/rss?searchterm={searchTerms}"/> |
8 | <InputEncoding>UTF-8</InputEncoding> | 8 | <InputEncoding>UTF-8</InputEncoding> |
9 | <Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer> | 9 | <Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer> |
10 | <Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE | 10 | <Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE |
diff --git a/tpl/default/page.footer.html b/tpl/default/page.footer.html index 0899826b..51bdb2f0 100644 --- a/tpl/default/page.footer.html +++ b/tpl/default/page.footer.html | |||
@@ -10,7 +10,7 @@ | |||
10 | {/if} | 10 | {/if} |
11 | · | 11 | · |
12 | {'The personal, minimalist, super-fast, database free, bookmarking service'|t} {'by the Shaarli community'|t} · | 12 | {'The personal, minimalist, super-fast, database free, bookmarking service'|t} {'by the Shaarli community'|t} · |
13 | <a href="doc/html/index.html" rel="nofollow">{'Documentation'|t}</a> | 13 | <a href="{$base_path}/doc/html/index.html" rel="nofollow">{'Documentation'|t}</a> |
14 | {loop="$plugins_footer.text"} | 14 | {loop="$plugins_footer.text"} |
15 | {$value} | 15 | {$value} |
16 | {/loop} | 16 | {/loop} |
@@ -25,7 +25,7 @@ | |||
25 | {/loop} | 25 | {/loop} |
26 | 26 | ||
27 | {loop="$plugins_footer.js_files"} | 27 | {loop="$plugins_footer.js_files"} |
28 | <script src="{$value}#"></script> | 28 | <script src="{$base_path}/{$value}#"></script> |
29 | {/loop} | 29 | {/loop} |
30 | 30 | ||
31 | <div id="js-translations" class="hidden"> | 31 | <div id="js-translations" class="hidden"> |
@@ -39,4 +39,5 @@ | |||
39 | </span> | 39 | </span> |
40 | </div> | 40 | </div> |
41 | 41 | ||
42 | <script src="js/shaarli.min.js?v={$version_hash}"></script> | 42 | <input type="hidden" name="js_base_path" value="{$base_path}" /> |
43 | <script src="{$asset_path}/js/shaarli.min.js?v={$version_hash}#"></script> | ||
diff --git a/tpl/default/page.header.html b/tpl/default/page.header.html index 4f063dc3..a71464c7 100644 --- a/tpl/default/page.header.html +++ b/tpl/default/page.header.html | |||
@@ -21,24 +21,24 @@ | |||
21 | </li> | 21 | </li> |
22 | {if="$is_logged_in || $openshaarli"} | 22 | {if="$is_logged_in || $openshaarli"} |
23 | <li class="pure-menu-item"> | 23 | <li class="pure-menu-item"> |
24 | <a href="?do=addlink" class="pure-menu-link" id="shaarli-menu-shaare"> | 24 | <a href="{$base_path}/admin/add-shaare" class="pure-menu-link" id="shaarli-menu-shaare"> |
25 | <i class="fa fa-plus" aria-hidden="true"></i> {'Shaare'|t} | 25 | <i class="fa fa-plus" aria-hidden="true"></i> {'Shaare'|t} |
26 | </a> | 26 | </a> |
27 | </li> | 27 | </li> |
28 | <li class="pure-menu-item" id="shaarli-menu-tools"> | 28 | <li class="pure-menu-item" id="shaarli-menu-tools"> |
29 | <a href="?do=tools" class="pure-menu-link">{'Tools'|t}</a> | 29 | <a href="{$base_path}/admin/tools" class="pure-menu-link">{'Tools'|t}</a> |
30 | </li> | 30 | </li> |
31 | {/if} | 31 | {/if} |
32 | <li class="pure-menu-item" id="shaarli-menu-tags"> | 32 | <li class="pure-menu-item" id="shaarli-menu-tags"> |
33 | <a href="?do=tagcloud" class="pure-menu-link">{'Tag cloud'|t}</a> | 33 | <a href="{$base_path}/tags/cloud" class="pure-menu-link">{'Tag cloud'|t}</a> |
34 | </li> | 34 | </li> |
35 | {if="$thumbnails_enabled"} | 35 | {if="$thumbnails_enabled"} |
36 | <li class="pure-menu-item" id="shaarli-menu-picwall"> | 36 | <li class="pure-menu-item" id="shaarli-menu-picwall"> |
37 | <a href="?do=picwall{$searchcrits}" class="pure-menu-link">{'Picture wall'|t}</a> | 37 | <a href="{$base_path}/picture-wall?{function="ltrim($searchcrits, '&')"}" class="pure-menu-link">{'Picture wall'|t}</a> |
38 | </li> | 38 | </li> |
39 | {/if} | 39 | {/if} |
40 | <li class="pure-menu-item" id="shaarli-menu-daily"> | 40 | <li class="pure-menu-item" id="shaarli-menu-daily"> |
41 | <a href="?do=daily" class="pure-menu-link">{'Daily'|t}</a> | 41 | <a href="{$base_path}/daily" class="pure-menu-link">{'Daily'|t}</a> |
42 | </li> | 42 | </li> |
43 | {loop="$plugins_header.buttons_toolbar"} | 43 | {loop="$plugins_header.buttons_toolbar"} |
44 | <li class="pure-menu-item shaarli-menu-plugin"> | 44 | <li class="pure-menu-item shaarli-menu-plugin"> |
@@ -52,15 +52,15 @@ | |||
52 | </li> | 52 | </li> |
53 | {/loop} | 53 | {/loop} |
54 | <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss"> | 54 | <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss"> |
55 | <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a> | 55 | <a href="{$base_path}/feed/{$feed_type}?{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a> |
56 | </li> | 56 | </li> |
57 | {if="$is_logged_in"} | 57 | {if="$is_logged_in"} |
58 | <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout"> | 58 | <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout"> |
59 | <a href="?do=logout" class="pure-menu-link">{'Logout'|t}</a> | 59 | <a href="{$base_path}/admin/logout" class="pure-menu-link">{'Logout'|t}</a> |
60 | </li> | 60 | </li> |
61 | {else} | 61 | {else} |
62 | <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-login"> | 62 | <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-login"> |
63 | <a href="?do=login" class="pure-menu-link">{'Login'|t}</a> | 63 | <a href="{$base_path}/login" class="pure-menu-link">{'Login'|t}</a> |
64 | </li> | 64 | </li> |
65 | {/if} | 65 | {/if} |
66 | </ul> | 66 | </ul> |
@@ -74,13 +74,13 @@ | |||
74 | </a> | 74 | </a> |
75 | </li> | 75 | </li> |
76 | <li class="pure-menu-item" id="shaarli-menu-desktop-rss"> | 76 | <li class="pure-menu-item" id="shaarli-menu-desktop-rss"> |
77 | <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}" aria-label="{'RSS Feed'|t}"> | 77 | <a href="{$base_path}/feed/{$feed_type}?{$searchcrits}" class="pure-menu-link" title="{'RSS Feed'|t}" aria-label="{'RSS Feed'|t}"> |
78 | <i class="fa fa-rss" aria-hidden="true"></i> | 78 | <i class="fa fa-rss" aria-hidden="true"></i> |
79 | </a> | 79 | </a> |
80 | </li> | 80 | </li> |
81 | {if="!$is_logged_in"} | 81 | {if="!$is_logged_in"} |
82 | <li class="pure-menu-item" id="shaarli-menu-desktop-login"> | 82 | <li class="pure-menu-item" id="shaarli-menu-desktop-login"> |
83 | <a href="?do=login" class="pure-menu-link" | 83 | <a href="{$base_path}/login" class="pure-menu-link" |
84 | data-open-id="header-login-form" | 84 | data-open-id="header-login-form" |
85 | id="login-button" aria-label="{'Login'|t}" title="{'Login'|t}"> | 85 | id="login-button" aria-label="{'Login'|t}" title="{'Login'|t}"> |
86 | <i class="fa fa-user" aria-hidden="true"></i> | 86 | <i class="fa fa-user" aria-hidden="true"></i> |
@@ -88,7 +88,7 @@ | |||
88 | </li> | 88 | </li> |
89 | {else} | 89 | {else} |
90 | <li class="pure-menu-item" id="shaarli-menu-desktop-logout"> | 90 | <li class="pure-menu-item" id="shaarli-menu-desktop-logout"> |
91 | <a href="?do=logout" class="pure-menu-link" aria-label="{'Logout'|t}" title="{'Logout'|t}"> | 91 | <a href="{$base_path}/admin/logout" class="pure-menu-link" aria-label="{'Logout'|t}" title="{'Logout'|t}"> |
92 | <i class="fa fa-sign-out" aria-hidden="true"></i> | 92 | <i class="fa fa-sign-out" aria-hidden="true"></i> |
93 | </a> | 93 | </a> |
94 | </li> | 94 | </li> |
@@ -101,7 +101,7 @@ | |||
101 | 101 | ||
102 | <main id="content" class="container" role="main"> | 102 | <main id="content" class="container" role="main"> |
103 | <div id="search" class="subheader-form searchform-block header-search"> | 103 | <div id="search" class="subheader-form searchform-block header-search"> |
104 | <form method="GET" class="pure-form searchform" name="searchform"> | 104 | <form method="GET" class="pure-form searchform" name="searchform" action="{$base_path}/"> |
105 | <input type="text" id="searchform_value" name="searchterm" aria-label="{'Search text'|t}" placeholder="{'Search text'|t}" | 105 | <input type="text" id="searchform_value" name="searchterm" aria-label="{'Search text'|t}" placeholder="{'Search text'|t}" |
106 | {if="!empty($search_term)"} | 106 | {if="!empty($search_term)"} |
107 | value="{$search_term}" | 107 | value="{$search_term}" |
@@ -184,8 +184,22 @@ | |||
184 | </div> | 184 | </div> |
185 | {/if} | 185 | {/if} |
186 | 186 | ||
187 | {if="!empty($global_warnings) && $is_logged_in"} | 187 | {if="!empty($global_errors)"} |
188 | <div class="pure-g pure-alert pure-alert-warning pure-alert-closable" id="shaarli-warnings-alert"> | 188 | <div class="pure-g header-alert-message pure-alert pure-alert-error pure-alert-closable" id="shaarli-errors-alert"> |
189 | <div class="pure-u-2-24"></div> | ||
190 | <div class="pure-u-20-24"> | ||
191 | {loop="$global_errors"} | ||
192 | <p>{$value}</p> | ||
193 | {/loop} | ||
194 | </div> | ||
195 | <div class="pure-u-2-24"> | ||
196 | <i class="fa fa-times pure-alert-close"></i> | ||
197 | </div> | ||
198 | </div> | ||
199 | {/if} | ||
200 | |||
201 | {if="!empty($global_warnings)"} | ||
202 | <div class="pure-g header-alert-message pure-alert pure-alert-warning pure-alert-closable" id="shaarli-warnings-alert"> | ||
189 | <div class="pure-u-2-24"></div> | 203 | <div class="pure-u-2-24"></div> |
190 | <div class="pure-u-20-24"> | 204 | <div class="pure-u-20-24"> |
191 | {loop="global_warnings"} | 205 | {loop="global_warnings"} |
@@ -198,4 +212,18 @@ | |||
198 | </div> | 212 | </div> |
199 | {/if} | 213 | {/if} |
200 | 214 | ||
215 | {if="!empty($global_successes)"} | ||
216 | <div class="pure-g header-alert-message new-version-message pure-alert pure-alert-success pure-alert-closable" id="shaarli-success-alert"> | ||
217 | <div class="pure-u-2-24"></div> | ||
218 | <div class="pure-u-20-24"> | ||
219 | {loop="$global_successes"} | ||
220 | <p>{$value}</p> | ||
221 | {/loop} | ||
222 | </div> | ||
223 | <div class="pure-u-2-24"> | ||
224 | <i class="fa fa-times pure-alert-close"></i> | ||
225 | </div> | ||
226 | </div> | ||
227 | {/if} | ||
228 | |||
201 | <div class="clear"></div> | 229 | <div class="clear"></div> |
diff --git a/tpl/default/picwall.html b/tpl/default/picwall.html index 73359949..b7a56c89 100644 --- a/tpl/default/picwall.html +++ b/tpl/default/picwall.html | |||
@@ -5,61 +5,55 @@ | |||
5 | </head> | 5 | </head> |
6 | <body> | 6 | <body> |
7 | {include="page.header"} | 7 | {include="page.header"} |
8 | {if="!$thumbnails_enabled"} | 8 | |
9 | <div class="pure-g pure-alert pure-alert-warning page-single-alert"> | 9 | {if="count($linksToDisplay)===0 && $is_logged_in"} |
10 | <div class="pure-u-1 center"> | 10 | <div class="pure-g pure-alert pure-alert-warning page-single-alert"> |
11 | {'Picture wall unavailable (thumbnails are disabled).'|t} | 11 | <div class="pure-u-1 center"> |
12 | </div> | 12 | {'There is no cached thumbnail.'|t} |
13 | </div> | 13 | <a href="{$base_path}/admin/thumbnails">{'Try to synchronize them.'|t}</a> |
14 | {else} | ||
15 | {if="count($linksToDisplay)===0 && $is_logged_in"} | ||
16 | <div class="pure-g pure-alert pure-alert-warning page-single-alert"> | ||
17 | <div class="pure-u-1 center"> | ||
18 | {'There is no cached thumbnail. Try to <a href="?do=thumbs_update">synchronize them</a>.'|t} | ||
19 | </div> | ||
20 | </div> | 14 | </div> |
21 | {/if} | 15 | </div> |
16 | {/if} | ||
22 | 17 | ||
23 | <div class="pure-g"> | 18 | <div class="pure-g"> |
24 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> | 19 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> |
25 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor"> | 20 | <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor"> |
26 | {$countPics=count($linksToDisplay)} | 21 | {$countPics=count($linksToDisplay)} |
27 | <h2 class="window-title">{'Picture Wall'|t} - {$countPics} {'pics'|t}</h2> | 22 | <h2 class="window-title">{'Picture Wall'|t} - {$countPics} {'pics'|t}</h2> |
28 | 23 | ||
29 | <div id="plugin_zone_start_picwall" class="plugin_zone"> | 24 | <div id="plugin_zone_start_picwall" class="plugin_zone"> |
30 | {loop="$plugin_start_zone"} | 25 | {loop="$plugin_start_zone"} |
31 | {$value} | 26 | {$value} |
32 | {/loop} | 27 | {/loop} |
33 | </div> | 28 | </div> |
34 | 29 | ||
35 | <div id="picwall-container" class="picwall-container" role="list"> | 30 | <div id="picwall-container" class="picwall-container" role="list"> |
36 | {loop="$linksToDisplay"} | 31 | {loop="$linksToDisplay"} |
37 | <div class="picwall-pictureframe" role="listitem"> | 32 | <div class="picwall-pictureframe" role="listitem"> |
38 | {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} | 33 | {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} |
39 | <img data-src="{$value.thumbnail}#" class="b-lazy" | 34 | <img data-src="{$value.thumbnail}#" class="b-lazy" |
40 | src="" | 35 | src="" |
41 | alt="" width="{$thumbnails_width}" height="{$thumbnails_height}" /> | 36 | alt="" width="{$thumbnails_width}" height="{$thumbnails_height}" /> |
42 | <a href="{$value.real_url}"><span class="info">{$value.title}</span></a> | 37 | <a href="{$value.real_url}"><span class="info">{$value.title}</span></a> |
43 | {loop="$value.picwall_plugin"} | 38 | {loop="$value.picwall_plugin"} |
44 | {$value} | 39 | {$value} |
45 | {/loop} | 40 | {/loop} |
46 | </div> | 41 | </div> |
47 | {/loop} | 42 | {/loop} |
48 | <div class="clear"></div> | 43 | <div class="clear"></div> |
49 | </div> | 44 | </div> |
50 | 45 | ||
51 | <div id="plugin_zone_end_picwall" class="plugin_zone"> | 46 | <div id="plugin_zone_end_picwall" class="plugin_zone"> |
52 | {loop="$plugin_end_zone"} | 47 | {loop="$plugin_end_zone"} |
53 | {$value} | 48 | {$value} |
54 | {/loop} | 49 | {/loop} |
55 | </div> | ||
56 | </div> | 50 | </div> |
57 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> | ||
58 | </div> | 51 | </div> |
59 | {/if} | 52 | <div class="pure-u-lg-1-6 pure-u-1-24"></div> |
53 | </div> | ||
60 | 54 | ||
61 | {include="page.footer"} | 55 | {include="page.footer"} |
62 | <script src="js/thumbnails.min.js?v={$version_hash}"></script> | 56 | <script src="{$asset_path}/js/thumbnails.min.js?v={$version_hash}#"></script> |
63 | </body> | 57 | </body> |
64 | </html> | 58 | </html> |
65 | 59 | ||
diff --git a/tpl/default/pluginsadmin.html b/tpl/default/pluginsadmin.html index 4bfaa934..05d13556 100644 --- a/tpl/default/pluginsadmin.html +++ b/tpl/default/pluginsadmin.html | |||
@@ -16,7 +16,7 @@ | |||
16 | <div class="clear"></div> | 16 | <div class="clear"></div> |
17 | </noscript> | 17 | </noscript> |
18 | 18 | ||
19 | <form method="POST" action="?do=save_pluginadmin" name="pluginform" id="pluginform" class="pluginform-container"> | 19 | <form method="POST" action="{$base_path}/admin/plugins" name="pluginform" id="pluginform" class="pluginform-container"> |
20 | <div class="pure-g"> | 20 | <div class="pure-g"> |
21 | <div class="pure-u-lg-1-8 pure-u-1-24"></div> | 21 | <div class="pure-u-lg-1-8 pure-u-1-24"></div> |
22 | <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete"> | 22 | <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete"> |
@@ -127,7 +127,7 @@ | |||
127 | <input type="hidden" name="token" value="{$token}"> | 127 | <input type="hidden" name="token" value="{$token}"> |
128 | </form> | 128 | </form> |
129 | 129 | ||
130 | <form action="?do=save_pluginadmin" method="POST"> | 130 | <form action="{$base_path}/admin/plugins" method="POST"> |
131 | <div class="pure-g"> | 131 | <div class="pure-g"> |
132 | <div class="pure-u-lg-1-8 pure-u-1-24"></div> | 132 | <div class="pure-u-lg-1-8 pure-u-1-24"></div> |
133 | <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-light"> | 133 | <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-light"> |
@@ -173,10 +173,11 @@ | |||
173 | </section> | 173 | </section> |
174 | </div> | 174 | </div> |
175 | </div> | 175 | </div> |
176 | <input type="hidden" name="token" value="{$token}"> | ||
176 | </form> | 177 | </form> |
177 | 178 | ||
178 | {include="page.footer"} | 179 | {include="page.footer"} |
179 | <script src="js/pluginsadmin.min.js?v={$version_hash}"></script> | 180 | <script src="{$asset_path}/js/pluginsadmin.min.js?v={$version_hash}#"></script> |
180 | 181 | ||
181 | </body> | 182 | </body> |
182 | </html> | 183 | </html> |
diff --git a/tpl/default/tag.cloud.html b/tpl/default/tag.cloud.html index b9c0b162..c067e1d4 100644 --- a/tpl/default/tag.cloud.html +++ b/tpl/default/tag.cloud.html | |||
@@ -15,7 +15,7 @@ | |||
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="center"> | 17 | <p class="center"> |
18 | <a href="?searchtags={$search_tags|urlencode}" class="pure-button pure-button-shaarli"> | 18 | <a href="{$base_path}/?searchtags={$search_tags_url}" class="pure-button pure-button-shaarli"> |
19 | {'List all links with those tags'|t} | 19 | {'List all links with those tags'|t} |
20 | </a> | 20 | </a> |
21 | </p> | 21 | </p> |
@@ -32,6 +32,7 @@ | |||
32 | {/if} | 32 | {/if} |
33 | autocomplete="off" data-multiple data-autofirst data-minChars="1" | 33 | autocomplete="off" data-multiple data-autofirst data-minChars="1" |
34 | data-list="{loop="$tags"}{$key}, {/loop}" | 34 | data-list="{loop="$tags"}{$key}, {/loop}" |
35 | class="autofocus" | ||
35 | > | 36 | > |
36 | <button type="submit" class="search-button" aria-label="{'Search'|t}"><i class="fa fa-search" aria-hidden="true"></i></button> | 37 | <button type="submit" class="search-button" aria-label="{'Search'|t}"><i class="fa fa-search" aria-hidden="true"></i></button> |
37 | </form> | 38 | </form> |
@@ -47,8 +48,8 @@ | |||
47 | 48 | ||
48 | <div id="cloudtag" class="cloudtag-container"> | 49 | <div id="cloudtag" class="cloudtag-container"> |
49 | {loop="tags"} | 50 | {loop="tags"} |
50 | <a href="?searchtags={$key|urlencode} {$search_tags|urlencode}" style="font-size:{$value.size}em;">{$key}</a | 51 | <a href="{$base_path}/?searchtags={$tags_url.$key1} {$search_tags_url}" style="font-size:{$value.size}em;">{$key}</a |
51 | ><a href="?addtag={$key|urlencode}" title="{'Filter by tag'|t}" class="count">{$value.count}</a> | 52 | ><a href="{$base_path}/add-tag/{$tags_url.$key1}" title="{'Filter by tag'|t}" class="count">{$value.count}</a> |
52 | {loop="$value.tag_plugin"} | 53 | {loop="$value.tag_plugin"} |
53 | {$value} | 54 | {$value} |
54 | {/loop} | 55 | {/loop} |
diff --git a/tpl/default/tag.list.html b/tpl/default/tag.list.html index d5777465..96e7fbe0 100644 --- a/tpl/default/tag.list.html +++ b/tpl/default/tag.list.html | |||
@@ -15,7 +15,7 @@ | |||
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}" class="pure-button pure-button-shaarli"> | 18 | <a href="{$base_path}/?searchtags={$search_tags_url}" class="pure-button pure-button-shaarli"> |
19 | {'List all links with those tags'|t} | 19 | {'List all links with those tags'|t} |
20 | </a> | 20 | </a> |
21 | </p> | 21 | </p> |
@@ -47,17 +47,17 @@ | |||
47 | 47 | ||
48 | <div id="taglist" class="taglist-container"> | 48 | <div id="taglist" class="taglist-container"> |
49 | {loop="tags"} | 49 | {loop="tags"} |
50 | <div class="tag-list-item pure-g" data-tag="{$key}"> | 50 | <div class="tag-list-item pure-g" data-tag="{$key}" data-tag-url="{$tags_url.$key1}"> |
51 | <div class="pure-u-1"> | 51 | <div class="pure-u-1"> |
52 | {if="$is_logged_in===true"} | 52 | {if="$is_logged_in===true"} |
53 | <a href="#" class="delete-tag" aria-label="{'Delete'|t}"><i class="fa fa-trash" aria-hidden="true"></i></a> | 53 | <a href="#" class="delete-tag" aria-label="{'Delete'|t}"><i class="fa fa-trash" aria-hidden="true"></i></a> |
54 | <a href="?do=changetag&fromtag={$key|urlencode}" class="rename-tag" aria-label="{'Rename tag'|t}"> | 54 | <a href="{$base_path}/admin/tags?fromtag={$tags_url.$key1}" class="rename-tag" aria-label="{'Rename tag'|t}"> |
55 | <i class="fa fa-pencil-square-o {$key}" aria-hidden="true"></i> | 55 | <i class="fa fa-pencil-square-o {$key}" aria-hidden="true"></i> |
56 | </a> | 56 | </a> |
57 | {/if} | 57 | {/if} |
58 | 58 | ||
59 | <a href="?addtag={$key|urlencode}" title="{'Filter by tag'|t}" class="count">{$value}</a> | 59 | <a href="{$base_path}/add-tag/{$tags_url.$key1}" title="{'Filter by tag'|t}" class="count">{$value}</a> |
60 | <a href="?searchtags={$key|urlencode} {$search_tags|urlencode}" class="tag-link">{$key}</a> | 60 | <a href="{$base_path}/?searchtags={$tags_url.$key1} {$search_tags_url}" class="tag-link">{$key}</a> |
61 | 61 | ||
62 | {loop="$value.tag_plugin"} | 62 | {loop="$value.tag_plugin"} |
63 | {$value} | 63 | {$value} |
diff --git a/tpl/default/tag.sort.html b/tpl/default/tag.sort.html index d24c9f64..b3764e29 100644 --- a/tpl/default/tag.sort.html +++ b/tpl/default/tag.sort.html | |||
@@ -1,8 +1,8 @@ | |||
1 | <div class="pure-g"> | 1 | <div class="pure-g"> |
2 | <div class="pure-u-1 pure-alert pure-alert-success tag-sort"> | 2 | <div class="pure-u-1 pure-alert pure-alert-success tag-sort"> |
3 | {'Sort by:'|t} | 3 | {'Sort by:'|t} |
4 | <a href="?do=tagcloud">{'Cloud'|t}</a> · | 4 | <a href="{$base_path}/tags/cloud">{'Cloud'|t}</a> |
5 | <a href="?do=taglist&sort=usage">{'Most used'|t}</a> · | 5 | <a href="{$base_path}/tags/list?sort=usage">{'Most used'|t}</a> |
6 | <a href="?do=taglist&sort=alpha">{'Alphabetical'|t}</a> | 6 | <a href="{$base_path}/tags/list?sort=alpha">{'Alphabetical'|t}</a> |
7 | </div> | 7 | </div> |
8 | </div> \ No newline at end of file | 8 | </div> |
diff --git a/tpl/default/thumbnails.html b/tpl/default/thumbnails.html index f1939798..504644ca 100644 --- a/tpl/default/thumbnails.html +++ b/tpl/default/thumbnails.html | |||
@@ -38,11 +38,11 @@ | |||
38 | </div> | 38 | </div> |
39 | </div> | 39 | </div> |
40 | 40 | ||
41 | <input type="hidden" name="ids" value="{function="implode($ids, ',')"}" /> | 41 | <input type="hidden" name="ids" value="{function="implode(',', $ids)"}" /> |
42 | </div> | 42 | </div> |
43 | </div> | 43 | </div> |
44 | 44 | ||
45 | {include="page.footer"} | 45 | {include="page.footer"} |
46 | <script src="js/thumbnails_update.min.js?v={$version_hash}"></script> | 46 | <script src="{$asset_path}/js/thumbnails_update.min.js?v={$version_hash}#"></script> |
47 | </body> | 47 | </body> |
48 | </html> | 48 | </html> |
diff --git a/tpl/default/tools.html b/tpl/default/tools.html index 20d0c893..2cb08e38 100644 --- a/tpl/default/tools.html +++ b/tpl/default/tools.html | |||
@@ -11,35 +11,35 @@ | |||
11 | <div class="pure-u-lg-1-3 pure-u-22-24 page-form page-form-light"> | 11 | <div class="pure-u-lg-1-3 pure-u-22-24 page-form page-form-light"> |
12 | <h2 class="window-title">{'Settings'|t}</h2> | 12 | <h2 class="window-title">{'Settings'|t}</h2> |
13 | <div class="tools-item"> | 13 | <div class="tools-item"> |
14 | <a href="?do=configure" title="{'Change Shaarli settings: title, timezone, etc.'|t}"> | 14 | <a href="{$base_path}/admin/configure" title="{'Change Shaarli settings: title, timezone, etc.'|t}"> |
15 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Configure your Shaarli'|t}</span> | 15 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Configure your Shaarli'|t}</span> |
16 | </a> | 16 | </a> |
17 | </div> | 17 | </div> |
18 | <div class="tools-item"> | 18 | <div class="tools-item"> |
19 | <a href="?do=pluginadmin" title="{'Enable, disable and configure plugins'|t}"> | 19 | <a href="{$base_path}/admin/plugins" title="{'Enable, disable and configure plugins'|t}"> |
20 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Plugin administration'|t}</span> | 20 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Plugin administration'|t}</span> |
21 | </a> | 21 | </a> |
22 | </div> | 22 | </div> |
23 | {if="!$openshaarli"} | 23 | {if="!$openshaarli"} |
24 | <div class="tools-item"> | 24 | <div class="tools-item"> |
25 | <a href="?do=changepasswd" title="{'Change your password'|t}"> | 25 | <a href="{$base_path}/admin/password" title="{'Change your password'|t}"> |
26 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Change password'|t}</span> | 26 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Change password'|t}</span> |
27 | </a> | 27 | </a> |
28 | </div> | 28 | </div> |
29 | {/if} | 29 | {/if} |
30 | <div class="tools-item"> | 30 | <div class="tools-item"> |
31 | <a href="?do=changetag" title="{'Rename or delete a tag in all links'|t}"> | 31 | <a href="{$base_path}/admin/tags" title="{'Rename or delete a tag in all links'|t}"> |
32 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Manage tags'|t}</span> | 32 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Manage tags'|t}</span> |
33 | </a> | 33 | </a> |
34 | </div> | 34 | </div> |
35 | <div class="tools-item"> | 35 | <div class="tools-item"> |
36 | <a href="?do=import" | 36 | <a href="{$base_path}/admin/import" |
37 | title="{'Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, delicious...)'|t}"> | 37 | title="{'Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, delicious...)'|t}"> |
38 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Import links'|t}</span> | 38 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Import links'|t}</span> |
39 | </a> | 39 | </a> |
40 | </div> | 40 | </div> |
41 | <div class="tools-item"> | 41 | <div class="tools-item"> |
42 | <a href="?do=export" | 42 | <a href="{$base_path}/admin/export" |
43 | title="{'Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)'|t}"> | 43 | title="{'Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)'|t}"> |
44 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Export database'|t}</span> | 44 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Export database'|t}</span> |
45 | </a> | 45 | </a> |
@@ -47,7 +47,7 @@ | |||
47 | 47 | ||
48 | {if="$thumbnails_enabled"} | 48 | {if="$thumbnails_enabled"} |
49 | <div class="tools-item"> | 49 | <div class="tools-item"> |
50 | <a href="?do=thumbs_update" title="{'Synchronize all link thumbnails'|t}"> | 50 | <a href="{$base_path}/admin/thumbnails" title="{'Synchronize all link thumbnails'|t}"> |
51 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Synchronize thumbnails'|t}</span> | 51 | <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Synchronize thumbnails'|t}</span> |
52 | </a> | 52 | </a> |
53 | </div> | 53 | </div> |
@@ -86,7 +86,7 @@ | |||
86 | alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}'); | 86 | alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}'); |
87 | } | 87 | } |
88 | window.open( | 88 | window.open( |
89 | '{$pageabsaddr}?post='%20+%20encodeURIComponent(url)+ | 89 | '{$pageabsaddr}admin/shaare?post='%20+%20encodeURIComponent(url)+ |
90 | '&title='%20+%20encodeURIComponent(title)+ | 90 | '&title='%20+%20encodeURIComponent(title)+ |
91 | '&description='%20+%20encodeURIComponent(desc)+ | 91 | '&description='%20+%20encodeURIComponent(desc)+ |
92 | '&source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1' | 92 | '&source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1' |
diff --git a/tpl/vintage/404.html b/tpl/vintage/404.html index 53e98e2e..0fef0f08 100644 --- a/tpl/vintage/404.html +++ b/tpl/vintage/404.html | |||
@@ -10,7 +10,7 @@ | |||
10 | <div class="error-container"> | 10 | <div class="error-container"> |
11 | <h1>404 Not found <small>Oh crap!</small></h1> | 11 | <h1>404 Not found <small>Oh crap!</small></h1> |
12 | <p>{$error_message}</p> | 12 | <p>{$error_message}</p> |
13 | <p>Would you mind <a href="?">clicking here</a>?</p> | 13 | <p>Would you mind <a href="{$base_path}/">clicking here</a>?</p> |
14 | </div> | 14 | </div> |
15 | {include="page.footer"} | 15 | {include="page.footer"} |
16 | </body> | 16 | </body> |
diff --git a/tpl/vintage/addlink.html b/tpl/vintage/addlink.html index da50f45e..ade08c7c 100644 --- a/tpl/vintage/addlink.html +++ b/tpl/vintage/addlink.html | |||
@@ -5,7 +5,7 @@ | |||
5 | <div id="pageheader"> | 5 | <div id="pageheader"> |
6 | {include="page.header"} | 6 | {include="page.header"} |
7 | <div id="headerform"> | 7 | <div id="headerform"> |
8 | <form method="GET" action="" name="addform" class="addform"> | 8 | <form method="GET" action="{$base_path}/admin/shaare" name="addform" class="addform"> |
9 | <input type="text" name="post" class="linkurl"> | 9 | <input type="text" name="post" class="linkurl"> |
10 | <input type="submit" value="Add link" class="bigbutton"> | 10 | <input type="submit" value="Add link" class="bigbutton"> |
11 | </form> | 11 | </form> |
diff --git a/tpl/vintage/changepassword.html b/tpl/vintage/changepassword.html index c40daf9d..7e37b9a3 100644 --- a/tpl/vintage/changepassword.html +++ b/tpl/vintage/changepassword.html | |||
@@ -4,7 +4,7 @@ | |||
4 | <body onload="document.changepasswordform.oldpassword.focus();"> | 4 | <body onload="document.changepasswordform.oldpassword.focus();"> |
5 | <div id="pageheader"> | 5 | <div id="pageheader"> |
6 | {include="page.header"} | 6 | {include="page.header"} |
7 | <form method="POST" action="#" name="changepasswordform" id="changepasswordform"> | 7 | <form method="POST" action="{$base_path}/admin/password" name="changepasswordform" id="changepasswordform"> |
8 | Old password: <input type="password" name="oldpassword"> | 8 | Old password: <input type="password" name="oldpassword"> |
9 | New password: <input type="password" name="setpassword"> | 9 | New password: <input type="password" name="setpassword"> |
10 | <input type="hidden" name="token" value="{$token}"> | 10 | <input type="hidden" name="token" value="{$token}"> |
@@ -12,4 +12,4 @@ | |||
12 | </div> | 12 | </div> |
13 | {include="page.footer"} | 13 | {include="page.footer"} |
14 | </body> | 14 | </body> |
15 | </html> \ No newline at end of file | 15 | </html> |
diff --git a/tpl/vintage/changetag.html b/tpl/vintage/changetag.html index 670a8dd7..6ef60252 100644 --- a/tpl/vintage/changetag.html +++ b/tpl/vintage/changetag.html | |||
@@ -5,7 +5,7 @@ | |||
5 | <body onload="document.changetag.fromtag.focus();"> | 5 | <body onload="document.changetag.fromtag.focus();"> |
6 | <div id="pageheader"> | 6 | <div id="pageheader"> |
7 | {include="page.header"} | 7 | {include="page.header"} |
8 | <form method="POST" action="" name="changetag" id="changetag"> | 8 | <form method="POST" action="{$base_path}/admin/tags" name="changetag" id="changetag"> |
9 | <input type="hidden" name="token" value="{$token}"> | 9 | <input type="hidden" name="token" value="{$token}"> |
10 | <div> | 10 | <div> |
11 | <label for="fromtag">Tag:</label> | 11 | <label for="fromtag">Tag:</label> |
diff --git a/tpl/vintage/configure.html b/tpl/vintage/configure.html index 160286a5..ba4f3f71 100644 --- a/tpl/vintage/configure.html +++ b/tpl/vintage/configure.html | |||
@@ -4,7 +4,7 @@ | |||
4 | <body onload="document.configform.title.focus();"> | 4 | <body onload="document.configform.title.focus();"> |
5 | <div id="pageheader"> | 5 | <div id="pageheader"> |
6 | {include="page.header"} | 6 | {include="page.header"} |
7 | <form method="POST" action="#" name="configform" id="configform"> | 7 | <form method="POST" action="{$base_path}/admin/configure" name="configform" id="configform"> |
8 | <input type="hidden" name="token" value="{$token}"> | 8 | <input type="hidden" name="token" value="{$token}"> |
9 | <table id="configuration_table"> | 9 | <table id="configuration_table"> |
10 | 10 | ||
@@ -16,7 +16,7 @@ | |||
16 | <tr> | 16 | <tr> |
17 | <td><b>Home link:</b></td> | 17 | <td><b>Home link:</b></td> |
18 | <td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label | 18 | <td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label |
19 | for="titleLink">(default value is: ?)</label></td> | 19 | for="titleLink">(default value is: {$base_path}/)</label></td> |
20 | </tr> | 20 | </tr> |
21 | 21 | ||
22 | <tr> | 22 | <tr> |
@@ -33,6 +33,19 @@ | |||
33 | </tr> | 33 | </tr> |
34 | 34 | ||
35 | <tr> | 35 | <tr> |
36 | <td><b>Description formatter:</b></td> | ||
37 | <td> | ||
38 | <select name="formatter" id="formatter"> | ||
39 | {loop="$formatter_available"} | ||
40 | <option value="{$value}" {if="$value===$formatter"}selected{/if}> | ||
41 | {$value|ucfirst} | ||
42 | </option> | ||
43 | {/loop} | ||
44 | </select> | ||
45 | </td> | ||
46 | </tr> | ||
47 | |||
48 | <tr> | ||
36 | <td><b>Timezone:</b></td> | 49 | <td><b>Timezone:</b></td> |
37 | <td> | 50 | <td> |
38 | <select id="continent" name="continent"> | 51 | <select id="continent" name="continent"> |
@@ -146,7 +159,7 @@ | |||
146 | {if="! $gd_enabled"} | 159 | {if="! $gd_enabled"} |
147 | {'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t} | 160 | {'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t} |
148 | {elseif="$thumbnails_enabled"} | 161 | {elseif="$thumbnails_enabled"} |
149 | <a href="?do=thumbs_update">{'Synchonize thumbnails'|t}</a> | 162 | <a href="{$base_path}/admin/thumbnails">{'Synchonize thumbnails'|t}</a> |
150 | {/if} | 163 | {/if} |
151 | </label> | 164 | </label> |
152 | </td> | 165 | </td> |
diff --git a/tpl/vintage/daily.html b/tpl/vintage/daily.html index 00f18e26..74f6cdc7 100644 --- a/tpl/vintage/daily.html +++ b/tpl/vintage/daily.html | |||
@@ -14,9 +14,9 @@ | |||
14 | 14 | ||
15 | <div class="dailyAbout"> | 15 | <div class="dailyAbout"> |
16 | All links of one day<br>in a single page.<br> | 16 | All links of one day<br>in a single page.<br> |
17 | {if="$previousday"} <a href="?do=daily&day={$previousday}"><b><</b>Previous day</a>{else}<b><</b>Previous day{/if} | 17 | {if="$previousday"} <a href="{$base_path}/daily&day={$previousday}"><b><</b>Previous day</a>{else}<b><</b>Previous day{/if} |
18 | - | 18 | - |
19 | {if="$nextday"}<a href="?do=daily&day={$nextday}">Next day<b>></b></a>{else}Next day<b>></b>{/if} | 19 | {if="$nextday"}<a href="{$base_path}/daily&day={$nextday}">Next day<b>></b></a>{else}Next day<b>></b>{/if} |
20 | <br> | 20 | <br> |
21 | 21 | ||
22 | {loop="$daily_about_plugin"} | 22 | {loop="$daily_about_plugin"} |
@@ -24,13 +24,13 @@ | |||
24 | {/loop} | 24 | {/loop} |
25 | 25 | ||
26 | <br> | 26 | <br> |
27 | <a href="?do=dailyrss" title="1 RSS entry per day"><img src="img/feed-icon-14x14.png" alt="rss_feed">Daily RSS Feed</a> | 27 | <a href="{$base_path}/daily-rss" title="1 RSS entry per day"><img src="{$asset_path}/img/feed-icon-14x14.png#" alt="rss_feed">Daily RSS Feed</a> |
28 | </div> | 28 | </div> |
29 | 29 | ||
30 | <div class="dailyTitle"> | 30 | <div class="dailyTitle"> |
31 | <img src="img/floral_left.png" width="51" height="50" class="nomobile" alt="floral_left"> | 31 | <img src="{$asset_path}/img/floral_left.png#" width="51" height="50" class="nomobile" alt="floral_left"> |
32 | The Daily Shaarli | 32 | The Daily Shaarli |
33 | <img src="img/floral_right.png" width="51" height="50" class="nomobile" alt="floral_right"> | 33 | <img src="{$asset_path}/img/floral_right.png#" width="51" height="50" class="nomobile" alt="floral_right"> |
34 | </div> | 34 | </div> |
35 | 35 | ||
36 | <div class="dailyDate"> | 36 | <div class="dailyDate"> |
@@ -52,13 +52,13 @@ | |||
52 | {$link=$value} | 52 | {$link=$value} |
53 | <div class="dailyEntry"> | 53 | <div class="dailyEntry"> |
54 | <div class="dailyEntryPermalink"> | 54 | <div class="dailyEntryPermalink"> |
55 | <a href="?{$value.shorturl}"> | 55 | <a href="{$base_path}/?{$value.shorturl}"> |
56 | <img src="img/squiggle.png" width="25" height="26" title="permalink" alt="permalink"> | 56 | <img src="{$asset_path}/img/squiggle.png#" width="25" height="26" title="permalink" alt="permalink"> |
57 | </a> | 57 | </a> |
58 | </div> | 58 | </div> |
59 | {if="!$hide_timestamps || $is_logged_in"} | 59 | {if="!$hide_timestamps || $is_logged_in"} |
60 | <div class="dailyEntryLinkdate"> | 60 | <div class="dailyEntryLinkdate"> |
61 | <a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a> | 61 | <a href="{$base_path}/?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a> |
62 | </div> | 62 | </div> |
63 | {/if} | 63 | {/if} |
64 | {if="$link.tags"} | 64 | {if="$link.tags"} |
@@ -101,9 +101,9 @@ | |||
101 | {$value} | 101 | {$value} |
102 | {/loop} | 102 | {/loop} |
103 | </div> | 103 | </div> |
104 | <div id="closing"><img src="img/squiggle_closing.png" width="66" height="61" alt="-"></div> | 104 | <div id="closing"><img src="{$asset_path}/img/squiggle_closing.png#" width="66" height="61" alt="-"></div> |
105 | </div> | 105 | </div> |
106 | {include="page.footer"} | 106 | {include="page.footer"} |
107 | <script src="js/thumbnails.min.js?v={$version_hash}"></script> | 107 | <script src="{$asset_path}/js/thumbnails.min.js?v={$version_hash}#"></script> |
108 | </body> | 108 | </body> |
109 | </html> | 109 | </html> |
diff --git a/tpl/vintage/dailyrss.html b/tpl/vintage/dailyrss.html index f589b06e..ff19bbfb 100644 --- a/tpl/vintage/dailyrss.html +++ b/tpl/vintage/dailyrss.html | |||
@@ -1,16 +1,32 @@ | |||
1 | <item> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | <title>{$title} - {function="strftime('%A %e %B %Y', $daydate)"}</title> | 2 | <rss version="2.0"> |
3 | <guid>{$absurl}</guid> | 3 | <channel> |
4 | <link>{$absurl}</link> | 4 | <title>Daily - {$title}</title> |
5 | <pubDate>{$rssdate}</pubDate> | 5 | <link>{$index_url}</link> |
6 | <description><![CDATA[ | 6 | <description>Daily shaared bookmarks</description> |
7 | {loop="links"} | 7 | <language>{$language}</language> |
8 | <h3><a href="{$value.url}">{$value.title}</a></h3> | 8 | <copyright>{$index_url}</copyright> |
9 | <small>{if="!$hide_timestamps"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> | 9 | <generator>Shaarli</generator> |
10 | {$value.url}</small><br> | 10 | |
11 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> | 11 | {loop="$days"} |
12 | {if="$value.description"}{$value.formatedDescription}{/if} | 12 | <item> |
13 | <br><br><hr> | 13 | <title>{$value.date_human} - {$title}</title> |
14 | <guid>{$value.absolute_url}</guid> | ||
15 | <link>{$value.absolute_url}</link> | ||
16 | <pubDate>{$value.date_rss}</pubDate> | ||
17 | <description><![CDATA[ | ||
18 | {loop="$value.links"} | ||
19 | <h3><a href="{$value.url}">{$value.title}</a></h3> | ||
20 | <small> | ||
21 | {if="!$hide_timestamps"}{$value.created|format_date} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> | ||
22 | {$value.url} | ||
23 | </small><br> | ||
24 | {if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br> | ||
25 | {if="$value.description"}{$value.description}{/if} | ||
26 | <br><br><hr> | ||
14 | {/loop} | 27 | {/loop} |
15 | ]]></description> | 28 | ]]></description> |
16 | </item> | 29 | </item> |
30 | {/loop} | ||
31 | </channel> | ||
32 | </rss><!-- Cached version of {$page_url} --> | ||
diff --git a/tpl/vintage/editlink.html b/tpl/vintage/editlink.html index 5fa7d194..eb8807b5 100644 --- a/tpl/vintage/editlink.html +++ b/tpl/vintage/editlink.html | |||
@@ -1,21 +1,16 @@ | |||
1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
2 | <html> | 2 | <html> |
3 | <head>{include="includes"} | 3 | <head>{include="includes"} |
4 | <link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" /> | ||
5 | </head> | 4 | </head> |
6 | <body | 5 | <body |
7 | {if="$link.title==''"}onload="document.linkform.lf_title.focus();" | 6 | {if="$link.title==''"}onload="document.linkform.lf_title.focus();" |
8 | {elseif="$link.description==''"}onload="document.linkform.lf_description.focus();" | 7 | {elseif="$link.description==''"}onload="document.linkform.lf_description.focus();" |
9 | {else}onload="document.linkform.lf_tags.focus();"{/if} > | 8 | {else}onload="document.linkform.lf_tags.focus();"{/if} > |
10 | <div id="pageheader"> | 9 | <div id="pageheader"> |
11 | {if="$source !== 'firefoxsocialapi'"} | ||
12 | {include="page.header"} | 10 | {include="page.header"} |
13 | {else} | ||
14 | <div id="shaarli_title"><a href="{$titleLink}">{$shaarlititle}</a></div> | 11 | <div id="shaarli_title"><a href="{$titleLink}">{$shaarlititle}</a></div> |
15 | {/if} | ||
16 | <div id="editlinkform"> | 12 | <div id="editlinkform"> |
17 | <form method="post" name="linkform"> | 13 | <form method="post" name="linkform" action="{$base_path}/admin/shaare"> |
18 | <input type="hidden" name="lf_linkdate" value="{$link.linkdate}"> | ||
19 | {if="isset($link.id)"} | 14 | {if="isset($link.id)"} |
20 | <input type="hidden" name="lf_id" value="{$link.id}"> | 15 | <input type="hidden" name="lf_id" value="{$link.id}"> |
21 | {/if} | 16 | {/if} |
@@ -26,7 +21,16 @@ | |||
26 | <input type="text" name="lf_tags" id="lf_tags" value="{$link.tags}" class="lf_input" | 21 | <input type="text" name="lf_tags" id="lf_tags" value="{$link.tags}" class="lf_input" |
27 | data-list="{loop="$tags"}{$key}, {/loop}" data-multiple autocomplete="off" ><br> | 22 | data-list="{loop="$tags"}{$key}, {/loop}" data-multiple autocomplete="off" ><br> |
28 | 23 | ||
29 | {loop="$edit_link_plugin"} | 24 | {if="$formatter==='markdown'"} |
25 | <div class="md_help"> | ||
26 | {'Description will be rendered with'|t} | ||
27 | <a href="http://daringfireball.net/projects/markdown/syntax" title="{'Markdown syntax documentation'|t}"> | ||
28 | {'Markdown syntax'|t} | ||
29 | </a>. | ||
30 | </div> | ||
31 | {/if} | ||
32 | |||
33 | {loop="$edit_link_plugin"} | ||
30 | {$value} | 34 | {$value} |
31 | {/loop} | 35 | {/loop} |
32 | 36 | ||
@@ -38,21 +42,19 @@ | |||
38 | <label for="lf_private"><i>Private</i></label><br><br> | 42 | <label for="lf_private"><i>Private</i></label><br><br> |
39 | {/if} | 43 | {/if} |
40 | <input type="submit" value="Save" name="save_edit" class="bigbutton"> | 44 | <input type="submit" value="Save" name="save_edit" class="bigbutton"> |
41 | <input type="submit" value="Cancel" name="cancel_edit" class="bigbutton"> | ||
42 | {if="!$link_is_new && isset($link.id)"} | 45 | {if="!$link_is_new && isset($link.id)"} |
43 | <a href="?delete_link&lf_linkdate={$link.id}&token={$token}" | 46 | <a href="{$base_path}/admin/shaare/delete?id={$link.id}&token={$token}" |
44 | name="delete_link" class="bigbutton" | 47 | name="delete_link" class="bigbutton" |
45 | onClick="return confirmDeleteLink();"> | 48 | onClick="return confirmDeleteLink();"> |
46 | {'Delete'|t} | 49 | {'Delete'|t} |
47 | </a> | 50 | </a> |
48 | {/if} | 51 | {/if} |
49 | <input type="hidden" name="token" value="{$token}"> | 52 | <input type="hidden" name="token" value="{$token}"> |
53 | <input type="hidden" name="source" value="{$source}"> | ||
50 | {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if} | 54 | {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if} |
51 | </form> | 55 | </form> |
52 | </div> | 56 | </div> |
53 | </div> | 57 | </div> |
54 | {if="$source !== 'firefoxsocialapi'"} | ||
55 | {include="page.footer"} | 58 | {include="page.footer"} |
56 | {/if} | ||
57 | </body> | 59 | </body> |
58 | </html> | 60 | </html> |
diff --git a/tpl/vintage/error.html b/tpl/vintage/error.html new file mode 100644 index 00000000..64f54cd2 --- /dev/null +++ b/tpl/vintage/error.html | |||
@@ -0,0 +1,25 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html> | ||
3 | <head> | ||
4 | {include="includes"} | ||
5 | </head> | ||
6 | <body> | ||
7 | <div id="pageheader"> | ||
8 | {include="page.header"} | ||
9 | </div> | ||
10 | <div class="error-container"> | ||
11 | <h1>Error</h1> | ||
12 | <p>{$message}</p> | ||
13 | |||
14 | {if="!empty($stacktrace)"} | ||
15 | <br> | ||
16 | <pre> | ||
17 | {$stacktrace} | ||
18 | </pre> | ||
19 | {/if} | ||
20 | |||
21 | <p>Would you mind <a href="{$base_path}/">clicking here</a>?</p> | ||
22 | </div> | ||
23 | {include="page.footer"} | ||
24 | </body> | ||
25 | </html> | ||
diff --git a/tpl/vintage/export.html b/tpl/vintage/export.html index 67c3d05f..c30e3b0a 100644 --- a/tpl/vintage/export.html +++ b/tpl/vintage/export.html | |||
@@ -5,12 +5,13 @@ | |||
5 | <div id="pageheader"> | 5 | <div id="pageheader"> |
6 | {include="page.header"} | 6 | {include="page.header"} |
7 | <div id="toolsdiv"> | 7 | <div id="toolsdiv"> |
8 | <form method="GET"> | 8 | <form method="POST" action="{$base_path}/admin/export"> |
9 | <input type="hidden" name="do" value="export"> | ||
10 | Selection:<br> | 9 | Selection:<br> |
11 | <input type="radio" name="selection" value="all" checked="true"> All<br> | 10 | <input type="radio" name="selection" value="all" checked="true"> All<br> |
12 | <input type="radio" name="selection" value="private"> Private<br> | 11 | <input type="radio" name="selection" value="private"> Private<br> |
13 | <input type="radio" name="selection" value="public"> Public<br> | 12 | <input type="radio" name="selection" value="public"> Public<br> |
13 | <input type="hidden" name="token" value="{$token}"> | ||
14 | |||
14 | <br> | 15 | <br> |
15 | <input type="checkbox" name="prepend_note_url" id="prepend_note_url"> | 16 | <input type="checkbox" name="prepend_note_url" id="prepend_note_url"> |
16 | <label for="prepend_note_url"> | 17 | <label for="prepend_note_url"> |
diff --git a/tpl/vintage/feed.atom.html b/tpl/vintage/feed.atom.html index 49798e85..5919bb49 100644 --- a/tpl/vintage/feed.atom.html +++ b/tpl/vintage/feed.atom.html | |||
@@ -6,13 +6,13 @@ | |||
6 | <updated>{$last_update}</updated> | 6 | <updated>{$last_update}</updated> |
7 | {/if} | 7 | {/if} |
8 | <link rel="self" href="{$self_link}#" /> | 8 | <link rel="self" href="{$self_link}#" /> |
9 | <link rel="search" type="application/opensearchdescription+xml" href="{$index_url}?do=opensearch#" | 9 | <link rel="search" type="application/opensearchdescription+xml" href="{$index_url}open-search#" |
10 | title="Shaarli search - {$shaarlititle}" /> | 10 | title="Shaarli search - {$shaarlititle}" /> |
11 | {loop="$feed_plugins_header"} | 11 | {loop="$feed_plugins_header"} |
12 | {$value} | 12 | {$value} |
13 | {/loop} | 13 | {/loop} |
14 | <author> | 14 | <author> |
15 | <name>{$index_url}</name> | 15 | <name>{$pagetitle}</name> |
16 | <uri>{$index_url}</uri> | 16 | <uri>{$index_url}</uri> |
17 | </author> | 17 | </author> |
18 | <id>{$index_url}</id> | 18 | <id>{$index_url}</id> |
diff --git a/tpl/vintage/feed.rss.html b/tpl/vintage/feed.rss.html index ee3fef88..4be8202f 100644 --- a/tpl/vintage/feed.rss.html +++ b/tpl/vintage/feed.rss.html | |||
@@ -8,7 +8,7 @@ | |||
8 | <copyright>{$index_url}</copyright> | 8 | <copyright>{$index_url}</copyright> |
9 | <generator>Shaarli</generator> | 9 | <generator>Shaarli</generator> |
10 | <atom:link rel="self" href="{$self_link}" /> | 10 | <atom:link rel="self" href="{$self_link}" /> |
11 | <atom:link rel="search" type="application/opensearchdescription+xml" href="{$index_url}?do=opensearch#" | 11 | <atom:link rel="search" type="application/opensearchdescription+xml" href="{$index_url}open-search#" |
12 | title="Shaarli search - {$shaarlititle}" /> | 12 | title="Shaarli search - {$shaarlititle}" /> |
13 | {loop="$feed_plugins_header"} | 13 | {loop="$feed_plugins_header"} |
14 | {$value} | 14 | {$value} |
diff --git a/tpl/vintage/import.html b/tpl/vintage/import.html index bb9e4a56..7d6eac76 100644 --- a/tpl/vintage/import.html +++ b/tpl/vintage/import.html | |||
@@ -6,7 +6,7 @@ | |||
6 | {include="page.header"} | 6 | {include="page.header"} |
7 | <div id="uploaddiv"> | 7 | <div id="uploaddiv"> |
8 | Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize}). | 8 | Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize}). |
9 | <form method="POST" action="?do=import" enctype="multipart/form-data" | 9 | <form method="POST" action="{$base_path}/admin/import" enctype="multipart/form-data" |
10 | name="uploadform" id="uploadform"> | 10 | name="uploadform" id="uploadform"> |
11 | <input type="hidden" name="token" value="{$token}"> | 11 | <input type="hidden" name="token" value="{$token}"> |
12 | <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}"> | 12 | <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}"> |
diff --git a/tpl/vintage/includes.html b/tpl/vintage/includes.html index 2efb6b10..eac05701 100644 --- a/tpl/vintage/includes.html +++ b/tpl/vintage/includes.html | |||
@@ -3,15 +3,19 @@ | |||
3 | <meta name="format-detection" content="telephone=no" /> | 3 | <meta name="format-detection" content="telephone=no" /> |
4 | <meta name="viewport" content="width=device-width,initial-scale=1.0" /> | 4 | <meta name="viewport" content="width=device-width,initial-scale=1.0" /> |
5 | <meta name="referrer" content="same-origin"> | 5 | <meta name="referrer" content="same-origin"> |
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}feed/rss?{$searchcrits}#" title="RSS Feed" /> |
7 | <link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> | 7 | <link rel="alternate" type="application/atom+xml" href="{$feedurl}feed/atom?{$searchcrits}#" title="ATOM Feed" /> |
8 | <link href="img/favicon.ico" rel="shortcut icon" type="image/x-icon" /> | 8 | <link href="img/favicon.ico" rel="shortcut icon" type="image/x-icon" /> |
9 | <link type="text/css" rel="stylesheet" href="css/shaarli.min.css" /> | 9 | <link type="text/css" rel="stylesheet" href="{$asset_path}/css/shaarli.min.css#" /> |
10 | {if="$formatter==='markdown'"} | ||
11 | <link type="text/css" rel="stylesheet" href="{$asset_path}/css/markdown.min.css?v={$version_hash}#" /> | ||
12 | {/if} | ||
10 | {loop="$plugins_includes.css_files"} | 13 | {loop="$plugins_includes.css_files"} |
11 | <link type="text/css" rel="stylesheet" href="{$value}#"/> | 14 | <link type="text/css" rel="stylesheet" href="{$base_path}/{$value}#"/> |
12 | {/loop} | 15 | {/loop} |
13 | {if="is_file('data/user.css')"}<link type="text/css" rel="stylesheet" href="data/user.css#" />{/if} | 16 | {if="is_file('data/user.css')"}<link type="text/css" rel="stylesheet" href="{$base_path}/data/user.css#" />{/if} |
14 | <link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle|htmlspecialchars}"/> | 17 | <link rel="search" type="application/opensearchdescription+xml" href="{$base_path}/open-search#" |
18 | title="Shaarli search - {$shaarlititle|htmlspecialchars}" /> | ||
15 | {if="! empty($links) && count($links) === 1"} | 19 | {if="! empty($links) && count($links) === 1"} |
16 | {$link=reset($links)} | 20 | {$link=reset($links)} |
17 | <meta property="og:title" content="{$link.title}" /> | 21 | <meta property="og:title" content="{$link.title}" /> |
@@ -19,12 +23,12 @@ | |||
19 | <meta property="og:url" content="{$index_url}?{$link.shorturl}" /> | 23 | <meta property="og:url" content="{$index_url}?{$link.shorturl}" /> |
20 | {$ogDescription=isset($link.description_src) ? $link.description_src : $link.description} | 24 | {$ogDescription=isset($link.description_src) ? $link.description_src : $link.description} |
21 | <meta property="og:description" content="{function="mb_substr(strip_tags($ogDescription), 0, 300)"}" /> | 25 | <meta property="og:description" content="{function="mb_substr(strip_tags($ogDescription), 0, 300)"}" /> |
22 | {if="$link.thumbnail"} | 26 | {if="!empty($link.thumbnail)"} |
23 | <meta property="og:image" content="{$index_url}{$link.thumbnail}" /> | 27 | <meta property="og:image" content="{$index_url}{$link.thumbnail}" /> |
24 | {/if} | 28 | {/if} |
25 | {if="!$hide_timestamps || $is_logged_in"} | 29 | {if="!$hide_timestamps || $is_logged_in"} |
26 | <meta property="article:published_time" content="{$link.created->format(DateTime::ATOM)}" /> | 30 | <meta property="article:published_time" content="{$link.created->format(DateTime::ATOM)}" /> |
27 | {if="$link.updated"} | 31 | {if="!empty($link.updated)"} |
28 | <meta property="article:modified_time" content="{$link.updated->format(DateTime::ATOM)}" /> | 32 | <meta property="article:modified_time" content="{$link.updated->format(DateTime::ATOM)}" /> |
29 | {/if} | 33 | {/if} |
30 | {/if} | 34 | {/if} |
diff --git a/tpl/vintage/install.html b/tpl/vintage/install.html index aca890d6..8c10b2cb 100644 --- a/tpl/vintage/install.html +++ b/tpl/vintage/install.html | |||
@@ -5,7 +5,7 @@ | |||
5 | <div id="install"> | 5 | <div id="install"> |
6 | <h1>Shaarli</h1> | 6 | <h1>Shaarli</h1> |
7 | It looks like it's the first time you run Shaarli. Please configure it:<br> | 7 | It looks like it's the first time you run Shaarli. Please configure it:<br> |
8 | <form method="POST" action="#" name="installform" id="installform"> | 8 | <form method="POST" action="{$base_path}/install" name="installform" id="installform"> |
9 | <table> | 9 | <table> |
10 | <tr><td><b>Login:</b></td><td><input type="text" name="setlogin" size="30"></td></tr> | 10 | <tr><td><b>Login:</b></td><td><input type="text" name="setlogin" size="30"></td></tr> |
11 | <tr><td><b>Password:</b></td><td><input type="password" name="setpassword" size="30"></td></tr> | 11 | <tr><td><b>Password:</b></td><td><input type="password" name="setpassword" size="30"></td></tr> |
diff --git a/tpl/vintage/linklist.html b/tpl/vintage/linklist.html index dcb14e90..00896eb5 100644 --- a/tpl/vintage/linklist.html +++ b/tpl/vintage/linklist.html | |||
@@ -1,7 +1,6 @@ | |||
1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
2 | <html> | 2 | <html> |
3 | <head> | 3 | <head> |
4 | <link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" /> | ||
5 | {include="includes"} | 4 | {include="includes"} |
6 | </head> | 5 | </head> |
7 | <body> | 6 | <body> |
@@ -66,12 +65,12 @@ | |||
66 | tagged | 65 | tagged |
67 | {loop="$exploded_tags"} | 66 | {loop="$exploded_tags"} |
68 | <span class="linktag" title="Remove tag"> | 67 | <span class="linktag" title="Remove tag"> |
69 | <a href="?removetag={function="urlencode($value)"}">{$value} <span class="remove">x</span></a> | 68 | <a href="{$base_path}/remove-tag/{function="urlencode($value)"}">{$value} <span class="remove">x</span></a> |
70 | </span> | 69 | </span> |
71 | {/loop} | 70 | {/loop} |
72 | {elseif="$search_tags === false"} | 71 | {elseif="$search_tags === false"} |
73 | <span class="linktag" title="Remove tag"> | 72 | <span class="linktag" title="Remove tag"> |
74 | <a href="?">untagged <span class="remove">x</span></a> | 73 | <a href="{$base_path}/">untagged <span class="remove">x</span></a> |
75 | </span> | 74 | </span> |
76 | {/if} | 75 | {/if} |
77 | </div> | 76 | </div> |
@@ -84,7 +83,7 @@ | |||
84 | <div class="thumbnail"> | 83 | <div class="thumbnail"> |
85 | <a href="{$value.real_url}"> | 84 | <a href="{$value.real_url}"> |
86 | {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} | 85 | {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} |
87 | <img data-src="{$value.thumbnail}#" class="b-lazy" | 86 | <img data-src="{$base_path}/{$value.thumbnail}#" class="b-lazy" |
88 | src="" | 87 | src="" |
89 | alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" /> | 88 | alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" /> |
90 | </a> | 89 | </a> |
@@ -93,17 +92,16 @@ | |||
93 | <div class="linkcontainer"> | 92 | <div class="linkcontainer"> |
94 | {if="$is_logged_in"} | 93 | {if="$is_logged_in"} |
95 | <div class="linkeditbuttons"> | 94 | <div class="linkeditbuttons"> |
96 | <form method="GET" class="buttoneditform"> | 95 | <a href="{$base_path}/admin/shaare/{$value.id}" title="Edit" class="button_edit"> |
97 | <input type="hidden" name="edit_link" value="{$value.id}"> | 96 | <img src="{$asset_path}/img/edit_icon.png#"> |
98 | <input type="image" alt="Edit" src="img/edit_icon.png" title="Edit" class="button_edit"> | 97 | </a> |
99 | </form><br> | 98 | <br> |
100 | <form method="GET" class="buttoneditform"> | 99 | <a href="{$base_path}/admin/shaare/delete?id={$value.id}&token={$token}" label="Delete" |
101 | <input type="hidden" name="lf_linkdate" value="{$value.id}"> | 100 | onClick="return confirmDeleteLink();" |
102 | <input type="hidden" name="token" value="{$token}"> | 101 | class="button_delete" |
103 | <input type="hidden" name="delete_link"> | 102 | > |
104 | <input type="image" alt="Delete" src="img/delete_icon.png" title="Delete" | 103 | <img src="{$asset_path}/img/delete_icon.png#"> |
105 | class="button_delete" onClick="return confirmDeleteLink();"> | 104 | </a> |
106 | </form> | ||
107 | </div> | 105 | </div> |
108 | {/if} | 106 | {/if} |
109 | <span class="linktitle"> | 107 | <span class="linktitle"> |
@@ -114,7 +112,7 @@ | |||
114 | {if="!$hide_timestamps || $is_logged_in"} | 112 | {if="!$hide_timestamps || $is_logged_in"} |
115 | {$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'} | 113 | {$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'} |
116 | <span class="linkdate" title="Permalink"> | 114 | <span class="linkdate" title="Permalink"> |
117 | <a href="?{$value.shorturl}"> | 115 | <a href="{$base_path}/shaare/{$value.shorturl}"> |
118 | <span title="{$updated}"> | 116 | <span title="{$updated}"> |
119 | {$value.created|format_date} | 117 | {$value.created|format_date} |
120 | {if="$value.updated_timestamp"}*{/if} | 118 | {if="$value.updated_timestamp"}*{/if} |
@@ -123,7 +121,7 @@ | |||
123 | </a> - | 121 | </a> - |
124 | </span> | 122 | </span> |
125 | {else} | 123 | {else} |
126 | <span class="linkdate" title="Short link here"><a href="?{$value.shorturl}">permalink</a> - </span> | 124 | <span class="linkdate" title="Short link here"><a href="{$base_path}/shaare/{$value.shorturl}">permalink</a> - </span> |
127 | {/if} | 125 | {/if} |
128 | 126 | ||
129 | {loop="$value.link_plugin"} | 127 | {loop="$value.link_plugin"} |
@@ -133,7 +131,7 @@ | |||
133 | <a href="{$value.real_url}"><span class="linkurl" title="Short link">{$value.url}</span></a><br> | 131 | <a href="{$value.real_url}"><span class="linkurl" title="Short link">{$value.url}</span></a><br> |
134 | {if="$value.tags"} | 132 | {if="$value.tags"} |
135 | <div class="linktaglist"> | 133 | <div class="linktaglist"> |
136 | {loop="$value.taglist"}<span class="linktag" title="Add tag"><a href="?addtag={$value|urlencode}">{$value}</a></span> {/loop} | 134 | {loop="$value.taglist"}<span class="linktag" title="Add tag"><a href="{$base_path}/add-tag/{$value|urlencode}">{$value}</a></span> {/loop} |
137 | </div> | 135 | </div> |
138 | {/if} | 136 | {/if} |
139 | 137 | ||
@@ -154,7 +152,7 @@ | |||
154 | </div> | 152 | </div> |
155 | 153 | ||
156 | {include="page.footer"} | 154 | {include="page.footer"} |
157 | <script src="js/thumbnails.min.js"></script> | 155 | <script src="{$asset_path}/js/thumbnails.min.js#"></script> |
158 | 156 | ||
159 | </body> | 157 | </body> |
160 | </html> | 158 | </html> |
diff --git a/tpl/vintage/linklist.paging.html b/tpl/vintage/linklist.paging.html index 35149a6b..79daf16c 100644 --- a/tpl/vintage/linklist.paging.html +++ b/tpl/vintage/linklist.paging.html | |||
@@ -1,11 +1,11 @@ | |||
1 | <div class="paging"> | 1 | <div class="paging"> |
2 | {if="$is_logged_in"} | 2 | {if="$is_logged_in"} |
3 | <div class="paging_privatelinks"> | 3 | <div class="paging_privatelinks"> |
4 | <a href="?visibility=private"> | 4 | <a href="{$base_path}/admin/isibility/private"> |
5 | {if="$visibility=='private'"} | 5 | {if="$visibility=='private'"} |
6 | <img src="img/private_16x16_active.png" width="16" height="16" title="Click to see all links" alt="Click to see all links"> | 6 | <img src="{$asset_path}/img/private_16x16_active.png#" width="16" height="16" title="Click to see all links" alt="Click to see all links"> |
7 | {else} | 7 | {else} |
8 | <img src="img/private_16x16.png" width="16" height="16" title="Click to see only private links" alt="Click to see only private links"> | 8 | <img src="{$asset_path}/img/private_16x16.png#" width="16" height="16" title="Click to see only private links" alt="Click to see only private links"> |
9 | {/if} | 9 | {/if} |
10 | </a> | 10 | </a> |
11 | 11 | ||
@@ -23,10 +23,15 @@ | |||
23 | </div> | 23 | </div> |
24 | {/loop} | 24 | {/loop} |
25 | <div class="paging_linksperpage"> | 25 | <div class="paging_linksperpage"> |
26 | Links per page: <a href="?linksperpage=20">20</a> <a href="?linksperpage=50">50</a> <a href="?linksperpage=100">100</a> | 26 | Links per page: |
27 | <form method="GET" class="linksperpage"><input type="text" name="linksperpage" size="2"></form> | 27 | <a href="{$base_path}/links-per-page?nb=20">20</a> |
28 | <a href="{$base_path}/links-per-page?nb=50">50</a> | ||
29 | <a href="{$base_path}/links-per-page?nb=100">100</a> | ||
30 | <form method="GET" class="linksperpage" action="{$base_path}/links-per-page"> | ||
31 | <input type="text" name="nb" size="2"> | ||
32 | </form> | ||
28 | </div> | 33 | </div> |
29 | {if="$previous_page_url"} <a href="{$previous_page_url}" class="paging_older">◄Older</a> {/if} | 34 | {if="$previous_page_url"} <a href="{$previous_page_url}" class="paging_older">◄Older</a> {/if} |
30 | <div class="paging_current">page {$page_current} / {$page_max} </div> | 35 | {if="$page_max>1"}<div class="paging_current">page {$page_current} / {$page_max} </div>{/if} |
31 | {if="$next_page_url"} <a href="{$next_page_url}" class="paging_newer">Newer►</a> {/if} | 36 | {if="$next_page_url"} <a href="{$next_page_url}" class="paging_newer">Newer►</a> {/if} |
32 | </div> | 37 | </div> |
diff --git a/tpl/vintage/loginform.html b/tpl/vintage/loginform.html index 0f7d6387..6aa20ab1 100644 --- a/tpl/vintage/loginform.html +++ b/tpl/vintage/loginform.html | |||
@@ -2,36 +2,30 @@ | |||
2 | <html> | 2 | <html> |
3 | <head>{include="includes"}</head> | 3 | <head>{include="includes"}</head> |
4 | <body | 4 | <body |
5 | {if="$user_can_login"} | 5 | {if="empty($username)"} |
6 | {if="empty($username)"} | 6 | onload="document.loginform.login.focus();" |
7 | onload="document.loginform.login.focus();" | 7 | {else} |
8 | {else} | 8 | onload="document.loginform.password.focus();" |
9 | onload="document.loginform.password.focus();" | ||
10 | {/if} | ||
11 | {/if}> | 9 | {/if}> |
12 | <div id="pageheader"> | 10 | <div id="pageheader"> |
13 | {include="page.header"} | 11 | {include="page.header"} |
14 | 12 | ||
15 | <div id="headerform"> | 13 | <div id="headerform"> |
16 | {if="!$user_can_login"} | 14 | <form method="post" name="loginform" action="{$base_path}/login"> |
17 | You have been banned from login after too many failed attempts. Try later. | 15 | <label for="login">Login: <input type="text" id="login" name="login" tabindex="1" |
18 | {else} | 16 | {if="!empty($username)"}value="{$username}"{/if}> |
19 | <form method="post" name="loginform"> | 17 | </label> |
20 | <label for="login">Login: <input type="text" id="login" name="login" tabindex="1" | 18 | <label for="password">Password: <input type="password" id="password" name="password" tabindex="2"> |
21 | {if="!empty($username)"}value="{$username}"{/if}> | 19 | </label> |
22 | </label> | 20 | <input type="submit" value="Login" class="bigbutton" tabindex="4"> |
23 | <label for="password">Password: <input type="password" id="password" name="password" tabindex="2"> | 21 | <label for="longlastingsession"> |
24 | </label> | 22 | <input type="checkbox" name="longlastingsession" |
25 | <input type="submit" value="Login" class="bigbutton" tabindex="4"> | 23 | id="longlastingsession" tabindex="3" |
26 | <label for="longlastingsession"> | 24 | {if="$remember_user_default"}checked="checked"{/if}> |
27 | <input type="checkbox" name="longlastingsession" | 25 | Stay signed in (Do not check on public computers)</label> |
28 | id="longlastingsession" tabindex="3" | 26 | <input type="hidden" name="token" value="{$token}"> |
29 | {if="$remember_user_default"}checked="checked"{/if}> | 27 | {if="$returnurl"}<input type="hidden" name="returnurl" value="{$returnurl}">{/if} |
30 | Stay signed in (Do not check on public computers)</label> | 28 | </form> |
31 | <input type="hidden" name="token" value="{$token}"> | ||
32 | {if="$returnurl"}<input type="hidden" name="returnurl" value="{$returnurl}">{/if} | ||
33 | </form> | ||
34 | {/if} | ||
35 | </div> | 29 | </div> |
36 | </div> | 30 | </div> |
37 | 31 | ||
diff --git a/tpl/vintage/opensearch.html b/tpl/vintage/opensearch.html index 3fcc30b7..1c7f279b 100644 --- a/tpl/vintage/opensearch.html +++ b/tpl/vintage/opensearch.html | |||
@@ -3,8 +3,8 @@ | |||
3 | <ShortName>Shaarli search - {$pagetitle}</ShortName> | 3 | <ShortName>Shaarli search - {$pagetitle}</ShortName> |
4 | <Description>Shaarli search - {$pagetitle}</Description> | 4 | <Description>Shaarli search - {$pagetitle}</Description> |
5 | <Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" /> | 5 | <Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" /> |
6 | <Url type="application/atom+xml" template="{$serverurl}?do=atom&searchterm={searchTerms}"/> | 6 | <Url type="application/atom+xml" template="{$serverurl}feed/atom?searchterm={searchTerms}"/> |
7 | <Url type="application/rss+xml" template="{$serverurl}?do=rss&searchterm={searchTerms}"/> | 7 | <Url type="application/rss+xml" template="{$serverurl}feed/rss?searchterm={searchTerms}"/> |
8 | <InputEncoding>UTF-8</InputEncoding> | 8 | <InputEncoding>UTF-8</InputEncoding> |
9 | <Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer> | 9 | <Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer> |
10 | <Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE | 10 | <Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE |
diff --git a/tpl/vintage/page.footer.html b/tpl/vintage/page.footer.html index a3380841..0fe4c736 100644 --- a/tpl/vintage/page.footer.html +++ b/tpl/vintage/page.footer.html | |||
@@ -23,12 +23,14 @@ | |||
23 | </div> | 23 | </div> |
24 | {/if} | 24 | {/if} |
25 | 25 | ||
26 | <script src="js/shaarli.min.js"></script> | 26 | <script src="{$asset_path}/js/shaarli.min.js#"></script> |
27 | 27 | ||
28 | {if="$is_logged_in"} | 28 | {if="$is_logged_in"} |
29 | <script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script> | 29 | <script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script> |
30 | {/if} | 30 | {/if} |
31 | 31 | ||
32 | {loop="$plugins_footer.js_files"} | 32 | {loop="$plugins_footer.js_files"} |
33 | <script src="{$value}#"></script> | 33 | <script src="{$base_path}/{$value}#"></script> |
34 | {/loop} | 34 | {/loop} |
35 | |||
36 | <input type="hidden" name="js_base_path" value="{$base_path}" /> | ||
diff --git a/tpl/vintage/page.header.html b/tpl/vintage/page.header.html index 40c53e5b..0a33523b 100644 --- a/tpl/vintage/page.header.html +++ b/tpl/vintage/page.header.html | |||
@@ -18,22 +18,22 @@ | |||
18 | {else} | 18 | {else} |
19 | <li><a href="{$titleLink}" class="nomobile">Home</a></li> | 19 | <li><a href="{$titleLink}" class="nomobile">Home</a></li> |
20 | {if="$is_logged_in"} | 20 | {if="$is_logged_in"} |
21 | <li><a href="?do=logout">Logout</a></li> | 21 | <li><a href="{$base_path}/admin/logout">Logout</a></li> |
22 | <li><a href="?do=tools">Tools</a></li> | 22 | <li><a href="{$base_path}/admin/tools">Tools</a></li> |
23 | <li><a href="?do=addlink">Add link</a></li> | 23 | <li><a href="{$base_path}/admin/add-shaare">Add link</a></li> |
24 | {elseif="$openshaarli"} | 24 | {elseif="$openshaarli"} |
25 | <li><a href="?do=tools">Tools</a></li> | 25 | <li><a href="{$base_path}/admin/tools">Tools</a></li> |
26 | <li><a href="?do=addlink">Add link</a></li> | 26 | <li><a href="{$base_path}/admin/add-shaare">Add link</a></li> |
27 | {else} | 27 | {else} |
28 | <li><a href="?do=login">Login</a></li> | 28 | <li><a href="{$base_path}/login">Login</a></li> |
29 | {/if} | 29 | {/if} |
30 | <li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li> | 30 | <li><a href="{$feedurl}/feed/rss?{$searchcrits}" class="nomobile">RSS Feed</a></li> |
31 | {if="$showatom"} | 31 | {if="$showatom"} |
32 | <li><a href="{$feedurl}?do=atom{$searchcrits}" class="nomobile">ATOM Feed</a></li> | 32 | <li><a href="{$feedurl}/feed/atom?{$searchcrits}" class="nomobile">ATOM Feed</a></li> |
33 | {/if} | 33 | {/if} |
34 | <li><a href="?do=tagcloud">Tag cloud</a></li> | 34 | <li><a href="{$base_path}/tags/cloud">Tag cloud</a></li> |
35 | <li><a href="?do=picwall{$searchcrits}">Picture wall</a></li> | 35 | <li><a href="{$base_path}/picture-wall{function="ltrim($searchcrits, '&')"}">Picture wall</a></li> |
36 | <li><a href="?do=daily">Daily</a></li> | 36 | <li><a href="{$base_path}/daily">Daily</a></li> |
37 | {loop="$plugins_header.buttons_toolbar"} | 37 | {loop="$plugins_header.buttons_toolbar"} |
38 | <li><a | 38 | <li><a |
39 | {loop="$value.attr"} | 39 | {loop="$value.attr"} |
diff --git a/tpl/vintage/picwall.html b/tpl/vintage/picwall.html index b3a16791..da3aa36c 100644 --- a/tpl/vintage/picwall.html +++ b/tpl/vintage/picwall.html | |||
@@ -38,6 +38,6 @@ | |||
38 | 38 | ||
39 | {include="page.footer"} | 39 | {include="page.footer"} |
40 | 40 | ||
41 | <script src="js/thumbnails.min.js"></script> | 41 | <script src="{$asset_path}/js/thumbnails.min.js#"></script> |
42 | </body> | 42 | </body> |
43 | </html> | 43 | </html> |
diff --git a/tpl/vintage/pluginsadmin.html b/tpl/vintage/pluginsadmin.html index 63b45cac..d0972cd1 100644 --- a/tpl/vintage/pluginsadmin.html +++ b/tpl/vintage/pluginsadmin.html | |||
@@ -16,7 +16,7 @@ | |||
16 | </noscript> | 16 | </noscript> |
17 | 17 | ||
18 | <div id="pluginsadmin"> | 18 | <div id="pluginsadmin"> |
19 | <form action="?do=save_pluginadmin" method="POST"> | 19 | <form action="{$base_path}/admin/plugins" method="POST"> |
20 | <section id="enabled_plugins"> | 20 | <section id="enabled_plugins"> |
21 | <h1>Enabled Plugins</h1> | 21 | <h1>Enabled Plugins</h1> |
22 | 22 | ||
@@ -86,9 +86,10 @@ | |||
86 | <input type="submit" value="Save"/> | 86 | <input type="submit" value="Save"/> |
87 | </div> | 87 | </div> |
88 | </section> | 88 | </section> |
89 | <input type="hidden" name="token" value="{$token}"> | ||
89 | </form> | 90 | </form> |
90 | 91 | ||
91 | <form action="?do=save_pluginadmin" method="POST"> | 92 | <form action="{$base_path}/admin/plugins" method="POST"> |
92 | <section id="plugin_parameters"> | 93 | <section id="plugin_parameters"> |
93 | <h1>Enabled Plugin Parameters</h1> | 94 | <h1>Enabled Plugin Parameters</h1> |
94 | 95 | ||
@@ -124,6 +125,7 @@ | |||
124 | </div> | 125 | </div> |
125 | </div> | 126 | </div> |
126 | </section> | 127 | </section> |
128 | <input type="hidden" name="token" value="{$token}"> | ||
127 | </form> | 129 | </form> |
128 | 130 | ||
129 | </div> | 131 | </div> |
diff --git a/tpl/vintage/tag.cloud.html b/tpl/vintage/tag.cloud.html index d93bf4f9..5d21f239 100644 --- a/tpl/vintage/tag.cloud.html +++ b/tpl/vintage/tag.cloud.html | |||
@@ -12,8 +12,8 @@ | |||
12 | 12 | ||
13 | <div id="cloudtag"> | 13 | <div id="cloudtag"> |
14 | {loop="$tags"} | 14 | {loop="$tags"} |
15 | <a href="?addtag={$key|urlencode}" class="count">{$value.count}</a><a | 15 | <a href="{$base_path}/add-tag/{$key|urlencode}" class="count">{$value.count}</a><a |
16 | href="?searchtags={$key|urlencode}" style="font-size:{$value.size}em;">{$key}</a> | 16 | href="{$base_path}/?searchtags={$key|urlencode}" style="font-size:{$value.size}em;">{$key}</a> |
17 | {loop="$value.tag_plugin"} | 17 | {loop="$value.tag_plugin"} |
18 | {$value} | 18 | {$value} |
19 | {/loop} | 19 | {/loop} |
diff --git a/tpl/vintage/thumbnails.html b/tpl/vintage/thumbnails.html index 79aebf8d..18f296f7 100644 --- a/tpl/vintage/thumbnails.html +++ b/tpl/vintage/thumbnails.html | |||
@@ -20,9 +20,9 @@ | |||
20 | </div> | 20 | </div> |
21 | </div> | 21 | </div> |
22 | 22 | ||
23 | <input type="hidden" name="ids" value="{function="implode($ids, ',')"}" /> | 23 | <input type="hidden" name="ids" value="{function="implode(',', $ids)"}" /> |
24 | 24 | ||
25 | {include="page.footer"} | 25 | {include="page.footer"} |
26 | <script src="js/thumbnails_update.min.js?v={$version_hash}"></script> | 26 | <script src="{$asset_path}/js/thumbnails_update.min.js?v={$version_hash}#"></script> |
27 | </body> | 27 | </body> |
28 | </html> | 28 | </html> |
diff --git a/tpl/vintage/tools.html b/tpl/vintage/tools.html index 1cef726e..1125bba9 100644 --- a/tpl/vintage/tools.html +++ b/tpl/vintage/tools.html | |||
@@ -5,17 +5,17 @@ | |||
5 | <div id="pageheader"> | 5 | <div id="pageheader"> |
6 | {include="page.header"} | 6 | {include="page.header"} |
7 | <div id="toolsdiv"> | 7 | <div id="toolsdiv"> |
8 | <a href="?do=configure"><b>Configure your Shaarli</b><span>: Change Title, timezone...</span></a> | 8 | <a href="{$base_path}/admin/configure"><b>Configure your Shaarli</b><span>: Change Title, timezone...</span></a> |
9 | <br><br> | 9 | <br><br> |
10 | <a href="?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a> | 10 | <a href="{$base_path}/admin/plugins"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a> |
11 | <br><br> | 11 | <br><br> |
12 | {if="!$openshaarli"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a> | 12 | {if="!$openshaarli"}<a href="{$base_path}/admin/password"><b>Change password</b><span>: Change your password.</span></a> |
13 | <br><br>{/if} | 13 | <br><br>{/if} |
14 | <a href="?do=changetag"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a> | 14 | <a href="{$base_path}/admin/tags"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a> |
15 | <br><br> | 15 | <br><br> |
16 | <a href="?do=import"><b>Import</b><span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a> | 16 | <a href="{$base_path}/admin/import"><b>Import</b><span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a> |
17 | <br><br> | 17 | <br><br> |
18 | <a href="?do=export"><b>Export</b><span>: Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)</span></a> | 18 | <a href="{$base_path}/admin/export"><b>Export</b><span>: Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)</span></a> |
19 | <br><br> | 19 | <br><br> |
20 | <a class="smallbutton" | 20 | <a class="smallbutton" |
21 | onclick="return alertBookmarklet();" | 21 | onclick="return alertBookmarklet();" |
@@ -24,7 +24,7 @@ | |||
24 | var%20url%20=%20location.href; | 24 | var%20url%20=%20location.href; |
25 | var%20title%20=%20document.title%20||%20url; | 25 | var%20title%20=%20document.title%20||%20url; |
26 | window.open( | 26 | window.open( |
27 | '{$pageabsaddr}?post='%20+%20encodeURIComponent(url)+ | 27 | '{$pageabsaddr}admin/shaare?post='%20+%20encodeURIComponent(url)+ |
28 | '&title='%20+%20encodeURIComponent(title)+ | 28 | '&title='%20+%20encodeURIComponent(title)+ |
29 | '&description='%20+%20encodeURIComponent(document.getSelection())+ | 29 | '&description='%20+%20encodeURIComponent(document.getSelection())+ |
30 | '&source=bookmarklet','_blank','menubar=no,height=390,width=600,toolbar=no,scrollbars=no,status=no,dialog=1' | 30 | '&source=bookmarklet','_blank','menubar=no,height=390,width=600,toolbar=no,scrollbars=no,status=no,dialog=1' |
diff --git a/webpack.config.js b/webpack.config.js index ed548c73..a73758cc 100644 --- a/webpack.config.js +++ b/webpack.config.js | |||
@@ -2,26 +2,21 @@ const path = require('path'); | |||
2 | const glob = require('glob'); | 2 | const glob = require('glob'); |
3 | 3 | ||
4 | // Minify JS | 4 | // Minify JS |
5 | const MinifyPlugin = require('babel-minify-webpack-plugin'); | 5 | const TerserPlugin = require('terser-webpack-plugin'); |
6 | 6 | ||
7 | // This plugin extracts the CSS into its own file instead of tying it with the JS. | 7 | // This plugin extracts the CSS into its own file instead of tying it with the JS. |
8 | // It prevents: | 8 | // It prevents: |
9 | // - not having styles due to a JS error | 9 | // - not having styles due to a JS error |
10 | // - the flash page without styles during JS loading | 10 | // - the flash page without styles during JS loading |
11 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); | 11 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); |
12 | 12 | ||
13 | const extractCssDefault = new ExtractTextPlugin({ | 13 | const extractCss = new MiniCssExtractPlugin({ |
14 | filename: "../css/[name].min.css", | 14 | filename: "../css/[name].min.css", |
15 | publicPath: 'tpl/default/css/', | ||
16 | }); | ||
17 | |||
18 | const extractCssVintage = new ExtractTextPlugin({ | ||
19 | filename: "../css/[name].min.css", | ||
20 | publicPath: 'tpl/vintage/css/', | ||
21 | }); | 15 | }); |
22 | 16 | ||
23 | module.exports = [ | 17 | module.exports = [ |
24 | { | 18 | { |
19 | mode: 'production', | ||
25 | entry: { | 20 | entry: { |
26 | thumbnails: './assets/common/js/thumbnails.js', | 21 | thumbnails: './assets/common/js/thumbnails.js', |
27 | thumbnails_update: './assets/common/js/thumbnails-update.js', | 22 | thumbnails_update: './assets/common/js/thumbnails-update.js', |
@@ -30,6 +25,7 @@ module.exports = [ | |||
30 | './assets/default/js/base.js', | 25 | './assets/default/js/base.js', |
31 | './assets/default/scss/shaarli.scss', | 26 | './assets/default/scss/shaarli.scss', |
32 | ].concat(glob.sync('./assets/default/img/*')), | 27 | ].concat(glob.sync('./assets/default/img/*')), |
28 | markdown: './assets/common/css/markdown.css', | ||
33 | }, | 29 | }, |
34 | output: { | 30 | output: { |
35 | filename: '[name].min.js', | 31 | filename: '[name].min.js', |
@@ -44,23 +40,23 @@ module.exports = [ | |||
44 | loader: 'babel-loader', | 40 | loader: 'babel-loader', |
45 | options: { | 41 | options: { |
46 | presets: [ | 42 | presets: [ |
47 | 'babel-preset-env', | 43 | '@babel/preset-env', |
48 | ] | 44 | ] |
49 | } | 45 | } |
50 | } | 46 | } |
51 | }, | 47 | }, |
52 | { | 48 | { |
53 | test: /\.scss/, | 49 | test: /\.s?css/, |
54 | use: extractCssDefault.extract({ | 50 | use: [ |
55 | use: [{ | 51 | { |
56 | loader: "css-loader", | 52 | loader: MiniCssExtractPlugin.loader, |
57 | options: { | 53 | options: { |
58 | minimize: true, | 54 | publicPath: 'tpl/default/css/', |
59 | } | 55 | }, |
60 | }, { | 56 | }, |
61 | loader: "sass-loader" | 57 | 'css-loader', |
62 | }], | 58 | 'sass-loader', |
63 | }) | 59 | ], |
64 | }, | 60 | }, |
65 | { | 61 | { |
66 | test: /\.(gif|png|jpe?g|svg|ico)$/i, | 62 | test: /\.(gif|png|jpe?g|svg|ico)$/i, |
@@ -80,23 +76,28 @@ module.exports = [ | |||
80 | options: { | 76 | options: { |
81 | name: '../fonts/[name].[ext]', | 77 | name: '../fonts/[name].[ext]', |
82 | // do not add a publicPath here because it's already handled by CSS's publicPath | 78 | // do not add a publicPath here because it's already handled by CSS's publicPath |
83 | publicPath: '', | 79 | publicPath: '../default/', |
84 | } | 80 | } |
85 | }, | 81 | }, |
86 | ], | 82 | ], |
87 | }, | 83 | }, |
84 | optimization: { | ||
85 | minimize: true, | ||
86 | minimizer: [new TerserPlugin()], | ||
87 | }, | ||
88 | plugins: [ | 88 | plugins: [ |
89 | new MinifyPlugin(), | 89 | extractCss, |
90 | extractCssDefault, | ||
91 | ], | 90 | ], |
92 | }, | 91 | }, |
93 | { | 92 | { |
93 | mode: 'production', | ||
94 | entry: { | 94 | entry: { |
95 | shaarli: [ | 95 | shaarli: [ |
96 | './assets/vintage/js/base.js', | 96 | './assets/vintage/js/base.js', |
97 | './assets/vintage/css/reset.css', | 97 | './assets/vintage/css/reset.css', |
98 | './assets/vintage/css/shaarli.css', | 98 | './assets/vintage/css/shaarli.css', |
99 | ].concat(glob.sync('./assets/vintage/img/*')), | 99 | ].concat(glob.sync('./assets/vintage/img/*')), |
100 | markdown: './assets/common/css/markdown.css', | ||
100 | thumbnails: './assets/common/js/thumbnails.js', | 101 | thumbnails: './assets/common/js/thumbnails.js', |
101 | thumbnails_update: './assets/common/js/thumbnails-update.js', | 102 | thumbnails_update: './assets/common/js/thumbnails-update.js', |
102 | }, | 103 | }, |
@@ -113,21 +114,23 @@ module.exports = [ | |||
113 | loader: 'babel-loader', | 114 | loader: 'babel-loader', |
114 | options: { | 115 | options: { |
115 | presets: [ | 116 | presets: [ |
116 | 'babel-preset-env', | 117 | '@babel/preset-env', |
117 | ] | 118 | ] |
118 | } | 119 | } |
119 | } | 120 | } |
120 | }, | 121 | }, |
121 | { | 122 | { |
122 | test: /\.css$/, | 123 | test: /\.css$/, |
123 | use: extractCssVintage.extract({ | 124 | use: [ |
124 | use: [{ | 125 | { |
125 | loader: "css-loader", | 126 | loader: MiniCssExtractPlugin.loader, |
126 | options: { | 127 | options: { |
127 | minimize: true, | 128 | publicPath: 'tpl/vintage/css/', |
128 | } | 129 | }, |
129 | }], | 130 | }, |
130 | }) | 131 | 'css-loader', |
132 | 'sass-loader', | ||
133 | ], | ||
131 | }, | 134 | }, |
132 | { | 135 | { |
133 | test: /\.(gif|png|jpe?g|svg|ico)$/i, | 136 | test: /\.(gif|png|jpe?g|svg|ico)$/i, |
@@ -143,9 +146,12 @@ module.exports = [ | |||
143 | }, | 146 | }, |
144 | ], | 147 | ], |
145 | }, | 148 | }, |
149 | optimization: { | ||
150 | minimize: true, | ||
151 | minimizer: [new TerserPlugin()], | ||
152 | }, | ||
146 | plugins: [ | 153 | plugins: [ |
147 | new MinifyPlugin(), | 154 | extractCss, |
148 | extractCssVintage, | ||
149 | ], | 155 | ], |
150 | }, | 156 | }, |
151 | ]; | 157 | ]; |
@@ -2,1015 +2,1317 @@ | |||
2 | # yarn lockfile v1 | 2 | # yarn lockfile v1 |
3 | 3 | ||
4 | 4 | ||
5 | abbrev@1: | 5 | "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": |
6 | version "1.1.1" | 6 | version "7.10.4" |
7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" | 7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" |
8 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== | 8 | integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== |
9 | dependencies: | ||
10 | "@babel/highlight" "^7.10.4" | ||
11 | |||
12 | "@babel/compat-data@^7.10.4", "@babel/compat-data@^7.11.0": | ||
13 | version "7.11.0" | ||
14 | resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.11.0.tgz#e9f73efe09af1355b723a7f39b11bad637d7c99c" | ||
15 | integrity sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ== | ||
16 | dependencies: | ||
17 | browserslist "^4.12.0" | ||
18 | invariant "^2.2.4" | ||
19 | semver "^5.5.0" | ||
20 | |||
21 | "@babel/core@>=7.9.0", "@babel/core@^7.11.6": | ||
22 | version "7.11.6" | ||
23 | resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651" | ||
24 | integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg== | ||
25 | dependencies: | ||
26 | "@babel/code-frame" "^7.10.4" | ||
27 | "@babel/generator" "^7.11.6" | ||
28 | "@babel/helper-module-transforms" "^7.11.0" | ||
29 | "@babel/helpers" "^7.10.4" | ||
30 | "@babel/parser" "^7.11.5" | ||
31 | "@babel/template" "^7.10.4" | ||
32 | "@babel/traverse" "^7.11.5" | ||
33 | "@babel/types" "^7.11.5" | ||
34 | convert-source-map "^1.7.0" | ||
35 | debug "^4.1.0" | ||
36 | gensync "^1.0.0-beta.1" | ||
37 | json5 "^2.1.2" | ||
38 | lodash "^4.17.19" | ||
39 | resolve "^1.3.2" | ||
40 | semver "^5.4.1" | ||
41 | source-map "^0.5.0" | ||
42 | |||
43 | "@babel/generator@^7.11.5", "@babel/generator@^7.11.6": | ||
44 | version "7.11.6" | ||
45 | resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" | ||
46 | integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA== | ||
47 | dependencies: | ||
48 | "@babel/types" "^7.11.5" | ||
49 | jsesc "^2.5.1" | ||
50 | source-map "^0.5.0" | ||
51 | |||
52 | "@babel/helper-annotate-as-pure@^7.10.4": | ||
53 | version "7.10.4" | ||
54 | resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" | ||
55 | integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== | ||
56 | dependencies: | ||
57 | "@babel/types" "^7.10.4" | ||
58 | |||
59 | "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": | ||
60 | version "7.10.4" | ||
61 | resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" | ||
62 | integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg== | ||
63 | dependencies: | ||
64 | "@babel/helper-explode-assignable-expression" "^7.10.4" | ||
65 | "@babel/types" "^7.10.4" | ||
66 | |||
67 | "@babel/helper-compilation-targets@^7.10.4": | ||
68 | version "7.10.4" | ||
69 | resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" | ||
70 | integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ== | ||
71 | dependencies: | ||
72 | "@babel/compat-data" "^7.10.4" | ||
73 | browserslist "^4.12.0" | ||
74 | invariant "^2.2.4" | ||
75 | levenary "^1.1.1" | ||
76 | semver "^5.5.0" | ||
77 | |||
78 | "@babel/helper-create-class-features-plugin@^7.10.4": | ||
79 | version "7.10.5" | ||
80 | resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" | ||
81 | integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A== | ||
82 | dependencies: | ||
83 | "@babel/helper-function-name" "^7.10.4" | ||
84 | "@babel/helper-member-expression-to-functions" "^7.10.5" | ||
85 | "@babel/helper-optimise-call-expression" "^7.10.4" | ||
86 | "@babel/helper-plugin-utils" "^7.10.4" | ||
87 | "@babel/helper-replace-supers" "^7.10.4" | ||
88 | "@babel/helper-split-export-declaration" "^7.10.4" | ||
89 | |||
90 | "@babel/helper-create-regexp-features-plugin@^7.10.4": | ||
91 | version "7.10.4" | ||
92 | resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" | ||
93 | integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g== | ||
94 | dependencies: | ||
95 | "@babel/helper-annotate-as-pure" "^7.10.4" | ||
96 | "@babel/helper-regex" "^7.10.4" | ||
97 | regexpu-core "^4.7.0" | ||
98 | |||
99 | "@babel/helper-define-map@^7.10.4": | ||
100 | version "7.10.5" | ||
101 | resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30" | ||
102 | integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ== | ||
103 | dependencies: | ||
104 | "@babel/helper-function-name" "^7.10.4" | ||
105 | "@babel/types" "^7.10.5" | ||
106 | lodash "^4.17.19" | ||
107 | |||
108 | "@babel/helper-explode-assignable-expression@^7.10.4": | ||
109 | version "7.11.4" | ||
110 | resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz#2d8e3470252cc17aba917ede7803d4a7a276a41b" | ||
111 | integrity sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ== | ||
112 | dependencies: | ||
113 | "@babel/types" "^7.10.4" | ||
114 | |||
115 | "@babel/helper-function-name@^7.10.4": | ||
116 | version "7.10.4" | ||
117 | resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" | ||
118 | integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== | ||
119 | dependencies: | ||
120 | "@babel/helper-get-function-arity" "^7.10.4" | ||
121 | "@babel/template" "^7.10.4" | ||
122 | "@babel/types" "^7.10.4" | ||
123 | |||
124 | "@babel/helper-get-function-arity@^7.10.4": | ||
125 | version "7.10.4" | ||
126 | resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" | ||
127 | integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== | ||
128 | dependencies: | ||
129 | "@babel/types" "^7.10.4" | ||
130 | |||
131 | "@babel/helper-hoist-variables@^7.10.4": | ||
132 | version "7.10.4" | ||
133 | resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" | ||
134 | integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA== | ||
135 | dependencies: | ||
136 | "@babel/types" "^7.10.4" | ||
137 | |||
138 | "@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": | ||
139 | version "7.11.0" | ||
140 | resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" | ||
141 | integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q== | ||
142 | dependencies: | ||
143 | "@babel/types" "^7.11.0" | ||
144 | |||
145 | "@babel/helper-module-imports@^7.10.4": | ||
146 | version "7.10.4" | ||
147 | resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" | ||
148 | integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== | ||
149 | dependencies: | ||
150 | "@babel/types" "^7.10.4" | ||
151 | |||
152 | "@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.11.0": | ||
153 | version "7.11.0" | ||
154 | resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" | ||
155 | integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg== | ||
156 | dependencies: | ||
157 | "@babel/helper-module-imports" "^7.10.4" | ||
158 | "@babel/helper-replace-supers" "^7.10.4" | ||
159 | "@babel/helper-simple-access" "^7.10.4" | ||
160 | "@babel/helper-split-export-declaration" "^7.11.0" | ||
161 | "@babel/template" "^7.10.4" | ||
162 | "@babel/types" "^7.11.0" | ||
163 | lodash "^4.17.19" | ||
164 | |||
165 | "@babel/helper-optimise-call-expression@^7.10.4": | ||
166 | version "7.10.4" | ||
167 | resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" | ||
168 | integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== | ||
169 | dependencies: | ||
170 | "@babel/types" "^7.10.4" | ||
171 | |||
172 | "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": | ||
173 | version "7.10.4" | ||
174 | resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" | ||
175 | integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== | ||
176 | |||
177 | "@babel/helper-regex@^7.10.4": | ||
178 | version "7.10.5" | ||
179 | resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" | ||
180 | integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg== | ||
181 | dependencies: | ||
182 | lodash "^4.17.19" | ||
183 | |||
184 | "@babel/helper-remap-async-to-generator@^7.10.4": | ||
185 | version "7.11.4" | ||
186 | resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz#4474ea9f7438f18575e30b0cac784045b402a12d" | ||
187 | integrity sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA== | ||
188 | dependencies: | ||
189 | "@babel/helper-annotate-as-pure" "^7.10.4" | ||
190 | "@babel/helper-wrap-function" "^7.10.4" | ||
191 | "@babel/template" "^7.10.4" | ||
192 | "@babel/types" "^7.10.4" | ||
193 | |||
194 | "@babel/helper-replace-supers@^7.10.4": | ||
195 | version "7.10.4" | ||
196 | resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" | ||
197 | integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== | ||
198 | dependencies: | ||
199 | "@babel/helper-member-expression-to-functions" "^7.10.4" | ||
200 | "@babel/helper-optimise-call-expression" "^7.10.4" | ||
201 | "@babel/traverse" "^7.10.4" | ||
202 | "@babel/types" "^7.10.4" | ||
203 | |||
204 | "@babel/helper-simple-access@^7.10.4": | ||
205 | version "7.10.4" | ||
206 | resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" | ||
207 | integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== | ||
208 | dependencies: | ||
209 | "@babel/template" "^7.10.4" | ||
210 | "@babel/types" "^7.10.4" | ||
211 | |||
212 | "@babel/helper-skip-transparent-expression-wrappers@^7.11.0": | ||
213 | version "7.11.0" | ||
214 | resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729" | ||
215 | integrity sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q== | ||
216 | dependencies: | ||
217 | "@babel/types" "^7.11.0" | ||
218 | |||
219 | "@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": | ||
220 | version "7.11.0" | ||
221 | resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" | ||
222 | integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== | ||
223 | dependencies: | ||
224 | "@babel/types" "^7.11.0" | ||
225 | |||
226 | "@babel/helper-validator-identifier@^7.10.4": | ||
227 | version "7.10.4" | ||
228 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" | ||
229 | integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== | ||
230 | |||
231 | "@babel/helper-wrap-function@^7.10.4": | ||
232 | version "7.10.4" | ||
233 | resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" | ||
234 | integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug== | ||
235 | dependencies: | ||
236 | "@babel/helper-function-name" "^7.10.4" | ||
237 | "@babel/template" "^7.10.4" | ||
238 | "@babel/traverse" "^7.10.4" | ||
239 | "@babel/types" "^7.10.4" | ||
240 | |||
241 | "@babel/helpers@^7.10.4": | ||
242 | version "7.10.4" | ||
243 | resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" | ||
244 | integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== | ||
245 | dependencies: | ||
246 | "@babel/template" "^7.10.4" | ||
247 | "@babel/traverse" "^7.10.4" | ||
248 | "@babel/types" "^7.10.4" | ||
249 | |||
250 | "@babel/highlight@^7.10.4": | ||
251 | version "7.10.4" | ||
252 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" | ||
253 | integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== | ||
254 | dependencies: | ||
255 | "@babel/helper-validator-identifier" "^7.10.4" | ||
256 | chalk "^2.0.0" | ||
257 | js-tokens "^4.0.0" | ||
258 | |||
259 | "@babel/parser@^7.10.4", "@babel/parser@^7.11.5": | ||
260 | version "7.11.5" | ||
261 | resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" | ||
262 | integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== | ||
9 | 263 | ||
10 | acorn-dynamic-import@^2.0.0: | 264 | "@babel/plugin-proposal-async-generator-functions@^7.10.4": |
11 | version "2.0.2" | 265 | version "7.10.5" |
12 | resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" | 266 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558" |
13 | integrity sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ= | 267 | integrity sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg== |
268 | dependencies: | ||
269 | "@babel/helper-plugin-utils" "^7.10.4" | ||
270 | "@babel/helper-remap-async-to-generator" "^7.10.4" | ||
271 | "@babel/plugin-syntax-async-generators" "^7.8.0" | ||
272 | |||
273 | "@babel/plugin-proposal-class-properties@^7.10.4": | ||
274 | version "7.10.4" | ||
275 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" | ||
276 | integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== | ||
14 | dependencies: | 277 | dependencies: |
15 | acorn "^4.0.3" | 278 | "@babel/helper-create-class-features-plugin" "^7.10.4" |
16 | 279 | "@babel/helper-plugin-utils" "^7.10.4" | |
17 | acorn-jsx@^3.0.0: | 280 | |
18 | version "3.0.1" | 281 | "@babel/plugin-proposal-dynamic-import@^7.10.4": |
19 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" | 282 | version "7.10.4" |
20 | integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= | 283 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" |
284 | integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ== | ||
285 | dependencies: | ||
286 | "@babel/helper-plugin-utils" "^7.10.4" | ||
287 | "@babel/plugin-syntax-dynamic-import" "^7.8.0" | ||
288 | |||
289 | "@babel/plugin-proposal-export-namespace-from@^7.10.4": | ||
290 | version "7.10.4" | ||
291 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz#570d883b91031637b3e2958eea3c438e62c05f54" | ||
292 | integrity sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg== | ||
21 | dependencies: | 293 | dependencies: |
22 | acorn "^3.0.4" | 294 | "@babel/helper-plugin-utils" "^7.10.4" |
295 | "@babel/plugin-syntax-export-namespace-from" "^7.8.3" | ||
23 | 296 | ||
24 | acorn@^3.0.4: | 297 | "@babel/plugin-proposal-json-strings@^7.10.4": |
25 | version "3.3.0" | 298 | version "7.10.4" |
26 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" | 299 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db" |
27 | integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= | 300 | integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw== |
301 | dependencies: | ||
302 | "@babel/helper-plugin-utils" "^7.10.4" | ||
303 | "@babel/plugin-syntax-json-strings" "^7.8.0" | ||
28 | 304 | ||
29 | acorn@^4.0.3: | 305 | "@babel/plugin-proposal-logical-assignment-operators@^7.11.0": |
30 | version "4.0.13" | 306 | version "7.11.0" |
31 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" | 307 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz#9f80e482c03083c87125dee10026b58527ea20c8" |
32 | integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c= | 308 | integrity sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q== |
309 | dependencies: | ||
310 | "@babel/helper-plugin-utils" "^7.10.4" | ||
311 | "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" | ||
33 | 312 | ||
34 | acorn@^5.0.0, acorn@^5.5.0: | 313 | "@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": |
35 | version "5.7.3" | 314 | version "7.10.4" |
36 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" | 315 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" |
37 | integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== | 316 | integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== |
317 | dependencies: | ||
318 | "@babel/helper-plugin-utils" "^7.10.4" | ||
319 | "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" | ||
38 | 320 | ||
39 | ajv-keywords@^1.0.0: | 321 | "@babel/plugin-proposal-numeric-separator@^7.10.4": |
40 | version "1.5.1" | 322 | version "7.10.4" |
41 | resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" | 323 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06" |
42 | integrity sha1-MU3QpLM2j609/NxU7eYXG4htrzw= | 324 | integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA== |
325 | dependencies: | ||
326 | "@babel/helper-plugin-utils" "^7.10.4" | ||
327 | "@babel/plugin-syntax-numeric-separator" "^7.10.4" | ||
43 | 328 | ||
44 | ajv-keywords@^2.1.0: | 329 | "@babel/plugin-proposal-object-rest-spread@^7.11.0": |
45 | version "2.1.1" | 330 | version "7.11.0" |
46 | resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" | 331 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz#bd81f95a1f746760ea43b6c2d3d62b11790ad0af" |
47 | integrity sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I= | 332 | integrity sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA== |
333 | dependencies: | ||
334 | "@babel/helper-plugin-utils" "^7.10.4" | ||
335 | "@babel/plugin-syntax-object-rest-spread" "^7.8.0" | ||
336 | "@babel/plugin-transform-parameters" "^7.10.4" | ||
48 | 337 | ||
49 | ajv-keywords@^3.1.0: | 338 | "@babel/plugin-proposal-optional-catch-binding@^7.10.4": |
50 | version "3.4.0" | 339 | version "7.10.4" |
51 | resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.0.tgz#4b831e7b531415a7cc518cd404e73f6193c6349d" | 340 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd" |
52 | integrity sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw== | 341 | integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g== |
342 | dependencies: | ||
343 | "@babel/helper-plugin-utils" "^7.10.4" | ||
344 | "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" | ||
53 | 345 | ||
54 | ajv@^4.7.0: | 346 | "@babel/plugin-proposal-optional-chaining@^7.11.0": |
55 | version "4.11.8" | 347 | version "7.11.0" |
56 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" | 348 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076" |
57 | integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= | 349 | integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA== |
58 | dependencies: | 350 | dependencies: |
59 | co "^4.6.0" | 351 | "@babel/helper-plugin-utils" "^7.10.4" |
60 | json-stable-stringify "^1.0.1" | 352 | "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" |
353 | "@babel/plugin-syntax-optional-chaining" "^7.8.0" | ||
61 | 354 | ||
62 | ajv@^5.0.0, ajv@^5.2.3, ajv@^5.3.0: | 355 | "@babel/plugin-proposal-private-methods@^7.10.4": |
63 | version "5.5.2" | 356 | version "7.10.4" |
64 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" | 357 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909" |
65 | integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= | 358 | integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw== |
66 | dependencies: | 359 | dependencies: |
67 | co "^4.6.0" | 360 | "@babel/helper-create-class-features-plugin" "^7.10.4" |
68 | fast-deep-equal "^1.0.0" | 361 | "@babel/helper-plugin-utils" "^7.10.4" |
69 | fast-json-stable-stringify "^2.0.0" | ||
70 | json-schema-traverse "^0.3.0" | ||
71 | 362 | ||
72 | ajv@^6.1.0, ajv@^6.5.5: | 363 | "@babel/plugin-proposal-unicode-property-regex@^7.10.4", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": |
73 | version "6.10.0" | 364 | version "7.10.4" |
74 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" | 365 | resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d" |
75 | integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== | 366 | integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA== |
76 | dependencies: | 367 | dependencies: |
77 | fast-deep-equal "^2.0.1" | 368 | "@babel/helper-create-regexp-features-plugin" "^7.10.4" |
78 | fast-json-stable-stringify "^2.0.0" | 369 | "@babel/helper-plugin-utils" "^7.10.4" |
79 | json-schema-traverse "^0.4.1" | ||
80 | uri-js "^4.2.2" | ||
81 | 370 | ||
82 | align-text@^0.1.1, align-text@^0.1.3: | 371 | "@babel/plugin-syntax-async-generators@^7.8.0": |
83 | version "0.1.4" | 372 | version "7.8.4" |
84 | resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" | 373 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" |
85 | integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= | 374 | integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== |
86 | dependencies: | 375 | dependencies: |
87 | kind-of "^3.0.2" | 376 | "@babel/helper-plugin-utils" "^7.8.0" |
88 | longest "^1.0.1" | ||
89 | repeat-string "^1.5.2" | ||
90 | 377 | ||
91 | alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: | 378 | "@babel/plugin-syntax-class-properties@^7.10.4": |
92 | version "1.0.2" | 379 | version "7.10.4" |
93 | resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" | 380 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c" |
94 | integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= | 381 | integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA== |
382 | dependencies: | ||
383 | "@babel/helper-plugin-utils" "^7.10.4" | ||
95 | 384 | ||
96 | amdefine@>=0.0.4: | 385 | "@babel/plugin-syntax-dynamic-import@^7.8.0": |
97 | version "1.0.1" | 386 | version "7.8.3" |
98 | resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" | 387 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" |
99 | integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= | 388 | integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== |
389 | dependencies: | ||
390 | "@babel/helper-plugin-utils" "^7.8.0" | ||
100 | 391 | ||
101 | ansi-escapes@^1.1.0: | 392 | "@babel/plugin-syntax-export-namespace-from@^7.8.3": |
102 | version "1.4.0" | 393 | version "7.8.3" |
103 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" | 394 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" |
104 | integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= | 395 | integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== |
396 | dependencies: | ||
397 | "@babel/helper-plugin-utils" "^7.8.3" | ||
105 | 398 | ||
106 | ansi-escapes@^3.0.0: | 399 | "@babel/plugin-syntax-json-strings@^7.8.0": |
107 | version "3.2.0" | 400 | version "7.8.3" |
108 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" | 401 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" |
109 | integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== | 402 | integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== |
403 | dependencies: | ||
404 | "@babel/helper-plugin-utils" "^7.8.0" | ||
110 | 405 | ||
111 | ansi-regex@^2.0.0: | 406 | "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": |
112 | version "2.1.1" | 407 | version "7.10.4" |
113 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" | 408 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" |
114 | integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= | 409 | integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== |
410 | dependencies: | ||
411 | "@babel/helper-plugin-utils" "^7.10.4" | ||
115 | 412 | ||
116 | ansi-regex@^3.0.0: | 413 | "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": |
117 | version "3.0.0" | 414 | version "7.8.3" |
118 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" | 415 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" |
119 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= | 416 | integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== |
417 | dependencies: | ||
418 | "@babel/helper-plugin-utils" "^7.8.0" | ||
120 | 419 | ||
121 | ansi-styles@^2.2.1: | 420 | "@babel/plugin-syntax-numeric-separator@^7.10.4": |
122 | version "2.2.1" | 421 | version "7.10.4" |
123 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" | 422 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" |
124 | integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= | 423 | integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== |
424 | dependencies: | ||
425 | "@babel/helper-plugin-utils" "^7.10.4" | ||
125 | 426 | ||
126 | ansi-styles@^3.2.1: | 427 | "@babel/plugin-syntax-object-rest-spread@^7.8.0": |
127 | version "3.2.1" | 428 | version "7.8.3" |
128 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" | 429 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" |
129 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== | 430 | integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== |
130 | dependencies: | 431 | dependencies: |
131 | color-convert "^1.9.0" | 432 | "@babel/helper-plugin-utils" "^7.8.0" |
132 | 433 | ||
133 | anymatch@^2.0.0: | 434 | "@babel/plugin-syntax-optional-catch-binding@^7.8.0": |
134 | version "2.0.0" | 435 | version "7.8.3" |
135 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" | 436 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" |
136 | integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== | 437 | integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== |
137 | dependencies: | 438 | dependencies: |
138 | micromatch "^3.1.4" | 439 | "@babel/helper-plugin-utils" "^7.8.0" |
139 | normalize-path "^2.1.1" | ||
140 | 440 | ||
141 | aproba@^1.0.3: | 441 | "@babel/plugin-syntax-optional-chaining@^7.8.0": |
142 | version "1.2.0" | 442 | version "7.8.3" |
143 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" | 443 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" |
144 | integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== | 444 | integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== |
445 | dependencies: | ||
446 | "@babel/helper-plugin-utils" "^7.8.0" | ||
145 | 447 | ||
146 | are-we-there-yet@~1.1.2: | 448 | "@babel/plugin-syntax-top-level-await@^7.10.4": |
147 | version "1.1.5" | 449 | version "7.10.4" |
148 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" | 450 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d" |
149 | integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== | 451 | integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ== |
150 | dependencies: | 452 | dependencies: |
151 | delegates "^1.0.0" | 453 | "@babel/helper-plugin-utils" "^7.10.4" |
152 | readable-stream "^2.0.6" | ||
153 | 454 | ||
154 | argparse@^1.0.7: | 455 | "@babel/plugin-transform-arrow-functions@^7.10.4": |
155 | version "1.0.10" | 456 | version "7.10.4" |
156 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" | 457 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd" |
157 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== | 458 | integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA== |
158 | dependencies: | 459 | dependencies: |
159 | sprintf-js "~1.0.2" | 460 | "@babel/helper-plugin-utils" "^7.10.4" |
160 | 461 | ||
161 | arr-diff@^4.0.0: | 462 | "@babel/plugin-transform-async-to-generator@^7.10.4": |
162 | version "4.0.0" | 463 | version "7.10.4" |
163 | resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" | 464 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37" |
164 | integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= | 465 | integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ== |
466 | dependencies: | ||
467 | "@babel/helper-module-imports" "^7.10.4" | ||
468 | "@babel/helper-plugin-utils" "^7.10.4" | ||
469 | "@babel/helper-remap-async-to-generator" "^7.10.4" | ||
165 | 470 | ||
166 | arr-flatten@^1.1.0: | 471 | "@babel/plugin-transform-block-scoped-functions@^7.10.4": |
167 | version "1.1.0" | 472 | version "7.10.4" |
168 | resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" | 473 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8" |
169 | integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== | 474 | integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA== |
475 | dependencies: | ||
476 | "@babel/helper-plugin-utils" "^7.10.4" | ||
170 | 477 | ||
171 | arr-union@^3.1.0: | 478 | "@babel/plugin-transform-block-scoping@^7.10.4": |
172 | version "3.1.0" | 479 | version "7.11.1" |
173 | resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" | 480 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz#5b7efe98852bef8d652c0b28144cd93a9e4b5215" |
174 | integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= | 481 | integrity sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew== |
482 | dependencies: | ||
483 | "@babel/helper-plugin-utils" "^7.10.4" | ||
175 | 484 | ||
176 | array-find-index@^1.0.1: | 485 | "@babel/plugin-transform-classes@^7.10.4": |
177 | version "1.0.2" | 486 | version "7.10.4" |
178 | resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" | 487 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7" |
179 | integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= | 488 | integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA== |
489 | dependencies: | ||
490 | "@babel/helper-annotate-as-pure" "^7.10.4" | ||
491 | "@babel/helper-define-map" "^7.10.4" | ||
492 | "@babel/helper-function-name" "^7.10.4" | ||
493 | "@babel/helper-optimise-call-expression" "^7.10.4" | ||
494 | "@babel/helper-plugin-utils" "^7.10.4" | ||
495 | "@babel/helper-replace-supers" "^7.10.4" | ||
496 | "@babel/helper-split-export-declaration" "^7.10.4" | ||
497 | globals "^11.1.0" | ||
180 | 498 | ||
181 | array-includes@^3.0.3: | 499 | "@babel/plugin-transform-computed-properties@^7.10.4": |
182 | version "3.0.3" | 500 | version "7.10.4" |
183 | resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" | 501 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb" |
184 | integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= | 502 | integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw== |
185 | dependencies: | 503 | dependencies: |
186 | define-properties "^1.1.2" | 504 | "@babel/helper-plugin-utils" "^7.10.4" |
187 | es-abstract "^1.7.0" | ||
188 | 505 | ||
189 | array-unique@^0.3.2: | 506 | "@babel/plugin-transform-destructuring@^7.10.4": |
190 | version "0.3.2" | 507 | version "7.10.4" |
191 | resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" | 508 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5" |
192 | integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= | 509 | integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA== |
510 | dependencies: | ||
511 | "@babel/helper-plugin-utils" "^7.10.4" | ||
193 | 512 | ||
194 | asn1.js@^4.0.0: | 513 | "@babel/plugin-transform-dotall-regex@^7.10.4", "@babel/plugin-transform-dotall-regex@^7.4.4": |
195 | version "4.10.1" | 514 | version "7.10.4" |
196 | resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" | 515 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee" |
197 | integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== | 516 | integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA== |
198 | dependencies: | 517 | dependencies: |
199 | bn.js "^4.0.0" | 518 | "@babel/helper-create-regexp-features-plugin" "^7.10.4" |
200 | inherits "^2.0.1" | 519 | "@babel/helper-plugin-utils" "^7.10.4" |
201 | minimalistic-assert "^1.0.0" | ||
202 | 520 | ||
203 | asn1@~0.2.3: | 521 | "@babel/plugin-transform-duplicate-keys@^7.10.4": |
204 | version "0.2.4" | 522 | version "7.10.4" |
205 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" | 523 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47" |
206 | integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== | 524 | integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA== |
207 | dependencies: | 525 | dependencies: |
208 | safer-buffer "~2.1.0" | 526 | "@babel/helper-plugin-utils" "^7.10.4" |
209 | 527 | ||
210 | assert-plus@1.0.0, assert-plus@^1.0.0: | 528 | "@babel/plugin-transform-exponentiation-operator@^7.10.4": |
211 | version "1.0.0" | 529 | version "7.10.4" |
212 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" | 530 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e" |
213 | integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= | 531 | integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw== |
532 | dependencies: | ||
533 | "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" | ||
534 | "@babel/helper-plugin-utils" "^7.10.4" | ||
214 | 535 | ||
215 | assert@^1.1.1: | 536 | "@babel/plugin-transform-for-of@^7.10.4": |
216 | version "1.5.0" | 537 | version "7.10.4" |
217 | resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" | 538 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9" |
218 | integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== | 539 | integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ== |
219 | dependencies: | 540 | dependencies: |
220 | object-assign "^4.1.1" | 541 | "@babel/helper-plugin-utils" "^7.10.4" |
221 | util "0.10.3" | ||
222 | 542 | ||
223 | assign-symbols@^1.0.0: | 543 | "@babel/plugin-transform-function-name@^7.10.4": |
224 | version "1.0.0" | 544 | version "7.10.4" |
225 | resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" | 545 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7" |
226 | integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= | 546 | integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg== |
547 | dependencies: | ||
548 | "@babel/helper-function-name" "^7.10.4" | ||
549 | "@babel/helper-plugin-utils" "^7.10.4" | ||
227 | 550 | ||
228 | async-each@^1.0.1: | 551 | "@babel/plugin-transform-literals@^7.10.4": |
229 | version "1.0.3" | 552 | version "7.10.4" |
230 | resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" | 553 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c" |
231 | integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== | 554 | integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ== |
555 | dependencies: | ||
556 | "@babel/helper-plugin-utils" "^7.10.4" | ||
232 | 557 | ||
233 | async-foreach@^0.1.3: | 558 | "@babel/plugin-transform-member-expression-literals@^7.10.4": |
234 | version "0.1.3" | 559 | version "7.10.4" |
235 | resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" | 560 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7" |
236 | integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= | 561 | integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw== |
562 | dependencies: | ||
563 | "@babel/helper-plugin-utils" "^7.10.4" | ||
237 | 564 | ||
238 | async@^2.1.2, async@^2.4.1: | 565 | "@babel/plugin-transform-modules-amd@^7.10.4": |
239 | version "2.6.2" | 566 | version "7.10.5" |
240 | resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" | 567 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz#1b9cddaf05d9e88b3aad339cb3e445c4f020a9b1" |
241 | integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== | 568 | integrity sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw== |
242 | dependencies: | 569 | dependencies: |
243 | lodash "^4.17.11" | 570 | "@babel/helper-module-transforms" "^7.10.5" |
571 | "@babel/helper-plugin-utils" "^7.10.4" | ||
572 | babel-plugin-dynamic-import-node "^2.3.3" | ||
244 | 573 | ||
245 | asynckit@^0.4.0: | 574 | "@babel/plugin-transform-modules-commonjs@^7.10.4": |
246 | version "0.4.0" | 575 | version "7.10.4" |
247 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" | 576 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0" |
248 | integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= | 577 | integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w== |
578 | dependencies: | ||
579 | "@babel/helper-module-transforms" "^7.10.4" | ||
580 | "@babel/helper-plugin-utils" "^7.10.4" | ||
581 | "@babel/helper-simple-access" "^7.10.4" | ||
582 | babel-plugin-dynamic-import-node "^2.3.3" | ||
249 | 583 | ||
250 | atob@^2.1.1: | 584 | "@babel/plugin-transform-modules-systemjs@^7.10.4": |
251 | version "2.1.2" | 585 | version "7.10.5" |
252 | resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" | 586 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85" |
253 | integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== | 587 | integrity sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw== |
588 | dependencies: | ||
589 | "@babel/helper-hoist-variables" "^7.10.4" | ||
590 | "@babel/helper-module-transforms" "^7.10.5" | ||
591 | "@babel/helper-plugin-utils" "^7.10.4" | ||
592 | babel-plugin-dynamic-import-node "^2.3.3" | ||
254 | 593 | ||
255 | autoprefixer@^6.3.1: | 594 | "@babel/plugin-transform-modules-umd@^7.10.4": |
256 | version "6.7.7" | 595 | version "7.10.4" |
257 | resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" | 596 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e" |
258 | integrity sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ= | 597 | integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA== |
259 | dependencies: | 598 | dependencies: |
260 | browserslist "^1.7.6" | 599 | "@babel/helper-module-transforms" "^7.10.4" |
261 | caniuse-db "^1.0.30000634" | 600 | "@babel/helper-plugin-utils" "^7.10.4" |
262 | normalize-range "^0.1.2" | ||
263 | num2fraction "^1.2.2" | ||
264 | postcss "^5.2.16" | ||
265 | postcss-value-parser "^3.2.3" | ||
266 | 601 | ||
267 | awesomplete@^1.1.2: | 602 | "@babel/plugin-transform-named-capturing-groups-regex@^7.10.4": |
268 | version "1.1.4" | 603 | version "7.10.4" |
269 | resolved "https://registry.yarnpkg.com/awesomplete/-/awesomplete-1.1.4.tgz#cdfcbbb2391857ff3a3340b5b1ebde7701b355e6" | 604 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6" |
270 | integrity sha512-AgYrODNlVD3ZJ6Em54YesLnOSusuVCjoRAt0l5bi3L1Oiv5r5dkPdxVPJaG3/wnPlxRUmGcpGnK02VK7N02kCg== | 605 | integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA== |
606 | dependencies: | ||
607 | "@babel/helper-create-regexp-features-plugin" "^7.10.4" | ||
271 | 608 | ||
272 | aws-sign2@~0.7.0: | 609 | "@babel/plugin-transform-new-target@^7.10.4": |
273 | version "0.7.0" | 610 | version "7.10.4" |
274 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" | 611 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888" |
275 | integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= | 612 | integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw== |
613 | dependencies: | ||
614 | "@babel/helper-plugin-utils" "^7.10.4" | ||
276 | 615 | ||
277 | aws4@^1.8.0: | 616 | "@babel/plugin-transform-object-super@^7.10.4": |
278 | version "1.8.0" | 617 | version "7.10.4" |
279 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" | 618 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894" |
280 | integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== | 619 | integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ== |
620 | dependencies: | ||
621 | "@babel/helper-plugin-utils" "^7.10.4" | ||
622 | "@babel/helper-replace-supers" "^7.10.4" | ||
623 | |||
624 | "@babel/plugin-transform-parameters@^7.10.4": | ||
625 | version "7.10.5" | ||
626 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz#59d339d58d0b1950435f4043e74e2510005e2c4a" | ||
627 | integrity sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== | ||
628 | dependencies: | ||
629 | "@babel/helper-get-function-arity" "^7.10.4" | ||
630 | "@babel/helper-plugin-utils" "^7.10.4" | ||
631 | |||
632 | "@babel/plugin-transform-property-literals@^7.10.4": | ||
633 | version "7.10.4" | ||
634 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0" | ||
635 | integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g== | ||
636 | dependencies: | ||
637 | "@babel/helper-plugin-utils" "^7.10.4" | ||
638 | |||
639 | "@babel/plugin-transform-regenerator@^7.10.4": | ||
640 | version "7.10.4" | ||
641 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" | ||
642 | integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw== | ||
643 | dependencies: | ||
644 | regenerator-transform "^0.14.2" | ||
645 | |||
646 | "@babel/plugin-transform-reserved-words@^7.10.4": | ||
647 | version "7.10.4" | ||
648 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd" | ||
649 | integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ== | ||
650 | dependencies: | ||
651 | "@babel/helper-plugin-utils" "^7.10.4" | ||
652 | |||
653 | "@babel/plugin-transform-shorthand-properties@^7.10.4": | ||
654 | version "7.10.4" | ||
655 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" | ||
656 | integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== | ||
657 | dependencies: | ||
658 | "@babel/helper-plugin-utils" "^7.10.4" | ||
659 | |||
660 | "@babel/plugin-transform-spread@^7.11.0": | ||
661 | version "7.11.0" | ||
662 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz#fa84d300f5e4f57752fe41a6d1b3c554f13f17cc" | ||
663 | integrity sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw== | ||
664 | dependencies: | ||
665 | "@babel/helper-plugin-utils" "^7.10.4" | ||
666 | "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" | ||
667 | |||
668 | "@babel/plugin-transform-sticky-regex@^7.10.4": | ||
669 | version "7.10.4" | ||
670 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d" | ||
671 | integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ== | ||
672 | dependencies: | ||
673 | "@babel/helper-plugin-utils" "^7.10.4" | ||
674 | "@babel/helper-regex" "^7.10.4" | ||
675 | |||
676 | "@babel/plugin-transform-template-literals@^7.10.4": | ||
677 | version "7.10.5" | ||
678 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz#78bc5d626a6642db3312d9d0f001f5e7639fde8c" | ||
679 | integrity sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw== | ||
680 | dependencies: | ||
681 | "@babel/helper-annotate-as-pure" "^7.10.4" | ||
682 | "@babel/helper-plugin-utils" "^7.10.4" | ||
683 | |||
684 | "@babel/plugin-transform-typeof-symbol@^7.10.4": | ||
685 | version "7.10.4" | ||
686 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc" | ||
687 | integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA== | ||
688 | dependencies: | ||
689 | "@babel/helper-plugin-utils" "^7.10.4" | ||
690 | |||
691 | "@babel/plugin-transform-unicode-escapes@^7.10.4": | ||
692 | version "7.10.4" | ||
693 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" | ||
694 | integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg== | ||
695 | dependencies: | ||
696 | "@babel/helper-plugin-utils" "^7.10.4" | ||
697 | |||
698 | "@babel/plugin-transform-unicode-regex@^7.10.4": | ||
699 | version "7.10.4" | ||
700 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8" | ||
701 | integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A== | ||
702 | dependencies: | ||
703 | "@babel/helper-create-regexp-features-plugin" "^7.10.4" | ||
704 | "@babel/helper-plugin-utils" "^7.10.4" | ||
705 | |||
706 | "@babel/preset-env@^7.11.5": | ||
707 | version "7.11.5" | ||
708 | resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.11.5.tgz#18cb4b9379e3e92ffea92c07471a99a2914e4272" | ||
709 | integrity sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA== | ||
710 | dependencies: | ||
711 | "@babel/compat-data" "^7.11.0" | ||
712 | "@babel/helper-compilation-targets" "^7.10.4" | ||
713 | "@babel/helper-module-imports" "^7.10.4" | ||
714 | "@babel/helper-plugin-utils" "^7.10.4" | ||
715 | "@babel/plugin-proposal-async-generator-functions" "^7.10.4" | ||
716 | "@babel/plugin-proposal-class-properties" "^7.10.4" | ||
717 | "@babel/plugin-proposal-dynamic-import" "^7.10.4" | ||
718 | "@babel/plugin-proposal-export-namespace-from" "^7.10.4" | ||
719 | "@babel/plugin-proposal-json-strings" "^7.10.4" | ||
720 | "@babel/plugin-proposal-logical-assignment-operators" "^7.11.0" | ||
721 | "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" | ||
722 | "@babel/plugin-proposal-numeric-separator" "^7.10.4" | ||
723 | "@babel/plugin-proposal-object-rest-spread" "^7.11.0" | ||
724 | "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" | ||
725 | "@babel/plugin-proposal-optional-chaining" "^7.11.0" | ||
726 | "@babel/plugin-proposal-private-methods" "^7.10.4" | ||
727 | "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" | ||
728 | "@babel/plugin-syntax-async-generators" "^7.8.0" | ||
729 | "@babel/plugin-syntax-class-properties" "^7.10.4" | ||
730 | "@babel/plugin-syntax-dynamic-import" "^7.8.0" | ||
731 | "@babel/plugin-syntax-export-namespace-from" "^7.8.3" | ||
732 | "@babel/plugin-syntax-json-strings" "^7.8.0" | ||
733 | "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" | ||
734 | "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" | ||
735 | "@babel/plugin-syntax-numeric-separator" "^7.10.4" | ||
736 | "@babel/plugin-syntax-object-rest-spread" "^7.8.0" | ||
737 | "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" | ||
738 | "@babel/plugin-syntax-optional-chaining" "^7.8.0" | ||
739 | "@babel/plugin-syntax-top-level-await" "^7.10.4" | ||
740 | "@babel/plugin-transform-arrow-functions" "^7.10.4" | ||
741 | "@babel/plugin-transform-async-to-generator" "^7.10.4" | ||
742 | "@babel/plugin-transform-block-scoped-functions" "^7.10.4" | ||
743 | "@babel/plugin-transform-block-scoping" "^7.10.4" | ||
744 | "@babel/plugin-transform-classes" "^7.10.4" | ||
745 | "@babel/plugin-transform-computed-properties" "^7.10.4" | ||
746 | "@babel/plugin-transform-destructuring" "^7.10.4" | ||
747 | "@babel/plugin-transform-dotall-regex" "^7.10.4" | ||
748 | "@babel/plugin-transform-duplicate-keys" "^7.10.4" | ||
749 | "@babel/plugin-transform-exponentiation-operator" "^7.10.4" | ||
750 | "@babel/plugin-transform-for-of" "^7.10.4" | ||
751 | "@babel/plugin-transform-function-name" "^7.10.4" | ||
752 | "@babel/plugin-transform-literals" "^7.10.4" | ||
753 | "@babel/plugin-transform-member-expression-literals" "^7.10.4" | ||
754 | "@babel/plugin-transform-modules-amd" "^7.10.4" | ||
755 | "@babel/plugin-transform-modules-commonjs" "^7.10.4" | ||
756 | "@babel/plugin-transform-modules-systemjs" "^7.10.4" | ||
757 | "@babel/plugin-transform-modules-umd" "^7.10.4" | ||
758 | "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" | ||
759 | "@babel/plugin-transform-new-target" "^7.10.4" | ||
760 | "@babel/plugin-transform-object-super" "^7.10.4" | ||
761 | "@babel/plugin-transform-parameters" "^7.10.4" | ||
762 | "@babel/plugin-transform-property-literals" "^7.10.4" | ||
763 | "@babel/plugin-transform-regenerator" "^7.10.4" | ||
764 | "@babel/plugin-transform-reserved-words" "^7.10.4" | ||
765 | "@babel/plugin-transform-shorthand-properties" "^7.10.4" | ||
766 | "@babel/plugin-transform-spread" "^7.11.0" | ||
767 | "@babel/plugin-transform-sticky-regex" "^7.10.4" | ||
768 | "@babel/plugin-transform-template-literals" "^7.10.4" | ||
769 | "@babel/plugin-transform-typeof-symbol" "^7.10.4" | ||
770 | "@babel/plugin-transform-unicode-escapes" "^7.10.4" | ||
771 | "@babel/plugin-transform-unicode-regex" "^7.10.4" | ||
772 | "@babel/preset-modules" "^0.1.3" | ||
773 | "@babel/types" "^7.11.5" | ||
774 | browserslist "^4.12.0" | ||
775 | core-js-compat "^3.6.2" | ||
776 | invariant "^2.2.2" | ||
777 | levenary "^1.1.1" | ||
778 | semver "^5.5.0" | ||
281 | 779 | ||
282 | babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: | 780 | "@babel/preset-modules@^0.1.3": |
283 | version "6.26.0" | 781 | version "0.1.4" |
284 | resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" | 782 | resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" |
285 | integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= | 783 | integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== |
286 | dependencies: | 784 | dependencies: |
287 | chalk "^1.1.3" | 785 | "@babel/helper-plugin-utils" "^7.0.0" |
786 | "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" | ||
787 | "@babel/plugin-transform-dotall-regex" "^7.4.4" | ||
788 | "@babel/types" "^7.4.4" | ||
288 | esutils "^2.0.2" | 789 | esutils "^2.0.2" |
289 | js-tokens "^3.0.2" | 790 | |
290 | 791 | "@babel/runtime@^7.8.4": | |
291 | babel-core@^6.24.1, babel-core@^6.26.0: | 792 | version "7.11.2" |
292 | version "6.26.3" | 793 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" |
293 | resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" | 794 | integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== |
294 | integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== | 795 | dependencies: |
295 | dependencies: | 796 | regenerator-runtime "^0.13.4" |
296 | babel-code-frame "^6.26.0" | 797 | |
297 | babel-generator "^6.26.0" | 798 | "@babel/template@^7.10.4": |
298 | babel-helpers "^6.24.1" | 799 | version "7.10.4" |
299 | babel-messages "^6.23.0" | 800 | resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" |
300 | babel-register "^6.26.0" | 801 | integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== |
301 | babel-runtime "^6.26.0" | 802 | dependencies: |
302 | babel-template "^6.26.0" | 803 | "@babel/code-frame" "^7.10.4" |
303 | babel-traverse "^6.26.0" | 804 | "@babel/parser" "^7.10.4" |
304 | babel-types "^6.26.0" | 805 | "@babel/types" "^7.10.4" |
305 | babylon "^6.18.0" | 806 | |
306 | convert-source-map "^1.5.1" | 807 | "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5": |
307 | debug "^2.6.9" | 808 | version "7.11.5" |
308 | json5 "^0.5.1" | 809 | resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" |
309 | lodash "^4.17.4" | 810 | integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ== |
811 | dependencies: | ||
812 | "@babel/code-frame" "^7.10.4" | ||
813 | "@babel/generator" "^7.11.5" | ||
814 | "@babel/helper-function-name" "^7.10.4" | ||
815 | "@babel/helper-split-export-declaration" "^7.11.0" | ||
816 | "@babel/parser" "^7.11.5" | ||
817 | "@babel/types" "^7.11.5" | ||
818 | debug "^4.1.0" | ||
819 | globals "^11.1.0" | ||
820 | lodash "^4.17.19" | ||
821 | |||
822 | "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.4.4": | ||
823 | version "7.11.5" | ||
824 | resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" | ||
825 | integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q== | ||
826 | dependencies: | ||
827 | "@babel/helper-validator-identifier" "^7.10.4" | ||
828 | lodash "^4.17.19" | ||
829 | to-fast-properties "^2.0.0" | ||
830 | |||
831 | "@eslint/eslintrc@^0.1.3": | ||
832 | version "0.1.3" | ||
833 | resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085" | ||
834 | integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA== | ||
835 | dependencies: | ||
836 | ajv "^6.12.4" | ||
837 | debug "^4.1.1" | ||
838 | espree "^7.3.0" | ||
839 | globals "^12.1.0" | ||
840 | ignore "^4.0.6" | ||
841 | import-fresh "^3.2.1" | ||
842 | js-yaml "^3.13.1" | ||
843 | lodash "^4.17.19" | ||
310 | minimatch "^3.0.4" | 844 | minimatch "^3.0.4" |
311 | path-is-absolute "^1.0.1" | 845 | strip-json-comments "^3.1.1" |
312 | private "^0.1.8" | ||
313 | slash "^1.0.0" | ||
314 | source-map "^0.5.7" | ||
315 | |||
316 | babel-generator@^6.26.0: | ||
317 | version "6.26.1" | ||
318 | resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" | ||
319 | integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== | ||
320 | dependencies: | ||
321 | babel-messages "^6.23.0" | ||
322 | babel-runtime "^6.26.0" | ||
323 | babel-types "^6.26.0" | ||
324 | detect-indent "^4.0.0" | ||
325 | jsesc "^1.3.0" | ||
326 | lodash "^4.17.4" | ||
327 | source-map "^0.5.7" | ||
328 | trim-right "^1.0.1" | ||
329 | |||
330 | babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: | ||
331 | version "6.24.1" | ||
332 | resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" | ||
333 | integrity sha1-zORReto1b0IgvK6KAsKzRvmlZmQ= | ||
334 | dependencies: | ||
335 | babel-helper-explode-assignable-expression "^6.24.1" | ||
336 | babel-runtime "^6.22.0" | ||
337 | babel-types "^6.24.1" | ||
338 | |||
339 | babel-helper-call-delegate@^6.24.1: | ||
340 | version "6.24.1" | ||
341 | resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" | ||
342 | integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340= | ||
343 | dependencies: | ||
344 | babel-helper-hoist-variables "^6.24.1" | ||
345 | babel-runtime "^6.22.0" | ||
346 | babel-traverse "^6.24.1" | ||
347 | babel-types "^6.24.1" | ||
348 | |||
349 | babel-helper-define-map@^6.24.1: | ||
350 | version "6.26.0" | ||
351 | resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" | ||
352 | integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8= | ||
353 | dependencies: | ||
354 | babel-helper-function-name "^6.24.1" | ||
355 | babel-runtime "^6.26.0" | ||
356 | babel-types "^6.26.0" | ||
357 | lodash "^4.17.4" | ||
358 | |||
359 | babel-helper-evaluate-path@^0.2.0: | ||
360 | version "0.2.0" | ||
361 | resolved "https://registry.yarnpkg.com/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.2.0.tgz#0bb2eb01996c0cef53c5e8405e999fe4a0244c08" | ||
362 | integrity sha512-0EK9TUKMxHL549hWDPkQoS7R0Ozg1CDLheVBHYds2B2qoAvmr9ejY3zOXFsrICK73TN7bPhU14PBeKc8jcBTwg== | ||
363 | 846 | ||
364 | babel-helper-explode-assignable-expression@^6.24.1: | 847 | "@nodelib/fs.scandir@2.1.3": |
365 | version "6.24.1" | 848 | version "2.1.3" |
366 | resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" | 849 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" |
367 | integrity sha1-8luCz33BBDPFX3BZLVdGQArCLKo= | 850 | integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== |
368 | dependencies: | 851 | dependencies: |
369 | babel-runtime "^6.22.0" | 852 | "@nodelib/fs.stat" "2.0.3" |
370 | babel-traverse "^6.24.1" | 853 | run-parallel "^1.1.9" |
371 | babel-types "^6.24.1" | ||
372 | 854 | ||
373 | babel-helper-flip-expressions@^0.2.0: | 855 | "@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": |
374 | version "0.2.0" | 856 | version "2.0.3" |
375 | resolved "https://registry.yarnpkg.com/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.2.0.tgz#160d2090a3d9f9c64a750905321a0bc218f884ec" | 857 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" |
376 | integrity sha512-rAsPA1pWBc7e2E6HepkP2e1sXugT+Oq/VCqhyuHJ8aJ2d/ifwnJfd4Qxjm21qlW43AN8tqaeByagKK6wECFMSw== | 858 | integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== |
377 | 859 | ||
378 | babel-helper-function-name@^6.24.1: | 860 | "@nodelib/fs.walk@^1.2.3": |
379 | version "6.24.1" | 861 | version "1.2.4" |
380 | resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" | 862 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" |
381 | integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= | 863 | integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== |
382 | dependencies: | 864 | dependencies: |
383 | babel-helper-get-function-arity "^6.24.1" | 865 | "@nodelib/fs.scandir" "2.1.3" |
384 | babel-runtime "^6.22.0" | 866 | fastq "^1.6.0" |
385 | babel-template "^6.24.1" | ||
386 | babel-traverse "^6.24.1" | ||
387 | babel-types "^6.24.1" | ||
388 | 867 | ||
389 | babel-helper-get-function-arity@^6.24.1: | 868 | "@npmcli/move-file@^1.0.1": |
390 | version "6.24.1" | 869 | version "1.0.1" |
391 | resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" | 870 | resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" |
392 | integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0= | 871 | integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw== |
393 | dependencies: | 872 | dependencies: |
394 | babel-runtime "^6.22.0" | 873 | mkdirp "^1.0.4" |
395 | babel-types "^6.24.1" | ||
396 | 874 | ||
397 | babel-helper-hoist-variables@^6.24.1: | 875 | "@stylelint/postcss-css-in-js@^0.37.2": |
398 | version "6.24.1" | 876 | version "0.37.2" |
399 | resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" | 877 | resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2" |
400 | integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY= | 878 | integrity sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA== |
401 | dependencies: | 879 | dependencies: |
402 | babel-runtime "^6.22.0" | 880 | "@babel/core" ">=7.9.0" |
403 | babel-types "^6.24.1" | ||
404 | 881 | ||
405 | babel-helper-is-nodes-equiv@^0.0.1: | 882 | "@stylelint/postcss-markdown@^0.36.1": |
406 | version "0.0.1" | 883 | version "0.36.1" |
407 | resolved "https://registry.yarnpkg.com/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz#34e9b300b1479ddd98ec77ea0bbe9342dfe39684" | 884 | resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz#829b87e6c0f108014533d9d7b987dc9efb6632e8" |
408 | integrity sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ= | 885 | integrity sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw== |
886 | dependencies: | ||
887 | remark "^12.0.0" | ||
888 | unist-util-find-all-after "^3.0.1" | ||
409 | 889 | ||
410 | babel-helper-is-void-0@^0.2.0: | 890 | "@types/color-name@^1.1.1": |
411 | version "0.2.0" | 891 | version "1.1.1" |
412 | resolved "https://registry.yarnpkg.com/babel-helper-is-void-0/-/babel-helper-is-void-0-0.2.0.tgz#6ed0ada8a9b1c5b6e88af6b47c1b3b5c080860eb" | 892 | resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" |
413 | integrity sha512-Axj1AYuD0E3Dl7nT3KxROP7VekEofz3XtEljzURf3fABalLpr8PamtgLFt+zuxtaCxRf9iuZmbAMMYWri5Bazw== | 893 | integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== |
414 | 894 | ||
415 | babel-helper-mark-eval-scopes@^0.2.0: | 895 | "@types/json-schema@^7.0.5": |
416 | version "0.2.0" | 896 | version "7.0.6" |
417 | resolved "https://registry.yarnpkg.com/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.2.0.tgz#7648aaf2ec92aae9b09a20ad91e8df5e1fcc94b2" | 897 | resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" |
418 | integrity sha512-KJuwrOUcHbvbh6he4xRXZFLaivK9DF9o3CrvpWnK1Wp0B+1ANYABXBMgwrnNFIDK/AvicxQ9CNr8wsgivlp4Aw== | 898 | integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== |
419 | |||
420 | babel-helper-optimise-call-expression@^6.24.1: | ||
421 | version "6.24.1" | ||
422 | resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" | ||
423 | integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc= | ||
424 | dependencies: | ||
425 | babel-runtime "^6.22.0" | ||
426 | babel-types "^6.24.1" | ||
427 | |||
428 | babel-helper-regex@^6.24.1: | ||
429 | version "6.26.0" | ||
430 | resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" | ||
431 | integrity sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI= | ||
432 | dependencies: | ||
433 | babel-runtime "^6.26.0" | ||
434 | babel-types "^6.26.0" | ||
435 | lodash "^4.17.4" | ||
436 | |||
437 | babel-helper-remap-async-to-generator@^6.24.1: | ||
438 | version "6.24.1" | ||
439 | resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" | ||
440 | integrity sha1-XsWBgnrXI/7N04HxySg5BnbkVRs= | ||
441 | dependencies: | ||
442 | babel-helper-function-name "^6.24.1" | ||
443 | babel-runtime "^6.22.0" | ||
444 | babel-template "^6.24.1" | ||
445 | babel-traverse "^6.24.1" | ||
446 | babel-types "^6.24.1" | ||
447 | |||
448 | babel-helper-remove-or-void@^0.2.0: | ||
449 | version "0.2.0" | ||
450 | resolved "https://registry.yarnpkg.com/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.2.0.tgz#8e46ad5b30560d57d7510b3fd93f332ee7c67386" | ||
451 | integrity sha512-1Z41upf/XR+PwY7Nd+F15Jo5BiQi5205ZXUuKed3yoyQgDkMyoM7vAdjEJS/T+M6jy32sXjskMUgms4zeiVtRA== | ||
452 | |||
453 | babel-helper-replace-supers@^6.24.1: | ||
454 | version "6.24.1" | ||
455 | resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" | ||
456 | integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo= | ||
457 | dependencies: | ||
458 | babel-helper-optimise-call-expression "^6.24.1" | ||
459 | babel-messages "^6.23.0" | ||
460 | babel-runtime "^6.22.0" | ||
461 | babel-template "^6.24.1" | ||
462 | babel-traverse "^6.24.1" | ||
463 | babel-types "^6.24.1" | ||
464 | |||
465 | babel-helper-to-multiple-sequence-expressions@^0.2.0: | ||
466 | version "0.2.0" | ||
467 | resolved "https://registry.yarnpkg.com/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.2.0.tgz#d1a419634c6cb301f27858c659167cfee0a9d318" | ||
468 | integrity sha512-ij9lpfdP3+Zc/7kNwa+NXbTrUlsYEWPwt/ugmQO0qflzLrveTIkbfOqQztvitk81aG5NblYDQXDlRohzu3oa8Q== | ||
469 | 899 | ||
470 | babel-helpers@^6.24.1: | 900 | "@types/json5@^0.0.29": |
471 | version "6.24.1" | 901 | version "0.0.29" |
472 | resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" | 902 | resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" |
473 | integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= | 903 | integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= |
474 | dependencies: | ||
475 | babel-runtime "^6.22.0" | ||
476 | babel-template "^6.24.1" | ||
477 | 904 | ||
478 | babel-loader@^7.1.2: | 905 | "@types/minimist@^1.2.0": |
479 | version "7.1.5" | 906 | version "1.2.0" |
480 | resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68" | 907 | resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" |
481 | integrity sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw== | 908 | integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= |
482 | dependencies: | ||
483 | find-cache-dir "^1.0.0" | ||
484 | loader-utils "^1.0.2" | ||
485 | mkdirp "^0.5.1" | ||
486 | 909 | ||
487 | babel-messages@^6.23.0: | 910 | "@types/node@*": |
488 | version "6.23.0" | 911 | version "14.11.2" |
489 | resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" | 912 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" |
490 | integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= | 913 | integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA== |
491 | dependencies: | ||
492 | babel-runtime "^6.22.0" | ||
493 | 914 | ||
494 | babel-minify-webpack-plugin@^0.2.0: | 915 | "@types/normalize-package-data@^2.4.0": |
495 | version "0.2.0" | 916 | version "2.4.0" |
496 | resolved "https://registry.yarnpkg.com/babel-minify-webpack-plugin/-/babel-minify-webpack-plugin-0.2.0.tgz#ef9694d11a1b8ab8f3204d89f5c9278dd28fc2a9" | 917 | resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" |
497 | integrity sha512-+5G5Qqm+DIVl7gY4rkHqlFRkaf1FZtz0imzu/Dy9+88AfOIuy7D5MQjkNgQr5gU6/YSZ+rImgxDqFcWkvvrjkQ== | 918 | integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== |
498 | dependencies: | ||
499 | babel-core "^6.24.1" | ||
500 | babel-preset-minify "^0.2.0" | ||
501 | webpack-sources "^1.0.1" | ||
502 | 919 | ||
503 | babel-plugin-check-es2015-constants@^6.22.0: | 920 | "@types/parse-json@^4.0.0": |
504 | version "6.22.0" | 921 | version "4.0.0" |
505 | resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" | 922 | resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" |
506 | integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o= | 923 | integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== |
924 | |||
925 | "@types/unist@^2.0.0", "@types/unist@^2.0.2": | ||
926 | version "2.0.3" | ||
927 | resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" | ||
928 | integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== | ||
929 | |||
930 | "@webassemblyjs/ast@1.9.0": | ||
931 | version "1.9.0" | ||
932 | resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" | ||
933 | integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== | ||
934 | dependencies: | ||
935 | "@webassemblyjs/helper-module-context" "1.9.0" | ||
936 | "@webassemblyjs/helper-wasm-bytecode" "1.9.0" | ||
937 | "@webassemblyjs/wast-parser" "1.9.0" | ||
938 | |||
939 | "@webassemblyjs/floating-point-hex-parser@1.9.0": | ||
940 | version "1.9.0" | ||
941 | resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" | ||
942 | integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== | ||
943 | |||
944 | "@webassemblyjs/helper-api-error@1.9.0": | ||
945 | version "1.9.0" | ||
946 | resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" | ||
947 | integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== | ||
948 | |||
949 | "@webassemblyjs/helper-buffer@1.9.0": | ||
950 | version "1.9.0" | ||
951 | resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" | ||
952 | integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== | ||
953 | |||
954 | "@webassemblyjs/helper-code-frame@1.9.0": | ||
955 | version "1.9.0" | ||
956 | resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" | ||
957 | integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== | ||
958 | dependencies: | ||
959 | "@webassemblyjs/wast-printer" "1.9.0" | ||
960 | |||
961 | "@webassemblyjs/helper-fsm@1.9.0": | ||
962 | version "1.9.0" | ||
963 | resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" | ||
964 | integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== | ||
965 | |||
966 | "@webassemblyjs/helper-module-context@1.9.0": | ||
967 | version "1.9.0" | ||
968 | resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" | ||
969 | integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== | ||
970 | dependencies: | ||
971 | "@webassemblyjs/ast" "1.9.0" | ||
972 | |||
973 | "@webassemblyjs/helper-wasm-bytecode@1.9.0": | ||
974 | version "1.9.0" | ||
975 | resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" | ||
976 | integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== | ||
977 | |||
978 | "@webassemblyjs/helper-wasm-section@1.9.0": | ||
979 | version "1.9.0" | ||
980 | resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" | ||
981 | integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== | ||
982 | dependencies: | ||
983 | "@webassemblyjs/ast" "1.9.0" | ||
984 | "@webassemblyjs/helper-buffer" "1.9.0" | ||
985 | "@webassemblyjs/helper-wasm-bytecode" "1.9.0" | ||
986 | "@webassemblyjs/wasm-gen" "1.9.0" | ||
987 | |||
988 | "@webassemblyjs/ieee754@1.9.0": | ||
989 | version "1.9.0" | ||
990 | resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" | ||
991 | integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== | ||
992 | dependencies: | ||
993 | "@xtuc/ieee754" "^1.2.0" | ||
994 | |||
995 | "@webassemblyjs/leb128@1.9.0": | ||
996 | version "1.9.0" | ||
997 | resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" | ||
998 | integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== | ||
999 | dependencies: | ||
1000 | "@xtuc/long" "4.2.2" | ||
1001 | |||
1002 | "@webassemblyjs/utf8@1.9.0": | ||
1003 | version "1.9.0" | ||
1004 | resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" | ||
1005 | integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== | ||
1006 | |||
1007 | "@webassemblyjs/wasm-edit@1.9.0": | ||
1008 | version "1.9.0" | ||
1009 | resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" | ||
1010 | integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== | ||
1011 | dependencies: | ||
1012 | "@webassemblyjs/ast" "1.9.0" | ||
1013 | "@webassemblyjs/helper-buffer" "1.9.0" | ||
1014 | "@webassemblyjs/helper-wasm-bytecode" "1.9.0" | ||
1015 | "@webassemblyjs/helper-wasm-section" "1.9.0" | ||
1016 | "@webassemblyjs/wasm-gen" "1.9.0" | ||
1017 | "@webassemblyjs/wasm-opt" "1.9.0" | ||
1018 | "@webassemblyjs/wasm-parser" "1.9.0" | ||
1019 | "@webassemblyjs/wast-printer" "1.9.0" | ||
1020 | |||
1021 | "@webassemblyjs/wasm-gen@1.9.0": | ||
1022 | version "1.9.0" | ||
1023 | resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" | ||
1024 | integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== | ||
1025 | dependencies: | ||
1026 | "@webassemblyjs/ast" "1.9.0" | ||
1027 | "@webassemblyjs/helper-wasm-bytecode" "1.9.0" | ||
1028 | "@webassemblyjs/ieee754" "1.9.0" | ||
1029 | "@webassemblyjs/leb128" "1.9.0" | ||
1030 | "@webassemblyjs/utf8" "1.9.0" | ||
1031 | |||
1032 | "@webassemblyjs/wasm-opt@1.9.0": | ||
1033 | version "1.9.0" | ||
1034 | resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" | ||
1035 | integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== | ||
1036 | dependencies: | ||
1037 | "@webassemblyjs/ast" "1.9.0" | ||
1038 | "@webassemblyjs/helper-buffer" "1.9.0" | ||
1039 | "@webassemblyjs/wasm-gen" "1.9.0" | ||
1040 | "@webassemblyjs/wasm-parser" "1.9.0" | ||
1041 | |||
1042 | "@webassemblyjs/wasm-parser@1.9.0": | ||
1043 | version "1.9.0" | ||
1044 | resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" | ||
1045 | integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== | ||
1046 | dependencies: | ||
1047 | "@webassemblyjs/ast" "1.9.0" | ||
1048 | "@webassemblyjs/helper-api-error" "1.9.0" | ||
1049 | "@webassemblyjs/helper-wasm-bytecode" "1.9.0" | ||
1050 | "@webassemblyjs/ieee754" "1.9.0" | ||
1051 | "@webassemblyjs/leb128" "1.9.0" | ||
1052 | "@webassemblyjs/utf8" "1.9.0" | ||
1053 | |||
1054 | "@webassemblyjs/wast-parser@1.9.0": | ||
1055 | version "1.9.0" | ||
1056 | resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" | ||
1057 | integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== | ||
1058 | dependencies: | ||
1059 | "@webassemblyjs/ast" "1.9.0" | ||
1060 | "@webassemblyjs/floating-point-hex-parser" "1.9.0" | ||
1061 | "@webassemblyjs/helper-api-error" "1.9.0" | ||
1062 | "@webassemblyjs/helper-code-frame" "1.9.0" | ||
1063 | "@webassemblyjs/helper-fsm" "1.9.0" | ||
1064 | "@xtuc/long" "4.2.2" | ||
1065 | |||
1066 | "@webassemblyjs/wast-printer@1.9.0": | ||
1067 | version "1.9.0" | ||
1068 | resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" | ||
1069 | integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== | ||
1070 | dependencies: | ||
1071 | "@webassemblyjs/ast" "1.9.0" | ||
1072 | "@webassemblyjs/wast-parser" "1.9.0" | ||
1073 | "@xtuc/long" "4.2.2" | ||
1074 | |||
1075 | "@xtuc/ieee754@^1.2.0": | ||
1076 | version "1.2.0" | ||
1077 | resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" | ||
1078 | integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== | ||
1079 | |||
1080 | "@xtuc/long@4.2.2": | ||
1081 | version "4.2.2" | ||
1082 | resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" | ||
1083 | integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== | ||
1084 | |||
1085 | acorn-jsx@^5.2.0: | ||
1086 | version "5.3.1" | ||
1087 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" | ||
1088 | integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== | ||
1089 | |||
1090 | acorn@^6.4.1: | ||
1091 | version "6.4.1" | ||
1092 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" | ||
1093 | integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== | ||
1094 | |||
1095 | acorn@^7.4.0: | ||
1096 | version "7.4.0" | ||
1097 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" | ||
1098 | integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== | ||
1099 | |||
1100 | aggregate-error@^3.0.0: | ||
1101 | version "3.1.0" | ||
1102 | resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" | ||
1103 | integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== | ||
507 | dependencies: | 1104 | dependencies: |
508 | babel-runtime "^6.22.0" | 1105 | clean-stack "^2.0.0" |
1106 | indent-string "^4.0.0" | ||
509 | 1107 | ||
510 | babel-plugin-minify-builtins@^0.2.0: | 1108 | ajv-errors@^1.0.0: |
511 | version "0.2.0" | 1109 | version "1.0.1" |
512 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.2.0.tgz#317f824b0907210b6348671bb040ca072e2e0c82" | 1110 | resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" |
513 | integrity sha512-4i+8ntaS8gwVUcOz5y+zE+55OVOl2nTbmHV51D4wAIiKcRI8U5K//ip1GHfhsgk/NJrrHK7h97Oy5jpqt0Iixg== | 1111 | integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== |
1112 | |||
1113 | ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: | ||
1114 | version "3.5.2" | ||
1115 | resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" | ||
1116 | integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== | ||
1117 | |||
1118 | ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4: | ||
1119 | version "6.12.5" | ||
1120 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" | ||
1121 | integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== | ||
514 | dependencies: | 1122 | dependencies: |
515 | babel-helper-evaluate-path "^0.2.0" | 1123 | fast-deep-equal "^3.1.1" |
1124 | fast-json-stable-stringify "^2.0.0" | ||
1125 | json-schema-traverse "^0.4.1" | ||
1126 | uri-js "^4.2.2" | ||
516 | 1127 | ||
517 | babel-plugin-minify-constant-folding@^0.2.0: | 1128 | ansi-colors@^4.1.1: |
518 | version "0.2.0" | 1129 | version "4.1.1" |
519 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-constant-folding/-/babel-plugin-minify-constant-folding-0.2.0.tgz#8c70b528b2eb7c13e94d95c8789077d4cdbc3970" | 1130 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" |
520 | integrity sha512-B3ffQBEUQ8ydlIkYv2MkZtTCbV7FAkWAV7NkyhcXlGpD10PaCxNGQ/B9oguXGowR1m16Q5nGhvNn8Pkn1MO6Hw== | 1131 | integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== |
1132 | |||
1133 | ansi-regex@^4.1.0: | ||
1134 | version "4.1.0" | ||
1135 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" | ||
1136 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== | ||
1137 | |||
1138 | ansi-regex@^5.0.0: | ||
1139 | version "5.0.0" | ||
1140 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" | ||
1141 | integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== | ||
1142 | |||
1143 | ansi-styles@^3.2.0, ansi-styles@^3.2.1: | ||
1144 | version "3.2.1" | ||
1145 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" | ||
1146 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== | ||
521 | dependencies: | 1147 | dependencies: |
522 | babel-helper-evaluate-path "^0.2.0" | 1148 | color-convert "^1.9.0" |
523 | 1149 | ||
524 | babel-plugin-minify-dead-code-elimination@^0.2.0: | 1150 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: |
525 | version "0.2.0" | 1151 | version "4.2.1" |
526 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.2.0.tgz#e8025ee10a1e5e4f202633a6928ce892c33747e3" | 1152 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" |
527 | integrity sha512-zE7y3pRyzA4zK5nBou0kTcwUTSQ/AiFrynt1cIEYN7vcO2gS9ZFZoI0aO9JYLUdct5fsC1vfB35408yrzTyVfg== | 1153 | integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== |
528 | dependencies: | 1154 | dependencies: |
529 | babel-helper-evaluate-path "^0.2.0" | 1155 | "@types/color-name" "^1.1.1" |
530 | babel-helper-mark-eval-scopes "^0.2.0" | 1156 | color-convert "^2.0.1" |
531 | babel-helper-remove-or-void "^0.2.0" | ||
532 | lodash.some "^4.6.0" | ||
533 | 1157 | ||
534 | babel-plugin-minify-flip-comparisons@^0.2.0: | 1158 | anymatch@^2.0.0: |
535 | version "0.2.0" | 1159 | version "2.0.0" |
536 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.2.0.tgz#0c9c8e93155c8f09dedad8118b634c259f709ef5" | 1160 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" |
537 | integrity sha512-QOqXSEmD/LhT3LpM1WCyzAGcQZYYKJF7oOHvS6QbpomHenydrV53DMdPX2mK01icBExKZcJAHF209wvDBa+CSg== | 1161 | integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== |
538 | dependencies: | 1162 | dependencies: |
539 | babel-helper-is-void-0 "^0.2.0" | 1163 | micromatch "^3.1.4" |
1164 | normalize-path "^2.1.1" | ||
540 | 1165 | ||
541 | babel-plugin-minify-guarded-expressions@^0.2.0: | 1166 | anymatch@~3.1.1: |
542 | version "0.2.0" | 1167 | version "3.1.1" |
543 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-guarded-expressions/-/babel-plugin-minify-guarded-expressions-0.2.0.tgz#8a8c950040fce3e258a12e6eb21eab94ad7235ab" | 1168 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" |
544 | integrity sha512-5+NSPdRQ9mnrHaA+zFj+D5OzmSiv90EX5zGH6cWQgR/OUqmCHSDqgTRPFvOctgpo8MJyO7Rt7ajs2UfLnlAwYg== | 1169 | integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== |
545 | dependencies: | 1170 | dependencies: |
546 | babel-helper-flip-expressions "^0.2.0" | 1171 | normalize-path "^3.0.0" |
1172 | picomatch "^2.0.4" | ||
547 | 1173 | ||
548 | babel-plugin-minify-infinity@^0.2.0: | 1174 | aproba@^1.1.1: |
549 | version "0.2.0" | 1175 | version "1.2.0" |
550 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.2.0.tgz#30960c615ddbc657c045bb00a1d8eb4af257cf03" | 1176 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" |
551 | integrity sha512-U694vrla1lN6vDHWGrR832t3a/A2eh+kyl019LxEE2+sS4VTydyOPRsAOIYAdJegWRA4cMX1lm9azAN0cLIr8g== | 1177 | integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== |
552 | 1178 | ||
553 | babel-plugin-minify-mangle-names@^0.2.0: | 1179 | argparse@^1.0.7: |
554 | version "0.2.0" | 1180 | version "1.0.10" |
555 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.2.0.tgz#719892297ff0106a6ec1a4b0fc062f1f8b6a8529" | 1181 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" |
556 | integrity sha512-Gixuak1/CO7VCdjn15/8Bxe/QsAtDG4zPbnsNoe1mIJGCIH/kcmSjFhMlGJtXDQZd6EKzeMfA5WmX9+jvGRefw== | 1182 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== |
557 | dependencies: | 1183 | dependencies: |
558 | babel-helper-mark-eval-scopes "^0.2.0" | 1184 | sprintf-js "~1.0.2" |
559 | 1185 | ||
560 | babel-plugin-minify-numeric-literals@^0.2.0: | 1186 | arr-diff@^4.0.0: |
561 | version "0.2.0" | 1187 | version "4.0.0" |
562 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.2.0.tgz#5746e851700167a380c05e93f289a7070459a0d1" | 1188 | resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" |
563 | integrity sha512-VcLpb+r1YS7+RIOXdRsFVLLqoh22177USpHf+JM/g1nZbzdqENmfd5v534MLAbRErhbz6SyK+NQViVzVtBxu8g== | 1189 | integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= |
564 | 1190 | ||
565 | babel-plugin-minify-replace@^0.2.0: | 1191 | arr-flatten@^1.1.0: |
566 | version "0.2.0" | 1192 | version "1.1.0" |
567 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-replace/-/babel-plugin-minify-replace-0.2.0.tgz#3c1f06bc4e6d3e301eacb763edc1be611efc39b0" | 1193 | resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" |
568 | integrity sha512-SEW6zoSVxh3OH6E1LCgyhhTWMnCv+JIRu5h5IlJDA11tU4ZeSF7uPQcO4vN/o52+FssRB26dmzJ/8D+z0QPg5Q== | 1194 | integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== |
569 | 1195 | ||
570 | babel-plugin-minify-simplify@^0.2.0: | 1196 | arr-union@^3.1.0: |
571 | version "0.2.0" | 1197 | version "3.1.0" |
572 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-simplify/-/babel-plugin-minify-simplify-0.2.0.tgz#21ceec4857100c5476d7cef121f351156e5c9bc0" | 1198 | resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" |
573 | integrity sha512-Mj3Mwy2zVosMfXDWXZrQH5/uMAyfJdmDQ1NVqit+ArbHC3LlXVzptuyC1JxTyai/wgFvjLaichm/7vSUshkWqw== | 1199 | integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= |
1200 | |||
1201 | array-includes@^3.1.1: | ||
1202 | version "3.1.1" | ||
1203 | resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" | ||
1204 | integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== | ||
574 | dependencies: | 1205 | dependencies: |
575 | babel-helper-flip-expressions "^0.2.0" | 1206 | define-properties "^1.1.3" |
576 | babel-helper-is-nodes-equiv "^0.0.1" | 1207 | es-abstract "^1.17.0" |
577 | babel-helper-to-multiple-sequence-expressions "^0.2.0" | 1208 | is-string "^1.0.5" |
578 | 1209 | ||
579 | babel-plugin-minify-type-constructors@^0.2.0: | 1210 | array-union@^2.1.0: |
580 | version "0.2.0" | 1211 | version "2.1.0" |
581 | resolved "https://registry.yarnpkg.com/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.2.0.tgz#7f3b6458be0863cfd59e9985bed6d134aa7a2e17" | 1212 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" |
582 | integrity sha512-NiOvvA9Pq6bki6nP4BayXwT5GZadw7DJFDDzHmkpnOQpENWe8RtHtKZM44MG1R6EQ5XxgbLdsdhswIzTkFlO5g== | 1213 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== |
583 | dependencies: | ||
584 | babel-helper-is-void-0 "^0.2.0" | ||
585 | |||
586 | babel-plugin-syntax-async-functions@^6.8.0: | ||
587 | version "6.13.0" | ||
588 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" | ||
589 | integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU= | ||
590 | |||
591 | babel-plugin-syntax-exponentiation-operator@^6.8.0: | ||
592 | version "6.13.0" | ||
593 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" | ||
594 | integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4= | ||
595 | |||
596 | babel-plugin-syntax-trailing-function-commas@^6.22.0: | ||
597 | version "6.22.0" | ||
598 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" | ||
599 | integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= | ||
600 | |||
601 | babel-plugin-transform-async-to-generator@^6.22.0: | ||
602 | version "6.24.1" | ||
603 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" | ||
604 | integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E= | ||
605 | dependencies: | ||
606 | babel-helper-remap-async-to-generator "^6.24.1" | ||
607 | babel-plugin-syntax-async-functions "^6.8.0" | ||
608 | babel-runtime "^6.22.0" | ||
609 | |||
610 | babel-plugin-transform-es2015-arrow-functions@^6.22.0: | ||
611 | version "6.22.0" | ||
612 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" | ||
613 | integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE= | ||
614 | dependencies: | ||
615 | babel-runtime "^6.22.0" | ||
616 | |||
617 | babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: | ||
618 | version "6.22.0" | ||
619 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" | ||
620 | integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE= | ||
621 | dependencies: | ||
622 | babel-runtime "^6.22.0" | ||
623 | |||
624 | babel-plugin-transform-es2015-block-scoping@^6.23.0: | ||
625 | version "6.26.0" | ||
626 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" | ||
627 | integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= | ||
628 | dependencies: | ||
629 | babel-runtime "^6.26.0" | ||
630 | babel-template "^6.26.0" | ||
631 | babel-traverse "^6.26.0" | ||
632 | babel-types "^6.26.0" | ||
633 | lodash "^4.17.4" | ||
634 | |||
635 | babel-plugin-transform-es2015-classes@^6.23.0: | ||
636 | version "6.24.1" | ||
637 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" | ||
638 | integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= | ||
639 | dependencies: | ||
640 | babel-helper-define-map "^6.24.1" | ||
641 | babel-helper-function-name "^6.24.1" | ||
642 | babel-helper-optimise-call-expression "^6.24.1" | ||
643 | babel-helper-replace-supers "^6.24.1" | ||
644 | babel-messages "^6.23.0" | ||
645 | babel-runtime "^6.22.0" | ||
646 | babel-template "^6.24.1" | ||
647 | babel-traverse "^6.24.1" | ||
648 | babel-types "^6.24.1" | ||
649 | |||
650 | babel-plugin-transform-es2015-computed-properties@^6.22.0: | ||
651 | version "6.24.1" | ||
652 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" | ||
653 | integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= | ||
654 | dependencies: | ||
655 | babel-runtime "^6.22.0" | ||
656 | babel-template "^6.24.1" | ||
657 | |||
658 | babel-plugin-transform-es2015-destructuring@^6.23.0: | ||
659 | version "6.23.0" | ||
660 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" | ||
661 | integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= | ||
662 | dependencies: | ||
663 | babel-runtime "^6.22.0" | ||
664 | |||
665 | babel-plugin-transform-es2015-duplicate-keys@^6.22.0: | ||
666 | version "6.24.1" | ||
667 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" | ||
668 | integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4= | ||
669 | dependencies: | ||
670 | babel-runtime "^6.22.0" | ||
671 | babel-types "^6.24.1" | ||
672 | |||
673 | babel-plugin-transform-es2015-for-of@^6.23.0: | ||
674 | version "6.23.0" | ||
675 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" | ||
676 | integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= | ||
677 | dependencies: | ||
678 | babel-runtime "^6.22.0" | ||
679 | |||
680 | babel-plugin-transform-es2015-function-name@^6.22.0: | ||
681 | version "6.24.1" | ||
682 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" | ||
683 | integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= | ||
684 | dependencies: | ||
685 | babel-helper-function-name "^6.24.1" | ||
686 | babel-runtime "^6.22.0" | ||
687 | babel-types "^6.24.1" | ||
688 | |||
689 | babel-plugin-transform-es2015-literals@^6.22.0: | ||
690 | version "6.22.0" | ||
691 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" | ||
692 | integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4= | ||
693 | dependencies: | ||
694 | babel-runtime "^6.22.0" | ||
695 | |||
696 | babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: | ||
697 | version "6.24.1" | ||
698 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" | ||
699 | integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ= | ||
700 | dependencies: | ||
701 | babel-plugin-transform-es2015-modules-commonjs "^6.24.1" | ||
702 | babel-runtime "^6.22.0" | ||
703 | babel-template "^6.24.1" | ||
704 | |||
705 | babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: | ||
706 | version "6.26.2" | ||
707 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" | ||
708 | integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== | ||
709 | dependencies: | ||
710 | babel-plugin-transform-strict-mode "^6.24.1" | ||
711 | babel-runtime "^6.26.0" | ||
712 | babel-template "^6.26.0" | ||
713 | babel-types "^6.26.0" | ||
714 | |||
715 | babel-plugin-transform-es2015-modules-systemjs@^6.23.0: | ||
716 | version "6.24.1" | ||
717 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" | ||
718 | integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= | ||
719 | dependencies: | ||
720 | babel-helper-hoist-variables "^6.24.1" | ||
721 | babel-runtime "^6.22.0" | ||
722 | babel-template "^6.24.1" | ||
723 | |||
724 | babel-plugin-transform-es2015-modules-umd@^6.23.0: | ||
725 | version "6.24.1" | ||
726 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" | ||
727 | integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg= | ||
728 | dependencies: | ||
729 | babel-plugin-transform-es2015-modules-amd "^6.24.1" | ||
730 | babel-runtime "^6.22.0" | ||
731 | babel-template "^6.24.1" | ||
732 | |||
733 | babel-plugin-transform-es2015-object-super@^6.22.0: | ||
734 | version "6.24.1" | ||
735 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" | ||
736 | integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40= | ||
737 | dependencies: | ||
738 | babel-helper-replace-supers "^6.24.1" | ||
739 | babel-runtime "^6.22.0" | ||
740 | |||
741 | babel-plugin-transform-es2015-parameters@^6.23.0: | ||
742 | version "6.24.1" | ||
743 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" | ||
744 | integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= | ||
745 | dependencies: | ||
746 | babel-helper-call-delegate "^6.24.1" | ||
747 | babel-helper-get-function-arity "^6.24.1" | ||
748 | babel-runtime "^6.22.0" | ||
749 | babel-template "^6.24.1" | ||
750 | babel-traverse "^6.24.1" | ||
751 | babel-types "^6.24.1" | ||
752 | |||
753 | babel-plugin-transform-es2015-shorthand-properties@^6.22.0: | ||
754 | version "6.24.1" | ||
755 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" | ||
756 | integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= | ||
757 | dependencies: | ||
758 | babel-runtime "^6.22.0" | ||
759 | babel-types "^6.24.1" | ||
760 | |||
761 | babel-plugin-transform-es2015-spread@^6.22.0: | ||
762 | version "6.22.0" | ||
763 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" | ||
764 | integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE= | ||
765 | dependencies: | ||
766 | babel-runtime "^6.22.0" | ||
767 | |||
768 | babel-plugin-transform-es2015-sticky-regex@^6.22.0: | ||
769 | version "6.24.1" | ||
770 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" | ||
771 | integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw= | ||
772 | dependencies: | ||
773 | babel-helper-regex "^6.24.1" | ||
774 | babel-runtime "^6.22.0" | ||
775 | babel-types "^6.24.1" | ||
776 | |||
777 | babel-plugin-transform-es2015-template-literals@^6.22.0: | ||
778 | version "6.22.0" | ||
779 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" | ||
780 | integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0= | ||
781 | dependencies: | ||
782 | babel-runtime "^6.22.0" | ||
783 | |||
784 | babel-plugin-transform-es2015-typeof-symbol@^6.23.0: | ||
785 | version "6.23.0" | ||
786 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" | ||
787 | integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I= | ||
788 | dependencies: | ||
789 | babel-runtime "^6.22.0" | ||
790 | |||
791 | babel-plugin-transform-es2015-unicode-regex@^6.22.0: | ||
792 | version "6.24.1" | ||
793 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" | ||
794 | integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek= | ||
795 | dependencies: | ||
796 | babel-helper-regex "^6.24.1" | ||
797 | babel-runtime "^6.22.0" | ||
798 | regexpu-core "^2.0.0" | ||
799 | |||
800 | babel-plugin-transform-exponentiation-operator@^6.22.0: | ||
801 | version "6.24.1" | ||
802 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" | ||
803 | integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4= | ||
804 | dependencies: | ||
805 | babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" | ||
806 | babel-plugin-syntax-exponentiation-operator "^6.8.0" | ||
807 | babel-runtime "^6.22.0" | ||
808 | |||
809 | babel-plugin-transform-inline-consecutive-adds@^0.2.0: | ||
810 | version "0.2.0" | ||
811 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.2.0.tgz#15dae78921057f4004f8eafd79e15ddc5f12f426" | ||
812 | integrity sha512-GlhOuLOQ28ua9prg0hT33HslCrEmz9xWXy9ZNZSACppCyRxxRW+haYtRgm7uYXCcd0q8ggCWD2pfWEJp5iiZfQ== | ||
813 | 1214 | ||
814 | babel-plugin-transform-member-expression-literals@^6.8.5: | 1215 | array-unique@^0.3.2: |
815 | version "6.9.4" | 1216 | version "0.3.2" |
816 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz#37039c9a0c3313a39495faac2ff3a6b5b9d038bf" | 1217 | resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" |
817 | integrity sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8= | 1218 | integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= |
818 | 1219 | ||
819 | babel-plugin-transform-merge-sibling-variables@^6.8.6: | 1220 | array.prototype.flat@^1.2.3: |
820 | version "6.9.4" | 1221 | version "1.2.3" |
821 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz#85b422fc3377b449c9d1cde44087203532401dae" | 1222 | resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" |
822 | integrity sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4= | 1223 | integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== |
1224 | dependencies: | ||
1225 | define-properties "^1.1.3" | ||
1226 | es-abstract "^1.17.0-next.1" | ||
823 | 1227 | ||
824 | babel-plugin-transform-minify-booleans@^6.8.3: | 1228 | arrify@^1.0.1: |
825 | version "6.9.4" | 1229 | version "1.0.1" |
826 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz#acbb3e56a3555dd23928e4b582d285162dd2b198" | 1230 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" |
827 | integrity sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg= | 1231 | integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= |
828 | 1232 | ||
829 | babel-plugin-transform-property-literals@^6.8.5: | 1233 | asn1.js@^5.2.0: |
830 | version "6.9.4" | 1234 | version "5.4.1" |
831 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz#98c1d21e255736573f93ece54459f6ce24985d39" | 1235 | resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" |
832 | integrity sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk= | 1236 | integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== |
833 | dependencies: | 1237 | dependencies: |
834 | esutils "^2.0.2" | 1238 | bn.js "^4.0.0" |
1239 | inherits "^2.0.1" | ||
1240 | minimalistic-assert "^1.0.0" | ||
1241 | safer-buffer "^2.1.0" | ||
835 | 1242 | ||
836 | babel-plugin-transform-regenerator@^6.22.0: | 1243 | assert@^1.1.1: |
837 | version "6.26.0" | 1244 | version "1.5.0" |
838 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" | 1245 | resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" |
839 | integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8= | 1246 | integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== |
840 | dependencies: | 1247 | dependencies: |
841 | regenerator-transform "^0.10.0" | 1248 | object-assign "^4.1.1" |
1249 | util "0.10.3" | ||
842 | 1250 | ||
843 | babel-plugin-transform-regexp-constructors@^0.2.0: | 1251 | assign-symbols@^1.0.0: |
844 | version "0.2.0" | 1252 | version "1.0.0" |
845 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.2.0.tgz#6aa5dd0acc515db4be929bbcec4ed4c946c534a3" | 1253 | resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" |
846 | integrity sha512-7IsQ6aQx6LAaOqy97/PthTf+5Nx9grZww3r6E62IdWe76Yr8KsuwVjxzqSPQvESJqTE3EMADQ9S0RtwWDGNG9Q== | 1254 | integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= |
847 | 1255 | ||
848 | babel-plugin-transform-remove-console@^6.8.5: | 1256 | astral-regex@^1.0.0: |
849 | version "6.9.4" | 1257 | version "1.0.0" |
850 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz#b980360c067384e24b357a588d807d3c83527780" | 1258 | resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" |
851 | integrity sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A= | 1259 | integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== |
852 | 1260 | ||
853 | babel-plugin-transform-remove-debugger@^6.8.5: | 1261 | astral-regex@^2.0.0: |
854 | version "6.9.4" | 1262 | version "2.0.0" |
855 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz#42b727631c97978e1eb2d199a7aec84a18339ef2" | 1263 | resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" |
856 | integrity sha1-QrcnYxyXl44estGZp67IShgznvI= | 1264 | integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== |
857 | 1265 | ||
858 | babel-plugin-transform-remove-undefined@^0.2.0: | 1266 | async-each@^1.0.1: |
859 | version "0.2.0" | 1267 | version "1.0.3" |
860 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-undefined/-/babel-plugin-transform-remove-undefined-0.2.0.tgz#94f052062054c707e8d094acefe79416b63452b1" | 1268 | resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" |
861 | integrity sha512-O8v57tPMHkp89kA4ZfQEYds/pzgvz/QYerBJjIuL5/Jc7RnvMVRA5gJY9zFKP7WayW8WOSBV4vh8Y8FJRio+ow== | 1269 | integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== |
862 | dependencies: | ||
863 | babel-helper-evaluate-path "^0.2.0" | ||
864 | 1270 | ||
865 | babel-plugin-transform-simplify-comparison-operators@^6.8.5: | 1271 | atob@^2.1.2: |
866 | version "6.9.4" | 1272 | version "2.1.2" |
867 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz#f62afe096cab0e1f68a2d753fdf283888471ceb9" | 1273 | resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" |
868 | integrity sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk= | 1274 | integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== |
869 | 1275 | ||
870 | babel-plugin-transform-strict-mode@^6.24.1: | 1276 | autoprefixer@^9.8.6: |
871 | version "6.24.1" | 1277 | version "9.8.6" |
872 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" | 1278 | resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" |
873 | integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g= | 1279 | integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== |
874 | dependencies: | 1280 | dependencies: |
875 | babel-runtime "^6.22.0" | 1281 | browserslist "^4.12.0" |
876 | babel-types "^6.24.1" | 1282 | caniuse-lite "^1.0.30001109" |
877 | 1283 | colorette "^1.2.1" | |
878 | babel-plugin-transform-undefined-to-void@^6.8.3: | 1284 | normalize-range "^0.1.2" |
879 | version "6.9.4" | 1285 | num2fraction "^1.2.2" |
880 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz#be241ca81404030678b748717322b89d0c8fe280" | 1286 | postcss "^7.0.32" |
881 | integrity sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA= | 1287 | postcss-value-parser "^4.1.0" |
882 | |||
883 | babel-preset-env@^1.6.1: | ||
884 | version "1.7.0" | ||
885 | resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" | ||
886 | integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== | ||
887 | dependencies: | ||
888 | babel-plugin-check-es2015-constants "^6.22.0" | ||
889 | babel-plugin-syntax-trailing-function-commas "^6.22.0" | ||
890 | babel-plugin-transform-async-to-generator "^6.22.0" | ||
891 | babel-plugin-transform-es2015-arrow-functions "^6.22.0" | ||
892 | babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" | ||
893 | babel-plugin-transform-es2015-block-scoping "^6.23.0" | ||
894 | babel-plugin-transform-es2015-classes "^6.23.0" | ||
895 | babel-plugin-transform-es2015-computed-properties "^6.22.0" | ||
896 | babel-plugin-transform-es2015-destructuring "^6.23.0" | ||
897 | babel-plugin-transform-es2015-duplicate-keys "^6.22.0" | ||
898 | babel-plugin-transform-es2015-for-of "^6.23.0" | ||
899 | babel-plugin-transform-es2015-function-name "^6.22.0" | ||
900 | babel-plugin-transform-es2015-literals "^6.22.0" | ||
901 | babel-plugin-transform-es2015-modules-amd "^6.22.0" | ||
902 | babel-plugin-transform-es2015-modules-commonjs "^6.23.0" | ||
903 | babel-plugin-transform-es2015-modules-systemjs "^6.23.0" | ||
904 | babel-plugin-transform-es2015-modules-umd "^6.23.0" | ||
905 | babel-plugin-transform-es2015-object-super "^6.22.0" | ||
906 | babel-plugin-transform-es2015-parameters "^6.23.0" | ||
907 | babel-plugin-transform-es2015-shorthand-properties "^6.22.0" | ||
908 | babel-plugin-transform-es2015-spread "^6.22.0" | ||
909 | babel-plugin-transform-es2015-sticky-regex "^6.22.0" | ||
910 | babel-plugin-transform-es2015-template-literals "^6.22.0" | ||
911 | babel-plugin-transform-es2015-typeof-symbol "^6.23.0" | ||
912 | babel-plugin-transform-es2015-unicode-regex "^6.22.0" | ||
913 | babel-plugin-transform-exponentiation-operator "^6.22.0" | ||
914 | babel-plugin-transform-regenerator "^6.22.0" | ||
915 | browserslist "^3.2.6" | ||
916 | invariant "^2.2.2" | ||
917 | semver "^5.3.0" | ||
918 | 1288 | ||
919 | babel-preset-minify@^0.2.0: | 1289 | awesomplete@^1.1.2: |
920 | version "0.2.0" | 1290 | version "1.1.5" |
921 | resolved "https://registry.yarnpkg.com/babel-preset-minify/-/babel-preset-minify-0.2.0.tgz#006566552d9b83834472273f306c0131062a0acc" | 1291 | resolved "https://registry.yarnpkg.com/awesomplete/-/awesomplete-1.1.5.tgz#1b2b5dd106d3955595619c03da472a1dc0faf0af" |
922 | integrity sha512-mR8Q44RmMzm18bM2Lqd9uiPopzk5GDCtVuquNbLFmX6lOKnqWoenaNBxnWW0UhBFC75lEHTIgNGCbnsRI0pJVw== | 1292 | integrity sha512-UFw1mPW8NaSECDSTC36HbAOTpF9JK2wBUJcNn4MSvlNtK7SZ9N72gB+ajHtA6D1abYXRcszZnBA4nHBwvFwzHw== |
923 | dependencies: | ||
924 | babel-plugin-minify-builtins "^0.2.0" | ||
925 | babel-plugin-minify-constant-folding "^0.2.0" | ||
926 | babel-plugin-minify-dead-code-elimination "^0.2.0" | ||
927 | babel-plugin-minify-flip-comparisons "^0.2.0" | ||
928 | babel-plugin-minify-guarded-expressions "^0.2.0" | ||
929 | babel-plugin-minify-infinity "^0.2.0" | ||
930 | babel-plugin-minify-mangle-names "^0.2.0" | ||
931 | babel-plugin-minify-numeric-literals "^0.2.0" | ||
932 | babel-plugin-minify-replace "^0.2.0" | ||
933 | babel-plugin-minify-simplify "^0.2.0" | ||
934 | babel-plugin-minify-type-constructors "^0.2.0" | ||
935 | babel-plugin-transform-inline-consecutive-adds "^0.2.0" | ||
936 | babel-plugin-transform-member-expression-literals "^6.8.5" | ||
937 | babel-plugin-transform-merge-sibling-variables "^6.8.6" | ||
938 | babel-plugin-transform-minify-booleans "^6.8.3" | ||
939 | babel-plugin-transform-property-literals "^6.8.5" | ||
940 | babel-plugin-transform-regexp-constructors "^0.2.0" | ||
941 | babel-plugin-transform-remove-console "^6.8.5" | ||
942 | babel-plugin-transform-remove-debugger "^6.8.5" | ||
943 | babel-plugin-transform-remove-undefined "^0.2.0" | ||
944 | babel-plugin-transform-simplify-comparison-operators "^6.8.5" | ||
945 | babel-plugin-transform-undefined-to-void "^6.8.3" | ||
946 | lodash.isplainobject "^4.0.6" | ||
947 | |||
948 | babel-register@^6.26.0: | ||
949 | version "6.26.0" | ||
950 | resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" | ||
951 | integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= | ||
952 | dependencies: | ||
953 | babel-core "^6.26.0" | ||
954 | babel-runtime "^6.26.0" | ||
955 | core-js "^2.5.0" | ||
956 | home-or-tmp "^2.0.0" | ||
957 | lodash "^4.17.4" | ||
958 | mkdirp "^0.5.1" | ||
959 | source-map-support "^0.4.15" | ||
960 | |||
961 | babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: | ||
962 | version "6.26.0" | ||
963 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" | ||
964 | integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= | ||
965 | dependencies: | ||
966 | core-js "^2.4.0" | ||
967 | regenerator-runtime "^0.11.0" | ||
968 | |||
969 | babel-template@^6.24.1, babel-template@^6.26.0: | ||
970 | version "6.26.0" | ||
971 | resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" | ||
972 | integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= | ||
973 | dependencies: | ||
974 | babel-runtime "^6.26.0" | ||
975 | babel-traverse "^6.26.0" | ||
976 | babel-types "^6.26.0" | ||
977 | babylon "^6.18.0" | ||
978 | lodash "^4.17.4" | ||
979 | |||
980 | babel-traverse@^6.24.1, babel-traverse@^6.26.0: | ||
981 | version "6.26.0" | ||
982 | resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" | ||
983 | integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= | ||
984 | dependencies: | ||
985 | babel-code-frame "^6.26.0" | ||
986 | babel-messages "^6.23.0" | ||
987 | babel-runtime "^6.26.0" | ||
988 | babel-types "^6.26.0" | ||
989 | babylon "^6.18.0" | ||
990 | debug "^2.6.8" | ||
991 | globals "^9.18.0" | ||
992 | invariant "^2.2.2" | ||
993 | lodash "^4.17.4" | ||
994 | 1293 | ||
995 | babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: | 1294 | babel-loader@^8.1.0: |
996 | version "6.26.0" | 1295 | version "8.1.0" |
997 | resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" | 1296 | resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" |
998 | integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= | 1297 | integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== |
999 | dependencies: | 1298 | dependencies: |
1000 | babel-runtime "^6.26.0" | 1299 | find-cache-dir "^2.1.0" |
1001 | esutils "^2.0.2" | 1300 | loader-utils "^1.4.0" |
1002 | lodash "^4.17.4" | 1301 | mkdirp "^0.5.3" |
1003 | to-fast-properties "^1.0.3" | 1302 | pify "^4.0.1" |
1303 | schema-utils "^2.6.5" | ||
1004 | 1304 | ||
1005 | babylon@^6.18.0: | 1305 | babel-plugin-dynamic-import-node@^2.3.3: |
1006 | version "6.18.0" | 1306 | version "2.3.3" |
1007 | resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" | 1307 | resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" |
1008 | integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== | 1308 | integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== |
1309 | dependencies: | ||
1310 | object.assign "^4.1.0" | ||
1009 | 1311 | ||
1010 | balanced-match@^0.4.2: | 1312 | bail@^1.0.0: |
1011 | version "0.4.2" | 1313 | version "1.0.5" |
1012 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" | 1314 | resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" |
1013 | integrity sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg= | 1315 | integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== |
1014 | 1316 | ||
1015 | balanced-match@^1.0.0: | 1317 | balanced-match@^1.0.0: |
1016 | version "1.0.0" | 1318 | version "1.0.0" |
@@ -1018,9 +1320,9 @@ balanced-match@^1.0.0: | |||
1018 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= | 1320 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= |
1019 | 1321 | ||
1020 | base64-js@^1.0.2: | 1322 | base64-js@^1.0.2: |
1021 | version "1.3.0" | 1323 | version "1.3.1" |
1022 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" | 1324 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" |
1023 | integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== | 1325 | integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== |
1024 | 1326 | ||
1025 | base@^0.11.1: | 1327 | base@^0.11.1: |
1026 | version "0.11.2" | 1328 | version "0.11.2" |
@@ -1035,13 +1337,6 @@ base@^0.11.1: | |||
1035 | mixin-deep "^1.2.0" | 1337 | mixin-deep "^1.2.0" |
1036 | pascalcase "^0.1.1" | 1338 | pascalcase "^0.1.1" |
1037 | 1339 | ||
1038 | bcrypt-pbkdf@^1.0.0: | ||
1039 | version "1.0.2" | ||
1040 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" | ||
1041 | integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= | ||
1042 | dependencies: | ||
1043 | tweetnacl "^0.14.3" | ||
1044 | |||
1045 | big.js@^5.2.2: | 1340 | big.js@^5.2.2: |
1046 | version "5.2.2" | 1341 | version "5.2.2" |
1047 | resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" | 1342 | resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" |
@@ -1052,22 +1347,37 @@ binary-extensions@^1.0.0: | |||
1052 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" | 1347 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" |
1053 | integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== | 1348 | integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== |
1054 | 1349 | ||
1350 | binary-extensions@^2.0.0: | ||
1351 | version "2.1.0" | ||
1352 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" | ||
1353 | integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== | ||
1354 | |||
1355 | bindings@^1.5.0: | ||
1356 | version "1.5.0" | ||
1357 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" | ||
1358 | integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== | ||
1359 | dependencies: | ||
1360 | file-uri-to-path "1.0.0" | ||
1361 | |||
1055 | blazy@^1.8.2: | 1362 | blazy@^1.8.2: |
1056 | version "1.8.2" | 1363 | version "1.8.2" |
1057 | resolved "https://registry.yarnpkg.com/blazy/-/blazy-1.8.2.tgz#50dfd638baaf9003efd6eb3a836aca54184ab6da" | 1364 | resolved "https://registry.yarnpkg.com/blazy/-/blazy-1.8.2.tgz#50dfd638baaf9003efd6eb3a836aca54184ab6da" |
1058 | integrity sha1-UN/WOLqvkAPv1us6g2rKVBhKtto= | 1365 | integrity sha1-UN/WOLqvkAPv1us6g2rKVBhKtto= |
1059 | 1366 | ||
1060 | block-stream@*: | 1367 | bluebird@^3.5.5: |
1061 | version "0.0.9" | 1368 | version "3.7.2" |
1062 | resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" | 1369 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" |
1063 | integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= | 1370 | integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== |
1064 | dependencies: | 1371 | |
1065 | inherits "~2.0.0" | 1372 | bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0: |
1373 | version "4.11.9" | ||
1374 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" | ||
1375 | integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== | ||
1066 | 1376 | ||
1067 | bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: | 1377 | bn.js@^5.1.1: |
1068 | version "4.11.8" | 1378 | version "5.1.3" |
1069 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" | 1379 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" |
1070 | integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== | 1380 | integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== |
1071 | 1381 | ||
1072 | brace-expansion@^1.1.7: | 1382 | brace-expansion@^1.1.7: |
1073 | version "1.1.11" | 1383 | version "1.1.11" |
@@ -1093,6 +1403,13 @@ braces@^2.3.1, braces@^2.3.2: | |||
1093 | split-string "^3.0.2" | 1403 | split-string "^3.0.2" |
1094 | to-regex "^3.0.1" | 1404 | to-regex "^3.0.1" |
1095 | 1405 | ||
1406 | braces@^3.0.1, braces@~3.0.2: | ||
1407 | version "3.0.2" | ||
1408 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" | ||
1409 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== | ||
1410 | dependencies: | ||
1411 | fill-range "^7.0.1" | ||
1412 | |||
1096 | brorand@^1.0.1: | 1413 | brorand@^1.0.1: |
1097 | version "1.1.0" | 1414 | version "1.1.0" |
1098 | resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" | 1415 | resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" |
@@ -1129,7 +1446,7 @@ browserify-des@^1.0.0: | |||
1129 | inherits "^2.0.1" | 1446 | inherits "^2.0.1" |
1130 | safe-buffer "^5.1.2" | 1447 | safe-buffer "^5.1.2" |
1131 | 1448 | ||
1132 | browserify-rsa@^4.0.0: | 1449 | browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: |
1133 | version "4.0.1" | 1450 | version "4.0.1" |
1134 | resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" | 1451 | resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" |
1135 | integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= | 1452 | integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= |
@@ -1138,17 +1455,19 @@ browserify-rsa@^4.0.0: | |||
1138 | randombytes "^2.0.1" | 1455 | randombytes "^2.0.1" |
1139 | 1456 | ||
1140 | browserify-sign@^4.0.0: | 1457 | browserify-sign@^4.0.0: |
1141 | version "4.0.4" | 1458 | version "4.2.1" |
1142 | resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" | 1459 | resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" |
1143 | integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= | 1460 | integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== |
1144 | dependencies: | 1461 | dependencies: |
1145 | bn.js "^4.1.1" | 1462 | bn.js "^5.1.1" |
1146 | browserify-rsa "^4.0.0" | 1463 | browserify-rsa "^4.0.1" |
1147 | create-hash "^1.1.0" | 1464 | create-hash "^1.2.0" |
1148 | create-hmac "^1.1.2" | 1465 | create-hmac "^1.1.7" |
1149 | elliptic "^6.0.0" | 1466 | elliptic "^6.5.3" |
1150 | inherits "^2.0.1" | 1467 | inherits "^2.0.4" |
1151 | parse-asn1 "^5.0.0" | 1468 | parse-asn1 "^5.1.5" |
1469 | readable-stream "^3.6.0" | ||
1470 | safe-buffer "^5.2.0" | ||
1152 | 1471 | ||
1153 | browserify-zlib@^0.2.0: | 1472 | browserify-zlib@^0.2.0: |
1154 | version "0.2.0" | 1473 | version "0.2.0" |
@@ -1157,21 +1476,15 @@ browserify-zlib@^0.2.0: | |||
1157 | dependencies: | 1476 | dependencies: |
1158 | pako "~1.0.5" | 1477 | pako "~1.0.5" |
1159 | 1478 | ||
1160 | browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: | 1479 | browserslist@^4.12.0, browserslist@^4.8.5: |
1161 | version "1.7.7" | 1480 | version "4.14.3" |
1162 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" | 1481 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.3.tgz#381f9e7f13794b2eb17e1761b4f118e8ae665a53" |
1163 | integrity sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk= | 1482 | integrity sha512-GcZPC5+YqyPO4SFnz48/B0YaCwS47Q9iPChRGi6t7HhflKBcINzFrJvRfC+jp30sRMKxF+d4EHGs27Z0XP1NaQ== |
1164 | dependencies: | 1483 | dependencies: |
1165 | caniuse-db "^1.0.30000639" | 1484 | caniuse-lite "^1.0.30001131" |
1166 | electron-to-chromium "^1.2.7" | 1485 | electron-to-chromium "^1.3.570" |
1167 | 1486 | escalade "^3.1.0" | |
1168 | browserslist@^3.2.6: | 1487 | node-releases "^1.1.61" |
1169 | version "3.2.8" | ||
1170 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" | ||
1171 | integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== | ||
1172 | dependencies: | ||
1173 | caniuse-lite "^1.0.30000844" | ||
1174 | electron-to-chromium "^1.3.47" | ||
1175 | 1488 | ||
1176 | buffer-from@^1.0.0: | 1489 | buffer-from@^1.0.0: |
1177 | version "1.1.1" | 1490 | version "1.1.1" |
@@ -1184,9 +1497,9 @@ buffer-xor@^1.0.3: | |||
1184 | integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= | 1497 | integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= |
1185 | 1498 | ||
1186 | buffer@^4.3.0: | 1499 | buffer@^4.3.0: |
1187 | version "4.9.1" | 1500 | version "4.9.2" |
1188 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" | 1501 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" |
1189 | integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= | 1502 | integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== |
1190 | dependencies: | 1503 | dependencies: |
1191 | base64-js "^1.0.2" | 1504 | base64-js "^1.0.2" |
1192 | ieee754 "^1.1.4" | 1505 | ieee754 "^1.1.4" |
@@ -1197,6 +1510,50 @@ builtin-status-codes@^3.0.0: | |||
1197 | resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" | 1510 | resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" |
1198 | integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= | 1511 | integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= |
1199 | 1512 | ||
1513 | cacache@^12.0.2: | ||
1514 | version "12.0.4" | ||
1515 | resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" | ||
1516 | integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== | ||
1517 | dependencies: | ||
1518 | bluebird "^3.5.5" | ||
1519 | chownr "^1.1.1" | ||
1520 | figgy-pudding "^3.5.1" | ||
1521 | glob "^7.1.4" | ||
1522 | graceful-fs "^4.1.15" | ||
1523 | infer-owner "^1.0.3" | ||
1524 | lru-cache "^5.1.1" | ||
1525 | mississippi "^3.0.0" | ||
1526 | mkdirp "^0.5.1" | ||
1527 | move-concurrently "^1.0.1" | ||
1528 | promise-inflight "^1.0.1" | ||
1529 | rimraf "^2.6.3" | ||
1530 | ssri "^6.0.1" | ||
1531 | unique-filename "^1.1.1" | ||
1532 | y18n "^4.0.0" | ||
1533 | |||
1534 | cacache@^15.0.5: | ||
1535 | version "15.0.5" | ||
1536 | resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0" | ||
1537 | integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A== | ||
1538 | dependencies: | ||
1539 | "@npmcli/move-file" "^1.0.1" | ||
1540 | chownr "^2.0.0" | ||
1541 | fs-minipass "^2.0.0" | ||
1542 | glob "^7.1.4" | ||
1543 | infer-owner "^1.0.4" | ||
1544 | lru-cache "^6.0.0" | ||
1545 | minipass "^3.1.1" | ||
1546 | minipass-collect "^1.0.2" | ||
1547 | minipass-flush "^1.0.5" | ||
1548 | minipass-pipeline "^1.2.2" | ||
1549 | mkdirp "^1.0.3" | ||
1550 | p-map "^4.0.0" | ||
1551 | promise-inflight "^1.0.1" | ||
1552 | rimraf "^3.0.2" | ||
1553 | ssri "^8.0.0" | ||
1554 | tar "^6.0.2" | ||
1555 | unique-filename "^1.1.1" | ||
1556 | |||
1200 | cache-base@^1.0.1: | 1557 | cache-base@^1.0.1: |
1201 | version "1.0.1" | 1558 | version "1.0.1" |
1202 | resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" | 1559 | resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" |
@@ -1212,91 +1569,41 @@ cache-base@^1.0.1: | |||
1212 | union-value "^1.0.0" | 1569 | union-value "^1.0.0" |
1213 | unset-value "^1.0.0" | 1570 | unset-value "^1.0.0" |
1214 | 1571 | ||
1215 | caller-path@^0.1.0: | 1572 | callsites@^3.0.0: |
1216 | version "0.1.0" | 1573 | version "3.1.0" |
1217 | resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" | 1574 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" |
1218 | integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= | 1575 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== |
1219 | dependencies: | ||
1220 | callsites "^0.2.0" | ||
1221 | |||
1222 | callsites@^0.2.0: | ||
1223 | version "0.2.0" | ||
1224 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" | ||
1225 | integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= | ||
1226 | 1576 | ||
1227 | camelcase-keys@^2.0.0: | 1577 | camelcase-keys@^6.2.2: |
1228 | version "2.1.0" | 1578 | version "6.2.2" |
1229 | resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" | 1579 | resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" |
1230 | integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= | 1580 | integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== |
1231 | dependencies: | 1581 | dependencies: |
1232 | camelcase "^2.0.0" | 1582 | camelcase "^5.3.1" |
1233 | map-obj "^1.0.0" | 1583 | map-obj "^4.0.0" |
1234 | 1584 | quick-lru "^4.0.1" | |
1235 | camelcase@^1.0.2: | ||
1236 | version "1.2.1" | ||
1237 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" | ||
1238 | integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= | ||
1239 | 1585 | ||
1240 | camelcase@^2.0.0: | 1586 | camelcase@^5.0.0, camelcase@^5.3.1: |
1241 | version "2.1.1" | 1587 | version "5.3.1" |
1242 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" | 1588 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" |
1243 | integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= | 1589 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== |
1244 | 1590 | ||
1245 | camelcase@^3.0.0: | 1591 | camelcase@^6.0.0: |
1246 | version "3.0.0" | 1592 | version "6.0.0" |
1247 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" | 1593 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e" |
1248 | integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= | 1594 | integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== |
1249 | 1595 | ||
1250 | camelcase@^4.1.0: | 1596 | caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001131: |
1251 | version "4.1.0" | 1597 | version "1.0.30001135" |
1252 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" | 1598 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001135.tgz#995b1eb94404a3c9a0d7600c113c9bb27f2cd8aa" |
1253 | integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= | 1599 | integrity sha512-ziNcheTGTHlu9g34EVoHQdIu5g4foc8EsxMGC7Xkokmvw0dqNtX8BS8RgCgFBaAiSp2IdjvBxNdh0ssib28eVQ== |
1254 | 1600 | ||
1255 | caniuse-api@^1.5.2: | 1601 | ccount@^1.0.0: |
1256 | version "1.6.1" | 1602 | version "1.0.5" |
1257 | resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" | 1603 | resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" |
1258 | integrity sha1-tTTnxzTE+B7F++isoq0kNUuWLGw= | 1604 | integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== |
1259 | dependencies: | ||
1260 | browserslist "^1.3.6" | ||
1261 | caniuse-db "^1.0.30000529" | ||
1262 | lodash.memoize "^4.1.2" | ||
1263 | lodash.uniq "^4.5.0" | ||
1264 | |||
1265 | caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: | ||
1266 | version "1.0.30000969" | ||
1267 | resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000969.tgz#e6aeca9b1bac88865990913a0b041f587180cd59" | ||
1268 | integrity sha512-ttrmwpIXvEL/kg0JSg6Q+xEbMxAEcjZOOgZMGPcMe5JMYgi20Nvs9bqMRGfyIOQtd1jYa6yRWODIR6apj3xPQw== | ||
1269 | |||
1270 | caniuse-lite@^1.0.30000844: | ||
1271 | version "1.0.30000969" | ||
1272 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000969.tgz#7664f571f2072657bde70b00a1fc1ba41f1942a9" | ||
1273 | integrity sha512-Kus0yxkoAJgVc0bax7S4gLSlFifCa7MnSZL9p9VuS/HIKEL4seaqh28KIQAAO50cD/rJ5CiJkJFapkdDAlhFxQ== | ||
1274 | |||
1275 | caseless@~0.12.0: | ||
1276 | version "0.12.0" | ||
1277 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" | ||
1278 | integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= | ||
1279 | |||
1280 | center-align@^0.1.1: | ||
1281 | version "0.1.3" | ||
1282 | resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" | ||
1283 | integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60= | ||
1284 | dependencies: | ||
1285 | align-text "^0.1.3" | ||
1286 | lazy-cache "^1.0.3" | ||
1287 | |||
1288 | chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: | ||
1289 | version "1.1.3" | ||
1290 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" | ||
1291 | integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= | ||
1292 | dependencies: | ||
1293 | ansi-styles "^2.2.1" | ||
1294 | escape-string-regexp "^1.0.2" | ||
1295 | has-ansi "^2.0.0" | ||
1296 | strip-ansi "^3.0.0" | ||
1297 | supports-color "^2.0.0" | ||
1298 | 1605 | ||
1299 | chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1: | 1606 | chalk@^2.0.0, chalk@^2.4.2: |
1300 | version "2.4.2" | 1607 | version "2.4.2" |
1301 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" | 1608 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" |
1302 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== | 1609 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== |
@@ -1305,15 +1612,53 @@ chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1: | |||
1305 | escape-string-regexp "^1.0.5" | 1612 | escape-string-regexp "^1.0.5" |
1306 | supports-color "^5.3.0" | 1613 | supports-color "^5.3.0" |
1307 | 1614 | ||
1308 | chardet@^0.4.0: | 1615 | chalk@^4.0.0, chalk@^4.1.0: |
1309 | version "0.4.2" | 1616 | version "4.1.0" |
1310 | resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" | 1617 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" |
1311 | integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= | 1618 | integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== |
1619 | dependencies: | ||
1620 | ansi-styles "^4.1.0" | ||
1621 | supports-color "^7.1.0" | ||
1622 | |||
1623 | character-entities-html4@^1.0.0: | ||
1624 | version "1.1.4" | ||
1625 | resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" | ||
1626 | integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== | ||
1627 | |||
1628 | character-entities-legacy@^1.0.0: | ||
1629 | version "1.1.4" | ||
1630 | resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" | ||
1631 | integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== | ||
1312 | 1632 | ||
1313 | chokidar@^2.0.2: | 1633 | character-entities@^1.0.0: |
1314 | version "2.1.6" | 1634 | version "1.2.4" |
1315 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5" | 1635 | resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" |
1316 | integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g== | 1636 | integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== |
1637 | |||
1638 | character-reference-invalid@^1.0.0: | ||
1639 | version "1.1.4" | ||
1640 | resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" | ||
1641 | integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== | ||
1642 | |||
1643 | "chokidar@>=2.0.0 <4.0.0", chokidar@^3.4.1: | ||
1644 | version "3.4.2" | ||
1645 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" | ||
1646 | integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== | ||
1647 | dependencies: | ||
1648 | anymatch "~3.1.1" | ||
1649 | braces "~3.0.2" | ||
1650 | glob-parent "~5.1.0" | ||
1651 | is-binary-path "~2.1.0" | ||
1652 | is-glob "~4.0.1" | ||
1653 | normalize-path "~3.0.0" | ||
1654 | readdirp "~3.4.0" | ||
1655 | optionalDependencies: | ||
1656 | fsevents "~2.1.2" | ||
1657 | |||
1658 | chokidar@^2.1.8: | ||
1659 | version "2.1.8" | ||
1660 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" | ||
1661 | integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== | ||
1317 | dependencies: | 1662 | dependencies: |
1318 | anymatch "^2.0.0" | 1663 | anymatch "^2.0.0" |
1319 | async-each "^1.0.1" | 1664 | async-each "^1.0.1" |
@@ -1330,9 +1675,21 @@ chokidar@^2.0.2: | |||
1330 | fsevents "^1.2.7" | 1675 | fsevents "^1.2.7" |
1331 | 1676 | ||
1332 | chownr@^1.1.1: | 1677 | chownr@^1.1.1: |
1333 | version "1.1.1" | 1678 | version "1.1.4" |
1334 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" | 1679 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" |
1335 | integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== | 1680 | integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== |
1681 | |||
1682 | chownr@^2.0.0: | ||
1683 | version "2.0.0" | ||
1684 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" | ||
1685 | integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== | ||
1686 | |||
1687 | chrome-trace-event@^1.0.2: | ||
1688 | version "1.0.2" | ||
1689 | resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" | ||
1690 | integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== | ||
1691 | dependencies: | ||
1692 | tslib "^1.9.0" | ||
1336 | 1693 | ||
1337 | cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: | 1694 | cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: |
1338 | version "1.0.4" | 1695 | version "1.0.4" |
@@ -1342,18 +1699,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: | |||
1342 | inherits "^2.0.1" | 1699 | inherits "^2.0.1" |
1343 | safe-buffer "^5.0.1" | 1700 | safe-buffer "^5.0.1" |
1344 | 1701 | ||
1345 | circular-json@^0.3.1: | ||
1346 | version "0.3.3" | ||
1347 | resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" | ||
1348 | integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== | ||
1349 | |||
1350 | clap@^1.0.9: | ||
1351 | version "1.2.3" | ||
1352 | resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" | ||
1353 | integrity sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA== | ||
1354 | dependencies: | ||
1355 | chalk "^1.1.3" | ||
1356 | |||
1357 | class-utils@^0.3.5: | 1702 | class-utils@^0.3.5: |
1358 | version "0.3.6" | 1703 | version "0.3.6" |
1359 | resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" | 1704 | resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" |
@@ -1364,74 +1709,31 @@ class-utils@^0.3.5: | |||
1364 | isobject "^3.0.0" | 1709 | isobject "^3.0.0" |
1365 | static-extend "^0.1.1" | 1710 | static-extend "^0.1.1" |
1366 | 1711 | ||
1367 | cli-cursor@^1.0.1: | 1712 | clean-stack@^2.0.0: |
1368 | version "1.0.2" | ||
1369 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" | ||
1370 | integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= | ||
1371 | dependencies: | ||
1372 | restore-cursor "^1.0.1" | ||
1373 | |||
1374 | cli-cursor@^2.1.0: | ||
1375 | version "2.1.0" | ||
1376 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" | ||
1377 | integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= | ||
1378 | dependencies: | ||
1379 | restore-cursor "^2.0.0" | ||
1380 | |||
1381 | cli-width@^2.0.0: | ||
1382 | version "2.2.0" | 1713 | version "2.2.0" |
1383 | resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" | 1714 | resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" |
1384 | integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= | 1715 | integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== |
1385 | |||
1386 | cliui@^2.1.0: | ||
1387 | version "2.1.0" | ||
1388 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" | ||
1389 | integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= | ||
1390 | dependencies: | ||
1391 | center-align "^0.1.1" | ||
1392 | right-align "^0.1.1" | ||
1393 | wordwrap "0.0.2" | ||
1394 | |||
1395 | cliui@^3.2.0: | ||
1396 | version "3.2.0" | ||
1397 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" | ||
1398 | integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= | ||
1399 | dependencies: | ||
1400 | string-width "^1.0.1" | ||
1401 | strip-ansi "^3.0.1" | ||
1402 | wrap-ansi "^2.0.0" | ||
1403 | 1716 | ||
1404 | clone-deep@^2.0.1: | 1717 | cliui@^5.0.0: |
1405 | version "2.0.2" | 1718 | version "5.0.0" |
1406 | resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" | 1719 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" |
1407 | integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ== | 1720 | integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== |
1408 | dependencies: | 1721 | dependencies: |
1409 | for-own "^1.0.0" | 1722 | string-width "^3.1.0" |
1410 | is-plain-object "^2.0.4" | 1723 | strip-ansi "^5.2.0" |
1411 | kind-of "^6.0.0" | 1724 | wrap-ansi "^5.1.0" |
1412 | shallow-clone "^1.0.0" | ||
1413 | |||
1414 | clone@^1.0.2: | ||
1415 | version "1.0.4" | ||
1416 | resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" | ||
1417 | integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= | ||
1418 | |||
1419 | co@^4.6.0: | ||
1420 | version "4.6.0" | ||
1421 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" | ||
1422 | integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= | ||
1423 | 1725 | ||
1424 | coa@~1.0.1: | 1726 | clone-regexp@^2.1.0: |
1425 | version "1.0.4" | 1727 | version "2.2.0" |
1426 | resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" | 1728 | resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" |
1427 | integrity sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0= | 1729 | integrity sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q== |
1428 | dependencies: | 1730 | dependencies: |
1429 | q "^1.1.2" | 1731 | is-regexp "^2.0.0" |
1430 | 1732 | ||
1431 | code-point-at@^1.0.0: | 1733 | collapse-white-space@^1.0.2: |
1432 | version "1.1.0" | 1734 | version "1.0.6" |
1433 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" | 1735 | resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" |
1434 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= | 1736 | integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== |
1435 | 1737 | ||
1436 | collection-visit@^1.0.0: | 1738 | collection-visit@^1.0.0: |
1437 | version "1.0.0" | 1739 | version "1.0.0" |
@@ -1441,64 +1743,39 @@ collection-visit@^1.0.0: | |||
1441 | map-visit "^1.0.0" | 1743 | map-visit "^1.0.0" |
1442 | object-visit "^1.0.0" | 1744 | object-visit "^1.0.0" |
1443 | 1745 | ||
1444 | color-convert@^1.3.0, color-convert@^1.9.0: | 1746 | color-convert@^1.9.0: |
1445 | version "1.9.3" | 1747 | version "1.9.3" |
1446 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" | 1748 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" |
1447 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== | 1749 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== |
1448 | dependencies: | 1750 | dependencies: |
1449 | color-name "1.1.3" | 1751 | color-name "1.1.3" |
1450 | 1752 | ||
1753 | color-convert@^2.0.1: | ||
1754 | version "2.0.1" | ||
1755 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" | ||
1756 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== | ||
1757 | dependencies: | ||
1758 | color-name "~1.1.4" | ||
1759 | |||
1451 | color-name@1.1.3: | 1760 | color-name@1.1.3: |
1452 | version "1.1.3" | 1761 | version "1.1.3" |
1453 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" | 1762 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" |
1454 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= | 1763 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= |
1455 | 1764 | ||
1456 | color-name@^1.0.0: | 1765 | color-name@~1.1.4: |
1457 | version "1.1.4" | 1766 | version "1.1.4" |
1458 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" | 1767 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" |
1459 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== | 1768 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== |
1460 | 1769 | ||
1461 | color-string@^0.3.0: | 1770 | colorette@^1.2.1: |
1462 | version "0.3.0" | 1771 | version "1.2.1" |
1463 | resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" | 1772 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" |
1464 | integrity sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE= | 1773 | integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== |
1465 | dependencies: | ||
1466 | color-name "^1.0.0" | ||
1467 | |||
1468 | color@^0.11.0: | ||
1469 | version "0.11.4" | ||
1470 | resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" | ||
1471 | integrity sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q= | ||
1472 | dependencies: | ||
1473 | clone "^1.0.2" | ||
1474 | color-convert "^1.3.0" | ||
1475 | color-string "^0.3.0" | ||
1476 | |||
1477 | colormin@^1.0.5: | ||
1478 | version "1.1.2" | ||
1479 | resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" | ||
1480 | integrity sha1-6i90IKcrlogaOKrlnsEkpvcpgTM= | ||
1481 | dependencies: | ||
1482 | color "^0.11.0" | ||
1483 | css-color-names "0.0.4" | ||
1484 | has "^1.0.1" | ||
1485 | |||
1486 | colors@~1.1.2: | ||
1487 | version "1.1.2" | ||
1488 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" | ||
1489 | integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM= | ||
1490 | |||
1491 | combined-stream@^1.0.6, combined-stream@~1.0.6: | ||
1492 | version "1.0.8" | ||
1493 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" | ||
1494 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== | ||
1495 | dependencies: | ||
1496 | delayed-stream "~1.0.0" | ||
1497 | 1774 | ||
1498 | commander@^2.8.1: | 1775 | commander@^2.20.0: |
1499 | version "2.20.0" | 1776 | version "2.20.3" |
1500 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" | 1777 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" |
1501 | integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== | 1778 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== |
1502 | 1779 | ||
1503 | commondir@^1.0.1: | 1780 | commondir@^1.0.1: |
1504 | version "1.0.1" | 1781 | version "1.0.1" |
@@ -1515,7 +1792,7 @@ concat-map@0.0.1: | |||
1515 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" | 1792 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" |
1516 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= | 1793 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= |
1517 | 1794 | ||
1518 | concat-stream@^1.4.6, concat-stream@^1.6.0: | 1795 | concat-stream@^1.5.0: |
1519 | version "1.6.2" | 1796 | version "1.6.2" |
1520 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" | 1797 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" |
1521 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== | 1798 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== |
@@ -1525,17 +1802,15 @@ concat-stream@^1.4.6, concat-stream@^1.6.0: | |||
1525 | readable-stream "^2.2.2" | 1802 | readable-stream "^2.2.2" |
1526 | typedarray "^0.0.6" | 1803 | typedarray "^0.0.6" |
1527 | 1804 | ||
1528 | console-browserify@^1.1.0: | 1805 | confusing-browser-globals@^1.0.9: |
1529 | version "1.1.0" | 1806 | version "1.0.9" |
1530 | resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" | 1807 | resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" |
1531 | integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= | 1808 | integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== |
1532 | dependencies: | ||
1533 | date-now "^0.1.4" | ||
1534 | 1809 | ||
1535 | console-control-strings@^1.0.0, console-control-strings@~1.1.0: | 1810 | console-browserify@^1.1.0: |
1536 | version "1.1.0" | 1811 | version "1.2.0" |
1537 | resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" | 1812 | resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" |
1538 | integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= | 1813 | integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== |
1539 | 1814 | ||
1540 | constants-browserify@^1.0.0: | 1815 | constants-browserify@^1.0.0: |
1541 | version "1.0.0" | 1816 | version "1.0.0" |
@@ -1547,37 +1822,63 @@ contains-path@^0.1.0: | |||
1547 | resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" | 1822 | resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" |
1548 | integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= | 1823 | integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= |
1549 | 1824 | ||
1550 | convert-source-map@^1.5.1: | 1825 | convert-source-map@^1.7.0: |
1551 | version "1.6.0" | 1826 | version "1.7.0" |
1552 | resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" | 1827 | resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" |
1553 | integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== | 1828 | integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== |
1554 | dependencies: | 1829 | dependencies: |
1555 | safe-buffer "~5.1.1" | 1830 | safe-buffer "~5.1.1" |
1556 | 1831 | ||
1832 | copy-concurrently@^1.0.0: | ||
1833 | version "1.0.5" | ||
1834 | resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" | ||
1835 | integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== | ||
1836 | dependencies: | ||
1837 | aproba "^1.1.1" | ||
1838 | fs-write-stream-atomic "^1.0.8" | ||
1839 | iferr "^0.1.5" | ||
1840 | mkdirp "^0.5.1" | ||
1841 | rimraf "^2.5.4" | ||
1842 | run-queue "^1.0.0" | ||
1843 | |||
1557 | copy-descriptor@^0.1.0: | 1844 | copy-descriptor@^0.1.0: |
1558 | version "0.1.1" | 1845 | version "0.1.1" |
1559 | resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" | 1846 | resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" |
1560 | integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= | 1847 | integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= |
1561 | 1848 | ||
1562 | core-js@^2.4.0, core-js@^2.5.0: | 1849 | core-js-compat@^3.6.2: |
1563 | version "2.6.5" | 1850 | version "3.6.5" |
1564 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" | 1851 | resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" |
1565 | integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A== | 1852 | integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== |
1853 | dependencies: | ||
1854 | browserslist "^4.8.5" | ||
1855 | semver "7.0.0" | ||
1566 | 1856 | ||
1567 | core-util-is@1.0.2, core-util-is@~1.0.0: | 1857 | core-util-is@~1.0.0: |
1568 | version "1.0.2" | 1858 | version "1.0.2" |
1569 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" | 1859 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" |
1570 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= | 1860 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= |
1571 | 1861 | ||
1862 | cosmiconfig@^7.0.0: | ||
1863 | version "7.0.0" | ||
1864 | resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" | ||
1865 | integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== | ||
1866 | dependencies: | ||
1867 | "@types/parse-json" "^4.0.0" | ||
1868 | import-fresh "^3.2.1" | ||
1869 | parse-json "^5.0.0" | ||
1870 | path-type "^4.0.0" | ||
1871 | yaml "^1.10.0" | ||
1872 | |||
1572 | create-ecdh@^4.0.0: | 1873 | create-ecdh@^4.0.0: |
1573 | version "4.0.3" | 1874 | version "4.0.4" |
1574 | resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" | 1875 | resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" |
1575 | integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== | 1876 | integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== |
1576 | dependencies: | 1877 | dependencies: |
1577 | bn.js "^4.1.0" | 1878 | bn.js "^4.1.0" |
1578 | elliptic "^6.0.0" | 1879 | elliptic "^6.5.3" |
1579 | 1880 | ||
1580 | create-hash@^1.1.0, create-hash@^1.1.2: | 1881 | create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: |
1581 | version "1.2.0" | 1882 | version "1.2.0" |
1582 | resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" | 1883 | resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" |
1583 | integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== | 1884 | integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== |
@@ -1588,7 +1889,7 @@ create-hash@^1.1.0, create-hash@^1.1.2: | |||
1588 | ripemd160 "^2.0.1" | 1889 | ripemd160 "^2.0.1" |
1589 | sha.js "^2.4.0" | 1890 | sha.js "^2.4.0" |
1590 | 1891 | ||
1591 | create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: | 1892 | create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: |
1592 | version "1.1.7" | 1893 | version "1.1.7" |
1593 | resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" | 1894 | resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" |
1594 | integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== | 1895 | integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== |
@@ -1600,22 +1901,25 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: | |||
1600 | safe-buffer "^5.0.1" | 1901 | safe-buffer "^5.0.1" |
1601 | sha.js "^2.4.8" | 1902 | sha.js "^2.4.8" |
1602 | 1903 | ||
1603 | cross-spawn@^3.0.0: | 1904 | cross-spawn@^6.0.5: |
1604 | version "3.0.1" | 1905 | version "6.0.5" |
1605 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" | 1906 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" |
1606 | integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= | 1907 | integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== |
1607 | dependencies: | 1908 | dependencies: |
1608 | lru-cache "^4.0.1" | 1909 | nice-try "^1.0.4" |
1910 | path-key "^2.0.1" | ||
1911 | semver "^5.5.0" | ||
1912 | shebang-command "^1.2.0" | ||
1609 | which "^1.2.9" | 1913 | which "^1.2.9" |
1610 | 1914 | ||
1611 | cross-spawn@^5.0.1, cross-spawn@^5.1.0: | 1915 | cross-spawn@^7.0.2: |
1612 | version "5.1.0" | 1916 | version "7.0.3" |
1613 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" | 1917 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" |
1614 | integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= | 1918 | integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== |
1615 | dependencies: | 1919 | dependencies: |
1616 | lru-cache "^4.0.1" | 1920 | path-key "^3.1.0" |
1617 | shebang-command "^1.2.0" | 1921 | shebang-command "^2.0.0" |
1618 | which "^1.2.9" | 1922 | which "^2.0.1" |
1619 | 1923 | ||
1620 | crypto-browserify@^3.11.0: | 1924 | crypto-browserify@^3.11.0: |
1621 | version "3.12.0" | 1925 | version "3.12.0" |
@@ -1634,132 +1938,57 @@ crypto-browserify@^3.11.0: | |||
1634 | randombytes "^2.0.0" | 1938 | randombytes "^2.0.0" |
1635 | randomfill "^1.0.3" | 1939 | randomfill "^1.0.3" |
1636 | 1940 | ||
1637 | css-color-names@0.0.4: | 1941 | css-loader@^4.3.0: |
1638 | version "0.0.4" | 1942 | version "4.3.0" |
1639 | resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" | 1943 | resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e" |
1640 | integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= | 1944 | integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg== |
1641 | 1945 | dependencies: | |
1642 | css-loader@^0.28.9: | 1946 | camelcase "^6.0.0" |
1643 | version "0.28.11" | 1947 | cssesc "^3.0.0" |
1644 | resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7" | 1948 | icss-utils "^4.1.1" |
1645 | integrity sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg== | 1949 | loader-utils "^2.0.0" |
1646 | dependencies: | 1950 | postcss "^7.0.32" |
1647 | babel-code-frame "^6.26.0" | 1951 | postcss-modules-extract-imports "^2.0.0" |
1648 | css-selector-tokenizer "^0.7.0" | 1952 | postcss-modules-local-by-default "^3.0.3" |
1649 | cssnano "^3.10.0" | 1953 | postcss-modules-scope "^2.2.0" |
1650 | icss-utils "^2.1.0" | 1954 | postcss-modules-values "^3.0.0" |
1651 | loader-utils "^1.0.2" | 1955 | postcss-value-parser "^4.1.0" |
1652 | lodash.camelcase "^4.3.0" | 1956 | schema-utils "^2.7.1" |
1653 | object-assign "^4.1.1" | 1957 | semver "^7.3.2" |
1654 | postcss "^5.0.6" | 1958 | |
1655 | postcss-modules-extract-imports "^1.2.0" | 1959 | cssesc@^3.0.0: |
1656 | postcss-modules-local-by-default "^1.2.0" | 1960 | version "3.0.0" |
1657 | postcss-modules-scope "^1.1.0" | 1961 | resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" |
1658 | postcss-modules-values "^1.3.0" | 1962 | integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== |
1659 | postcss-value-parser "^3.3.0" | ||
1660 | source-list-map "^2.0.0" | ||
1661 | |||
1662 | css-selector-tokenizer@^0.7.0: | ||
1663 | version "0.7.1" | ||
1664 | resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz#a177271a8bca5019172f4f891fc6eed9cbf68d5d" | ||
1665 | integrity sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA== | ||
1666 | dependencies: | ||
1667 | cssesc "^0.1.0" | ||
1668 | fastparse "^1.1.1" | ||
1669 | regexpu-core "^1.0.0" | ||
1670 | |||
1671 | cssesc@^0.1.0: | ||
1672 | version "0.1.0" | ||
1673 | resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" | ||
1674 | integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= | ||
1675 | |||
1676 | cssnano@^3.10.0: | ||
1677 | version "3.10.0" | ||
1678 | resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" | ||
1679 | integrity sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg= | ||
1680 | dependencies: | ||
1681 | autoprefixer "^6.3.1" | ||
1682 | decamelize "^1.1.2" | ||
1683 | defined "^1.0.0" | ||
1684 | has "^1.0.1" | ||
1685 | object-assign "^4.0.1" | ||
1686 | postcss "^5.0.14" | ||
1687 | postcss-calc "^5.2.0" | ||
1688 | postcss-colormin "^2.1.8" | ||
1689 | postcss-convert-values "^2.3.4" | ||
1690 | postcss-discard-comments "^2.0.4" | ||
1691 | postcss-discard-duplicates "^2.0.1" | ||
1692 | postcss-discard-empty "^2.0.1" | ||
1693 | postcss-discard-overridden "^0.1.1" | ||
1694 | postcss-discard-unused "^2.2.1" | ||
1695 | postcss-filter-plugins "^2.0.0" | ||
1696 | postcss-merge-idents "^2.1.5" | ||
1697 | postcss-merge-longhand "^2.0.1" | ||
1698 | postcss-merge-rules "^2.0.3" | ||
1699 | postcss-minify-font-values "^1.0.2" | ||
1700 | postcss-minify-gradients "^1.0.1" | ||
1701 | postcss-minify-params "^1.0.4" | ||
1702 | postcss-minify-selectors "^2.0.4" | ||
1703 | postcss-normalize-charset "^1.1.0" | ||
1704 | postcss-normalize-url "^3.0.7" | ||
1705 | postcss-ordered-values "^2.1.0" | ||
1706 | postcss-reduce-idents "^2.2.2" | ||
1707 | postcss-reduce-initial "^1.0.0" | ||
1708 | postcss-reduce-transforms "^1.0.3" | ||
1709 | postcss-svgo "^2.1.1" | ||
1710 | postcss-unique-selectors "^2.0.2" | ||
1711 | postcss-value-parser "^3.2.3" | ||
1712 | postcss-zindex "^2.0.1" | ||
1713 | |||
1714 | csso@~2.3.1: | ||
1715 | version "2.3.2" | ||
1716 | resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" | ||
1717 | integrity sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U= | ||
1718 | dependencies: | ||
1719 | clap "^1.0.9" | ||
1720 | source-map "^0.5.3" | ||
1721 | |||
1722 | currently-unhandled@^0.4.1: | ||
1723 | version "0.4.1" | ||
1724 | resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" | ||
1725 | integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= | ||
1726 | dependencies: | ||
1727 | array-find-index "^1.0.1" | ||
1728 | |||
1729 | d@1: | ||
1730 | version "1.0.0" | ||
1731 | resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" | ||
1732 | integrity sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8= | ||
1733 | dependencies: | ||
1734 | es5-ext "^0.10.9" | ||
1735 | |||
1736 | dashdash@^1.12.0: | ||
1737 | version "1.14.1" | ||
1738 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" | ||
1739 | integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= | ||
1740 | dependencies: | ||
1741 | assert-plus "^1.0.0" | ||
1742 | 1963 | ||
1743 | date-now@^0.1.4: | 1964 | cyclist@^1.0.1: |
1744 | version "0.1.4" | 1965 | version "1.0.1" |
1745 | resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" | 1966 | resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" |
1746 | integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= | 1967 | integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= |
1747 | 1968 | ||
1748 | debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: | 1969 | debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: |
1749 | version "2.6.9" | 1970 | version "2.6.9" |
1750 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" | 1971 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" |
1751 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== | 1972 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== |
1752 | dependencies: | 1973 | dependencies: |
1753 | ms "2.0.0" | 1974 | ms "2.0.0" |
1754 | 1975 | ||
1755 | debug@^3.1.0, debug@^3.2.6: | 1976 | debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: |
1756 | version "3.2.6" | 1977 | version "4.2.0" |
1757 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" | 1978 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" |
1758 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== | 1979 | integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== |
1980 | dependencies: | ||
1981 | ms "2.1.2" | ||
1982 | |||
1983 | decamelize-keys@^1.1.0: | ||
1984 | version "1.1.0" | ||
1985 | resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" | ||
1986 | integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= | ||
1759 | dependencies: | 1987 | dependencies: |
1760 | ms "^2.1.1" | 1988 | decamelize "^1.1.0" |
1989 | map-obj "^1.0.0" | ||
1761 | 1990 | ||
1762 | decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: | 1991 | decamelize@^1.1.0, decamelize@^1.2.0: |
1763 | version "1.2.0" | 1992 | version "1.2.0" |
1764 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" | 1993 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" |
1765 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= | 1994 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= |
@@ -1769,17 +1998,12 @@ decode-uri-component@^0.2.0: | |||
1769 | resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" | 1998 | resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" |
1770 | integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= | 1999 | integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= |
1771 | 2000 | ||
1772 | deep-extend@^0.6.0: | 2001 | deep-is@^0.1.3: |
1773 | version "0.6.0" | ||
1774 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" | ||
1775 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== | ||
1776 | |||
1777 | deep-is@~0.1.3: | ||
1778 | version "0.1.3" | 2002 | version "0.1.3" |
1779 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" | 2003 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" |
1780 | integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= | 2004 | integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= |
1781 | 2005 | ||
1782 | define-properties@^1.1.2: | 2006 | define-properties@^1.1.3: |
1783 | version "1.1.3" | 2007 | version "1.1.3" |
1784 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" | 2008 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" |
1785 | integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== | 2009 | integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== |
@@ -1808,40 +2032,18 @@ define-property@^2.0.2: | |||
1808 | is-descriptor "^1.0.2" | 2032 | is-descriptor "^1.0.2" |
1809 | isobject "^3.0.1" | 2033 | isobject "^3.0.1" |
1810 | 2034 | ||
1811 | defined@^1.0.0: | ||
1812 | version "1.0.0" | ||
1813 | resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" | ||
1814 | integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= | ||
1815 | |||
1816 | delayed-stream@~1.0.0: | ||
1817 | version "1.0.0" | ||
1818 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" | ||
1819 | integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= | ||
1820 | |||
1821 | delegates@^1.0.0: | ||
1822 | version "1.0.0" | ||
1823 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" | ||
1824 | integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= | ||
1825 | |||
1826 | des.js@^1.0.0: | 2035 | des.js@^1.0.0: |
1827 | version "1.0.0" | 2036 | version "1.0.1" |
1828 | resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" | 2037 | resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" |
1829 | integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= | 2038 | integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== |
1830 | dependencies: | 2039 | dependencies: |
1831 | inherits "^2.0.1" | 2040 | inherits "^2.0.1" |
1832 | minimalistic-assert "^1.0.0" | 2041 | minimalistic-assert "^1.0.0" |
1833 | 2042 | ||
1834 | detect-indent@^4.0.0: | 2043 | detect-file@^1.0.0: |
1835 | version "4.0.0" | 2044 | version "1.0.0" |
1836 | resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" | 2045 | resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" |
1837 | integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= | 2046 | integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= |
1838 | dependencies: | ||
1839 | repeating "^2.0.0" | ||
1840 | |||
1841 | detect-libc@^1.0.2: | ||
1842 | version "1.0.3" | ||
1843 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" | ||
1844 | integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= | ||
1845 | 2047 | ||
1846 | diffie-hellman@^5.0.0: | 2048 | diffie-hellman@^5.0.0: |
1847 | version "5.0.3" | 2049 | version "5.0.3" |
@@ -1852,7 +2054,14 @@ diffie-hellman@^5.0.0: | |||
1852 | miller-rabin "^4.0.0" | 2054 | miller-rabin "^4.0.0" |
1853 | randombytes "^2.0.0" | 2055 | randombytes "^2.0.0" |
1854 | 2056 | ||
1855 | doctrine@1.5.0, doctrine@^1.2.2: | 2057 | dir-glob@^3.0.1: |
2058 | version "3.0.1" | ||
2059 | resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" | ||
2060 | integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== | ||
2061 | dependencies: | ||
2062 | path-type "^4.0.0" | ||
2063 | |||
2064 | doctrine@1.5.0: | ||
1856 | version "1.5.0" | 2065 | version "1.5.0" |
1857 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" | 2066 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" |
1858 | integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= | 2067 | integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= |
@@ -1860,35 +2069,70 @@ doctrine@1.5.0, doctrine@^1.2.2: | |||
1860 | esutils "^2.0.2" | 2069 | esutils "^2.0.2" |
1861 | isarray "^1.0.0" | 2070 | isarray "^1.0.0" |
1862 | 2071 | ||
1863 | doctrine@^2.1.0: | 2072 | doctrine@^3.0.0: |
1864 | version "2.1.0" | 2073 | version "3.0.0" |
1865 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" | 2074 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" |
1866 | integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== | 2075 | integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== |
1867 | dependencies: | 2076 | dependencies: |
1868 | esutils "^2.0.2" | 2077 | esutils "^2.0.2" |
1869 | 2078 | ||
2079 | dom-serializer@0: | ||
2080 | version "0.2.2" | ||
2081 | resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" | ||
2082 | integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== | ||
2083 | dependencies: | ||
2084 | domelementtype "^2.0.1" | ||
2085 | entities "^2.0.0" | ||
2086 | |||
1870 | domain-browser@^1.1.1: | 2087 | domain-browser@^1.1.1: |
1871 | version "1.2.0" | 2088 | version "1.2.0" |
1872 | resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" | 2089 | resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" |
1873 | integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== | 2090 | integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== |
1874 | 2091 | ||
1875 | ecc-jsbn@~0.1.1: | 2092 | domelementtype@1, domelementtype@^1.3.1: |
1876 | version "0.1.2" | 2093 | version "1.3.1" |
1877 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" | 2094 | resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" |
1878 | integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= | 2095 | integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== |
2096 | |||
2097 | domelementtype@^2.0.1: | ||
2098 | version "2.0.2" | ||
2099 | resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.2.tgz#f3b6e549201e46f588b59463dd77187131fe6971" | ||
2100 | integrity sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA== | ||
2101 | |||
2102 | domhandler@^2.3.0: | ||
2103 | version "2.4.2" | ||
2104 | resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" | ||
2105 | integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== | ||
1879 | dependencies: | 2106 | dependencies: |
1880 | jsbn "~0.1.0" | 2107 | domelementtype "1" |
1881 | safer-buffer "^2.1.0" | 2108 | |
2109 | domutils@^1.5.1: | ||
2110 | version "1.7.0" | ||
2111 | resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" | ||
2112 | integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== | ||
2113 | dependencies: | ||
2114 | dom-serializer "0" | ||
2115 | domelementtype "1" | ||
2116 | |||
2117 | duplexify@^3.4.2, duplexify@^3.6.0: | ||
2118 | version "3.7.1" | ||
2119 | resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" | ||
2120 | integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== | ||
2121 | dependencies: | ||
2122 | end-of-stream "^1.0.0" | ||
2123 | inherits "^2.0.1" | ||
2124 | readable-stream "^2.0.0" | ||
2125 | stream-shift "^1.0.0" | ||
1882 | 2126 | ||
1883 | electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.47: | 2127 | electron-to-chromium@^1.3.570: |
1884 | version "1.3.135" | 2128 | version "1.3.570" |
1885 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.135.tgz#f5799b95f2bcd8de17cde47d63392d83a4477041" | 2129 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz#3f5141cc39b4e3892a276b4889980dabf1d29c7f" |
1886 | integrity sha512-xXLNstRdVsisPF3pL3H9TVZo2XkMILfqtD6RiWIUmDK2sFX1Bjwqmd8LBp0Kuo2FgKO63JXPoEVGm8WyYdwP0Q== | 2130 | integrity sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg== |
1887 | 2131 | ||
1888 | elliptic@^6.0.0: | 2132 | elliptic@^6.5.3: |
1889 | version "6.4.1" | 2133 | version "6.5.3" |
1890 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" | 2134 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" |
1891 | integrity sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ== | 2135 | integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== |
1892 | dependencies: | 2136 | dependencies: |
1893 | bn.js "^4.4.0" | 2137 | bn.js "^4.4.0" |
1894 | brorand "^1.0.1" | 2138 | brorand "^1.0.1" |
@@ -1898,325 +2142,284 @@ elliptic@^6.0.0: | |||
1898 | minimalistic-assert "^1.0.0" | 2142 | minimalistic-assert "^1.0.0" |
1899 | minimalistic-crypto-utils "^1.0.0" | 2143 | minimalistic-crypto-utils "^1.0.0" |
1900 | 2144 | ||
1901 | emojis-list@^2.0.0: | 2145 | emoji-regex@^7.0.1: |
1902 | version "2.1.0" | 2146 | version "7.0.3" |
1903 | resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" | 2147 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" |
1904 | integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= | 2148 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== |
1905 | 2149 | ||
1906 | enhanced-resolve@^3.4.0: | 2150 | emoji-regex@^8.0.0: |
1907 | version "3.4.1" | 2151 | version "8.0.0" |
1908 | resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" | 2152 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" |
1909 | integrity sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24= | 2153 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== |
2154 | |||
2155 | emojis-list@^3.0.0: | ||
2156 | version "3.0.0" | ||
2157 | resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" | ||
2158 | integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== | ||
2159 | |||
2160 | end-of-stream@^1.0.0, end-of-stream@^1.1.0: | ||
2161 | version "1.4.4" | ||
2162 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" | ||
2163 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== | ||
2164 | dependencies: | ||
2165 | once "^1.4.0" | ||
2166 | |||
2167 | enhanced-resolve@^4.1.1, enhanced-resolve@^4.3.0: | ||
2168 | version "4.3.0" | ||
2169 | resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" | ||
2170 | integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== | ||
1910 | dependencies: | 2171 | dependencies: |
1911 | graceful-fs "^4.1.2" | 2172 | graceful-fs "^4.1.2" |
1912 | memory-fs "^0.4.0" | 2173 | memory-fs "^0.5.0" |
1913 | object-assign "^4.0.1" | 2174 | tapable "^1.0.0" |
1914 | tapable "^0.2.7" | ||
1915 | 2175 | ||
1916 | errno@^0.1.3: | 2176 | enquirer@^2.3.5: |
2177 | version "2.3.6" | ||
2178 | resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" | ||
2179 | integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== | ||
2180 | dependencies: | ||
2181 | ansi-colors "^4.1.1" | ||
2182 | |||
2183 | entities@^1.1.1: | ||
2184 | version "1.1.2" | ||
2185 | resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" | ||
2186 | integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== | ||
2187 | |||
2188 | entities@^2.0.0: | ||
2189 | version "2.0.3" | ||
2190 | resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" | ||
2191 | integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== | ||
2192 | |||
2193 | errno@^0.1.3, errno@~0.1.7: | ||
1917 | version "0.1.7" | 2194 | version "0.1.7" |
1918 | resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" | 2195 | resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" |
1919 | integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== | 2196 | integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== |
1920 | dependencies: | 2197 | dependencies: |
1921 | prr "~1.0.1" | 2198 | prr "~1.0.1" |
1922 | 2199 | ||
1923 | error-ex@^1.2.0: | 2200 | error-ex@^1.2.0, error-ex@^1.3.1: |
1924 | version "1.3.2" | 2201 | version "1.3.2" |
1925 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" | 2202 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" |
1926 | integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== | 2203 | integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== |
1927 | dependencies: | 2204 | dependencies: |
1928 | is-arrayish "^0.2.1" | 2205 | is-arrayish "^0.2.1" |
1929 | 2206 | ||
1930 | es-abstract@^1.7.0: | 2207 | es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: |
1931 | version "1.13.0" | 2208 | version "1.17.6" |
1932 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" | 2209 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" |
1933 | integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== | 2210 | integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== |
1934 | dependencies: | 2211 | dependencies: |
1935 | es-to-primitive "^1.2.0" | 2212 | es-to-primitive "^1.2.1" |
1936 | function-bind "^1.1.1" | 2213 | function-bind "^1.1.1" |
1937 | has "^1.0.3" | 2214 | has "^1.0.3" |
1938 | is-callable "^1.1.4" | 2215 | has-symbols "^1.0.1" |
1939 | is-regex "^1.0.4" | 2216 | is-callable "^1.2.0" |
1940 | object-keys "^1.0.12" | 2217 | is-regex "^1.1.0" |
1941 | 2218 | object-inspect "^1.7.0" | |
1942 | es-to-primitive@^1.2.0: | 2219 | object-keys "^1.1.1" |
1943 | version "1.2.0" | 2220 | object.assign "^4.1.0" |
1944 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" | 2221 | string.prototype.trimend "^1.0.1" |
1945 | integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== | 2222 | string.prototype.trimstart "^1.0.1" |
2223 | |||
2224 | es-abstract@^1.18.0-next.0: | ||
2225 | version "1.18.0-next.0" | ||
2226 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc" | ||
2227 | integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ== | ||
2228 | dependencies: | ||
2229 | es-to-primitive "^1.2.1" | ||
2230 | function-bind "^1.1.1" | ||
2231 | has "^1.0.3" | ||
2232 | has-symbols "^1.0.1" | ||
2233 | is-callable "^1.2.0" | ||
2234 | is-negative-zero "^2.0.0" | ||
2235 | is-regex "^1.1.1" | ||
2236 | object-inspect "^1.8.0" | ||
2237 | object-keys "^1.1.1" | ||
2238 | object.assign "^4.1.0" | ||
2239 | string.prototype.trimend "^1.0.1" | ||
2240 | string.prototype.trimstart "^1.0.1" | ||
2241 | |||
2242 | es-to-primitive@^1.2.1: | ||
2243 | version "1.2.1" | ||
2244 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" | ||
2245 | integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== | ||
1946 | dependencies: | 2246 | dependencies: |
1947 | is-callable "^1.1.4" | 2247 | is-callable "^1.1.4" |
1948 | is-date-object "^1.0.1" | 2248 | is-date-object "^1.0.1" |
1949 | is-symbol "^1.0.2" | 2249 | is-symbol "^1.0.2" |
1950 | 2250 | ||
1951 | es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: | 2251 | escalade@^3.1.0: |
1952 | version "0.10.50" | 2252 | version "3.1.0" |
1953 | resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778" | 2253 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e" |
1954 | integrity sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw== | 2254 | integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig== |
1955 | dependencies: | ||
1956 | es6-iterator "~2.0.3" | ||
1957 | es6-symbol "~3.1.1" | ||
1958 | next-tick "^1.0.0" | ||
1959 | |||
1960 | es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: | ||
1961 | version "2.0.3" | ||
1962 | resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" | ||
1963 | integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= | ||
1964 | dependencies: | ||
1965 | d "1" | ||
1966 | es5-ext "^0.10.35" | ||
1967 | es6-symbol "^3.1.1" | ||
1968 | |||
1969 | es6-map@^0.1.3: | ||
1970 | version "0.1.5" | ||
1971 | resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" | ||
1972 | integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= | ||
1973 | dependencies: | ||
1974 | d "1" | ||
1975 | es5-ext "~0.10.14" | ||
1976 | es6-iterator "~2.0.1" | ||
1977 | es6-set "~0.1.5" | ||
1978 | es6-symbol "~3.1.1" | ||
1979 | event-emitter "~0.3.5" | ||
1980 | |||
1981 | es6-set@~0.1.5: | ||
1982 | version "0.1.5" | ||
1983 | resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" | ||
1984 | integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= | ||
1985 | dependencies: | ||
1986 | d "1" | ||
1987 | es5-ext "~0.10.14" | ||
1988 | es6-iterator "~2.0.1" | ||
1989 | es6-symbol "3.1.1" | ||
1990 | event-emitter "~0.3.5" | ||
1991 | |||
1992 | es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: | ||
1993 | version "3.1.1" | ||
1994 | resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" | ||
1995 | integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= | ||
1996 | dependencies: | ||
1997 | d "1" | ||
1998 | es5-ext "~0.10.14" | ||
1999 | |||
2000 | es6-weak-map@^2.0.1: | ||
2001 | version "2.0.2" | ||
2002 | resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" | ||
2003 | integrity sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8= | ||
2004 | dependencies: | ||
2005 | d "1" | ||
2006 | es5-ext "^0.10.14" | ||
2007 | es6-iterator "^2.0.1" | ||
2008 | es6-symbol "^3.1.1" | ||
2009 | 2255 | ||
2010 | escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: | 2256 | escape-string-regexp@^1.0.5: |
2011 | version "1.0.5" | 2257 | version "1.0.5" |
2012 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" | 2258 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" |
2013 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= | 2259 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= |
2014 | 2260 | ||
2015 | escope@^3.6.0: | 2261 | eslint-config-airbnb-base@^14.2.0: |
2016 | version "3.6.0" | 2262 | version "14.2.0" |
2017 | resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" | 2263 | resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz#fe89c24b3f9dc8008c9c0d0d88c28f95ed65e9c4" |
2018 | integrity sha1-4Bl16BJ4GhY6ba392AOY3GTIicM= | 2264 | integrity sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q== |
2019 | dependencies: | ||
2020 | es6-map "^0.1.3" | ||
2021 | es6-weak-map "^2.0.1" | ||
2022 | esrecurse "^4.1.0" | ||
2023 | estraverse "^4.1.1" | ||
2024 | |||
2025 | eslint-config-airbnb-base@^12.1.0: | ||
2026 | version "12.1.0" | ||
2027 | resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz#386441e54a12ccd957b0a92564a4bafebd747944" | ||
2028 | integrity sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA== | ||
2029 | dependencies: | 2265 | dependencies: |
2030 | eslint-restricted-globals "^0.1.1" | 2266 | confusing-browser-globals "^1.0.9" |
2267 | object.assign "^4.1.0" | ||
2268 | object.entries "^1.1.2" | ||
2031 | 2269 | ||
2032 | eslint-import-resolver-node@^0.3.2: | 2270 | eslint-import-resolver-node@^0.3.3: |
2033 | version "0.3.2" | 2271 | version "0.3.4" |
2034 | resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" | 2272 | resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" |
2035 | integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q== | 2273 | integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== |
2036 | dependencies: | 2274 | dependencies: |
2037 | debug "^2.6.9" | 2275 | debug "^2.6.9" |
2038 | resolve "^1.5.0" | 2276 | resolve "^1.13.1" |
2039 | 2277 | ||
2040 | eslint-module-utils@^2.4.0: | 2278 | eslint-module-utils@^2.6.0: |
2041 | version "2.4.0" | 2279 | version "2.6.0" |
2042 | resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a" | 2280 | resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" |
2043 | integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw== | 2281 | integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== |
2044 | dependencies: | 2282 | dependencies: |
2045 | debug "^2.6.8" | 2283 | debug "^2.6.9" |
2046 | pkg-dir "^2.0.0" | 2284 | pkg-dir "^2.0.0" |
2047 | 2285 | ||
2048 | eslint-plugin-import@^2.8.0: | 2286 | eslint-plugin-import@^2.22.0: |
2049 | version "2.17.2" | 2287 | version "2.22.0" |
2050 | resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.2.tgz#d227d5c6dc67eca71eb590d2bb62fb38d86e9fcb" | 2288 | resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" |
2051 | integrity sha512-m+cSVxM7oLsIpmwNn2WXTJoReOF9f/CtLMo7qOVmKd1KntBy0hEcuNZ3erTmWjx+DxRO0Zcrm5KwAvI9wHcV5g== | 2289 | integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== |
2052 | dependencies: | 2290 | dependencies: |
2053 | array-includes "^3.0.3" | 2291 | array-includes "^3.1.1" |
2292 | array.prototype.flat "^1.2.3" | ||
2054 | contains-path "^0.1.0" | 2293 | contains-path "^0.1.0" |
2055 | debug "^2.6.9" | 2294 | debug "^2.6.9" |
2056 | doctrine "1.5.0" | 2295 | doctrine "1.5.0" |
2057 | eslint-import-resolver-node "^0.3.2" | 2296 | eslint-import-resolver-node "^0.3.3" |
2058 | eslint-module-utils "^2.4.0" | 2297 | eslint-module-utils "^2.6.0" |
2059 | has "^1.0.3" | 2298 | has "^1.0.3" |
2060 | lodash "^4.17.11" | ||
2061 | minimatch "^3.0.4" | 2299 | minimatch "^3.0.4" |
2300 | object.values "^1.1.1" | ||
2062 | read-pkg-up "^2.0.0" | 2301 | read-pkg-up "^2.0.0" |
2063 | resolve "^1.10.0" | 2302 | resolve "^1.17.0" |
2064 | 2303 | tsconfig-paths "^3.9.0" | |
2065 | eslint-restricted-globals@^0.1.1: | ||
2066 | version "0.1.1" | ||
2067 | resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" | ||
2068 | integrity sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc= | ||
2069 | 2304 | ||
2070 | eslint-scope@^3.7.1: | 2305 | eslint-scope@^4.0.3: |
2071 | version "3.7.3" | 2306 | version "4.0.3" |
2072 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" | 2307 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" |
2073 | integrity sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA== | 2308 | integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== |
2074 | dependencies: | 2309 | dependencies: |
2075 | esrecurse "^4.1.0" | 2310 | esrecurse "^4.1.0" |
2076 | estraverse "^4.1.1" | 2311 | estraverse "^4.1.1" |
2077 | 2312 | ||
2078 | eslint-visitor-keys@^1.0.0: | 2313 | eslint-scope@^5.1.0: |
2079 | version "1.0.0" | 2314 | version "5.1.1" |
2080 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" | 2315 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" |
2081 | integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== | 2316 | integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== |
2082 | 2317 | dependencies: | |
2083 | eslint@^2.7.0: | 2318 | esrecurse "^4.3.0" |
2084 | version "2.13.1" | 2319 | estraverse "^4.1.1" |
2085 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11" | 2320 | |
2086 | integrity sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE= | 2321 | eslint-utils@^2.1.0: |
2087 | dependencies: | 2322 | version "2.1.0" |
2088 | chalk "^1.1.3" | 2323 | resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" |
2089 | concat-stream "^1.4.6" | 2324 | integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== |
2090 | debug "^2.1.1" | 2325 | dependencies: |
2091 | doctrine "^1.2.2" | 2326 | eslint-visitor-keys "^1.1.0" |
2092 | es6-map "^0.1.3" | 2327 | |
2093 | escope "^3.6.0" | 2328 | eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: |
2094 | espree "^3.1.6" | 2329 | version "1.3.0" |
2095 | estraverse "^4.2.0" | 2330 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" |
2096 | esutils "^2.0.2" | 2331 | integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== |
2097 | file-entry-cache "^1.1.1" | 2332 | |
2098 | glob "^7.0.3" | 2333 | eslint@^7.9.0: |
2099 | globals "^9.2.0" | 2334 | version "7.9.0" |
2100 | ignore "^3.1.2" | 2335 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.9.0.tgz#522aeccc5c3a19017cf0cb46ebfd660a79acf337" |
2101 | imurmurhash "^0.1.4" | 2336 | integrity sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA== |
2102 | inquirer "^0.12.0" | 2337 | dependencies: |
2103 | is-my-json-valid "^2.10.0" | 2338 | "@babel/code-frame" "^7.0.0" |
2104 | is-resolvable "^1.0.0" | 2339 | "@eslint/eslintrc" "^0.1.3" |
2105 | js-yaml "^3.5.1" | 2340 | ajv "^6.10.0" |
2106 | json-stable-stringify "^1.0.0" | 2341 | chalk "^4.0.0" |
2107 | levn "^0.3.0" | 2342 | cross-spawn "^7.0.2" |
2108 | lodash "^4.0.0" | 2343 | debug "^4.0.1" |
2109 | mkdirp "^0.5.0" | 2344 | doctrine "^3.0.0" |
2110 | optionator "^0.8.1" | 2345 | enquirer "^2.3.5" |
2111 | path-is-absolute "^1.0.0" | 2346 | eslint-scope "^5.1.0" |
2112 | path-is-inside "^1.0.1" | 2347 | eslint-utils "^2.1.0" |
2113 | pluralize "^1.2.1" | 2348 | eslint-visitor-keys "^1.3.0" |
2114 | progress "^1.1.8" | 2349 | espree "^7.3.0" |
2115 | require-uncached "^1.0.2" | 2350 | esquery "^1.2.0" |
2116 | shelljs "^0.6.0" | ||
2117 | strip-json-comments "~1.0.1" | ||
2118 | table "^3.7.8" | ||
2119 | text-table "~0.2.0" | ||
2120 | user-home "^2.0.0" | ||
2121 | |||
2122 | eslint@^4.16.0: | ||
2123 | version "4.19.1" | ||
2124 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" | ||
2125 | integrity sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ== | ||
2126 | dependencies: | ||
2127 | ajv "^5.3.0" | ||
2128 | babel-code-frame "^6.22.0" | ||
2129 | chalk "^2.1.0" | ||
2130 | concat-stream "^1.6.0" | ||
2131 | cross-spawn "^5.1.0" | ||
2132 | debug "^3.1.0" | ||
2133 | doctrine "^2.1.0" | ||
2134 | eslint-scope "^3.7.1" | ||
2135 | eslint-visitor-keys "^1.0.0" | ||
2136 | espree "^3.5.4" | ||
2137 | esquery "^1.0.0" | ||
2138 | esutils "^2.0.2" | 2351 | esutils "^2.0.2" |
2139 | file-entry-cache "^2.0.0" | 2352 | file-entry-cache "^5.0.1" |
2140 | functional-red-black-tree "^1.0.1" | 2353 | functional-red-black-tree "^1.0.1" |
2141 | glob "^7.1.2" | 2354 | glob-parent "^5.0.0" |
2142 | globals "^11.0.1" | 2355 | globals "^12.1.0" |
2143 | ignore "^3.3.3" | 2356 | ignore "^4.0.6" |
2357 | import-fresh "^3.0.0" | ||
2144 | imurmurhash "^0.1.4" | 2358 | imurmurhash "^0.1.4" |
2145 | inquirer "^3.0.6" | 2359 | is-glob "^4.0.0" |
2146 | is-resolvable "^1.0.0" | 2360 | js-yaml "^3.13.1" |
2147 | js-yaml "^3.9.1" | ||
2148 | json-stable-stringify-without-jsonify "^1.0.1" | 2361 | json-stable-stringify-without-jsonify "^1.0.1" |
2149 | levn "^0.3.0" | 2362 | levn "^0.4.1" |
2150 | lodash "^4.17.4" | 2363 | lodash "^4.17.19" |
2151 | minimatch "^3.0.2" | 2364 | minimatch "^3.0.4" |
2152 | mkdirp "^0.5.1" | ||
2153 | natural-compare "^1.4.0" | 2365 | natural-compare "^1.4.0" |
2154 | optionator "^0.8.2" | 2366 | optionator "^0.9.1" |
2155 | path-is-inside "^1.0.2" | ||
2156 | pluralize "^7.0.0" | ||
2157 | progress "^2.0.0" | 2367 | progress "^2.0.0" |
2158 | regexpp "^1.0.1" | 2368 | regexpp "^3.1.0" |
2159 | require-uncached "^1.0.3" | 2369 | semver "^7.2.1" |
2160 | semver "^5.3.0" | 2370 | strip-ansi "^6.0.0" |
2161 | strip-ansi "^4.0.0" | 2371 | strip-json-comments "^3.1.0" |
2162 | strip-json-comments "~2.0.1" | 2372 | table "^5.2.3" |
2163 | table "4.0.2" | 2373 | text-table "^0.2.0" |
2164 | text-table "~0.2.0" | 2374 | v8-compile-cache "^2.0.3" |
2165 | 2375 | ||
2166 | espree@^3.1.6, espree@^3.5.4: | 2376 | espree@^7.3.0: |
2167 | version "3.5.4" | 2377 | version "7.3.0" |
2168 | resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" | 2378 | resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" |
2169 | integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== | 2379 | integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== |
2170 | dependencies: | 2380 | dependencies: |
2171 | acorn "^5.5.0" | 2381 | acorn "^7.4.0" |
2172 | acorn-jsx "^3.0.0" | 2382 | acorn-jsx "^5.2.0" |
2173 | 2383 | eslint-visitor-keys "^1.3.0" | |
2174 | esprima@^2.6.0: | ||
2175 | version "2.7.3" | ||
2176 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" | ||
2177 | integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= | ||
2178 | 2384 | ||
2179 | esprima@^4.0.0: | 2385 | esprima@^4.0.0: |
2180 | version "4.0.1" | 2386 | version "4.0.1" |
2181 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" | 2387 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" |
2182 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== | 2388 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== |
2183 | 2389 | ||
2184 | esquery@^1.0.0: | 2390 | esquery@^1.2.0: |
2185 | version "1.0.1" | 2391 | version "1.3.1" |
2186 | resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" | 2392 | resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" |
2187 | integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== | 2393 | integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== |
2188 | dependencies: | 2394 | dependencies: |
2189 | estraverse "^4.0.0" | 2395 | estraverse "^5.1.0" |
2190 | 2396 | ||
2191 | esrecurse@^4.1.0: | 2397 | esrecurse@^4.1.0, esrecurse@^4.3.0: |
2192 | version "4.2.1" | 2398 | version "4.3.0" |
2193 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" | 2399 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" |
2194 | integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== | 2400 | integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== |
2195 | dependencies: | 2401 | dependencies: |
2196 | estraverse "^4.1.0" | 2402 | estraverse "^5.2.0" |
2197 | 2403 | ||
2198 | estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: | 2404 | estraverse@^4.1.1: |
2199 | version "4.2.0" | 2405 | version "4.3.0" |
2200 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" | 2406 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" |
2201 | integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= | 2407 | integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== |
2202 | 2408 | ||
2203 | esutils@^2.0.2: | 2409 | estraverse@^5.1.0, estraverse@^5.2.0: |
2204 | version "2.0.2" | 2410 | version "5.2.0" |
2205 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" | 2411 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" |
2206 | integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= | 2412 | integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== |
2207 | 2413 | ||
2208 | event-emitter@~0.3.5: | 2414 | esutils@^2.0.2: |
2209 | version "0.3.5" | 2415 | version "2.0.3" |
2210 | resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" | 2416 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" |
2211 | integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= | 2417 | integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== |
2212 | dependencies: | ||
2213 | d "1" | ||
2214 | es5-ext "~0.10.14" | ||
2215 | 2418 | ||
2216 | events@^3.0.0: | 2419 | events@^3.0.0: |
2217 | version "3.0.0" | 2420 | version "3.2.0" |
2218 | resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" | 2421 | resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" |
2219 | integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== | 2422 | integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== |
2220 | 2423 | ||
2221 | evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: | 2424 | evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: |
2222 | version "1.0.3" | 2425 | version "1.0.3" |
@@ -2226,23 +2429,12 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: | |||
2226 | md5.js "^1.3.4" | 2429 | md5.js "^1.3.4" |
2227 | safe-buffer "^5.1.1" | 2430 | safe-buffer "^5.1.1" |
2228 | 2431 | ||
2229 | execa@^0.7.0: | 2432 | execall@^2.0.0: |
2230 | version "0.7.0" | 2433 | version "2.0.0" |
2231 | resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" | 2434 | resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45" |
2232 | integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= | 2435 | integrity sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow== |
2233 | dependencies: | 2436 | dependencies: |
2234 | cross-spawn "^5.0.1" | 2437 | clone-regexp "^2.1.0" |
2235 | get-stream "^3.0.0" | ||
2236 | is-stream "^1.1.0" | ||
2237 | npm-run-path "^2.0.0" | ||
2238 | p-finally "^1.0.0" | ||
2239 | signal-exit "^3.0.0" | ||
2240 | strip-eof "^1.0.0" | ||
2241 | |||
2242 | exit-hook@^1.0.0: | ||
2243 | version "1.1.1" | ||
2244 | resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" | ||
2245 | integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= | ||
2246 | 2438 | ||
2247 | expand-brackets@^2.1.4: | 2439 | expand-brackets@^2.1.4: |
2248 | version "2.1.4" | 2440 | version "2.1.4" |
@@ -2257,6 +2449,13 @@ expand-brackets@^2.1.4: | |||
2257 | snapdragon "^0.8.1" | 2449 | snapdragon "^0.8.1" |
2258 | to-regex "^3.0.1" | 2450 | to-regex "^3.0.1" |
2259 | 2451 | ||
2452 | expand-tilde@^2.0.0, expand-tilde@^2.0.2: | ||
2453 | version "2.0.2" | ||
2454 | resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" | ||
2455 | integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= | ||
2456 | dependencies: | ||
2457 | homedir-polyfill "^1.0.1" | ||
2458 | |||
2260 | extend-shallow@^2.0.1: | 2459 | extend-shallow@^2.0.1: |
2261 | version "2.0.1" | 2460 | version "2.0.1" |
2262 | resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" | 2461 | resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" |
@@ -2272,20 +2471,11 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: | |||
2272 | assign-symbols "^1.0.0" | 2471 | assign-symbols "^1.0.0" |
2273 | is-extendable "^1.0.1" | 2472 | is-extendable "^1.0.1" |
2274 | 2473 | ||
2275 | extend@~3.0.2: | 2474 | extend@^3.0.0: |
2276 | version "3.0.2" | 2475 | version "3.0.2" |
2277 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" | 2476 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" |
2278 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== | 2477 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== |
2279 | 2478 | ||
2280 | external-editor@^2.0.4: | ||
2281 | version "2.2.0" | ||
2282 | resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" | ||
2283 | integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== | ||
2284 | dependencies: | ||
2285 | chardet "^0.4.0" | ||
2286 | iconv-lite "^0.4.17" | ||
2287 | tmp "^0.0.33" | ||
2288 | |||
2289 | extglob@^2.0.4: | 2479 | extglob@^2.0.4: |
2290 | version "2.0.4" | 2480 | version "2.0.4" |
2291 | resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" | 2481 | resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" |
@@ -2300,81 +2490,56 @@ extglob@^2.0.4: | |||
2300 | snapdragon "^0.8.1" | 2490 | snapdragon "^0.8.1" |
2301 | to-regex "^3.0.1" | 2491 | to-regex "^3.0.1" |
2302 | 2492 | ||
2303 | extract-text-webpack-plugin@^3.0.2: | 2493 | fast-deep-equal@^3.1.1: |
2304 | version "3.0.2" | 2494 | version "3.1.3" |
2305 | resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7" | 2495 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" |
2306 | integrity sha512-bt/LZ4m5Rqt/Crl2HiKuAl/oqg0psx1tsTLkvWbJen1CtD+fftkZhMaQ9HOtY2gWsl2Wq+sABmMVi9z3DhKWQQ== | 2496 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== |
2307 | dependencies: | ||
2308 | async "^2.4.1" | ||
2309 | loader-utils "^1.1.0" | ||
2310 | schema-utils "^0.3.0" | ||
2311 | webpack-sources "^1.0.1" | ||
2312 | |||
2313 | extsprintf@1.3.0: | ||
2314 | version "1.3.0" | ||
2315 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" | ||
2316 | integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= | ||
2317 | |||
2318 | extsprintf@^1.2.0: | ||
2319 | version "1.4.0" | ||
2320 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" | ||
2321 | integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= | ||
2322 | 2497 | ||
2323 | fast-deep-equal@^1.0.0: | 2498 | fast-glob@^3.1.1, fast-glob@^3.2.4: |
2324 | version "1.1.0" | 2499 | version "3.2.4" |
2325 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" | 2500 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" |
2326 | integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= | 2501 | integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== |
2327 | 2502 | dependencies: | |
2328 | fast-deep-equal@^2.0.1: | 2503 | "@nodelib/fs.stat" "^2.0.2" |
2329 | version "2.0.1" | 2504 | "@nodelib/fs.walk" "^1.2.3" |
2330 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" | 2505 | glob-parent "^5.1.0" |
2331 | integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= | 2506 | merge2 "^1.3.0" |
2507 | micromatch "^4.0.2" | ||
2508 | picomatch "^2.2.1" | ||
2332 | 2509 | ||
2333 | fast-json-stable-stringify@^2.0.0: | 2510 | fast-json-stable-stringify@^2.0.0: |
2334 | version "2.0.0" | 2511 | version "2.1.0" |
2335 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" | 2512 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" |
2336 | integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= | 2513 | integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== |
2337 | 2514 | ||
2338 | fast-levenshtein@~2.0.4: | 2515 | fast-levenshtein@^2.0.6: |
2339 | version "2.0.6" | 2516 | version "2.0.6" |
2340 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" | 2517 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" |
2341 | integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= | 2518 | integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= |
2342 | 2519 | ||
2343 | fastparse@^1.1.1: | 2520 | fastest-levenshtein@^1.0.12: |
2344 | version "1.1.2" | 2521 | version "1.0.12" |
2345 | resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" | 2522 | resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" |
2346 | integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== | 2523 | integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== |
2347 | |||
2348 | figures@^1.3.5: | ||
2349 | version "1.7.0" | ||
2350 | resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" | ||
2351 | integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= | ||
2352 | dependencies: | ||
2353 | escape-string-regexp "^1.0.5" | ||
2354 | object-assign "^4.1.0" | ||
2355 | 2524 | ||
2356 | figures@^2.0.0: | 2525 | fastq@^1.6.0: |
2357 | version "2.0.0" | 2526 | version "1.8.0" |
2358 | resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" | 2527 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" |
2359 | integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= | 2528 | integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== |
2360 | dependencies: | 2529 | dependencies: |
2361 | escape-string-regexp "^1.0.5" | 2530 | reusify "^1.0.4" |
2362 | 2531 | ||
2363 | file-entry-cache@^1.1.1: | 2532 | figgy-pudding@^3.5.1: |
2364 | version "1.3.1" | 2533 | version "3.5.2" |
2365 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-1.3.1.tgz#44c61ea607ae4be9c1402f41f44270cbfe334ff8" | 2534 | resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" |
2366 | integrity sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g= | 2535 | integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== |
2367 | dependencies: | ||
2368 | flat-cache "^1.2.1" | ||
2369 | object-assign "^4.0.1" | ||
2370 | 2536 | ||
2371 | file-entry-cache@^2.0.0: | 2537 | file-entry-cache@^5.0.1: |
2372 | version "2.0.0" | 2538 | version "5.0.1" |
2373 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" | 2539 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" |
2374 | integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= | 2540 | integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== |
2375 | dependencies: | 2541 | dependencies: |
2376 | flat-cache "^1.2.1" | 2542 | flat-cache "^2.0.1" |
2377 | object-assign "^4.0.1" | ||
2378 | 2543 | ||
2379 | file-loader@^1.1.6: | 2544 | file-loader@^1.1.6: |
2380 | version "1.1.11" | 2545 | version "1.1.11" |
@@ -2384,6 +2549,11 @@ file-loader@^1.1.6: | |||
2384 | loader-utils "^1.0.2" | 2549 | loader-utils "^1.0.2" |
2385 | schema-utils "^0.4.5" | 2550 | schema-utils "^0.4.5" |
2386 | 2551 | ||
2552 | file-uri-to-path@1.0.0: | ||
2553 | version "1.0.0" | ||
2554 | resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" | ||
2555 | integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== | ||
2556 | |||
2387 | fill-range@^4.0.0: | 2557 | fill-range@^4.0.0: |
2388 | version "4.0.0" | 2558 | version "4.0.0" |
2389 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" | 2559 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" |
@@ -2394,22 +2564,30 @@ fill-range@^4.0.0: | |||
2394 | repeat-string "^1.6.1" | 2564 | repeat-string "^1.6.1" |
2395 | to-regex-range "^2.1.0" | 2565 | to-regex-range "^2.1.0" |
2396 | 2566 | ||
2397 | find-cache-dir@^1.0.0: | 2567 | fill-range@^7.0.1: |
2398 | version "1.0.0" | 2568 | version "7.0.1" |
2399 | resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" | 2569 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" |
2400 | integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8= | 2570 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== |
2571 | dependencies: | ||
2572 | to-regex-range "^5.0.1" | ||
2573 | |||
2574 | find-cache-dir@^2.1.0: | ||
2575 | version "2.1.0" | ||
2576 | resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" | ||
2577 | integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== | ||
2401 | dependencies: | 2578 | dependencies: |
2402 | commondir "^1.0.1" | 2579 | commondir "^1.0.1" |
2403 | make-dir "^1.0.0" | 2580 | make-dir "^2.0.0" |
2404 | pkg-dir "^2.0.0" | 2581 | pkg-dir "^3.0.0" |
2405 | 2582 | ||
2406 | find-up@^1.0.0: | 2583 | find-cache-dir@^3.3.1: |
2407 | version "1.1.2" | 2584 | version "3.3.1" |
2408 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" | 2585 | resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" |
2409 | integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= | 2586 | integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== |
2410 | dependencies: | 2587 | dependencies: |
2411 | path-exists "^2.0.0" | 2588 | commondir "^1.0.1" |
2412 | pinkie-promise "^2.0.0" | 2589 | make-dir "^3.0.2" |
2590 | pkg-dir "^4.1.0" | ||
2413 | 2591 | ||
2414 | find-up@^2.0.0, find-up@^2.1.0: | 2592 | find-up@^2.0.0, find-up@^2.1.0: |
2415 | version "2.1.0" | 2593 | version "2.1.0" |
@@ -2418,57 +2596,63 @@ find-up@^2.0.0, find-up@^2.1.0: | |||
2418 | dependencies: | 2596 | dependencies: |
2419 | locate-path "^2.0.0" | 2597 | locate-path "^2.0.0" |
2420 | 2598 | ||
2421 | flat-cache@^1.2.1: | 2599 | find-up@^3.0.0: |
2422 | version "1.3.4" | 2600 | version "3.0.0" |
2423 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" | 2601 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" |
2424 | integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== | 2602 | integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== |
2425 | dependencies: | 2603 | dependencies: |
2426 | circular-json "^0.3.1" | 2604 | locate-path "^3.0.0" |
2427 | graceful-fs "^4.1.2" | ||
2428 | rimraf "~2.6.2" | ||
2429 | write "^0.2.1" | ||
2430 | 2605 | ||
2431 | flatten@^1.0.2: | 2606 | find-up@^4.0.0, find-up@^4.1.0: |
2432 | version "1.0.2" | 2607 | version "4.1.0" |
2433 | resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" | 2608 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" |
2434 | integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I= | 2609 | integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== |
2610 | dependencies: | ||
2611 | locate-path "^5.0.0" | ||
2612 | path-exists "^4.0.0" | ||
2435 | 2613 | ||
2436 | for-in@^0.1.3: | 2614 | findup-sync@^3.0.0: |
2437 | version "0.1.8" | 2615 | version "3.0.0" |
2438 | resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" | 2616 | resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" |
2439 | integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE= | 2617 | integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== |
2618 | dependencies: | ||
2619 | detect-file "^1.0.0" | ||
2620 | is-glob "^4.0.0" | ||
2621 | micromatch "^3.0.4" | ||
2622 | resolve-dir "^1.0.1" | ||
2440 | 2623 | ||
2441 | for-in@^1.0.1, for-in@^1.0.2: | 2624 | flat-cache@^2.0.1: |
2442 | version "1.0.2" | 2625 | version "2.0.1" |
2443 | resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" | 2626 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" |
2444 | integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= | 2627 | integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== |
2628 | dependencies: | ||
2629 | flatted "^2.0.0" | ||
2630 | rimraf "2.6.3" | ||
2631 | write "1.0.3" | ||
2445 | 2632 | ||
2446 | for-own@^1.0.0: | 2633 | flatted@^2.0.0: |
2447 | version "1.0.0" | 2634 | version "2.0.2" |
2448 | resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" | 2635 | resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" |
2449 | integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= | 2636 | integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== |
2637 | |||
2638 | flush-write-stream@^1.0.0: | ||
2639 | version "1.1.1" | ||
2640 | resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" | ||
2641 | integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== | ||
2450 | dependencies: | 2642 | dependencies: |
2451 | for-in "^1.0.1" | 2643 | inherits "^2.0.3" |
2644 | readable-stream "^2.3.6" | ||
2452 | 2645 | ||
2453 | forever-agent@~0.6.1: | 2646 | for-in@^1.0.2: |
2454 | version "0.6.1" | 2647 | version "1.0.2" |
2455 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" | 2648 | resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" |
2456 | integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= | 2649 | integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= |
2457 | 2650 | ||
2458 | fork-awesome@^1.1.7: | 2651 | fork-awesome@^1.1.7: |
2459 | version "1.1.7" | 2652 | version "1.1.7" |
2460 | resolved "https://registry.yarnpkg.com/fork-awesome/-/fork-awesome-1.1.7.tgz#1427da1cac3d1713046ee88427e5fcecb9501d21" | 2653 | resolved "https://registry.yarnpkg.com/fork-awesome/-/fork-awesome-1.1.7.tgz#1427da1cac3d1713046ee88427e5fcecb9501d21" |
2461 | integrity sha512-IHI7XCSXrKfUIWslse8c/PaaVDT1oBaYge+ju40ihL2ooiQeBpTr4wvIXhgTd2NuhntlvX+M5jYHAPTzNlmv0g== | 2654 | integrity sha512-IHI7XCSXrKfUIWslse8c/PaaVDT1oBaYge+ju40ihL2ooiQeBpTr4wvIXhgTd2NuhntlvX+M5jYHAPTzNlmv0g== |
2462 | 2655 | ||
2463 | form-data@~2.3.2: | ||
2464 | version "2.3.3" | ||
2465 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" | ||
2466 | integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== | ||
2467 | dependencies: | ||
2468 | asynckit "^0.4.0" | ||
2469 | combined-stream "^1.0.6" | ||
2470 | mime-types "^2.1.12" | ||
2471 | |||
2472 | fragment-cache@^0.2.1: | 2656 | fragment-cache@^0.2.1: |
2473 | version "0.2.1" | 2657 | version "0.2.1" |
2474 | resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" | 2658 | resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" |
@@ -2476,28 +2660,30 @@ fragment-cache@^0.2.1: | |||
2476 | dependencies: | 2660 | dependencies: |
2477 | map-cache "^0.2.2" | 2661 | map-cache "^0.2.2" |
2478 | 2662 | ||
2479 | front-matter@2.1.2: | 2663 | from2@^2.1.0: |
2480 | version "2.1.2" | 2664 | version "2.3.0" |
2481 | resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-2.1.2.tgz#f75983b9f2f413be658c93dfd7bd8ce4078f5cdb" | 2665 | resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" |
2482 | integrity sha1-91mDufL0E75ljJPf172M5AePXNs= | 2666 | integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= |
2483 | dependencies: | 2667 | dependencies: |
2484 | js-yaml "^3.4.6" | 2668 | inherits "^2.0.1" |
2669 | readable-stream "^2.0.0" | ||
2485 | 2670 | ||
2486 | fs-extra@^3.0.1: | 2671 | fs-minipass@^2.0.0: |
2487 | version "3.0.1" | 2672 | version "2.1.0" |
2488 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" | 2673 | resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" |
2489 | integrity sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE= | 2674 | integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== |
2490 | dependencies: | 2675 | dependencies: |
2491 | graceful-fs "^4.1.2" | 2676 | minipass "^3.0.0" |
2492 | jsonfile "^3.0.0" | ||
2493 | universalify "^0.1.0" | ||
2494 | 2677 | ||
2495 | fs-minipass@^1.2.5: | 2678 | fs-write-stream-atomic@^1.0.8: |
2496 | version "1.2.6" | 2679 | version "1.0.10" |
2497 | resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" | 2680 | resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" |
2498 | integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== | 2681 | integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= |
2499 | dependencies: | 2682 | dependencies: |
2500 | minipass "^2.2.1" | 2683 | graceful-fs "^4.1.2" |
2684 | iferr "^0.1.5" | ||
2685 | imurmurhash "^0.1.4" | ||
2686 | readable-stream "1 || 2" | ||
2501 | 2687 | ||
2502 | fs.realpath@^1.0.0: | 2688 | fs.realpath@^1.0.0: |
2503 | version "1.0.0" | 2689 | version "1.0.0" |
@@ -2505,22 +2691,17 @@ fs.realpath@^1.0.0: | |||
2505 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= | 2691 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= |
2506 | 2692 | ||
2507 | fsevents@^1.2.7: | 2693 | fsevents@^1.2.7: |
2508 | version "1.2.9" | 2694 | version "1.2.13" |
2509 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" | 2695 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" |
2510 | integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== | 2696 | integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== |
2511 | dependencies: | 2697 | dependencies: |
2698 | bindings "^1.5.0" | ||
2512 | nan "^2.12.1" | 2699 | nan "^2.12.1" |
2513 | node-pre-gyp "^0.12.0" | ||
2514 | 2700 | ||
2515 | fstream@^1.0.0, fstream@^1.0.12: | 2701 | fsevents@~2.1.2: |
2516 | version "1.0.12" | 2702 | version "2.1.3" |
2517 | resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" | 2703 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" |
2518 | integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== | 2704 | integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== |
2519 | dependencies: | ||
2520 | graceful-fs "^4.1.2" | ||
2521 | inherits "~2.0.0" | ||
2522 | mkdirp ">=0.5 0" | ||
2523 | rimraf "2" | ||
2524 | 2705 | ||
2525 | function-bind@^1.1.1: | 2706 | function-bind@^1.1.1: |
2526 | version "1.1.1" | 2707 | version "1.1.1" |
@@ -2532,68 +2713,26 @@ functional-red-black-tree@^1.0.1: | |||
2532 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" | 2713 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" |
2533 | integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= | 2714 | integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= |
2534 | 2715 | ||
2535 | gauge@~2.7.3: | 2716 | gensync@^1.0.0-beta.1: |
2536 | version "2.7.4" | 2717 | version "1.0.0-beta.1" |
2537 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" | 2718 | resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" |
2538 | integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= | 2719 | integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== |
2539 | dependencies: | ||
2540 | aproba "^1.0.3" | ||
2541 | console-control-strings "^1.0.0" | ||
2542 | has-unicode "^2.0.0" | ||
2543 | object-assign "^4.1.0" | ||
2544 | signal-exit "^3.0.0" | ||
2545 | string-width "^1.0.1" | ||
2546 | strip-ansi "^3.0.1" | ||
2547 | wide-align "^1.1.0" | ||
2548 | |||
2549 | gaze@^1.0.0: | ||
2550 | version "1.1.3" | ||
2551 | resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" | ||
2552 | integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== | ||
2553 | dependencies: | ||
2554 | globule "^1.0.0" | ||
2555 | |||
2556 | generate-function@^2.0.0: | ||
2557 | version "2.3.1" | ||
2558 | resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" | ||
2559 | integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== | ||
2560 | dependencies: | ||
2561 | is-property "^1.0.2" | ||
2562 | 2720 | ||
2563 | generate-object-property@^1.1.0: | 2721 | get-caller-file@^2.0.1: |
2564 | version "1.2.0" | 2722 | version "2.0.5" |
2565 | resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" | 2723 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" |
2566 | integrity sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA= | 2724 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== |
2567 | dependencies: | ||
2568 | is-property "^1.0.0" | ||
2569 | 2725 | ||
2570 | get-caller-file@^1.0.1: | 2726 | get-stdin@^8.0.0: |
2571 | version "1.0.3" | 2727 | version "8.0.0" |
2572 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" | 2728 | resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" |
2573 | integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== | 2729 | integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== |
2574 | |||
2575 | get-stdin@^4.0.1: | ||
2576 | version "4.0.1" | ||
2577 | resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" | ||
2578 | integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= | ||
2579 | |||
2580 | get-stream@^3.0.0: | ||
2581 | version "3.0.0" | ||
2582 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" | ||
2583 | integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= | ||
2584 | 2730 | ||
2585 | get-value@^2.0.3, get-value@^2.0.6: | 2731 | get-value@^2.0.3, get-value@^2.0.6: |
2586 | version "2.0.6" | 2732 | version "2.0.6" |
2587 | resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" | 2733 | resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" |
2588 | integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= | 2734 | integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= |
2589 | 2735 | ||
2590 | getpass@^0.1.1: | ||
2591 | version "0.1.7" | ||
2592 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" | ||
2593 | integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= | ||
2594 | dependencies: | ||
2595 | assert-plus "^1.0.0" | ||
2596 | |||
2597 | glob-parent@^3.1.0: | 2736 | glob-parent@^3.1.0: |
2598 | version "3.1.0" | 2737 | version "3.1.0" |
2599 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" | 2738 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" |
@@ -2602,10 +2741,17 @@ glob-parent@^3.1.0: | |||
2602 | is-glob "^3.1.0" | 2741 | is-glob "^3.1.0" |
2603 | path-dirname "^1.0.0" | 2742 | path-dirname "^1.0.0" |
2604 | 2743 | ||
2605 | glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: | 2744 | glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: |
2606 | version "7.1.4" | 2745 | version "5.1.1" |
2607 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" | 2746 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" |
2608 | integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== | 2747 | integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== |
2748 | dependencies: | ||
2749 | is-glob "^4.0.1" | ||
2750 | |||
2751 | glob@^7.1.3, glob@^7.1.4: | ||
2752 | version "7.1.6" | ||
2753 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" | ||
2754 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== | ||
2609 | dependencies: | 2755 | dependencies: |
2610 | fs.realpath "^1.0.0" | 2756 | fs.realpath "^1.0.0" |
2611 | inflight "^1.0.4" | 2757 | inflight "^1.0.4" |
@@ -2614,81 +2760,102 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: | |||
2614 | once "^1.3.0" | 2760 | once "^1.3.0" |
2615 | path-is-absolute "^1.0.0" | 2761 | path-is-absolute "^1.0.0" |
2616 | 2762 | ||
2617 | globals@^11.0.1: | 2763 | global-modules@^1.0.0: |
2618 | version "11.12.0" | 2764 | version "1.0.0" |
2619 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" | 2765 | resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" |
2620 | integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== | 2766 | integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== |
2767 | dependencies: | ||
2768 | global-prefix "^1.0.1" | ||
2769 | is-windows "^1.0.1" | ||
2770 | resolve-dir "^1.0.0" | ||
2621 | 2771 | ||
2622 | globals@^9.18.0, globals@^9.2.0: | 2772 | global-modules@^2.0.0: |
2623 | version "9.18.0" | 2773 | version "2.0.0" |
2624 | resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" | 2774 | resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" |
2625 | integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== | 2775 | integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== |
2776 | dependencies: | ||
2777 | global-prefix "^3.0.0" | ||
2626 | 2778 | ||
2627 | globule@^1.0.0: | 2779 | global-prefix@^1.0.1: |
2628 | version "1.2.1" | 2780 | version "1.0.2" |
2629 | resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" | 2781 | resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" |
2630 | integrity sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ== | 2782 | integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= |
2631 | dependencies: | 2783 | dependencies: |
2632 | glob "~7.1.1" | 2784 | expand-tilde "^2.0.2" |
2633 | lodash "~4.17.10" | 2785 | homedir-polyfill "^1.0.1" |
2634 | minimatch "~3.0.2" | 2786 | ini "^1.3.4" |
2787 | is-windows "^1.0.1" | ||
2788 | which "^1.2.14" | ||
2635 | 2789 | ||
2636 | gonzales-pe-sl@^4.2.3: | 2790 | global-prefix@^3.0.0: |
2637 | version "4.2.3" | 2791 | version "3.0.0" |
2638 | resolved "https://registry.yarnpkg.com/gonzales-pe-sl/-/gonzales-pe-sl-4.2.3.tgz#6a868bc380645f141feeb042c6f97fcc71b59fe6" | 2792 | resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" |
2639 | integrity sha1-aoaLw4BkXxQf7rBCxvl/zHG1n+Y= | 2793 | integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== |
2640 | dependencies: | 2794 | dependencies: |
2641 | minimist "1.1.x" | 2795 | ini "^1.3.5" |
2796 | kind-of "^6.0.2" | ||
2797 | which "^1.3.1" | ||
2642 | 2798 | ||
2643 | graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: | 2799 | globals@^11.1.0: |
2644 | version "4.1.15" | 2800 | version "11.12.0" |
2645 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" | 2801 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" |
2646 | integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== | 2802 | integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== |
2647 | 2803 | ||
2648 | har-schema@^2.0.0: | 2804 | globals@^12.1.0: |
2649 | version "2.0.0" | 2805 | version "12.4.0" |
2650 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" | 2806 | resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" |
2651 | integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= | 2807 | integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== |
2808 | dependencies: | ||
2809 | type-fest "^0.8.1" | ||
2652 | 2810 | ||
2653 | har-validator@~5.1.0: | 2811 | globby@^11.0.1: |
2654 | version "5.1.3" | 2812 | version "11.0.1" |
2655 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" | 2813 | resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" |
2656 | integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== | 2814 | integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== |
2657 | dependencies: | 2815 | dependencies: |
2658 | ajv "^6.5.5" | 2816 | array-union "^2.1.0" |
2659 | har-schema "^2.0.0" | 2817 | dir-glob "^3.0.1" |
2818 | fast-glob "^3.1.1" | ||
2819 | ignore "^5.1.4" | ||
2820 | merge2 "^1.3.0" | ||
2821 | slash "^3.0.0" | ||
2660 | 2822 | ||
2661 | has-ansi@^2.0.0: | 2823 | globjoin@^0.1.4: |
2662 | version "2.0.0" | 2824 | version "0.1.4" |
2663 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" | 2825 | resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" |
2664 | integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= | 2826 | integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM= |
2827 | |||
2828 | gonzales-pe@^4.3.0: | ||
2829 | version "4.3.0" | ||
2830 | resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" | ||
2831 | integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== | ||
2665 | dependencies: | 2832 | dependencies: |
2666 | ansi-regex "^2.0.0" | 2833 | minimist "^1.2.5" |
2667 | 2834 | ||
2668 | has-flag@^1.0.0: | 2835 | graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: |
2669 | version "1.0.0" | 2836 | version "4.2.4" |
2670 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" | 2837 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" |
2671 | integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= | 2838 | integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== |
2672 | 2839 | ||
2673 | has-flag@^2.0.0: | 2840 | hard-rejection@^2.1.0: |
2674 | version "2.0.0" | 2841 | version "2.1.0" |
2675 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" | 2842 | resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" |
2676 | integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= | 2843 | integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== |
2677 | 2844 | ||
2678 | has-flag@^3.0.0: | 2845 | has-flag@^3.0.0: |
2679 | version "3.0.0" | 2846 | version "3.0.0" |
2680 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" | 2847 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" |
2681 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= | 2848 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= |
2682 | 2849 | ||
2683 | has-symbols@^1.0.0: | 2850 | has-flag@^4.0.0: |
2684 | version "1.0.0" | 2851 | version "4.0.0" |
2685 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" | 2852 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" |
2686 | integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= | 2853 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== |
2687 | 2854 | ||
2688 | has-unicode@^2.0.0: | 2855 | has-symbols@^1.0.1: |
2689 | version "2.0.1" | 2856 | version "1.0.1" |
2690 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" | 2857 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" |
2691 | integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= | 2858 | integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== |
2692 | 2859 | ||
2693 | has-value@^0.3.1: | 2860 | has-value@^0.3.1: |
2694 | version "0.3.1" | 2861 | version "0.3.1" |
@@ -2721,7 +2888,7 @@ has-values@^1.0.0: | |||
2721 | is-number "^3.0.0" | 2888 | is-number "^3.0.0" |
2722 | kind-of "^4.0.0" | 2889 | kind-of "^4.0.0" |
2723 | 2890 | ||
2724 | has@^1.0.1, has@^1.0.3: | 2891 | has@^1.0.3: |
2725 | version "1.0.3" | 2892 | version "1.0.3" |
2726 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" | 2893 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" |
2727 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== | 2894 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== |
@@ -2729,12 +2896,13 @@ has@^1.0.1, has@^1.0.3: | |||
2729 | function-bind "^1.1.1" | 2896 | function-bind "^1.1.1" |
2730 | 2897 | ||
2731 | hash-base@^3.0.0: | 2898 | hash-base@^3.0.0: |
2732 | version "3.0.4" | 2899 | version "3.1.0" |
2733 | resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" | 2900 | resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" |
2734 | integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= | 2901 | integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== |
2735 | dependencies: | 2902 | dependencies: |
2736 | inherits "^2.0.1" | 2903 | inherits "^2.0.4" |
2737 | safe-buffer "^5.0.1" | 2904 | readable-stream "^3.6.0" |
2905 | safe-buffer "^5.2.0" | ||
2738 | 2906 | ||
2739 | hash.js@^1.0.0, hash.js@^1.0.3: | 2907 | hash.js@^1.0.0, hash.js@^1.0.3: |
2740 | version "1.1.7" | 2908 | version "1.1.7" |
@@ -2753,100 +2921,107 @@ hmac-drbg@^1.0.0: | |||
2753 | minimalistic-assert "^1.0.0" | 2921 | minimalistic-assert "^1.0.0" |
2754 | minimalistic-crypto-utils "^1.0.1" | 2922 | minimalistic-crypto-utils "^1.0.1" |
2755 | 2923 | ||
2756 | home-or-tmp@^2.0.0: | 2924 | homedir-polyfill@^1.0.1: |
2757 | version "2.0.0" | 2925 | version "1.0.3" |
2758 | resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" | 2926 | resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" |
2759 | integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= | 2927 | integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== |
2760 | dependencies: | 2928 | dependencies: |
2761 | os-homedir "^1.0.0" | 2929 | parse-passwd "^1.0.0" |
2762 | os-tmpdir "^1.0.1" | ||
2763 | 2930 | ||
2764 | hosted-git-info@^2.1.4: | 2931 | hosted-git-info@^2.1.4: |
2765 | version "2.7.1" | 2932 | version "2.8.8" |
2766 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" | 2933 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" |
2767 | integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== | 2934 | integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== |
2768 | 2935 | ||
2769 | html-comment-regex@^1.1.0: | 2936 | html-tags@^3.1.0: |
2770 | version "1.1.2" | 2937 | version "3.1.0" |
2771 | resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" | 2938 | resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" |
2772 | integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== | 2939 | integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== |
2773 | 2940 | ||
2774 | http-signature@~1.2.0: | 2941 | htmlparser2@^3.10.0: |
2775 | version "1.2.0" | 2942 | version "3.10.1" |
2776 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" | 2943 | resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" |
2777 | integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= | 2944 | integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== |
2778 | dependencies: | 2945 | dependencies: |
2779 | assert-plus "^1.0.0" | 2946 | domelementtype "^1.3.1" |
2780 | jsprim "^1.2.2" | 2947 | domhandler "^2.3.0" |
2781 | sshpk "^1.7.0" | 2948 | domutils "^1.5.1" |
2949 | entities "^1.1.1" | ||
2950 | inherits "^2.0.1" | ||
2951 | readable-stream "^3.1.1" | ||
2782 | 2952 | ||
2783 | https-browserify@^1.0.0: | 2953 | https-browserify@^1.0.0: |
2784 | version "1.0.0" | 2954 | version "1.0.0" |
2785 | resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" | 2955 | resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" |
2786 | integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= | 2956 | integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= |
2787 | 2957 | ||
2788 | iconv-lite@^0.4.17, iconv-lite@^0.4.4: | 2958 | icss-utils@^4.0.0, icss-utils@^4.1.1: |
2789 | version "0.4.24" | 2959 | version "4.1.1" |
2790 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" | 2960 | resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" |
2791 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== | 2961 | integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== |
2792 | dependencies: | ||
2793 | safer-buffer ">= 2.1.2 < 3" | ||
2794 | |||
2795 | icss-replace-symbols@^1.1.0: | ||
2796 | version "1.1.0" | ||
2797 | resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" | ||
2798 | integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= | ||
2799 | |||
2800 | icss-utils@^2.1.0: | ||
2801 | version "2.1.0" | ||
2802 | resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962" | ||
2803 | integrity sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI= | ||
2804 | dependencies: | 2962 | dependencies: |
2805 | postcss "^6.0.1" | 2963 | postcss "^7.0.14" |
2806 | 2964 | ||
2807 | ieee754@^1.1.4: | 2965 | ieee754@^1.1.4: |
2808 | version "1.1.13" | 2966 | version "1.1.13" |
2809 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" | 2967 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" |
2810 | integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== | 2968 | integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== |
2811 | 2969 | ||
2812 | ignore-walk@^3.0.1: | 2970 | iferr@^0.1.5: |
2813 | version "3.0.1" | 2971 | version "0.1.5" |
2814 | resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" | 2972 | resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" |
2815 | integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== | 2973 | integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= |
2974 | |||
2975 | ignore@^4.0.6: | ||
2976 | version "4.0.6" | ||
2977 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" | ||
2978 | integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== | ||
2979 | |||
2980 | ignore@^5.1.4, ignore@^5.1.8: | ||
2981 | version "5.1.8" | ||
2982 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" | ||
2983 | integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== | ||
2984 | |||
2985 | import-fresh@^3.0.0, import-fresh@^3.2.1: | ||
2986 | version "3.2.1" | ||
2987 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" | ||
2988 | integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== | ||
2816 | dependencies: | 2989 | dependencies: |
2817 | minimatch "^3.0.4" | 2990 | parent-module "^1.0.0" |
2991 | resolve-from "^4.0.0" | ||
2818 | 2992 | ||
2819 | ignore@^3.1.2, ignore@^3.3.3: | 2993 | import-lazy@^4.0.0: |
2820 | version "3.3.10" | 2994 | version "4.0.0" |
2821 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" | 2995 | resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" |
2822 | integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== | 2996 | integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== |
2997 | |||
2998 | import-local@^2.0.0: | ||
2999 | version "2.0.0" | ||
3000 | resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" | ||
3001 | integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== | ||
3002 | dependencies: | ||
3003 | pkg-dir "^3.0.0" | ||
3004 | resolve-cwd "^2.0.0" | ||
2823 | 3005 | ||
2824 | imurmurhash@^0.1.4: | 3006 | imurmurhash@^0.1.4: |
2825 | version "0.1.4" | 3007 | version "0.1.4" |
2826 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" | 3008 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" |
2827 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= | 3009 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= |
2828 | 3010 | ||
2829 | in-publish@^2.0.0: | 3011 | indent-string@^4.0.0: |
2830 | version "2.0.0" | 3012 | version "4.0.0" |
2831 | resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" | 3013 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" |
2832 | integrity sha1-4g/146KvwmkDILbcVSaCqcf631E= | 3014 | integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== |
2833 | |||
2834 | indent-string@^2.1.0: | ||
2835 | version "2.1.0" | ||
2836 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" | ||
2837 | integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= | ||
2838 | dependencies: | ||
2839 | repeating "^2.0.0" | ||
2840 | 3015 | ||
2841 | indexes-of@^1.0.1: | 3016 | indexes-of@^1.0.1: |
2842 | version "1.0.1" | 3017 | version "1.0.1" |
2843 | resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" | 3018 | resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" |
2844 | integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= | 3019 | integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= |
2845 | 3020 | ||
2846 | indexof@0.0.1: | 3021 | infer-owner@^1.0.3, infer-owner@^1.0.4: |
2847 | version "0.0.1" | 3022 | version "1.0.4" |
2848 | resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" | 3023 | resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" |
2849 | integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= | 3024 | integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== |
2850 | 3025 | ||
2851 | inflight@^1.0.4: | 3026 | inflight@^1.0.4: |
2852 | version "1.0.6" | 3027 | version "1.0.6" |
@@ -2856,82 +3031,38 @@ inflight@^1.0.4: | |||
2856 | once "^1.3.0" | 3031 | once "^1.3.0" |
2857 | wrappy "1" | 3032 | wrappy "1" |
2858 | 3033 | ||
2859 | inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: | 3034 | inherits@2, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: |
2860 | version "2.0.3" | 3035 | version "2.0.4" |
2861 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" | 3036 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" |
2862 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= | 3037 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== |
2863 | 3038 | ||
2864 | inherits@2.0.1: | 3039 | inherits@2.0.1: |
2865 | version "2.0.1" | 3040 | version "2.0.1" |
2866 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" | 3041 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" |
2867 | integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= | 3042 | integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= |
2868 | 3043 | ||
2869 | ini@~1.3.0: | 3044 | inherits@2.0.3: |
3045 | version "2.0.3" | ||
3046 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" | ||
3047 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= | ||
3048 | |||
3049 | ini@^1.3.4, ini@^1.3.5: | ||
2870 | version "1.3.5" | 3050 | version "1.3.5" |
2871 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" | 3051 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" |
2872 | integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== | 3052 | integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== |
2873 | 3053 | ||
2874 | inquirer@^0.12.0: | 3054 | interpret@^1.4.0: |
2875 | version "0.12.0" | 3055 | version "1.4.0" |
2876 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" | 3056 | resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" |
2877 | integrity sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34= | 3057 | integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== |
2878 | dependencies: | ||
2879 | ansi-escapes "^1.1.0" | ||
2880 | ansi-regex "^2.0.0" | ||
2881 | chalk "^1.0.0" | ||
2882 | cli-cursor "^1.0.1" | ||
2883 | cli-width "^2.0.0" | ||
2884 | figures "^1.3.5" | ||
2885 | lodash "^4.3.0" | ||
2886 | readline2 "^1.0.1" | ||
2887 | run-async "^0.1.0" | ||
2888 | rx-lite "^3.1.2" | ||
2889 | string-width "^1.0.1" | ||
2890 | strip-ansi "^3.0.0" | ||
2891 | through "^2.3.6" | ||
2892 | |||
2893 | inquirer@^3.0.6: | ||
2894 | version "3.3.0" | ||
2895 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" | ||
2896 | integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== | ||
2897 | dependencies: | ||
2898 | ansi-escapes "^3.0.0" | ||
2899 | chalk "^2.0.0" | ||
2900 | cli-cursor "^2.1.0" | ||
2901 | cli-width "^2.0.0" | ||
2902 | external-editor "^2.0.4" | ||
2903 | figures "^2.0.0" | ||
2904 | lodash "^4.3.0" | ||
2905 | mute-stream "0.0.7" | ||
2906 | run-async "^2.2.0" | ||
2907 | rx-lite "^4.0.8" | ||
2908 | rx-lite-aggregates "^4.0.8" | ||
2909 | string-width "^2.1.0" | ||
2910 | strip-ansi "^4.0.0" | ||
2911 | through "^2.3.6" | ||
2912 | |||
2913 | interpret@^1.0.0: | ||
2914 | version "1.2.0" | ||
2915 | resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" | ||
2916 | integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== | ||
2917 | 3058 | ||
2918 | invariant@^2.2.2: | 3059 | invariant@^2.2.2, invariant@^2.2.4: |
2919 | version "2.2.4" | 3060 | version "2.2.4" |
2920 | resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" | 3061 | resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" |
2921 | integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== | 3062 | integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== |
2922 | dependencies: | 3063 | dependencies: |
2923 | loose-envify "^1.0.0" | 3064 | loose-envify "^1.0.0" |
2924 | 3065 | ||
2925 | invert-kv@^1.0.0: | ||
2926 | version "1.0.0" | ||
2927 | resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" | ||
2928 | integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= | ||
2929 | |||
2930 | is-absolute-url@^2.0.0: | ||
2931 | version "2.1.0" | ||
2932 | resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" | ||
2933 | integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= | ||
2934 | |||
2935 | is-accessor-descriptor@^0.1.6: | 3066 | is-accessor-descriptor@^0.1.6: |
2936 | version "0.1.6" | 3067 | version "0.1.6" |
2937 | resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" | 3068 | resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" |
@@ -2946,6 +3077,24 @@ is-accessor-descriptor@^1.0.0: | |||
2946 | dependencies: | 3077 | dependencies: |
2947 | kind-of "^6.0.0" | 3078 | kind-of "^6.0.0" |
2948 | 3079 | ||
3080 | is-alphabetical@^1.0.0: | ||
3081 | version "1.0.4" | ||
3082 | resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" | ||
3083 | integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== | ||
3084 | |||
3085 | is-alphanumeric@^1.0.0: | ||
3086 | version "1.0.0" | ||
3087 | resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4" | ||
3088 | integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ= | ||
3089 | |||
3090 | is-alphanumerical@^1.0.0: | ||
3091 | version "1.0.4" | ||
3092 | resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" | ||
3093 | integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== | ||
3094 | dependencies: | ||
3095 | is-alphabetical "^1.0.0" | ||
3096 | is-decimal "^1.0.0" | ||
3097 | |||
2949 | is-arrayish@^0.2.1: | 3098 | is-arrayish@^0.2.1: |
2950 | version "0.2.1" | 3099 | version "0.2.1" |
2951 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" | 3100 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" |
@@ -2958,15 +3107,27 @@ is-binary-path@^1.0.0: | |||
2958 | dependencies: | 3107 | dependencies: |
2959 | binary-extensions "^1.0.0" | 3108 | binary-extensions "^1.0.0" |
2960 | 3109 | ||
3110 | is-binary-path@~2.1.0: | ||
3111 | version "2.1.0" | ||
3112 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" | ||
3113 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== | ||
3114 | dependencies: | ||
3115 | binary-extensions "^2.0.0" | ||
3116 | |||
2961 | is-buffer@^1.1.5: | 3117 | is-buffer@^1.1.5: |
2962 | version "1.1.6" | 3118 | version "1.1.6" |
2963 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" | 3119 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" |
2964 | integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== | 3120 | integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== |
2965 | 3121 | ||
2966 | is-callable@^1.1.4: | 3122 | is-buffer@^2.0.0: |
2967 | version "1.1.4" | 3123 | version "2.0.4" |
2968 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" | 3124 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" |
2969 | integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== | 3125 | integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== |
3126 | |||
3127 | is-callable@^1.1.4, is-callable@^1.2.0: | ||
3128 | version "1.2.2" | ||
3129 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" | ||
3130 | integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== | ||
2970 | 3131 | ||
2971 | is-data-descriptor@^0.1.4: | 3132 | is-data-descriptor@^0.1.4: |
2972 | version "0.1.4" | 3133 | version "0.1.4" |
@@ -2983,9 +3144,14 @@ is-data-descriptor@^1.0.0: | |||
2983 | kind-of "^6.0.0" | 3144 | kind-of "^6.0.0" |
2984 | 3145 | ||
2985 | is-date-object@^1.0.1: | 3146 | is-date-object@^1.0.1: |
2986 | version "1.0.1" | 3147 | version "1.0.2" |
2987 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" | 3148 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" |
2988 | integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= | 3149 | integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== |
3150 | |||
3151 | is-decimal@^1.0.0, is-decimal@^1.0.2: | ||
3152 | version "1.0.4" | ||
3153 | resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" | ||
3154 | integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== | ||
2989 | 3155 | ||
2990 | is-descriptor@^0.1.0: | 3156 | is-descriptor@^0.1.0: |
2991 | version "0.1.6" | 3157 | version "0.1.6" |
@@ -3022,25 +3188,16 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: | |||
3022 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" | 3188 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" |
3023 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= | 3189 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= |
3024 | 3190 | ||
3025 | is-finite@^1.0.0: | ||
3026 | version "1.0.2" | ||
3027 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" | ||
3028 | integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= | ||
3029 | dependencies: | ||
3030 | number-is-nan "^1.0.0" | ||
3031 | |||
3032 | is-fullwidth-code-point@^1.0.0: | ||
3033 | version "1.0.0" | ||
3034 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" | ||
3035 | integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= | ||
3036 | dependencies: | ||
3037 | number-is-nan "^1.0.0" | ||
3038 | |||
3039 | is-fullwidth-code-point@^2.0.0: | 3191 | is-fullwidth-code-point@^2.0.0: |
3040 | version "2.0.0" | 3192 | version "2.0.0" |
3041 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" | 3193 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" |
3042 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= | 3194 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= |
3043 | 3195 | ||
3196 | is-fullwidth-code-point@^3.0.0: | ||
3197 | version "3.0.0" | ||
3198 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" | ||
3199 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== | ||
3200 | |||
3044 | is-glob@^3.1.0: | 3201 | is-glob@^3.1.0: |
3045 | version "3.1.0" | 3202 | version "3.1.0" |
3046 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" | 3203 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" |
@@ -3048,28 +3205,22 @@ is-glob@^3.1.0: | |||
3048 | dependencies: | 3205 | dependencies: |
3049 | is-extglob "^2.1.0" | 3206 | is-extglob "^2.1.0" |
3050 | 3207 | ||
3051 | is-glob@^4.0.0: | 3208 | is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: |
3052 | version "4.0.1" | 3209 | version "4.0.1" |
3053 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" | 3210 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" |
3054 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== | 3211 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== |
3055 | dependencies: | 3212 | dependencies: |
3056 | is-extglob "^2.1.1" | 3213 | is-extglob "^2.1.1" |
3057 | 3214 | ||
3058 | is-my-ip-valid@^1.0.0: | 3215 | is-hexadecimal@^1.0.0: |
3059 | version "1.0.0" | 3216 | version "1.0.4" |
3060 | resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" | 3217 | resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" |
3061 | integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== | 3218 | integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== |
3062 | 3219 | ||
3063 | is-my-json-valid@^2.10.0: | 3220 | is-negative-zero@^2.0.0: |
3064 | version "2.20.0" | 3221 | version "2.0.0" |
3065 | resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz#1345a6fca3e8daefc10d0fa77067f54cedafd59a" | 3222 | resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" |
3066 | integrity sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA== | 3223 | integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= |
3067 | dependencies: | ||
3068 | generate-function "^2.0.0" | ||
3069 | generate-object-property "^1.1.0" | ||
3070 | is-my-ip-valid "^1.0.0" | ||
3071 | jsonpointer "^4.0.0" | ||
3072 | xtend "^4.0.0" | ||
3073 | 3224 | ||
3074 | is-number@^3.0.0: | 3225 | is-number@^3.0.0: |
3075 | version "3.0.0" | 3226 | version "3.0.0" |
@@ -3078,74 +3229,77 @@ is-number@^3.0.0: | |||
3078 | dependencies: | 3229 | dependencies: |
3079 | kind-of "^3.0.2" | 3230 | kind-of "^3.0.2" |
3080 | 3231 | ||
3081 | is-plain-obj@^1.0.0: | 3232 | is-number@^7.0.0: |
3233 | version "7.0.0" | ||
3234 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" | ||
3235 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== | ||
3236 | |||
3237 | is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: | ||
3082 | version "1.1.0" | 3238 | version "1.1.0" |
3083 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" | 3239 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" |
3084 | integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= | 3240 | integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= |
3085 | 3241 | ||
3086 | is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: | 3242 | is-plain-obj@^2.0.0: |
3243 | version "2.1.0" | ||
3244 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" | ||
3245 | integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== | ||
3246 | |||
3247 | is-plain-object@^2.0.3, is-plain-object@^2.0.4: | ||
3087 | version "2.0.4" | 3248 | version "2.0.4" |
3088 | resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" | 3249 | resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" |
3089 | integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== | 3250 | integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== |
3090 | dependencies: | 3251 | dependencies: |
3091 | isobject "^3.0.1" | 3252 | isobject "^3.0.1" |
3092 | 3253 | ||
3093 | is-promise@^2.1.0: | 3254 | is-regex@^1.1.0, is-regex@^1.1.1: |
3094 | version "2.1.0" | 3255 | version "1.1.1" |
3095 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" | 3256 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" |
3096 | integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= | 3257 | integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== |
3097 | |||
3098 | is-property@^1.0.0, is-property@^1.0.2: | ||
3099 | version "1.0.2" | ||
3100 | resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" | ||
3101 | integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= | ||
3102 | |||
3103 | is-regex@^1.0.4: | ||
3104 | version "1.0.4" | ||
3105 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" | ||
3106 | integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= | ||
3107 | dependencies: | 3258 | dependencies: |
3108 | has "^1.0.1" | 3259 | has-symbols "^1.0.1" |
3109 | |||
3110 | is-resolvable@^1.0.0: | ||
3111 | version "1.1.0" | ||
3112 | resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" | ||
3113 | integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== | ||
3114 | |||
3115 | is-stream@^1.1.0: | ||
3116 | version "1.1.0" | ||
3117 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" | ||
3118 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= | ||
3119 | 3260 | ||
3120 | is-svg@^2.0.0: | 3261 | is-regexp@^2.0.0: |
3121 | version "2.1.0" | 3262 | version "2.1.0" |
3122 | resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" | 3263 | resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d" |
3123 | integrity sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk= | 3264 | integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA== |
3124 | dependencies: | 3265 | |
3125 | html-comment-regex "^1.1.0" | 3266 | is-string@^1.0.5: |
3267 | version "1.0.5" | ||
3268 | resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" | ||
3269 | integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== | ||
3126 | 3270 | ||
3127 | is-symbol@^1.0.2: | 3271 | is-symbol@^1.0.2: |
3128 | version "1.0.2" | 3272 | version "1.0.3" |
3129 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" | 3273 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" |
3130 | integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== | 3274 | integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== |
3131 | dependencies: | 3275 | dependencies: |
3132 | has-symbols "^1.0.0" | 3276 | has-symbols "^1.0.1" |
3133 | 3277 | ||
3134 | is-typedarray@~1.0.0: | 3278 | is-typedarray@^1.0.0: |
3135 | version "1.0.0" | 3279 | version "1.0.0" |
3136 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" | 3280 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" |
3137 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= | 3281 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= |
3138 | 3282 | ||
3139 | is-utf8@^0.2.0: | 3283 | is-whitespace-character@^1.0.0: |
3140 | version "0.2.1" | 3284 | version "1.0.4" |
3141 | resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" | 3285 | resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" |
3142 | integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= | 3286 | integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== |
3143 | 3287 | ||
3144 | is-windows@^1.0.2: | 3288 | is-windows@^1.0.1, is-windows@^1.0.2: |
3145 | version "1.0.2" | 3289 | version "1.0.2" |
3146 | resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" | 3290 | resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" |
3147 | integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== | 3291 | integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== |
3148 | 3292 | ||
3293 | is-word-character@^1.0.0: | ||
3294 | version "1.0.4" | ||
3295 | resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" | ||
3296 | integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== | ||
3297 | |||
3298 | is-wsl@^1.1.0: | ||
3299 | version "1.1.0" | ||
3300 | resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" | ||
3301 | integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= | ||
3302 | |||
3149 | isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: | 3303 | isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: |
3150 | version "1.0.0" | 3304 | version "1.0.0" |
3151 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" | 3305 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" |
@@ -3168,99 +3322,58 @@ isobject@^3.0.0, isobject@^3.0.1: | |||
3168 | resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" | 3322 | resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" |
3169 | integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= | 3323 | integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= |
3170 | 3324 | ||
3171 | isstream@~0.1.2: | 3325 | jest-worker@^26.3.0: |
3172 | version "0.1.2" | 3326 | version "26.3.0" |
3173 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" | 3327 | resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f" |
3174 | integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= | 3328 | integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw== |
3175 | 3329 | dependencies: | |
3176 | js-base64@^2.1.8, js-base64@^2.1.9: | 3330 | "@types/node" "*" |
3177 | version "2.5.1" | 3331 | merge-stream "^2.0.0" |
3178 | resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" | 3332 | supports-color "^7.0.0" |
3179 | integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== | ||
3180 | 3333 | ||
3181 | "js-tokens@^3.0.0 || ^4.0.0": | 3334 | "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: |
3182 | version "4.0.0" | 3335 | version "4.0.0" |
3183 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" | 3336 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" |
3184 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== | 3337 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== |
3185 | 3338 | ||
3186 | js-tokens@^3.0.2: | 3339 | js-yaml@^3.13.1: |
3187 | version "3.0.2" | 3340 | version "3.14.0" |
3188 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" | 3341 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" |
3189 | integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= | 3342 | integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== |
3190 | |||
3191 | js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.1: | ||
3192 | version "3.13.1" | ||
3193 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" | ||
3194 | integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== | ||
3195 | dependencies: | 3343 | dependencies: |
3196 | argparse "^1.0.7" | 3344 | argparse "^1.0.7" |
3197 | esprima "^4.0.0" | 3345 | esprima "^4.0.0" |
3198 | 3346 | ||
3199 | js-yaml@~3.7.0: | 3347 | jsesc@^2.5.1: |
3200 | version "3.7.0" | 3348 | version "2.5.2" |
3201 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" | 3349 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" |
3202 | integrity sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A= | 3350 | integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== |
3203 | dependencies: | ||
3204 | argparse "^1.0.7" | ||
3205 | esprima "^2.6.0" | ||
3206 | |||
3207 | jsbn@~0.1.0: | ||
3208 | version "0.1.1" | ||
3209 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" | ||
3210 | integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= | ||
3211 | |||
3212 | jsesc@^1.3.0: | ||
3213 | version "1.3.0" | ||
3214 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" | ||
3215 | integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= | ||
3216 | 3351 | ||
3217 | jsesc@~0.5.0: | 3352 | jsesc@~0.5.0: |
3218 | version "0.5.0" | 3353 | version "0.5.0" |
3219 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" | 3354 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" |
3220 | integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= | 3355 | integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= |
3221 | 3356 | ||
3222 | json-loader@^0.5.4: | 3357 | json-parse-better-errors@^1.0.2: |
3223 | version "0.5.7" | 3358 | version "1.0.2" |
3224 | resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" | 3359 | resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" |
3225 | integrity sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w== | 3360 | integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== |
3226 | 3361 | ||
3227 | json-schema-traverse@^0.3.0: | 3362 | json-parse-even-better-errors@^2.3.0: |
3228 | version "0.3.1" | 3363 | version "2.3.1" |
3229 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" | 3364 | resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" |
3230 | integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= | 3365 | integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== |
3231 | 3366 | ||
3232 | json-schema-traverse@^0.4.1: | 3367 | json-schema-traverse@^0.4.1: |
3233 | version "0.4.1" | 3368 | version "0.4.1" |
3234 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" | 3369 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" |
3235 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== | 3370 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== |
3236 | 3371 | ||
3237 | json-schema@0.2.3: | ||
3238 | version "0.2.3" | ||
3239 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" | ||
3240 | integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= | ||
3241 | |||
3242 | json-stable-stringify-without-jsonify@^1.0.1: | 3372 | json-stable-stringify-without-jsonify@^1.0.1: |
3243 | version "1.0.1" | 3373 | version "1.0.1" |
3244 | resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" | 3374 | resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" |
3245 | integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= | 3375 | integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= |
3246 | 3376 | ||
3247 | json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: | ||
3248 | version "1.0.1" | ||
3249 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" | ||
3250 | integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= | ||
3251 | dependencies: | ||
3252 | jsonify "~0.0.0" | ||
3253 | |||
3254 | json-stringify-safe@~5.0.1: | ||
3255 | version "5.0.1" | ||
3256 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" | ||
3257 | integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= | ||
3258 | |||
3259 | json5@^0.5.1: | ||
3260 | version "0.5.1" | ||
3261 | resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" | ||
3262 | integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= | ||
3263 | |||
3264 | json5@^1.0.1: | 3377 | json5@^1.0.1: |
3265 | version "1.0.1" | 3378 | version "1.0.1" |
3266 | resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" | 3379 | resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" |
@@ -3268,32 +3381,12 @@ json5@^1.0.1: | |||
3268 | dependencies: | 3381 | dependencies: |
3269 | minimist "^1.2.0" | 3382 | minimist "^1.2.0" |
3270 | 3383 | ||
3271 | jsonfile@^3.0.0: | 3384 | json5@^2.1.2: |
3272 | version "3.0.1" | 3385 | version "2.1.3" |
3273 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" | 3386 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" |
3274 | integrity sha1-pezG9l9T9mLEQVx2daAzHQmS7GY= | 3387 | integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== |
3275 | optionalDependencies: | ||
3276 | graceful-fs "^4.1.6" | ||
3277 | |||
3278 | jsonify@~0.0.0: | ||
3279 | version "0.0.0" | ||
3280 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" | ||
3281 | integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= | ||
3282 | |||
3283 | jsonpointer@^4.0.0: | ||
3284 | version "4.0.1" | ||
3285 | resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" | ||
3286 | integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= | ||
3287 | |||
3288 | jsprim@^1.2.2: | ||
3289 | version "1.4.1" | ||
3290 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" | ||
3291 | integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= | ||
3292 | dependencies: | 3388 | dependencies: |
3293 | assert-plus "1.0.0" | 3389 | minimist "^1.2.5" |
3294 | extsprintf "1.3.0" | ||
3295 | json-schema "0.2.3" | ||
3296 | verror "1.10.0" | ||
3297 | 3390 | ||
3298 | kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: | 3391 | kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: |
3299 | version "3.2.2" | 3392 | version "3.2.2" |
@@ -3314,46 +3407,45 @@ kind-of@^5.0.0: | |||
3314 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" | 3407 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" |
3315 | integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== | 3408 | integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== |
3316 | 3409 | ||
3317 | kind-of@^6.0.0, kind-of@^6.0.2: | 3410 | kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: |
3318 | version "6.0.2" | 3411 | version "6.0.3" |
3319 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" | 3412 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" |
3320 | integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== | 3413 | integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== |
3321 | 3414 | ||
3322 | known-css-properties@^0.3.0: | 3415 | klona@^2.0.3: |
3323 | version "0.3.0" | 3416 | version "2.0.4" |
3324 | resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.3.0.tgz#a3d135bbfc60ee8c6eacf2f7e7e6f2d4755e49a4" | 3417 | resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" |
3325 | integrity sha512-QMQcnKAiQccfQTqtBh/qwquGZ2XK/DXND1jrcN9M8gMMy99Gwla7GQjndVUsEqIaRyP6bsFRuhwRj5poafBGJQ== | 3418 | integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== |
3326 | 3419 | ||
3327 | lazy-cache@^1.0.3: | 3420 | known-css-properties@^0.19.0: |
3328 | version "1.0.4" | 3421 | version "0.19.0" |
3329 | resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" | 3422 | resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" |
3330 | integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= | 3423 | integrity sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA== |
3331 | 3424 | ||
3332 | lcid@^1.0.0: | 3425 | leven@^3.1.0: |
3333 | version "1.0.0" | 3426 | version "3.1.0" |
3334 | resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" | 3427 | resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" |
3335 | integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= | 3428 | integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== |
3336 | dependencies: | ||
3337 | invert-kv "^1.0.0" | ||
3338 | 3429 | ||
3339 | levn@^0.3.0, levn@~0.3.0: | 3430 | levenary@^1.1.1: |
3340 | version "0.3.0" | 3431 | version "1.1.1" |
3341 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" | 3432 | resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" |
3342 | integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= | 3433 | integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== |
3343 | dependencies: | 3434 | dependencies: |
3344 | prelude-ls "~1.1.2" | 3435 | leven "^3.1.0" |
3345 | type-check "~0.3.2" | ||
3346 | 3436 | ||
3347 | load-json-file@^1.0.0: | 3437 | levn@^0.4.1: |
3348 | version "1.1.0" | 3438 | version "0.4.1" |
3349 | resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" | 3439 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" |
3350 | integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= | 3440 | integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== |
3351 | dependencies: | 3441 | dependencies: |
3352 | graceful-fs "^4.1.2" | 3442 | prelude-ls "^1.2.1" |
3353 | parse-json "^2.2.0" | 3443 | type-check "~0.4.0" |
3354 | pify "^2.0.0" | 3444 | |
3355 | pinkie-promise "^2.0.0" | 3445 | lines-and-columns@^1.1.6: |
3356 | strip-bom "^2.0.0" | 3446 | version "1.1.6" |
3447 | resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" | ||
3448 | integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= | ||
3357 | 3449 | ||
3358 | load-json-file@^2.0.0: | 3450 | load-json-file@^2.0.0: |
3359 | version "2.0.0" | 3451 | version "2.0.0" |
@@ -3365,20 +3457,29 @@ load-json-file@^2.0.0: | |||
3365 | pify "^2.0.0" | 3457 | pify "^2.0.0" |
3366 | strip-bom "^3.0.0" | 3458 | strip-bom "^3.0.0" |
3367 | 3459 | ||
3368 | loader-runner@^2.3.0: | 3460 | loader-runner@^2.4.0: |
3369 | version "2.4.0" | 3461 | version "2.4.0" |
3370 | resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" | 3462 | resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" |
3371 | integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== | 3463 | integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== |
3372 | 3464 | ||
3373 | loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: | 3465 | loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: |
3374 | version "1.2.3" | 3466 | version "1.4.0" |
3375 | resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" | 3467 | resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" |
3376 | integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== | 3468 | integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== |
3377 | dependencies: | 3469 | dependencies: |
3378 | big.js "^5.2.2" | 3470 | big.js "^5.2.2" |
3379 | emojis-list "^2.0.0" | 3471 | emojis-list "^3.0.0" |
3380 | json5 "^1.0.1" | 3472 | json5 "^1.0.1" |
3381 | 3473 | ||
3474 | loader-utils@^2.0.0: | ||
3475 | version "2.0.0" | ||
3476 | resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" | ||
3477 | integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== | ||
3478 | dependencies: | ||
3479 | big.js "^5.2.2" | ||
3480 | emojis-list "^3.0.0" | ||
3481 | json5 "^2.1.2" | ||
3482 | |||
3382 | locate-path@^2.0.0: | 3483 | locate-path@^2.0.0: |
3383 | version "2.0.0" | 3484 | version "2.0.0" |
3384 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" | 3485 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" |
@@ -3387,55 +3488,37 @@ locate-path@^2.0.0: | |||
3387 | p-locate "^2.0.0" | 3488 | p-locate "^2.0.0" |
3388 | path-exists "^3.0.0" | 3489 | path-exists "^3.0.0" |
3389 | 3490 | ||
3390 | lodash.camelcase@^4.3.0: | 3491 | locate-path@^3.0.0: |
3391 | version "4.3.0" | 3492 | version "3.0.0" |
3392 | resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" | 3493 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" |
3393 | integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= | 3494 | integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== |
3394 | 3495 | dependencies: | |
3395 | lodash.capitalize@^4.1.0: | 3496 | p-locate "^3.0.0" |
3396 | version "4.2.1" | 3497 | path-exists "^3.0.0" |
3397 | resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" | ||
3398 | integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= | ||
3399 | |||
3400 | lodash.isplainobject@^4.0.6: | ||
3401 | version "4.0.6" | ||
3402 | resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" | ||
3403 | integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= | ||
3404 | |||
3405 | lodash.kebabcase@^4.0.0: | ||
3406 | version "4.1.1" | ||
3407 | resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" | ||
3408 | integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= | ||
3409 | |||
3410 | lodash.memoize@^4.1.2: | ||
3411 | version "4.1.2" | ||
3412 | resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" | ||
3413 | integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= | ||
3414 | |||
3415 | lodash.some@^4.6.0: | ||
3416 | version "4.6.0" | ||
3417 | resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" | ||
3418 | integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= | ||
3419 | 3498 | ||
3420 | lodash.tail@^4.1.1: | 3499 | locate-path@^5.0.0: |
3421 | version "4.1.1" | 3500 | version "5.0.0" |
3422 | resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" | 3501 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" |
3423 | integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ= | 3502 | integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== |
3503 | dependencies: | ||
3504 | p-locate "^4.1.0" | ||
3424 | 3505 | ||
3425 | lodash.uniq@^4.5.0: | 3506 | lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: |
3426 | version "4.5.0" | 3507 | version "4.17.20" |
3427 | resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" | 3508 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" |
3428 | integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= | 3509 | integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== |
3429 | 3510 | ||
3430 | lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.3.0, lodash@~4.17.10: | 3511 | log-symbols@^4.0.0: |
3431 | version "4.17.11" | 3512 | version "4.0.0" |
3432 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" | 3513 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" |
3433 | integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== | 3514 | integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== |
3515 | dependencies: | ||
3516 | chalk "^4.0.0" | ||
3434 | 3517 | ||
3435 | longest@^1.0.1: | 3518 | longest-streak@^2.0.1: |
3436 | version "1.0.1" | 3519 | version "2.0.4" |
3437 | resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" | 3520 | resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" |
3438 | integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= | 3521 | integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== |
3439 | 3522 | ||
3440 | loose-envify@^1.0.0: | 3523 | loose-envify@^1.0.0: |
3441 | version "1.4.0" | 3524 | version "1.4.0" |
@@ -3444,39 +3527,50 @@ loose-envify@^1.0.0: | |||
3444 | dependencies: | 3527 | dependencies: |
3445 | js-tokens "^3.0.0 || ^4.0.0" | 3528 | js-tokens "^3.0.0 || ^4.0.0" |
3446 | 3529 | ||
3447 | loud-rejection@^1.0.0: | 3530 | lru-cache@^5.1.1: |
3448 | version "1.6.0" | 3531 | version "5.1.1" |
3449 | resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" | 3532 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" |
3450 | integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= | 3533 | integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== |
3534 | dependencies: | ||
3535 | yallist "^3.0.2" | ||
3536 | |||
3537 | lru-cache@^6.0.0: | ||
3538 | version "6.0.0" | ||
3539 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" | ||
3540 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== | ||
3451 | dependencies: | 3541 | dependencies: |
3452 | currently-unhandled "^0.4.1" | 3542 | yallist "^4.0.0" |
3453 | signal-exit "^3.0.0" | ||
3454 | 3543 | ||
3455 | lru-cache@^4.0.1: | 3544 | make-dir@^2.0.0: |
3456 | version "4.1.5" | 3545 | version "2.1.0" |
3457 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" | 3546 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" |
3458 | integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== | 3547 | integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== |
3459 | dependencies: | 3548 | dependencies: |
3460 | pseudomap "^1.0.2" | 3549 | pify "^4.0.1" |
3461 | yallist "^2.1.2" | 3550 | semver "^5.6.0" |
3462 | 3551 | ||
3463 | make-dir@^1.0.0: | 3552 | make-dir@^3.0.2: |
3464 | version "1.3.0" | 3553 | version "3.1.0" |
3465 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" | 3554 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" |
3466 | integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== | 3555 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== |
3467 | dependencies: | 3556 | dependencies: |
3468 | pify "^3.0.0" | 3557 | semver "^6.0.0" |
3469 | 3558 | ||
3470 | map-cache@^0.2.2: | 3559 | map-cache@^0.2.2: |
3471 | version "0.2.2" | 3560 | version "0.2.2" |
3472 | resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" | 3561 | resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" |
3473 | integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= | 3562 | integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= |
3474 | 3563 | ||
3475 | map-obj@^1.0.0, map-obj@^1.0.1: | 3564 | map-obj@^1.0.0: |
3476 | version "1.0.1" | 3565 | version "1.0.1" |
3477 | resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" | 3566 | resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" |
3478 | integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= | 3567 | integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= |
3479 | 3568 | ||
3569 | map-obj@^4.0.0: | ||
3570 | version "4.1.0" | ||
3571 | resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5" | ||
3572 | integrity sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g== | ||
3573 | |||
3480 | map-visit@^1.0.0: | 3574 | map-visit@^1.0.0: |
3481 | version "1.0.0" | 3575 | version "1.0.0" |
3482 | resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" | 3576 | resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" |
@@ -3484,10 +3578,22 @@ map-visit@^1.0.0: | |||
3484 | dependencies: | 3578 | dependencies: |
3485 | object-visit "^1.0.0" | 3579 | object-visit "^1.0.0" |
3486 | 3580 | ||
3487 | math-expression-evaluator@^1.2.14: | 3581 | markdown-escapes@^1.0.0: |
3488 | version "1.2.17" | 3582 | version "1.0.4" |
3489 | resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" | 3583 | resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" |
3490 | integrity sha1-3oGf282E3M2PrlnGrreWFbnSZqw= | 3584 | integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== |
3585 | |||
3586 | markdown-table@^2.0.0: | ||
3587 | version "2.0.0" | ||
3588 | resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" | ||
3589 | integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== | ||
3590 | dependencies: | ||
3591 | repeat-string "^1.0.0" | ||
3592 | |||
3593 | mathml-tag-names@^2.1.3: | ||
3594 | version "2.1.3" | ||
3595 | resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" | ||
3596 | integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== | ||
3491 | 3597 | ||
3492 | md5.js@^1.3.4: | 3598 | md5.js@^1.3.4: |
3493 | version "1.3.5" | 3599 | version "1.3.5" |
@@ -3498,14 +3604,14 @@ md5.js@^1.3.4: | |||
3498 | inherits "^2.0.1" | 3604 | inherits "^2.0.1" |
3499 | safe-buffer "^5.1.2" | 3605 | safe-buffer "^5.1.2" |
3500 | 3606 | ||
3501 | mem@^1.1.0: | 3607 | mdast-util-compact@^2.0.0: |
3502 | version "1.1.0" | 3608 | version "2.0.1" |
3503 | resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" | 3609 | resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490" |
3504 | integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= | 3610 | integrity sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA== |
3505 | dependencies: | 3611 | dependencies: |
3506 | mimic-fn "^1.0.0" | 3612 | unist-util-visit "^2.0.0" |
3507 | 3613 | ||
3508 | memory-fs@^0.4.0, memory-fs@~0.4.1: | 3614 | memory-fs@^0.4.1: |
3509 | version "0.4.1" | 3615 | version "0.4.1" |
3510 | resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" | 3616 | resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" |
3511 | integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= | 3617 | integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= |
@@ -3513,28 +3619,42 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: | |||
3513 | errno "^0.1.3" | 3619 | errno "^0.1.3" |
3514 | readable-stream "^2.0.1" | 3620 | readable-stream "^2.0.1" |
3515 | 3621 | ||
3516 | meow@^3.7.0: | 3622 | memory-fs@^0.5.0: |
3517 | version "3.7.0" | 3623 | version "0.5.0" |
3518 | resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" | 3624 | resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" |
3519 | integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= | 3625 | integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== |
3520 | dependencies: | 3626 | dependencies: |
3521 | camelcase-keys "^2.0.0" | 3627 | errno "^0.1.3" |
3522 | decamelize "^1.1.2" | 3628 | readable-stream "^2.0.1" |
3523 | loud-rejection "^1.0.0" | ||
3524 | map-obj "^1.0.1" | ||
3525 | minimist "^1.1.3" | ||
3526 | normalize-package-data "^2.3.4" | ||
3527 | object-assign "^4.0.1" | ||
3528 | read-pkg-up "^1.0.1" | ||
3529 | redent "^1.0.0" | ||
3530 | trim-newlines "^1.0.0" | ||
3531 | 3629 | ||
3532 | merge@^1.2.0: | 3630 | meow@^7.1.1: |
3533 | version "1.2.1" | 3631 | version "7.1.1" |
3534 | resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" | 3632 | resolved "https://registry.yarnpkg.com/meow/-/meow-7.1.1.tgz#7c01595e3d337fcb0ec4e8eed1666ea95903d306" |
3535 | integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== | 3633 | integrity sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA== |
3634 | dependencies: | ||
3635 | "@types/minimist" "^1.2.0" | ||
3636 | camelcase-keys "^6.2.2" | ||
3637 | decamelize-keys "^1.1.0" | ||
3638 | hard-rejection "^2.1.0" | ||
3639 | minimist-options "4.1.0" | ||
3640 | normalize-package-data "^2.5.0" | ||
3641 | read-pkg-up "^7.0.1" | ||
3642 | redent "^3.0.0" | ||
3643 | trim-newlines "^3.0.0" | ||
3644 | type-fest "^0.13.1" | ||
3645 | yargs-parser "^18.1.3" | ||
3646 | |||
3647 | merge-stream@^2.0.0: | ||
3648 | version "2.0.0" | ||
3649 | resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" | ||
3650 | integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== | ||
3536 | 3651 | ||
3537 | micromatch@^3.1.10, micromatch@^3.1.4: | 3652 | merge2@^1.3.0: |
3653 | version "1.4.1" | ||
3654 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" | ||
3655 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== | ||
3656 | |||
3657 | micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: | ||
3538 | version "3.1.10" | 3658 | version "3.1.10" |
3539 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" | 3659 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" |
3540 | integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== | 3660 | integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== |
@@ -3553,6 +3673,14 @@ micromatch@^3.1.10, micromatch@^3.1.4: | |||
3553 | snapdragon "^0.8.1" | 3673 | snapdragon "^0.8.1" |
3554 | to-regex "^3.0.2" | 3674 | to-regex "^3.0.2" |
3555 | 3675 | ||
3676 | micromatch@^4.0.2: | ||
3677 | version "4.0.2" | ||
3678 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" | ||
3679 | integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== | ||
3680 | dependencies: | ||
3681 | braces "^3.0.1" | ||
3682 | picomatch "^2.0.5" | ||
3683 | |||
3556 | miller-rabin@^4.0.0: | 3684 | miller-rabin@^4.0.0: |
3557 | version "4.0.1" | 3685 | version "4.0.1" |
3558 | resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" | 3686 | resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" |
@@ -3561,27 +3689,20 @@ miller-rabin@^4.0.0: | |||
3561 | bn.js "^4.0.0" | 3689 | bn.js "^4.0.0" |
3562 | brorand "^1.0.1" | 3690 | brorand "^1.0.1" |
3563 | 3691 | ||
3564 | mime-db@1.40.0: | 3692 | min-indent@^1.0.0: |
3565 | version "1.40.0" | 3693 | version "1.0.1" |
3566 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" | 3694 | resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" |
3567 | integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== | 3695 | integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== |
3568 | 3696 | ||
3569 | mime-types@^2.1.12, mime-types@~2.1.19: | 3697 | mini-css-extract-plugin@^0.11.2: |
3570 | version "2.1.24" | 3698 | version "0.11.2" |
3571 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" | 3699 | resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.2.tgz#e3af4d5e04fbcaaf11838ab230510073060b37bf" |
3572 | integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== | 3700 | integrity sha512-h2LknfX4U1kScXxH8xE9LCOqT5B+068EAj36qicMb8l4dqdJoyHcmWmpd+ueyZfgu/POvIn+teoUnTtei2ikug== |
3573 | dependencies: | 3701 | dependencies: |
3574 | mime-db "1.40.0" | 3702 | loader-utils "^1.1.0" |
3575 | 3703 | normalize-url "1.9.1" | |
3576 | mime@^1.4.1: | 3704 | schema-utils "^1.0.0" |
3577 | version "1.6.0" | 3705 | webpack-sources "^1.1.0" |
3578 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" | ||
3579 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== | ||
3580 | |||
3581 | mimic-fn@^1.0.0: | ||
3582 | version "1.2.0" | ||
3583 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" | ||
3584 | integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== | ||
3585 | 3706 | ||
3586 | minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: | 3707 | minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: |
3587 | version "1.0.1" | 3708 | version "1.0.1" |
@@ -3593,90 +3714,125 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: | |||
3593 | resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" | 3714 | resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" |
3594 | integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= | 3715 | integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= |
3595 | 3716 | ||
3596 | minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: | 3717 | minimatch@^3.0.4: |
3597 | version "3.0.4" | 3718 | version "3.0.4" |
3598 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" | 3719 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" |
3599 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== | 3720 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== |
3600 | dependencies: | 3721 | dependencies: |
3601 | brace-expansion "^1.1.7" | 3722 | brace-expansion "^1.1.7" |
3602 | 3723 | ||
3603 | minimist@0.0.8: | 3724 | minimist-options@4.1.0: |
3604 | version "0.0.8" | 3725 | version "4.1.0" |
3605 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" | 3726 | resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" |
3606 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= | 3727 | integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== |
3728 | dependencies: | ||
3729 | arrify "^1.0.1" | ||
3730 | is-plain-obj "^1.1.0" | ||
3731 | kind-of "^6.0.3" | ||
3607 | 3732 | ||
3608 | minimist@1.1.x: | 3733 | minimist@^1.2.0, minimist@^1.2.5: |
3609 | version "1.1.3" | 3734 | version "1.2.5" |
3610 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8" | 3735 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" |
3611 | integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag= | 3736 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== |
3612 | 3737 | ||
3613 | minimist@^1.1.3, minimist@^1.2.0: | 3738 | minipass-collect@^1.0.2: |
3614 | version "1.2.0" | 3739 | version "1.0.2" |
3615 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" | 3740 | resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" |
3616 | integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= | 3741 | integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== |
3742 | dependencies: | ||
3743 | minipass "^3.0.0" | ||
3617 | 3744 | ||
3618 | minipass@^2.2.1, minipass@^2.3.4: | 3745 | minipass-flush@^1.0.5: |
3619 | version "2.3.5" | 3746 | version "1.0.5" |
3620 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" | 3747 | resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" |
3621 | integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== | 3748 | integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== |
3622 | dependencies: | 3749 | dependencies: |
3623 | safe-buffer "^5.1.2" | 3750 | minipass "^3.0.0" |
3624 | yallist "^3.0.0" | ||
3625 | 3751 | ||
3626 | minizlib@^1.1.1: | 3752 | minipass-pipeline@^1.2.2: |
3627 | version "1.2.1" | 3753 | version "1.2.4" |
3628 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" | 3754 | resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" |
3629 | integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== | 3755 | integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== |
3756 | dependencies: | ||
3757 | minipass "^3.0.0" | ||
3758 | |||
3759 | minipass@^3.0.0, minipass@^3.1.1: | ||
3760 | version "3.1.3" | ||
3761 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" | ||
3762 | integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== | ||
3763 | dependencies: | ||
3764 | yallist "^4.0.0" | ||
3765 | |||
3766 | minizlib@^2.1.1: | ||
3767 | version "2.1.2" | ||
3768 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" | ||
3769 | integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== | ||
3630 | dependencies: | 3770 | dependencies: |
3631 | minipass "^2.2.1" | 3771 | minipass "^3.0.0" |
3772 | yallist "^4.0.0" | ||
3773 | |||
3774 | mississippi@^3.0.0: | ||
3775 | version "3.0.0" | ||
3776 | resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" | ||
3777 | integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== | ||
3778 | dependencies: | ||
3779 | concat-stream "^1.5.0" | ||
3780 | duplexify "^3.4.2" | ||
3781 | end-of-stream "^1.1.0" | ||
3782 | flush-write-stream "^1.0.0" | ||
3783 | from2 "^2.1.0" | ||
3784 | parallel-transform "^1.1.0" | ||
3785 | pump "^3.0.0" | ||
3786 | pumpify "^1.3.3" | ||
3787 | stream-each "^1.1.0" | ||
3788 | through2 "^2.0.0" | ||
3632 | 3789 | ||
3633 | mixin-deep@^1.2.0: | 3790 | mixin-deep@^1.2.0: |
3634 | version "1.3.1" | 3791 | version "1.3.2" |
3635 | resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" | 3792 | resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" |
3636 | integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ== | 3793 | integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== |
3637 | dependencies: | 3794 | dependencies: |
3638 | for-in "^1.0.2" | 3795 | for-in "^1.0.2" |
3639 | is-extendable "^1.0.1" | 3796 | is-extendable "^1.0.1" |
3640 | 3797 | ||
3641 | mixin-object@^2.0.1: | 3798 | mkdirp@^0.5.1, mkdirp@^0.5.3: |
3642 | version "2.0.1" | 3799 | version "0.5.5" |
3643 | resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" | 3800 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" |
3644 | integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4= | 3801 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== |
3645 | dependencies: | 3802 | dependencies: |
3646 | for-in "^0.1.3" | 3803 | minimist "^1.2.5" |
3647 | is-extendable "^0.1.1" | ||
3648 | 3804 | ||
3649 | "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: | 3805 | mkdirp@^1.0.3, mkdirp@^1.0.4: |
3650 | version "0.5.1" | 3806 | version "1.0.4" |
3651 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" | 3807 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" |
3652 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= | 3808 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== |
3809 | |||
3810 | move-concurrently@^1.0.1: | ||
3811 | version "1.0.1" | ||
3812 | resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" | ||
3813 | integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= | ||
3653 | dependencies: | 3814 | dependencies: |
3654 | minimist "0.0.8" | 3815 | aproba "^1.1.1" |
3816 | copy-concurrently "^1.0.0" | ||
3817 | fs-write-stream-atomic "^1.0.8" | ||
3818 | mkdirp "^0.5.1" | ||
3819 | rimraf "^2.5.4" | ||
3820 | run-queue "^1.0.3" | ||
3655 | 3821 | ||
3656 | ms@2.0.0: | 3822 | ms@2.0.0: |
3657 | version "2.0.0" | 3823 | version "2.0.0" |
3658 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" | 3824 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" |
3659 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= | 3825 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= |
3660 | 3826 | ||
3661 | ms@^2.1.1: | 3827 | ms@2.1.2: |
3662 | version "2.1.1" | 3828 | version "2.1.2" |
3663 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" | 3829 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" |
3664 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== | 3830 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== |
3665 | |||
3666 | mute-stream@0.0.5: | ||
3667 | version "0.0.5" | ||
3668 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" | ||
3669 | integrity sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA= | ||
3670 | |||
3671 | mute-stream@0.0.7: | ||
3672 | version "0.0.7" | ||
3673 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" | ||
3674 | integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= | ||
3675 | 3831 | ||
3676 | nan@^2.12.1, nan@^2.13.2: | 3832 | nan@^2.12.1: |
3677 | version "2.14.0" | 3833 | version "2.14.1" |
3678 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" | 3834 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" |
3679 | integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== | 3835 | integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== |
3680 | 3836 | ||
3681 | nanomatch@^1.2.9: | 3837 | nanomatch@^1.2.9: |
3682 | version "1.2.13" | 3838 | version "1.2.13" |
@@ -3700,47 +3856,20 @@ natural-compare@^1.4.0: | |||
3700 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" | 3856 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" |
3701 | integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= | 3857 | integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= |
3702 | 3858 | ||
3703 | needle@^2.2.1: | 3859 | neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: |
3704 | version "2.4.0" | 3860 | version "2.6.2" |
3705 | resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" | 3861 | resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" |
3706 | integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== | 3862 | integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== |
3707 | dependencies: | ||
3708 | debug "^3.2.6" | ||
3709 | iconv-lite "^0.4.4" | ||
3710 | sax "^1.2.4" | ||
3711 | |||
3712 | neo-async@^2.5.0: | ||
3713 | version "2.6.1" | ||
3714 | resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" | ||
3715 | integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== | ||
3716 | 3863 | ||
3717 | next-tick@^1.0.0: | 3864 | nice-try@^1.0.4: |
3718 | version "1.0.0" | 3865 | version "1.0.5" |
3719 | resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" | 3866 | resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" |
3720 | integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= | 3867 | integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== |
3721 | 3868 | ||
3722 | node-gyp@^3.8.0: | 3869 | node-libs-browser@^2.2.1: |
3723 | version "3.8.0" | 3870 | version "2.2.1" |
3724 | resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" | 3871 | resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" |
3725 | integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA== | 3872 | integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== |
3726 | dependencies: | ||
3727 | fstream "^1.0.0" | ||
3728 | glob "^7.0.3" | ||
3729 | graceful-fs "^4.1.2" | ||
3730 | mkdirp "^0.5.0" | ||
3731 | nopt "2 || 3" | ||
3732 | npmlog "0 || 1 || 2 || 3 || 4" | ||
3733 | osenv "0" | ||
3734 | request "^2.87.0" | ||
3735 | rimraf "2" | ||
3736 | semver "~5.3.0" | ||
3737 | tar "^2.0.0" | ||
3738 | which "1" | ||
3739 | |||
3740 | node-libs-browser@^2.0.0: | ||
3741 | version "2.2.0" | ||
3742 | resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.0.tgz#c72f60d9d46de08a940dedbb25f3ffa2f9bbaa77" | ||
3743 | integrity sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA== | ||
3744 | dependencies: | 3873 | dependencies: |
3745 | assert "^1.1.1" | 3874 | assert "^1.1.1" |
3746 | browserify-zlib "^0.2.0" | 3875 | browserify-zlib "^0.2.0" |
@@ -3752,7 +3881,7 @@ node-libs-browser@^2.0.0: | |||
3752 | events "^3.0.0" | 3881 | events "^3.0.0" |
3753 | https-browserify "^1.0.0" | 3882 | https-browserify "^1.0.0" |
3754 | os-browserify "^0.3.0" | 3883 | os-browserify "^0.3.0" |
3755 | path-browserify "0.0.0" | 3884 | path-browserify "0.0.1" |
3756 | process "^0.11.10" | 3885 | process "^0.11.10" |
3757 | punycode "^1.2.4" | 3886 | punycode "^1.2.4" |
3758 | querystring-es3 "^0.2.0" | 3887 | querystring-es3 "^0.2.0" |
@@ -3764,63 +3893,14 @@ node-libs-browser@^2.0.0: | |||
3764 | tty-browserify "0.0.0" | 3893 | tty-browserify "0.0.0" |
3765 | url "^0.11.0" | 3894 | url "^0.11.0" |
3766 | util "^0.11.0" | 3895 | util "^0.11.0" |
3767 | vm-browserify "0.0.4" | 3896 | vm-browserify "^1.0.1" |
3768 | 3897 | ||
3769 | node-pre-gyp@^0.12.0: | 3898 | node-releases@^1.1.61: |
3770 | version "0.12.0" | 3899 | version "1.1.61" |
3771 | resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" | 3900 | resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.61.tgz#707b0fca9ce4e11783612ba4a2fcba09047af16e" |
3772 | integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== | 3901 | integrity sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g== |
3773 | dependencies: | ||
3774 | detect-libc "^1.0.2" | ||
3775 | mkdirp "^0.5.1" | ||
3776 | needle "^2.2.1" | ||
3777 | nopt "^4.0.1" | ||
3778 | npm-packlist "^1.1.6" | ||
3779 | npmlog "^4.0.2" | ||
3780 | rc "^1.2.7" | ||
3781 | rimraf "^2.6.1" | ||
3782 | semver "^5.3.0" | ||
3783 | tar "^4" | ||
3784 | |||
3785 | node-sass@^4.12.0: | ||
3786 | version "4.12.0" | ||
3787 | resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017" | ||
3788 | integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ== | ||
3789 | dependencies: | ||
3790 | async-foreach "^0.1.3" | ||
3791 | chalk "^1.1.1" | ||
3792 | cross-spawn "^3.0.0" | ||
3793 | gaze "^1.0.0" | ||
3794 | get-stdin "^4.0.1" | ||
3795 | glob "^7.0.3" | ||
3796 | in-publish "^2.0.0" | ||
3797 | lodash "^4.17.11" | ||
3798 | meow "^3.7.0" | ||
3799 | mkdirp "^0.5.1" | ||
3800 | nan "^2.13.2" | ||
3801 | node-gyp "^3.8.0" | ||
3802 | npmlog "^4.0.0" | ||
3803 | request "^2.88.0" | ||
3804 | sass-graph "^2.2.4" | ||
3805 | stdout-stream "^1.4.0" | ||
3806 | "true-case-path" "^1.0.2" | ||
3807 | |||
3808 | "nopt@2 || 3": | ||
3809 | version "3.0.6" | ||
3810 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" | ||
3811 | integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= | ||
3812 | dependencies: | ||
3813 | abbrev "1" | ||
3814 | 3902 | ||
3815 | nopt@^4.0.1: | 3903 | normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: |
3816 | version "4.0.1" | ||
3817 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" | ||
3818 | integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= | ||
3819 | dependencies: | ||
3820 | abbrev "1" | ||
3821 | osenv "^0.1.4" | ||
3822 | |||
3823 | normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: | ||
3824 | version "2.5.0" | 3904 | version "2.5.0" |
3825 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" | 3905 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" |
3826 | integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== | 3906 | integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== |
@@ -3837,7 +3917,7 @@ normalize-path@^2.1.1: | |||
3837 | dependencies: | 3917 | dependencies: |
3838 | remove-trailing-separator "^1.0.1" | 3918 | remove-trailing-separator "^1.0.1" |
3839 | 3919 | ||
3840 | normalize-path@^3.0.0: | 3920 | normalize-path@^3.0.0, normalize-path@~3.0.0: |
3841 | version "3.0.0" | 3921 | version "3.0.0" |
3842 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" | 3922 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" |
3843 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== | 3923 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== |
@@ -3847,7 +3927,12 @@ normalize-range@^0.1.2: | |||
3847 | resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" | 3927 | resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" |
3848 | integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= | 3928 | integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= |
3849 | 3929 | ||
3850 | normalize-url@^1.4.0: | 3930 | normalize-selector@^0.2.0: |
3931 | version "0.2.0" | ||
3932 | resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03" | ||
3933 | integrity sha1-0LFF62kRicY6eNIB3E/bEpPvDAM= | ||
3934 | |||
3935 | normalize-url@1.9.1: | ||
3851 | version "1.9.1" | 3936 | version "1.9.1" |
3852 | resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" | 3937 | resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" |
3853 | integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= | 3938 | integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= |
@@ -3857,51 +3942,11 @@ normalize-url@^1.4.0: | |||
3857 | query-string "^4.1.0" | 3942 | query-string "^4.1.0" |
3858 | sort-keys "^1.0.0" | 3943 | sort-keys "^1.0.0" |
3859 | 3944 | ||
3860 | npm-bundled@^1.0.1: | ||
3861 | version "1.0.6" | ||
3862 | resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" | ||
3863 | integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== | ||
3864 | |||
3865 | npm-packlist@^1.1.6: | ||
3866 | version "1.4.1" | ||
3867 | resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" | ||
3868 | integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw== | ||
3869 | dependencies: | ||
3870 | ignore-walk "^3.0.1" | ||
3871 | npm-bundled "^1.0.1" | ||
3872 | |||
3873 | npm-run-path@^2.0.0: | ||
3874 | version "2.0.2" | ||
3875 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" | ||
3876 | integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= | ||
3877 | dependencies: | ||
3878 | path-key "^2.0.0" | ||
3879 | |||
3880 | "npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: | ||
3881 | version "4.1.2" | ||
3882 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" | ||
3883 | integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== | ||
3884 | dependencies: | ||
3885 | are-we-there-yet "~1.1.2" | ||
3886 | console-control-strings "~1.1.0" | ||
3887 | gauge "~2.7.3" | ||
3888 | set-blocking "~2.0.0" | ||
3889 | |||
3890 | num2fraction@^1.2.2: | 3945 | num2fraction@^1.2.2: |
3891 | version "1.2.2" | 3946 | version "1.2.2" |
3892 | resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" | 3947 | resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" |
3893 | integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= | 3948 | integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= |
3894 | 3949 | ||
3895 | number-is-nan@^1.0.0: | ||
3896 | version "1.0.1" | ||
3897 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" | ||
3898 | integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= | ||
3899 | |||
3900 | oauth-sign@~0.9.0: | ||
3901 | version "0.9.0" | ||
3902 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" | ||
3903 | integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== | ||
3904 | |||
3905 | object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: | 3950 | object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: |
3906 | version "4.1.1" | 3951 | version "4.1.1" |
3907 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" | 3952 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" |
@@ -3916,7 +3961,12 @@ object-copy@^0.1.0: | |||
3916 | define-property "^0.2.5" | 3961 | define-property "^0.2.5" |
3917 | kind-of "^3.0.3" | 3962 | kind-of "^3.0.3" |
3918 | 3963 | ||
3919 | object-keys@^1.0.12: | 3964 | object-inspect@^1.7.0, object-inspect@^1.8.0: |
3965 | version "1.8.0" | ||
3966 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" | ||
3967 | integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== | ||
3968 | |||
3969 | object-keys@^1.0.12, object-keys@^1.1.1: | ||
3920 | version "1.1.1" | 3970 | version "1.1.1" |
3921 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" | 3971 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" |
3922 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== | 3972 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== |
@@ -3928,6 +3978,25 @@ object-visit@^1.0.0: | |||
3928 | dependencies: | 3978 | dependencies: |
3929 | isobject "^3.0.0" | 3979 | isobject "^3.0.0" |
3930 | 3980 | ||
3981 | object.assign@^4.1.0: | ||
3982 | version "4.1.1" | ||
3983 | resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" | ||
3984 | integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== | ||
3985 | dependencies: | ||
3986 | define-properties "^1.1.3" | ||
3987 | es-abstract "^1.18.0-next.0" | ||
3988 | has-symbols "^1.0.1" | ||
3989 | object-keys "^1.1.1" | ||
3990 | |||
3991 | object.entries@^1.1.2: | ||
3992 | version "1.1.2" | ||
3993 | resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" | ||
3994 | integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== | ||
3995 | dependencies: | ||
3996 | define-properties "^1.1.3" | ||
3997 | es-abstract "^1.17.5" | ||
3998 | has "^1.0.3" | ||
3999 | |||
3931 | object.pick@^1.3.0: | 4000 | object.pick@^1.3.0: |
3932 | version "1.3.0" | 4001 | version "1.3.0" |
3933 | resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" | 4002 | resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" |
@@ -3935,81 +4004,40 @@ object.pick@^1.3.0: | |||
3935 | dependencies: | 4004 | dependencies: |
3936 | isobject "^3.0.1" | 4005 | isobject "^3.0.1" |
3937 | 4006 | ||
3938 | once@^1.3.0: | 4007 | object.values@^1.1.1: |
4008 | version "1.1.1" | ||
4009 | resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" | ||
4010 | integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== | ||
4011 | dependencies: | ||
4012 | define-properties "^1.1.3" | ||
4013 | es-abstract "^1.17.0-next.1" | ||
4014 | function-bind "^1.1.1" | ||
4015 | has "^1.0.3" | ||
4016 | |||
4017 | once@^1.3.0, once@^1.3.1, once@^1.4.0: | ||
3939 | version "1.4.0" | 4018 | version "1.4.0" |
3940 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" | 4019 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" |
3941 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= | 4020 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= |
3942 | dependencies: | 4021 | dependencies: |
3943 | wrappy "1" | 4022 | wrappy "1" |
3944 | 4023 | ||
3945 | onetime@^1.0.0: | 4024 | optionator@^0.9.1: |
3946 | version "1.1.0" | 4025 | version "0.9.1" |
3947 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" | 4026 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" |
3948 | integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= | 4027 | integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== |
3949 | |||
3950 | onetime@^2.0.0: | ||
3951 | version "2.0.1" | ||
3952 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" | ||
3953 | integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= | ||
3954 | dependencies: | 4028 | dependencies: |
3955 | mimic-fn "^1.0.0" | 4029 | deep-is "^0.1.3" |
3956 | 4030 | fast-levenshtein "^2.0.6" | |
3957 | optionator@^0.8.1, optionator@^0.8.2: | 4031 | levn "^0.4.1" |
3958 | version "0.8.2" | 4032 | prelude-ls "^1.2.1" |
3959 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" | 4033 | type-check "^0.4.0" |
3960 | integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= | 4034 | word-wrap "^1.2.3" |
3961 | dependencies: | ||
3962 | deep-is "~0.1.3" | ||
3963 | fast-levenshtein "~2.0.4" | ||
3964 | levn "~0.3.0" | ||
3965 | prelude-ls "~1.1.2" | ||
3966 | type-check "~0.3.2" | ||
3967 | wordwrap "~1.0.0" | ||
3968 | 4035 | ||
3969 | os-browserify@^0.3.0: | 4036 | os-browserify@^0.3.0: |
3970 | version "0.3.0" | 4037 | version "0.3.0" |
3971 | resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" | 4038 | resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" |
3972 | integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= | 4039 | integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= |
3973 | 4040 | ||
3974 | os-homedir@^1.0.0: | ||
3975 | version "1.0.2" | ||
3976 | resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" | ||
3977 | integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= | ||
3978 | |||
3979 | os-locale@^1.4.0: | ||
3980 | version "1.4.0" | ||
3981 | resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" | ||
3982 | integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= | ||
3983 | dependencies: | ||
3984 | lcid "^1.0.0" | ||
3985 | |||
3986 | os-locale@^2.0.0: | ||
3987 | version "2.1.0" | ||
3988 | resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" | ||
3989 | integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA== | ||
3990 | dependencies: | ||
3991 | execa "^0.7.0" | ||
3992 | lcid "^1.0.0" | ||
3993 | mem "^1.1.0" | ||
3994 | |||
3995 | os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: | ||
3996 | version "1.0.2" | ||
3997 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" | ||
3998 | integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= | ||
3999 | |||
4000 | osenv@0, osenv@^0.1.4: | ||
4001 | version "0.1.5" | ||
4002 | resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" | ||
4003 | integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== | ||
4004 | dependencies: | ||
4005 | os-homedir "^1.0.0" | ||
4006 | os-tmpdir "^1.0.0" | ||
4007 | |||
4008 | p-finally@^1.0.0: | ||
4009 | version "1.0.0" | ||
4010 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" | ||
4011 | integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= | ||
4012 | |||
4013 | p-limit@^1.1.0: | 4041 | p-limit@^1.1.0: |
4014 | version "1.3.0" | 4042 | version "1.3.0" |
4015 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" | 4043 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" |
@@ -4017,6 +4045,20 @@ p-limit@^1.1.0: | |||
4017 | dependencies: | 4045 | dependencies: |
4018 | p-try "^1.0.0" | 4046 | p-try "^1.0.0" |
4019 | 4047 | ||
4048 | p-limit@^2.0.0, p-limit@^2.2.0: | ||
4049 | version "2.3.0" | ||
4050 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" | ||
4051 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== | ||
4052 | dependencies: | ||
4053 | p-try "^2.0.0" | ||
4054 | |||
4055 | p-limit@^3.0.2: | ||
4056 | version "3.0.2" | ||
4057 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" | ||
4058 | integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== | ||
4059 | dependencies: | ||
4060 | p-try "^2.0.0" | ||
4061 | |||
4020 | p-locate@^2.0.0: | 4062 | p-locate@^2.0.0: |
4021 | version "2.0.0" | 4063 | version "2.0.0" |
4022 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" | 4064 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" |
@@ -4024,28 +4066,81 @@ p-locate@^2.0.0: | |||
4024 | dependencies: | 4066 | dependencies: |
4025 | p-limit "^1.1.0" | 4067 | p-limit "^1.1.0" |
4026 | 4068 | ||
4069 | p-locate@^3.0.0: | ||
4070 | version "3.0.0" | ||
4071 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" | ||
4072 | integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== | ||
4073 | dependencies: | ||
4074 | p-limit "^2.0.0" | ||
4075 | |||
4076 | p-locate@^4.1.0: | ||
4077 | version "4.1.0" | ||
4078 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" | ||
4079 | integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== | ||
4080 | dependencies: | ||
4081 | p-limit "^2.2.0" | ||
4082 | |||
4083 | p-map@^4.0.0: | ||
4084 | version "4.0.0" | ||
4085 | resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" | ||
4086 | integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== | ||
4087 | dependencies: | ||
4088 | aggregate-error "^3.0.0" | ||
4089 | |||
4027 | p-try@^1.0.0: | 4090 | p-try@^1.0.0: |
4028 | version "1.0.0" | 4091 | version "1.0.0" |
4029 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" | 4092 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" |
4030 | integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= | 4093 | integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= |
4031 | 4094 | ||
4095 | p-try@^2.0.0: | ||
4096 | version "2.2.0" | ||
4097 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" | ||
4098 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== | ||
4099 | |||
4032 | pako@~1.0.5: | 4100 | pako@~1.0.5: |
4033 | version "1.0.10" | 4101 | version "1.0.11" |
4034 | resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" | 4102 | resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" |
4035 | integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== | 4103 | integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== |
4036 | 4104 | ||
4037 | parse-asn1@^5.0.0: | 4105 | parallel-transform@^1.1.0: |
4038 | version "5.1.4" | 4106 | version "1.2.0" |
4039 | resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" | 4107 | resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" |
4040 | integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== | 4108 | integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== |
4109 | dependencies: | ||
4110 | cyclist "^1.0.1" | ||
4111 | inherits "^2.0.3" | ||
4112 | readable-stream "^2.1.5" | ||
4113 | |||
4114 | parent-module@^1.0.0: | ||
4115 | version "1.0.1" | ||
4116 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" | ||
4117 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== | ||
4118 | dependencies: | ||
4119 | callsites "^3.0.0" | ||
4120 | |||
4121 | parse-asn1@^5.0.0, parse-asn1@^5.1.5: | ||
4122 | version "5.1.6" | ||
4123 | resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" | ||
4124 | integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== | ||
4041 | dependencies: | 4125 | dependencies: |
4042 | asn1.js "^4.0.0" | 4126 | asn1.js "^5.2.0" |
4043 | browserify-aes "^1.0.0" | 4127 | browserify-aes "^1.0.0" |
4044 | create-hash "^1.1.0" | ||
4045 | evp_bytestokey "^1.0.0" | 4128 | evp_bytestokey "^1.0.0" |
4046 | pbkdf2 "^3.0.3" | 4129 | pbkdf2 "^3.0.3" |
4047 | safe-buffer "^5.1.1" | 4130 | safe-buffer "^5.1.1" |
4048 | 4131 | ||
4132 | parse-entities@^2.0.0: | ||
4133 | version "2.0.0" | ||
4134 | resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" | ||
4135 | integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== | ||
4136 | dependencies: | ||
4137 | character-entities "^1.0.0" | ||
4138 | character-entities-legacy "^1.0.0" | ||
4139 | character-reference-invalid "^1.0.0" | ||
4140 | is-alphanumerical "^1.0.0" | ||
4141 | is-decimal "^1.0.0" | ||
4142 | is-hexadecimal "^1.0.0" | ||
4143 | |||
4049 | parse-json@^2.2.0: | 4144 | parse-json@^2.2.0: |
4050 | version "2.2.0" | 4145 | version "2.2.0" |
4051 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" | 4146 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" |
@@ -4053,62 +4148,66 @@ parse-json@^2.2.0: | |||
4053 | dependencies: | 4148 | dependencies: |
4054 | error-ex "^1.2.0" | 4149 | error-ex "^1.2.0" |
4055 | 4150 | ||
4151 | parse-json@^5.0.0: | ||
4152 | version "5.1.0" | ||
4153 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" | ||
4154 | integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== | ||
4155 | dependencies: | ||
4156 | "@babel/code-frame" "^7.0.0" | ||
4157 | error-ex "^1.3.1" | ||
4158 | json-parse-even-better-errors "^2.3.0" | ||
4159 | lines-and-columns "^1.1.6" | ||
4160 | |||
4161 | parse-passwd@^1.0.0: | ||
4162 | version "1.0.0" | ||
4163 | resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" | ||
4164 | integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= | ||
4165 | |||
4056 | pascalcase@^0.1.1: | 4166 | pascalcase@^0.1.1: |
4057 | version "0.1.1" | 4167 | version "0.1.1" |
4058 | resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" | 4168 | resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" |
4059 | integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= | 4169 | integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= |
4060 | 4170 | ||
4061 | path-browserify@0.0.0: | 4171 | path-browserify@0.0.1: |
4062 | version "0.0.0" | 4172 | version "0.0.1" |
4063 | resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" | 4173 | resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" |
4064 | integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo= | 4174 | integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== |
4065 | 4175 | ||
4066 | path-dirname@^1.0.0: | 4176 | path-dirname@^1.0.0: |
4067 | version "1.0.2" | 4177 | version "1.0.2" |
4068 | resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" | 4178 | resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" |
4069 | integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= | 4179 | integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= |
4070 | 4180 | ||
4071 | path-exists@^2.0.0: | ||
4072 | version "2.1.0" | ||
4073 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" | ||
4074 | integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= | ||
4075 | dependencies: | ||
4076 | pinkie-promise "^2.0.0" | ||
4077 | |||
4078 | path-exists@^3.0.0: | 4181 | path-exists@^3.0.0: |
4079 | version "3.0.0" | 4182 | version "3.0.0" |
4080 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" | 4183 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" |
4081 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= | 4184 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= |
4082 | 4185 | ||
4083 | path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: | 4186 | path-exists@^4.0.0: |
4187 | version "4.0.0" | ||
4188 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" | ||
4189 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== | ||
4190 | |||
4191 | path-is-absolute@^1.0.0: | ||
4084 | version "1.0.1" | 4192 | version "1.0.1" |
4085 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" | 4193 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" |
4086 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= | 4194 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= |
4087 | 4195 | ||
4088 | path-is-inside@^1.0.1, path-is-inside@^1.0.2: | 4196 | path-key@^2.0.1: |
4089 | version "1.0.2" | ||
4090 | resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" | ||
4091 | integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= | ||
4092 | |||
4093 | path-key@^2.0.0: | ||
4094 | version "2.0.1" | 4197 | version "2.0.1" |
4095 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" | 4198 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" |
4096 | integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= | 4199 | integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= |
4097 | 4200 | ||
4201 | path-key@^3.1.0: | ||
4202 | version "3.1.1" | ||
4203 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" | ||
4204 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== | ||
4205 | |||
4098 | path-parse@^1.0.6: | 4206 | path-parse@^1.0.6: |
4099 | version "1.0.6" | 4207 | version "1.0.6" |
4100 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" | 4208 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" |
4101 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== | 4209 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== |
4102 | 4210 | ||
4103 | path-type@^1.0.0: | ||
4104 | version "1.1.0" | ||
4105 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" | ||
4106 | integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= | ||
4107 | dependencies: | ||
4108 | graceful-fs "^4.1.2" | ||
4109 | pify "^2.0.0" | ||
4110 | pinkie-promise "^2.0.0" | ||
4111 | |||
4112 | path-type@^2.0.0: | 4211 | path-type@^2.0.0: |
4113 | version "2.0.0" | 4212 | version "2.0.0" |
4114 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" | 4213 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" |
@@ -4116,10 +4215,15 @@ path-type@^2.0.0: | |||
4116 | dependencies: | 4215 | dependencies: |
4117 | pify "^2.0.0" | 4216 | pify "^2.0.0" |
4118 | 4217 | ||
4218 | path-type@^4.0.0: | ||
4219 | version "4.0.0" | ||
4220 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" | ||
4221 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== | ||
4222 | |||
4119 | pbkdf2@^3.0.3: | 4223 | pbkdf2@^3.0.3: |
4120 | version "3.0.17" | 4224 | version "3.1.1" |
4121 | resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" | 4225 | resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" |
4122 | integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== | 4226 | integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== |
4123 | dependencies: | 4227 | dependencies: |
4124 | create-hash "^1.1.2" | 4228 | create-hash "^1.1.2" |
4125 | create-hmac "^1.1.4" | 4229 | create-hmac "^1.1.4" |
@@ -4127,32 +4231,20 @@ pbkdf2@^3.0.3: | |||
4127 | safe-buffer "^5.0.1" | 4231 | safe-buffer "^5.0.1" |
4128 | sha.js "^2.4.8" | 4232 | sha.js "^2.4.8" |
4129 | 4233 | ||
4130 | performance-now@^2.1.0: | 4234 | picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: |
4131 | version "2.1.0" | 4235 | version "2.2.2" |
4132 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" | 4236 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" |
4133 | integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= | 4237 | integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== |
4134 | 4238 | ||
4135 | pify@^2.0.0: | 4239 | pify@^2.0.0: |
4136 | version "2.3.0" | 4240 | version "2.3.0" |
4137 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" | 4241 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" |
4138 | integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= | 4242 | integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= |
4139 | 4243 | ||
4140 | pify@^3.0.0: | 4244 | pify@^4.0.1: |
4141 | version "3.0.0" | 4245 | version "4.0.1" |
4142 | resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" | 4246 | resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" |
4143 | integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= | 4247 | integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== |
4144 | |||
4145 | pinkie-promise@^2.0.0: | ||
4146 | version "2.0.1" | ||
4147 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" | ||
4148 | integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= | ||
4149 | dependencies: | ||
4150 | pinkie "^2.0.0" | ||
4151 | |||
4152 | pinkie@^2.0.0: | ||
4153 | version "2.0.4" | ||
4154 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" | ||
4155 | integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= | ||
4156 | 4248 | ||
4157 | pkg-dir@^2.0.0: | 4249 | pkg-dir@^2.0.0: |
4158 | version "2.0.0" | 4250 | version "2.0.0" |
@@ -4161,350 +4253,168 @@ pkg-dir@^2.0.0: | |||
4161 | dependencies: | 4253 | dependencies: |
4162 | find-up "^2.1.0" | 4254 | find-up "^2.1.0" |
4163 | 4255 | ||
4164 | pluralize@^1.2.1: | 4256 | pkg-dir@^3.0.0: |
4165 | version "1.2.1" | 4257 | version "3.0.0" |
4166 | resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" | 4258 | resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" |
4167 | integrity sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU= | 4259 | integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== |
4260 | dependencies: | ||
4261 | find-up "^3.0.0" | ||
4168 | 4262 | ||
4169 | pluralize@^7.0.0: | 4263 | pkg-dir@^4.1.0: |
4170 | version "7.0.0" | 4264 | version "4.2.0" |
4171 | resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" | 4265 | resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" |
4172 | integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== | 4266 | integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== |
4267 | dependencies: | ||
4268 | find-up "^4.0.0" | ||
4173 | 4269 | ||
4174 | posix-character-classes@^0.1.0: | 4270 | posix-character-classes@^0.1.0: |
4175 | version "0.1.1" | 4271 | version "0.1.1" |
4176 | resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" | 4272 | resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" |
4177 | integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= | 4273 | integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= |
4178 | 4274 | ||
4179 | postcss-calc@^5.2.0: | 4275 | postcss-html@^0.36.0: |
4180 | version "5.3.1" | 4276 | version "0.36.0" |
4181 | resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" | 4277 | resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-0.36.0.tgz#b40913f94eaacc2453fd30a1327ad6ee1f88b204" |
4182 | integrity sha1-d7rnypKK2FcW4v2kLyYb98HWW14= | 4278 | integrity sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw== |
4183 | dependencies: | ||
4184 | postcss "^5.0.2" | ||
4185 | postcss-message-helpers "^2.0.0" | ||
4186 | reduce-css-calc "^1.2.6" | ||
4187 | |||
4188 | postcss-colormin@^2.1.8: | ||
4189 | version "2.2.2" | ||
4190 | resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" | ||
4191 | integrity sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks= | ||
4192 | dependencies: | ||
4193 | colormin "^1.0.5" | ||
4194 | postcss "^5.0.13" | ||
4195 | postcss-value-parser "^3.2.3" | ||
4196 | |||
4197 | postcss-convert-values@^2.3.4: | ||
4198 | version "2.6.1" | ||
4199 | resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" | ||
4200 | integrity sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0= | ||
4201 | dependencies: | 4279 | dependencies: |
4202 | postcss "^5.0.11" | 4280 | htmlparser2 "^3.10.0" |
4203 | postcss-value-parser "^3.1.2" | ||
4204 | 4281 | ||
4205 | postcss-discard-comments@^2.0.4: | 4282 | postcss-less@^3.1.4: |
4206 | version "2.0.4" | 4283 | version "3.1.4" |
4207 | resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" | 4284 | resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.4.tgz#369f58642b5928ef898ffbc1a6e93c958304c5ad" |
4208 | integrity sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0= | 4285 | integrity sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA== |
4209 | dependencies: | 4286 | dependencies: |
4210 | postcss "^5.0.14" | 4287 | postcss "^7.0.14" |
4211 | 4288 | ||
4212 | postcss-discard-duplicates@^2.0.1: | 4289 | postcss-media-query-parser@^0.2.3: |
4213 | version "2.1.0" | 4290 | version "0.2.3" |
4214 | resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" | 4291 | resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" |
4215 | integrity sha1-uavye4isGIFYpesSq8riAmO5GTI= | 4292 | integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ= |
4216 | dependencies: | ||
4217 | postcss "^5.0.4" | ||
4218 | |||
4219 | postcss-discard-empty@^2.0.1: | ||
4220 | version "2.1.0" | ||
4221 | resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" | ||
4222 | integrity sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU= | ||
4223 | dependencies: | ||
4224 | postcss "^5.0.14" | ||
4225 | |||
4226 | postcss-discard-overridden@^0.1.1: | ||
4227 | version "0.1.1" | ||
4228 | resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" | ||
4229 | integrity sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg= | ||
4230 | dependencies: | ||
4231 | postcss "^5.0.16" | ||
4232 | |||
4233 | postcss-discard-unused@^2.2.1: | ||
4234 | version "2.2.3" | ||
4235 | resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" | ||
4236 | integrity sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM= | ||
4237 | dependencies: | ||
4238 | postcss "^5.0.14" | ||
4239 | uniqs "^2.0.0" | ||
4240 | |||
4241 | postcss-filter-plugins@^2.0.0: | ||
4242 | version "2.0.3" | ||
4243 | resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz#82245fdf82337041645e477114d8e593aa18b8ec" | ||
4244 | integrity sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ== | ||
4245 | dependencies: | ||
4246 | postcss "^5.0.4" | ||
4247 | |||
4248 | postcss-merge-idents@^2.1.5: | ||
4249 | version "2.1.7" | ||
4250 | resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" | ||
4251 | integrity sha1-TFUwMTwI4dWzu/PSu8dH4njuonA= | ||
4252 | dependencies: | ||
4253 | has "^1.0.1" | ||
4254 | postcss "^5.0.10" | ||
4255 | postcss-value-parser "^3.1.1" | ||
4256 | |||
4257 | postcss-merge-longhand@^2.0.1: | ||
4258 | version "2.0.2" | ||
4259 | resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" | ||
4260 | integrity sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg= | ||
4261 | dependencies: | ||
4262 | postcss "^5.0.4" | ||
4263 | |||
4264 | postcss-merge-rules@^2.0.3: | ||
4265 | version "2.1.2" | ||
4266 | resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" | ||
4267 | integrity sha1-0d9d+qexrMO+VT8OnhDofGG19yE= | ||
4268 | dependencies: | ||
4269 | browserslist "^1.5.2" | ||
4270 | caniuse-api "^1.5.2" | ||
4271 | postcss "^5.0.4" | ||
4272 | postcss-selector-parser "^2.2.2" | ||
4273 | vendors "^1.0.0" | ||
4274 | 4293 | ||
4275 | postcss-message-helpers@^2.0.0: | 4294 | postcss-modules-extract-imports@^2.0.0: |
4276 | version "2.0.0" | 4295 | version "2.0.0" |
4277 | resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" | 4296 | resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" |
4278 | integrity sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4= | 4297 | integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== |
4279 | |||
4280 | postcss-minify-font-values@^1.0.2: | ||
4281 | version "1.0.5" | ||
4282 | resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" | ||
4283 | integrity sha1-S1jttWZB66fIR0qzUmyv17vey2k= | ||
4284 | dependencies: | ||
4285 | object-assign "^4.0.1" | ||
4286 | postcss "^5.0.4" | ||
4287 | postcss-value-parser "^3.0.2" | ||
4288 | |||
4289 | postcss-minify-gradients@^1.0.1: | ||
4290 | version "1.0.5" | ||
4291 | resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" | ||
4292 | integrity sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE= | ||
4293 | dependencies: | ||
4294 | postcss "^5.0.12" | ||
4295 | postcss-value-parser "^3.3.0" | ||
4296 | |||
4297 | postcss-minify-params@^1.0.4: | ||
4298 | version "1.2.2" | ||
4299 | resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" | ||
4300 | integrity sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM= | ||
4301 | dependencies: | ||
4302 | alphanum-sort "^1.0.1" | ||
4303 | postcss "^5.0.2" | ||
4304 | postcss-value-parser "^3.0.2" | ||
4305 | uniqs "^2.0.0" | ||
4306 | |||
4307 | postcss-minify-selectors@^2.0.4: | ||
4308 | version "2.1.1" | ||
4309 | resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" | ||
4310 | integrity sha1-ssapjAByz5G5MtGkllCBFDEXNb8= | ||
4311 | dependencies: | ||
4312 | alphanum-sort "^1.0.2" | ||
4313 | has "^1.0.1" | ||
4314 | postcss "^5.0.14" | ||
4315 | postcss-selector-parser "^2.0.0" | ||
4316 | |||
4317 | postcss-modules-extract-imports@^1.2.0: | ||
4318 | version "1.2.1" | ||
4319 | resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a" | ||
4320 | integrity sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw== | ||
4321 | dependencies: | 4298 | dependencies: |
4322 | postcss "^6.0.1" | 4299 | postcss "^7.0.5" |
4323 | 4300 | ||
4324 | postcss-modules-local-by-default@^1.2.0: | 4301 | postcss-modules-local-by-default@^3.0.3: |
4325 | version "1.2.0" | 4302 | version "3.0.3" |
4326 | resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" | 4303 | resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" |
4327 | integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= | 4304 | integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== |
4328 | dependencies: | ||
4329 | css-selector-tokenizer "^0.7.0" | ||
4330 | postcss "^6.0.1" | ||
4331 | |||
4332 | postcss-modules-scope@^1.1.0: | ||
4333 | version "1.1.0" | ||
4334 | resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" | ||
4335 | integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= | ||
4336 | dependencies: | ||
4337 | css-selector-tokenizer "^0.7.0" | ||
4338 | postcss "^6.0.1" | ||
4339 | |||
4340 | postcss-modules-values@^1.3.0: | ||
4341 | version "1.3.0" | ||
4342 | resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" | ||
4343 | integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= | ||
4344 | dependencies: | 4305 | dependencies: |
4345 | icss-replace-symbols "^1.1.0" | 4306 | icss-utils "^4.1.1" |
4346 | postcss "^6.0.1" | 4307 | postcss "^7.0.32" |
4308 | postcss-selector-parser "^6.0.2" | ||
4309 | postcss-value-parser "^4.1.0" | ||
4347 | 4310 | ||
4348 | postcss-normalize-charset@^1.1.0: | 4311 | postcss-modules-scope@^2.2.0: |
4349 | version "1.1.1" | 4312 | version "2.2.0" |
4350 | resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" | 4313 | resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" |
4351 | integrity sha1-757nEhLX/nWceO0WL2HtYrXLk/E= | 4314 | integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== |
4352 | dependencies: | 4315 | dependencies: |
4353 | postcss "^5.0.5" | 4316 | postcss "^7.0.6" |
4317 | postcss-selector-parser "^6.0.0" | ||
4354 | 4318 | ||
4355 | postcss-normalize-url@^3.0.7: | 4319 | postcss-modules-values@^3.0.0: |
4356 | version "3.0.8" | 4320 | version "3.0.0" |
4357 | resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" | 4321 | resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" |
4358 | integrity sha1-EI90s/L82viRov+j6kWSJ5/HgiI= | 4322 | integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== |
4359 | dependencies: | 4323 | dependencies: |
4360 | is-absolute-url "^2.0.0" | 4324 | icss-utils "^4.0.0" |
4361 | normalize-url "^1.4.0" | 4325 | postcss "^7.0.6" |
4362 | postcss "^5.0.14" | ||
4363 | postcss-value-parser "^3.2.3" | ||
4364 | 4326 | ||
4365 | postcss-ordered-values@^2.1.0: | 4327 | postcss-resolve-nested-selector@^0.1.1: |
4366 | version "2.2.3" | 4328 | version "0.1.1" |
4367 | resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" | 4329 | resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" |
4368 | integrity sha1-7sbCpntsQSqNsgQud/6NpD+VwR0= | 4330 | integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4= |
4369 | dependencies: | ||
4370 | postcss "^5.0.4" | ||
4371 | postcss-value-parser "^3.0.1" | ||
4372 | 4331 | ||
4373 | postcss-reduce-idents@^2.2.2: | 4332 | postcss-safe-parser@^4.0.2: |
4374 | version "2.4.0" | 4333 | version "4.0.2" |
4375 | resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" | 4334 | resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96" |
4376 | integrity sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM= | 4335 | integrity sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g== |
4377 | dependencies: | 4336 | dependencies: |
4378 | postcss "^5.0.4" | 4337 | postcss "^7.0.26" |
4379 | postcss-value-parser "^3.0.2" | ||
4380 | 4338 | ||
4381 | postcss-reduce-initial@^1.0.0: | 4339 | postcss-sass@^0.4.4: |
4382 | version "1.0.1" | 4340 | version "0.4.4" |
4383 | resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" | 4341 | resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.4.4.tgz#91f0f3447b45ce373227a98b61f8d8f0785285a3" |
4384 | integrity sha1-aPgGlfBF0IJjqHmtJA343WT2ROo= | 4342 | integrity sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg== |
4385 | dependencies: | 4343 | dependencies: |
4386 | postcss "^5.0.4" | 4344 | gonzales-pe "^4.3.0" |
4345 | postcss "^7.0.21" | ||
4387 | 4346 | ||
4388 | postcss-reduce-transforms@^1.0.3: | 4347 | postcss-scss@^2.1.1: |
4389 | version "1.0.4" | 4348 | version "2.1.1" |
4390 | resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" | 4349 | resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.1.1.tgz#ec3a75fa29a55e016b90bf3269026c53c1d2b383" |
4391 | integrity sha1-/3b02CEkN7McKYpC0uFEQCV3GuE= | 4350 | integrity sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA== |
4392 | dependencies: | 4351 | dependencies: |
4393 | has "^1.0.1" | 4352 | postcss "^7.0.6" |
4394 | postcss "^5.0.8" | ||
4395 | postcss-value-parser "^3.0.1" | ||
4396 | 4353 | ||
4397 | postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: | 4354 | postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: |
4398 | version "2.2.3" | 4355 | version "6.0.3" |
4399 | resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" | 4356 | resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.3.tgz#766d77728728817cc140fa1ac6da5e77f9fada98" |
4400 | integrity sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A= | 4357 | integrity sha512-0ClFaY4X1ra21LRqbW6y3rUbWcxnSVkDFG57R7Nxus9J9myPFlv+jYDMohzpkBx0RrjjiqjtycpchQ+PLGmZ9w== |
4401 | dependencies: | 4358 | dependencies: |
4402 | flatten "^1.0.2" | 4359 | cssesc "^3.0.0" |
4403 | indexes-of "^1.0.1" | 4360 | indexes-of "^1.0.1" |
4404 | uniq "^1.0.1" | 4361 | uniq "^1.0.1" |
4362 | util-deprecate "^1.0.2" | ||
4405 | 4363 | ||
4406 | postcss-svgo@^2.1.1: | 4364 | postcss-syntax@^0.36.2: |
4407 | version "2.1.6" | 4365 | version "0.36.2" |
4408 | resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" | 4366 | resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" |
4409 | integrity sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0= | 4367 | integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== |
4410 | dependencies: | ||
4411 | is-svg "^2.0.0" | ||
4412 | postcss "^5.0.14" | ||
4413 | postcss-value-parser "^3.2.3" | ||
4414 | svgo "^0.7.0" | ||
4415 | |||
4416 | postcss-unique-selectors@^2.0.2: | ||
4417 | version "2.0.2" | ||
4418 | resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" | ||
4419 | integrity sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0= | ||
4420 | dependencies: | ||
4421 | alphanum-sort "^1.0.1" | ||
4422 | postcss "^5.0.4" | ||
4423 | uniqs "^2.0.0" | ||
4424 | 4368 | ||
4425 | postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: | 4369 | postcss-value-parser@^4.1.0: |
4426 | version "3.3.1" | 4370 | version "4.1.0" |
4427 | resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" | 4371 | resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" |
4428 | integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== | 4372 | integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== |
4429 | |||
4430 | postcss-zindex@^2.0.1: | ||
4431 | version "2.2.0" | ||
4432 | resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" | ||
4433 | integrity sha1-0hCd3AVbka9n/EyzsCWUZjnSryI= | ||
4434 | dependencies: | ||
4435 | has "^1.0.1" | ||
4436 | postcss "^5.0.4" | ||
4437 | uniqs "^2.0.0" | ||
4438 | |||
4439 | postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: | ||
4440 | version "5.2.18" | ||
4441 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" | ||
4442 | integrity sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg== | ||
4443 | dependencies: | ||
4444 | chalk "^1.1.3" | ||
4445 | js-base64 "^2.1.9" | ||
4446 | source-map "^0.5.6" | ||
4447 | supports-color "^3.2.3" | ||
4448 | 4373 | ||
4449 | postcss@^6.0.1: | 4374 | postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: |
4450 | version "6.0.23" | 4375 | version "7.0.34" |
4451 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" | 4376 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.34.tgz#f2baf57c36010df7de4009940f21532c16d65c20" |
4452 | integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== | 4377 | integrity sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw== |
4453 | dependencies: | 4378 | dependencies: |
4454 | chalk "^2.4.1" | 4379 | chalk "^2.4.2" |
4455 | source-map "^0.6.1" | 4380 | source-map "^0.6.1" |
4456 | supports-color "^5.4.0" | 4381 | supports-color "^6.1.0" |
4457 | 4382 | ||
4458 | prelude-ls@~1.1.2: | 4383 | prelude-ls@^1.2.1: |
4459 | version "1.1.2" | 4384 | version "1.2.1" |
4460 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" | 4385 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" |
4461 | integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= | 4386 | integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== |
4462 | 4387 | ||
4463 | prepend-http@^1.0.0: | 4388 | prepend-http@^1.0.0: |
4464 | version "1.0.4" | 4389 | version "1.0.4" |
4465 | resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" | 4390 | resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" |
4466 | integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= | 4391 | integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= |
4467 | 4392 | ||
4468 | private@^0.1.6, private@^0.1.8: | ||
4469 | version "0.1.8" | ||
4470 | resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" | ||
4471 | integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== | ||
4472 | |||
4473 | process-nextick-args@~2.0.0: | 4393 | process-nextick-args@~2.0.0: |
4474 | version "2.0.0" | 4394 | version "2.0.1" |
4475 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" | 4395 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" |
4476 | integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== | 4396 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== |
4477 | 4397 | ||
4478 | process@^0.11.10: | 4398 | process@^0.11.10: |
4479 | version "0.11.10" | 4399 | version "0.11.10" |
4480 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" | 4400 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" |
4481 | integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= | 4401 | integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= |
4482 | 4402 | ||
4483 | progress@^1.1.8: | ||
4484 | version "1.1.8" | ||
4485 | resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" | ||
4486 | integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= | ||
4487 | |||
4488 | progress@^2.0.0: | 4403 | progress@^2.0.0: |
4489 | version "2.0.3" | 4404 | version "2.0.3" |
4490 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" | 4405 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" |
4491 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== | 4406 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== |
4492 | 4407 | ||
4408 | promise-inflight@^1.0.1: | ||
4409 | version "1.0.1" | ||
4410 | resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" | ||
4411 | integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= | ||
4412 | |||
4493 | prr@~1.0.1: | 4413 | prr@~1.0.1: |
4494 | version "1.0.1" | 4414 | version "1.0.1" |
4495 | resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" | 4415 | resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" |
4496 | integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= | 4416 | integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= |
4497 | 4417 | ||
4498 | pseudomap@^1.0.2: | ||
4499 | version "1.0.2" | ||
4500 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" | ||
4501 | integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= | ||
4502 | |||
4503 | psl@^1.1.24: | ||
4504 | version "1.1.31" | ||
4505 | resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" | ||
4506 | integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== | ||
4507 | |||
4508 | public-encrypt@^4.0.0: | 4418 | public-encrypt@^4.0.0: |
4509 | version "4.0.3" | 4419 | version "4.0.3" |
4510 | resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" | 4420 | resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" |
@@ -4517,12 +4427,37 @@ public-encrypt@^4.0.0: | |||
4517 | randombytes "^2.0.1" | 4427 | randombytes "^2.0.1" |
4518 | safe-buffer "^5.1.2" | 4428 | safe-buffer "^5.1.2" |
4519 | 4429 | ||
4430 | pump@^2.0.0: | ||
4431 | version "2.0.1" | ||
4432 | resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" | ||
4433 | integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== | ||
4434 | dependencies: | ||
4435 | end-of-stream "^1.1.0" | ||
4436 | once "^1.3.1" | ||
4437 | |||
4438 | pump@^3.0.0: | ||
4439 | version "3.0.0" | ||
4440 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" | ||
4441 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== | ||
4442 | dependencies: | ||
4443 | end-of-stream "^1.1.0" | ||
4444 | once "^1.3.1" | ||
4445 | |||
4446 | pumpify@^1.3.3: | ||
4447 | version "1.5.1" | ||
4448 | resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" | ||
4449 | integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== | ||
4450 | dependencies: | ||
4451 | duplexify "^3.6.0" | ||
4452 | inherits "^2.0.3" | ||
4453 | pump "^2.0.0" | ||
4454 | |||
4520 | punycode@1.3.2: | 4455 | punycode@1.3.2: |
4521 | version "1.3.2" | 4456 | version "1.3.2" |
4522 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" | 4457 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" |
4523 | integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= | 4458 | integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= |
4524 | 4459 | ||
4525 | punycode@^1.2.4, punycode@^1.4.1: | 4460 | punycode@^1.2.4: |
4526 | version "1.4.1" | 4461 | version "1.4.1" |
4527 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" | 4462 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" |
4528 | integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= | 4463 | integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= |
@@ -4538,19 +4473,9 @@ pure-extras@^1.0.0: | |||
4538 | integrity sha1-N+PMNZDLqFCYFFTNpdso4npjhxo= | 4473 | integrity sha1-N+PMNZDLqFCYFFTNpdso4npjhxo= |
4539 | 4474 | ||
4540 | purecss@^1.0.0: | 4475 | purecss@^1.0.0: |
4541 | version "1.0.0" | 4476 | version "1.0.1" |
4542 | resolved "https://registry.yarnpkg.com/purecss/-/purecss-1.0.0.tgz#3dbcd9e2a7592448a69acb705cce16311bf4b785" | 4477 | resolved "https://registry.yarnpkg.com/purecss/-/purecss-1.0.1.tgz#c83d84326a10beb5c3b36d20c0254e946e5568a7" |
4543 | integrity sha512-gfC78WCOWNnfkzulx9aoWwcl+0JflhwKeJ+k9s/ZyIawfYNA4bqBmt0DtfgtQK9iuYMtGfbdE8R2AQMjSWR2VQ== | 4478 | integrity sha512-mTUc5ZzpzafswEhCmTDfSRMMyRFdLYdd+KywMwnBC/MuA/Th7jug2z0Xso4WkxvtxoU/BS9aRb7WnBNyuA7YJQ== |
4544 | |||
4545 | q@^1.1.2: | ||
4546 | version "1.5.1" | ||
4547 | resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" | ||
4548 | integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= | ||
4549 | |||
4550 | qs@~6.5.2: | ||
4551 | version "6.5.2" | ||
4552 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" | ||
4553 | integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== | ||
4554 | 4479 | ||
4555 | query-string@^4.1.0: | 4480 | query-string@^4.1.0: |
4556 | version "4.3.4" | 4481 | version "4.3.4" |
@@ -4570,7 +4495,12 @@ querystring@0.2.0: | |||
4570 | resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" | 4495 | resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" |
4571 | integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= | 4496 | integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= |
4572 | 4497 | ||
4573 | randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: | 4498 | quick-lru@^4.0.1: |
4499 | version "4.0.1" | ||
4500 | resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" | ||
4501 | integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== | ||
4502 | |||
4503 | randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: | ||
4574 | version "2.1.0" | 4504 | version "2.1.0" |
4575 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" | 4505 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" |
4576 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== | 4506 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== |
@@ -4585,24 +4515,6 @@ randomfill@^1.0.3: | |||
4585 | randombytes "^2.0.5" | 4515 | randombytes "^2.0.5" |
4586 | safe-buffer "^5.1.0" | 4516 | safe-buffer "^5.1.0" |
4587 | 4517 | ||
4588 | rc@^1.2.7: | ||
4589 | version "1.2.8" | ||
4590 | resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" | ||
4591 | integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== | ||
4592 | dependencies: | ||
4593 | deep-extend "^0.6.0" | ||
4594 | ini "~1.3.0" | ||
4595 | minimist "^1.2.0" | ||
4596 | strip-json-comments "~2.0.1" | ||
4597 | |||
4598 | read-pkg-up@^1.0.1: | ||
4599 | version "1.0.1" | ||
4600 | resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" | ||
4601 | integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= | ||
4602 | dependencies: | ||
4603 | find-up "^1.0.0" | ||
4604 | read-pkg "^1.0.0" | ||
4605 | |||
4606 | read-pkg-up@^2.0.0: | 4518 | read-pkg-up@^2.0.0: |
4607 | version "2.0.0" | 4519 | version "2.0.0" |
4608 | resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" | 4520 | resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" |
@@ -4611,14 +4523,14 @@ read-pkg-up@^2.0.0: | |||
4611 | find-up "^2.0.0" | 4523 | find-up "^2.0.0" |
4612 | read-pkg "^2.0.0" | 4524 | read-pkg "^2.0.0" |
4613 | 4525 | ||
4614 | read-pkg@^1.0.0: | 4526 | read-pkg-up@^7.0.1: |
4615 | version "1.1.0" | 4527 | version "7.0.1" |
4616 | resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" | 4528 | resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" |
4617 | integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= | 4529 | integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== |
4618 | dependencies: | 4530 | dependencies: |
4619 | load-json-file "^1.0.0" | 4531 | find-up "^4.1.0" |
4620 | normalize-package-data "^2.3.2" | 4532 | read-pkg "^5.2.0" |
4621 | path-type "^1.0.0" | 4533 | type-fest "^0.8.1" |
4622 | 4534 | ||
4623 | read-pkg@^2.0.0: | 4535 | read-pkg@^2.0.0: |
4624 | version "2.0.0" | 4536 | version "2.0.0" |
@@ -4629,10 +4541,20 @@ read-pkg@^2.0.0: | |||
4629 | normalize-package-data "^2.3.2" | 4541 | normalize-package-data "^2.3.2" |
4630 | path-type "^2.0.0" | 4542 | path-type "^2.0.0" |
4631 | 4543 | ||
4632 | readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6: | 4544 | read-pkg@^5.2.0: |
4633 | version "2.3.6" | 4545 | version "5.2.0" |
4634 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" | 4546 | resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" |
4635 | integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== | 4547 | integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== |
4548 | dependencies: | ||
4549 | "@types/normalize-package-data" "^2.4.0" | ||
4550 | normalize-package-data "^2.5.0" | ||
4551 | parse-json "^5.0.0" | ||
4552 | type-fest "^0.6.0" | ||
4553 | |||
4554 | "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: | ||
4555 | version "2.3.7" | ||
4556 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" | ||
4557 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== | ||
4636 | dependencies: | 4558 | dependencies: |
4637 | core-util-is "~1.0.0" | 4559 | core-util-is "~1.0.0" |
4638 | inherits "~2.0.3" | 4560 | inherits "~2.0.3" |
@@ -4642,6 +4564,15 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable | |||
4642 | string_decoder "~1.1.1" | 4564 | string_decoder "~1.1.1" |
4643 | util-deprecate "~1.0.1" | 4565 | util-deprecate "~1.0.1" |
4644 | 4566 | ||
4567 | readable-stream@^3.1.1, readable-stream@^3.6.0: | ||
4568 | version "3.6.0" | ||
4569 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" | ||
4570 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== | ||
4571 | dependencies: | ||
4572 | inherits "^2.0.3" | ||
4573 | string_decoder "^1.1.1" | ||
4574 | util-deprecate "^1.0.1" | ||
4575 | |||
4645 | readdirp@^2.2.1: | 4576 | readdirp@^2.2.1: |
4646 | version "2.2.1" | 4577 | version "2.2.1" |
4647 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" | 4578 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" |
@@ -4651,57 +4582,44 @@ readdirp@^2.2.1: | |||
4651 | micromatch "^3.1.10" | 4582 | micromatch "^3.1.10" |
4652 | readable-stream "^2.0.2" | 4583 | readable-stream "^2.0.2" |
4653 | 4584 | ||
4654 | readline2@^1.0.1: | 4585 | readdirp@~3.4.0: |
4655 | version "1.0.1" | 4586 | version "3.4.0" |
4656 | resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" | 4587 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" |
4657 | integrity sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU= | 4588 | integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== |
4658 | dependencies: | ||
4659 | code-point-at "^1.0.0" | ||
4660 | is-fullwidth-code-point "^1.0.0" | ||
4661 | mute-stream "0.0.5" | ||
4662 | |||
4663 | redent@^1.0.0: | ||
4664 | version "1.0.0" | ||
4665 | resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" | ||
4666 | integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= | ||
4667 | dependencies: | 4589 | dependencies: |
4668 | indent-string "^2.1.0" | 4590 | picomatch "^2.2.1" |
4669 | strip-indent "^1.0.1" | ||
4670 | 4591 | ||
4671 | reduce-css-calc@^1.2.6: | 4592 | redent@^3.0.0: |
4672 | version "1.3.0" | 4593 | version "3.0.0" |
4673 | resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" | 4594 | resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" |
4674 | integrity sha1-dHyRTgSWFKTJz7umKYca0dKSdxY= | 4595 | integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== |
4675 | dependencies: | 4596 | dependencies: |
4676 | balanced-match "^0.4.2" | 4597 | indent-string "^4.0.0" |
4677 | math-expression-evaluator "^1.2.14" | 4598 | strip-indent "^3.0.0" |
4678 | reduce-function-call "^1.0.1" | ||
4679 | 4599 | ||
4680 | reduce-function-call@^1.0.1: | 4600 | regenerate-unicode-properties@^8.2.0: |
4681 | version "1.0.2" | 4601 | version "8.2.0" |
4682 | resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" | 4602 | resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" |
4683 | integrity sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk= | 4603 | integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== |
4684 | dependencies: | 4604 | dependencies: |
4685 | balanced-match "^0.4.2" | 4605 | regenerate "^1.4.0" |
4686 | 4606 | ||
4687 | regenerate@^1.2.1: | 4607 | regenerate@^1.4.0: |
4688 | version "1.4.0" | 4608 | version "1.4.1" |
4689 | resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" | 4609 | resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" |
4690 | integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== | 4610 | integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== |
4691 | 4611 | ||
4692 | regenerator-runtime@^0.11.0: | 4612 | regenerator-runtime@^0.13.4: |
4693 | version "0.11.1" | 4613 | version "0.13.7" |
4694 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" | 4614 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" |
4695 | integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== | 4615 | integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== |
4696 | 4616 | ||
4697 | regenerator-transform@^0.10.0: | 4617 | regenerator-transform@^0.14.2: |
4698 | version "0.10.1" | 4618 | version "0.14.5" |
4699 | resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" | 4619 | resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" |
4700 | integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== | 4620 | integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== |
4701 | dependencies: | 4621 | dependencies: |
4702 | babel-runtime "^6.18.0" | 4622 | "@babel/runtime" "^7.8.4" |
4703 | babel-types "^6.19.0" | ||
4704 | private "^0.1.6" | ||
4705 | 4623 | ||
4706 | regex-not@^1.0.0, regex-not@^1.0.2: | 4624 | regex-not@^1.0.0, regex-not@^1.0.2: |
4707 | version "1.0.2" | 4625 | version "1.0.2" |
@@ -4711,41 +4629,86 @@ regex-not@^1.0.0, regex-not@^1.0.2: | |||
4711 | extend-shallow "^3.0.2" | 4629 | extend-shallow "^3.0.2" |
4712 | safe-regex "^1.1.0" | 4630 | safe-regex "^1.1.0" |
4713 | 4631 | ||
4714 | regexpp@^1.0.1: | 4632 | regexpp@^3.1.0: |
4715 | version "1.1.0" | 4633 | version "3.1.0" |
4716 | resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" | 4634 | resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" |
4717 | integrity sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw== | 4635 | integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== |
4718 | 4636 | ||
4719 | regexpu-core@^1.0.0: | 4637 | regexpu-core@^4.7.0: |
4720 | version "1.0.0" | 4638 | version "4.7.1" |
4721 | resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" | 4639 | resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" |
4722 | integrity sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs= | 4640 | integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== |
4723 | dependencies: | 4641 | dependencies: |
4724 | regenerate "^1.2.1" | 4642 | regenerate "^1.4.0" |
4725 | regjsgen "^0.2.0" | 4643 | regenerate-unicode-properties "^8.2.0" |
4726 | regjsparser "^0.1.4" | 4644 | regjsgen "^0.5.1" |
4727 | 4645 | regjsparser "^0.6.4" | |
4728 | regexpu-core@^2.0.0: | 4646 | unicode-match-property-ecmascript "^1.0.4" |
4729 | version "2.0.0" | 4647 | unicode-match-property-value-ecmascript "^1.2.0" |
4730 | resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" | 4648 | |
4731 | integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA= | 4649 | regjsgen@^0.5.1: |
4732 | dependencies: | 4650 | version "0.5.2" |
4733 | regenerate "^1.2.1" | 4651 | resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" |
4734 | regjsgen "^0.2.0" | 4652 | integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== |
4735 | regjsparser "^0.1.4" | ||
4736 | |||
4737 | regjsgen@^0.2.0: | ||
4738 | version "0.2.0" | ||
4739 | resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" | ||
4740 | integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= | ||
4741 | 4653 | ||
4742 | regjsparser@^0.1.4: | 4654 | regjsparser@^0.6.4: |
4743 | version "0.1.5" | 4655 | version "0.6.4" |
4744 | resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" | 4656 | resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" |
4745 | integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= | 4657 | integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== |
4746 | dependencies: | 4658 | dependencies: |
4747 | jsesc "~0.5.0" | 4659 | jsesc "~0.5.0" |
4748 | 4660 | ||
4661 | remark-parse@^8.0.0: | ||
4662 | version "8.0.3" | ||
4663 | resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" | ||
4664 | integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== | ||
4665 | dependencies: | ||
4666 | ccount "^1.0.0" | ||
4667 | collapse-white-space "^1.0.2" | ||
4668 | is-alphabetical "^1.0.0" | ||
4669 | is-decimal "^1.0.0" | ||
4670 | is-whitespace-character "^1.0.0" | ||
4671 | is-word-character "^1.0.0" | ||
4672 | markdown-escapes "^1.0.0" | ||
4673 | parse-entities "^2.0.0" | ||
4674 | repeat-string "^1.5.4" | ||
4675 | state-toggle "^1.0.0" | ||
4676 | trim "0.0.1" | ||
4677 | trim-trailing-lines "^1.0.0" | ||
4678 | unherit "^1.0.4" | ||
4679 | unist-util-remove-position "^2.0.0" | ||
4680 | vfile-location "^3.0.0" | ||
4681 | xtend "^4.0.1" | ||
4682 | |||
4683 | remark-stringify@^8.0.0: | ||
4684 | version "8.1.1" | ||
4685 | resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.1.1.tgz#e2a9dc7a7bf44e46a155ec78996db896780d8ce5" | ||
4686 | integrity sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A== | ||
4687 | dependencies: | ||
4688 | ccount "^1.0.0" | ||
4689 | is-alphanumeric "^1.0.0" | ||
4690 | is-decimal "^1.0.0" | ||
4691 | is-whitespace-character "^1.0.0" | ||
4692 | longest-streak "^2.0.1" | ||
4693 | markdown-escapes "^1.0.0" | ||
4694 | markdown-table "^2.0.0" | ||
4695 | mdast-util-compact "^2.0.0" | ||
4696 | parse-entities "^2.0.0" | ||
4697 | repeat-string "^1.5.4" | ||
4698 | state-toggle "^1.0.0" | ||
4699 | stringify-entities "^3.0.0" | ||
4700 | unherit "^1.0.4" | ||
4701 | xtend "^4.0.1" | ||
4702 | |||
4703 | remark@^12.0.0: | ||
4704 | version "12.0.1" | ||
4705 | resolved "https://registry.yarnpkg.com/remark/-/remark-12.0.1.tgz#f1ddf68db7be71ca2bad0a33cd3678b86b9c709f" | ||
4706 | integrity sha512-gS7HDonkdIaHmmP/+shCPejCEEW+liMp/t/QwmF0Xt47Rpuhl32lLtDV1uKWvGoq+kxr5jSgg5oAIpGuyULjUw== | ||
4707 | dependencies: | ||
4708 | remark-parse "^8.0.0" | ||
4709 | remark-stringify "^8.0.0" | ||
4710 | unified "^9.0.0" | ||
4711 | |||
4749 | remove-trailing-separator@^1.0.1: | 4712 | remove-trailing-separator@^1.0.1: |
4750 | version "1.1.0" | 4713 | version "1.1.0" |
4751 | resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" | 4714 | resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" |
@@ -4756,114 +4719,99 @@ repeat-element@^1.1.2: | |||
4756 | resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" | 4719 | resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" |
4757 | integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== | 4720 | integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== |
4758 | 4721 | ||
4759 | repeat-string@^1.5.2, repeat-string@^1.6.1: | 4722 | repeat-string@^1.0.0, repeat-string@^1.5.4, repeat-string@^1.6.1: |
4760 | version "1.6.1" | 4723 | version "1.6.1" |
4761 | resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" | 4724 | resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" |
4762 | integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= | 4725 | integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= |
4763 | 4726 | ||
4764 | repeating@^2.0.0: | 4727 | replace-ext@1.0.0: |
4765 | version "2.0.1" | 4728 | version "1.0.0" |
4766 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" | 4729 | resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" |
4767 | integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= | 4730 | integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= |
4768 | dependencies: | ||
4769 | is-finite "^1.0.0" | ||
4770 | |||
4771 | request@^2.87.0, request@^2.88.0: | ||
4772 | version "2.88.0" | ||
4773 | resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" | ||
4774 | integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== | ||
4775 | dependencies: | ||
4776 | aws-sign2 "~0.7.0" | ||
4777 | aws4 "^1.8.0" | ||
4778 | caseless "~0.12.0" | ||
4779 | combined-stream "~1.0.6" | ||
4780 | extend "~3.0.2" | ||
4781 | forever-agent "~0.6.1" | ||
4782 | form-data "~2.3.2" | ||
4783 | har-validator "~5.1.0" | ||
4784 | http-signature "~1.2.0" | ||
4785 | is-typedarray "~1.0.0" | ||
4786 | isstream "~0.1.2" | ||
4787 | json-stringify-safe "~5.0.1" | ||
4788 | mime-types "~2.1.19" | ||
4789 | oauth-sign "~0.9.0" | ||
4790 | performance-now "^2.1.0" | ||
4791 | qs "~6.5.2" | ||
4792 | safe-buffer "^5.1.2" | ||
4793 | tough-cookie "~2.4.3" | ||
4794 | tunnel-agent "^0.6.0" | ||
4795 | uuid "^3.3.2" | ||
4796 | 4731 | ||
4797 | require-directory@^2.1.1: | 4732 | require-directory@^2.1.1: |
4798 | version "2.1.1" | 4733 | version "2.1.1" |
4799 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" | 4734 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" |
4800 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= | 4735 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= |
4801 | 4736 | ||
4802 | require-main-filename@^1.0.1: | 4737 | require-main-filename@^2.0.0: |
4803 | version "1.0.1" | 4738 | version "2.0.0" |
4804 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" | 4739 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" |
4805 | integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= | 4740 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== |
4806 | 4741 | ||
4807 | require-uncached@^1.0.2, require-uncached@^1.0.3: | 4742 | resolve-cwd@^2.0.0: |
4808 | version "1.0.3" | 4743 | version "2.0.0" |
4809 | resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" | 4744 | resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" |
4810 | integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= | 4745 | integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= |
4811 | dependencies: | 4746 | dependencies: |
4812 | caller-path "^0.1.0" | 4747 | resolve-from "^3.0.0" |
4813 | resolve-from "^1.0.0" | ||
4814 | 4748 | ||
4815 | resolve-from@^1.0.0: | 4749 | resolve-dir@^1.0.0, resolve-dir@^1.0.1: |
4816 | version "1.0.1" | 4750 | version "1.0.1" |
4817 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" | 4751 | resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" |
4818 | integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= | 4752 | integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= |
4753 | dependencies: | ||
4754 | expand-tilde "^2.0.0" | ||
4755 | global-modules "^1.0.0" | ||
4756 | |||
4757 | resolve-from@^3.0.0: | ||
4758 | version "3.0.0" | ||
4759 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" | ||
4760 | integrity sha1-six699nWiBvItuZTM17rywoYh0g= | ||
4761 | |||
4762 | resolve-from@^4.0.0: | ||
4763 | version "4.0.0" | ||
4764 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" | ||
4765 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== | ||
4766 | |||
4767 | resolve-from@^5.0.0: | ||
4768 | version "5.0.0" | ||
4769 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" | ||
4770 | integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== | ||
4819 | 4771 | ||
4820 | resolve-url@^0.2.1: | 4772 | resolve-url@^0.2.1: |
4821 | version "0.2.1" | 4773 | version "0.2.1" |
4822 | resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" | 4774 | resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" |
4823 | integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= | 4775 | integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= |
4824 | 4776 | ||
4825 | resolve@^1.10.0, resolve@^1.5.0: | 4777 | resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: |
4826 | version "1.11.0" | 4778 | version "1.17.0" |
4827 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232" | 4779 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" |
4828 | integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw== | 4780 | integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== |
4829 | dependencies: | 4781 | dependencies: |
4830 | path-parse "^1.0.6" | 4782 | path-parse "^1.0.6" |
4831 | 4783 | ||
4832 | restore-cursor@^1.0.1: | ||
4833 | version "1.0.1" | ||
4834 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" | ||
4835 | integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= | ||
4836 | dependencies: | ||
4837 | exit-hook "^1.0.0" | ||
4838 | onetime "^1.0.0" | ||
4839 | |||
4840 | restore-cursor@^2.0.0: | ||
4841 | version "2.0.0" | ||
4842 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" | ||
4843 | integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= | ||
4844 | dependencies: | ||
4845 | onetime "^2.0.0" | ||
4846 | signal-exit "^3.0.2" | ||
4847 | |||
4848 | ret@~0.1.10: | 4784 | ret@~0.1.10: |
4849 | version "0.1.15" | 4785 | version "0.1.15" |
4850 | resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" | 4786 | resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" |
4851 | integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== | 4787 | integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== |
4852 | 4788 | ||
4853 | right-align@^0.1.1: | 4789 | reusify@^1.0.4: |
4854 | version "0.1.3" | 4790 | version "1.0.4" |
4855 | resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" | 4791 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" |
4856 | integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8= | 4792 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== |
4857 | dependencies: | ||
4858 | align-text "^0.1.1" | ||
4859 | 4793 | ||
4860 | rimraf@2, rimraf@^2.6.1, rimraf@~2.6.2: | 4794 | rimraf@2.6.3: |
4861 | version "2.6.3" | 4795 | version "2.6.3" |
4862 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" | 4796 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" |
4863 | integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== | 4797 | integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== |
4864 | dependencies: | 4798 | dependencies: |
4865 | glob "^7.1.3" | 4799 | glob "^7.1.3" |
4866 | 4800 | ||
4801 | rimraf@^2.5.4, rimraf@^2.6.3: | ||
4802 | version "2.7.1" | ||
4803 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" | ||
4804 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== | ||
4805 | dependencies: | ||
4806 | glob "^7.1.3" | ||
4807 | |||
4808 | rimraf@^3.0.2: | ||
4809 | version "3.0.2" | ||
4810 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" | ||
4811 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== | ||
4812 | dependencies: | ||
4813 | glob "^7.1.3" | ||
4814 | |||
4867 | ripemd160@^2.0.0, ripemd160@^2.0.1: | 4815 | ripemd160@^2.0.0, ripemd160@^2.0.1: |
4868 | version "2.0.2" | 4816 | version "2.0.2" |
4869 | resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" | 4817 | resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" |
@@ -4872,38 +4820,24 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: | |||
4872 | hash-base "^3.0.0" | 4820 | hash-base "^3.0.0" |
4873 | inherits "^2.0.1" | 4821 | inherits "^2.0.1" |
4874 | 4822 | ||
4875 | run-async@^0.1.0: | 4823 | run-parallel@^1.1.9: |
4876 | version "0.1.0" | 4824 | version "1.1.9" |
4877 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" | 4825 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" |
4878 | integrity sha1-yK1KXhEGYeQCp9IbUw4AnyX444k= | 4826 | integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== |
4879 | dependencies: | ||
4880 | once "^1.3.0" | ||
4881 | |||
4882 | run-async@^2.2.0: | ||
4883 | version "2.3.0" | ||
4884 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" | ||
4885 | integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= | ||
4886 | dependencies: | ||
4887 | is-promise "^2.1.0" | ||
4888 | 4827 | ||
4889 | rx-lite-aggregates@^4.0.8: | 4828 | run-queue@^1.0.0, run-queue@^1.0.3: |
4890 | version "4.0.8" | 4829 | version "1.0.3" |
4891 | resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" | 4830 | resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" |
4892 | integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= | 4831 | integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= |
4893 | dependencies: | 4832 | dependencies: |
4894 | rx-lite "*" | 4833 | aproba "^1.1.1" |
4895 | |||
4896 | rx-lite@*, rx-lite@^4.0.8: | ||
4897 | version "4.0.8" | ||
4898 | resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" | ||
4899 | integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= | ||
4900 | 4834 | ||
4901 | rx-lite@^3.1.2: | 4835 | safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: |
4902 | version "3.1.2" | 4836 | version "5.2.1" |
4903 | resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" | 4837 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" |
4904 | integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= | 4838 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== |
4905 | 4839 | ||
4906 | safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: | 4840 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: |
4907 | version "5.1.2" | 4841 | version "5.1.2" |
4908 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" | 4842 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" |
4909 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== | 4843 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== |
@@ -4915,63 +4849,28 @@ safe-regex@^1.1.0: | |||
4915 | dependencies: | 4849 | dependencies: |
4916 | ret "~0.1.10" | 4850 | ret "~0.1.10" |
4917 | 4851 | ||
4918 | "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: | 4852 | safer-buffer@^2.1.0: |
4919 | version "2.1.2" | 4853 | version "2.1.2" |
4920 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" | 4854 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" |
4921 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== | 4855 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== |
4922 | 4856 | ||
4923 | sass-graph@^2.2.4: | 4857 | sass-loader@^10.0.2: |
4924 | version "2.2.4" | 4858 | version "10.0.2" |
4925 | resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" | 4859 | resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.0.2.tgz#c7b73010848b264792dd45372eea0b87cba4401e" |
4926 | integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= | 4860 | integrity sha512-wV6NDUVB8/iEYMalV/+139+vl2LaRFlZGEd5/xmdcdzQcgmis+npyco6NsDTVOlNA3y2NV9Gcz+vHyFMIT+ffg== |
4927 | dependencies: | ||
4928 | glob "^7.0.0" | ||
4929 | lodash "^4.0.0" | ||
4930 | scss-tokenizer "^0.2.3" | ||
4931 | yargs "^7.0.0" | ||
4932 | |||
4933 | sass-lint@^1.12.1: | ||
4934 | version "1.13.1" | ||
4935 | resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.13.1.tgz#5fd2b2792e9215272335eb0f0dc607f61e8acc8f" | ||
4936 | integrity sha512-DSyah8/MyjzW2BWYmQWekYEKir44BpLqrCFsgs9iaWiVTcwZfwXHF586hh3D1n+/9ihUNMfd8iHAyb9KkGgs7Q== | ||
4937 | dependencies: | ||
4938 | commander "^2.8.1" | ||
4939 | eslint "^2.7.0" | ||
4940 | front-matter "2.1.2" | ||
4941 | fs-extra "^3.0.1" | ||
4942 | glob "^7.0.0" | ||
4943 | globule "^1.0.0" | ||
4944 | gonzales-pe-sl "^4.2.3" | ||
4945 | js-yaml "^3.5.4" | ||
4946 | known-css-properties "^0.3.0" | ||
4947 | lodash.capitalize "^4.1.0" | ||
4948 | lodash.kebabcase "^4.0.0" | ||
4949 | merge "^1.2.0" | ||
4950 | path-is-absolute "^1.0.0" | ||
4951 | util "^0.10.3" | ||
4952 | |||
4953 | sass-loader@^6.0.6: | ||
4954 | version "6.0.7" | ||
4955 | resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.7.tgz#dd2fdb3e7eeff4a53f35ba6ac408715488353d00" | ||
4956 | integrity sha512-JoiyD00Yo1o61OJsoP2s2kb19L1/Y2p3QFcCdWdF6oomBGKVYuZyqHWemRBfQ2uGYsk+CH3eCguXNfpjzlcpaA== | ||
4957 | dependencies: | 4861 | dependencies: |
4958 | clone-deep "^2.0.1" | 4862 | klona "^2.0.3" |
4959 | loader-utils "^1.0.1" | 4863 | loader-utils "^2.0.0" |
4960 | lodash.tail "^4.1.1" | 4864 | neo-async "^2.6.2" |
4961 | neo-async "^2.5.0" | 4865 | schema-utils "^2.7.1" |
4962 | pify "^3.0.0" | 4866 | semver "^7.3.2" |
4963 | |||
4964 | sax@^1.2.4, sax@~1.2.1: | ||
4965 | version "1.2.4" | ||
4966 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" | ||
4967 | integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== | ||
4968 | 4867 | ||
4969 | schema-utils@^0.3.0: | 4868 | sass@^1.26.11: |
4970 | version "0.3.0" | 4869 | version "1.26.11" |
4971 | resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" | 4870 | resolved "https://registry.yarnpkg.com/sass/-/sass-1.26.11.tgz#0f22cc4ab2ba27dad1d4ca30837beb350b709847" |
4972 | integrity sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8= | 4871 | integrity sha512-W1l/+vjGjIamsJ6OnTe0K37U2DBO/dgsv2Z4c89XQ8ZOO6l/VwkqwLSqoYzJeJs6CLuGSTRWc91GbQFL3lvrvw== |
4973 | dependencies: | 4872 | dependencies: |
4974 | ajv "^5.0.0" | 4873 | chokidar ">=2.0.0 <4.0.0" |
4975 | 4874 | ||
4976 | schema-utils@^0.4.5: | 4875 | schema-utils@^0.4.5: |
4977 | version "0.4.7" | 4876 | version "0.4.7" |
@@ -4981,43 +4880,67 @@ schema-utils@^0.4.5: | |||
4981 | ajv "^6.1.0" | 4880 | ajv "^6.1.0" |
4982 | ajv-keywords "^3.1.0" | 4881 | ajv-keywords "^3.1.0" |
4983 | 4882 | ||
4984 | scss-tokenizer@^0.2.3: | 4883 | schema-utils@^1.0.0: |
4985 | version "0.2.3" | 4884 | version "1.0.0" |
4986 | resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" | 4885 | resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" |
4987 | integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE= | 4886 | integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== |
4988 | dependencies: | 4887 | dependencies: |
4989 | js-base64 "^2.1.8" | 4888 | ajv "^6.1.0" |
4990 | source-map "^0.4.2" | 4889 | ajv-errors "^1.0.0" |
4890 | ajv-keywords "^3.1.0" | ||
4991 | 4891 | ||
4992 | "semver@2 || 3 || 4 || 5", semver@^5.3.0: | 4892 | schema-utils@^2.6.5, schema-utils@^2.7.1: |
4993 | version "5.7.0" | 4893 | version "2.7.1" |
4994 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" | 4894 | resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" |
4995 | integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== | 4895 | integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== |
4896 | dependencies: | ||
4897 | "@types/json-schema" "^7.0.5" | ||
4898 | ajv "^6.12.4" | ||
4899 | ajv-keywords "^3.5.2" | ||
4996 | 4900 | ||
4997 | semver@~5.3.0: | 4901 | "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: |
4998 | version "5.3.0" | 4902 | version "5.7.1" |
4999 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" | 4903 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" |
5000 | integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= | 4904 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== |
5001 | 4905 | ||
5002 | set-blocking@^2.0.0, set-blocking@~2.0.0: | 4906 | semver@7.0.0: |
5003 | version "2.0.0" | 4907 | version "7.0.0" |
5004 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" | 4908 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" |
5005 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= | 4909 | integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== |
4910 | |||
4911 | semver@^6.0.0: | ||
4912 | version "6.3.0" | ||
4913 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" | ||
4914 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== | ||
5006 | 4915 | ||
5007 | set-value@^0.4.3: | 4916 | semver@^7.2.1, semver@^7.3.2: |
5008 | version "0.4.3" | 4917 | version "7.3.2" |
5009 | resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" | 4918 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" |
5010 | integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE= | 4919 | integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== |
4920 | |||
4921 | serialize-javascript@^4.0.0: | ||
4922 | version "4.0.0" | ||
4923 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" | ||
4924 | integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== | ||
5011 | dependencies: | 4925 | dependencies: |
5012 | extend-shallow "^2.0.1" | 4926 | randombytes "^2.1.0" |
5013 | is-extendable "^0.1.1" | ||
5014 | is-plain-object "^2.0.1" | ||
5015 | to-object-path "^0.3.0" | ||
5016 | 4927 | ||
5017 | set-value@^2.0.0: | 4928 | serialize-javascript@^5.0.1: |
4929 | version "5.0.1" | ||
4930 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" | ||
4931 | integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== | ||
4932 | dependencies: | ||
4933 | randombytes "^2.1.0" | ||
4934 | |||
4935 | set-blocking@^2.0.0: | ||
5018 | version "2.0.0" | 4936 | version "2.0.0" |
5019 | resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" | 4937 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" |
5020 | integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg== | 4938 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= |
4939 | |||
4940 | set-value@^2.0.0, set-value@^2.0.1: | ||
4941 | version "2.0.1" | ||
4942 | resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" | ||
4943 | integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== | ||
5021 | dependencies: | 4944 | dependencies: |
5022 | extend-shallow "^2.0.1" | 4945 | extend-shallow "^2.0.1" |
5023 | is-extendable "^0.1.1" | 4946 | is-extendable "^0.1.1" |
@@ -5037,15 +4960,6 @@ sha.js@^2.4.0, sha.js@^2.4.8: | |||
5037 | inherits "^2.0.1" | 4960 | inherits "^2.0.1" |
5038 | safe-buffer "^5.0.1" | 4961 | safe-buffer "^5.0.1" |
5039 | 4962 | ||
5040 | shallow-clone@^1.0.0: | ||
5041 | version "1.0.0" | ||
5042 | resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" | ||
5043 | integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA== | ||
5044 | dependencies: | ||
5045 | is-extendable "^0.1.1" | ||
5046 | kind-of "^5.0.0" | ||
5047 | mixin-object "^2.0.1" | ||
5048 | |||
5049 | shebang-command@^1.2.0: | 4963 | shebang-command@^1.2.0: |
5050 | version "1.2.0" | 4964 | version "1.2.0" |
5051 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" | 4965 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" |
@@ -5053,38 +4967,51 @@ shebang-command@^1.2.0: | |||
5053 | dependencies: | 4967 | dependencies: |
5054 | shebang-regex "^1.0.0" | 4968 | shebang-regex "^1.0.0" |
5055 | 4969 | ||
4970 | shebang-command@^2.0.0: | ||
4971 | version "2.0.0" | ||
4972 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" | ||
4973 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== | ||
4974 | dependencies: | ||
4975 | shebang-regex "^3.0.0" | ||
4976 | |||
5056 | shebang-regex@^1.0.0: | 4977 | shebang-regex@^1.0.0: |
5057 | version "1.0.0" | 4978 | version "1.0.0" |
5058 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" | 4979 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" |
5059 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= | 4980 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= |
5060 | 4981 | ||
5061 | shelljs@^0.6.0: | 4982 | shebang-regex@^3.0.0: |
5062 | version "0.6.1" | 4983 | version "3.0.0" |
5063 | resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8" | 4984 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" |
5064 | integrity sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg= | 4985 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== |
5065 | |||
5066 | signal-exit@^3.0.0, signal-exit@^3.0.2: | ||
5067 | version "3.0.2" | ||
5068 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" | ||
5069 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= | ||
5070 | 4986 | ||
5071 | slash@^1.0.0: | 4987 | signal-exit@^3.0.2: |
5072 | version "1.0.0" | 4988 | version "3.0.3" |
5073 | resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" | 4989 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" |
5074 | integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= | 4990 | integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== |
5075 | 4991 | ||
5076 | slice-ansi@0.0.4: | 4992 | slash@^3.0.0: |
5077 | version "0.0.4" | 4993 | version "3.0.0" |
5078 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" | 4994 | resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" |
5079 | integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= | 4995 | integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== |
5080 | 4996 | ||
5081 | slice-ansi@1.0.0: | 4997 | slice-ansi@^2.1.0: |
5082 | version "1.0.0" | 4998 | version "2.1.0" |
5083 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" | 4999 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" |
5084 | integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== | 5000 | integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== |
5085 | dependencies: | 5001 | dependencies: |
5002 | ansi-styles "^3.2.0" | ||
5003 | astral-regex "^1.0.0" | ||
5086 | is-fullwidth-code-point "^2.0.0" | 5004 | is-fullwidth-code-point "^2.0.0" |
5087 | 5005 | ||
5006 | slice-ansi@^4.0.0: | ||
5007 | version "4.0.0" | ||
5008 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" | ||
5009 | integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== | ||
5010 | dependencies: | ||
5011 | ansi-styles "^4.0.0" | ||
5012 | astral-regex "^2.0.0" | ||
5013 | is-fullwidth-code-point "^3.0.0" | ||
5014 | |||
5088 | snapdragon-node@^2.0.1: | 5015 | snapdragon-node@^2.0.1: |
5089 | version "2.1.1" | 5016 | version "2.1.1" |
5090 | resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" | 5017 | resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" |
@@ -5128,70 +5055,69 @@ source-list-map@^2.0.0: | |||
5128 | integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== | 5055 | integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== |
5129 | 5056 | ||
5130 | source-map-resolve@^0.5.0: | 5057 | source-map-resolve@^0.5.0: |
5131 | version "0.5.2" | 5058 | version "0.5.3" |
5132 | resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" | 5059 | resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" |
5133 | integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== | 5060 | integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== |
5134 | dependencies: | 5061 | dependencies: |
5135 | atob "^2.1.1" | 5062 | atob "^2.1.2" |
5136 | decode-uri-component "^0.2.0" | 5063 | decode-uri-component "^0.2.0" |
5137 | resolve-url "^0.2.1" | 5064 | resolve-url "^0.2.1" |
5138 | source-map-url "^0.4.0" | 5065 | source-map-url "^0.4.0" |
5139 | urix "^0.1.0" | 5066 | urix "^0.1.0" |
5140 | 5067 | ||
5141 | source-map-support@^0.4.15: | 5068 | source-map-support@~0.5.12: |
5142 | version "0.4.18" | 5069 | version "0.5.19" |
5143 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" | 5070 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" |
5144 | integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== | 5071 | integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== |
5145 | dependencies: | 5072 | dependencies: |
5146 | source-map "^0.5.6" | 5073 | buffer-from "^1.0.0" |
5074 | source-map "^0.6.0" | ||
5147 | 5075 | ||
5148 | source-map-url@^0.4.0: | 5076 | source-map-url@^0.4.0: |
5149 | version "0.4.0" | 5077 | version "0.4.0" |
5150 | resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" | 5078 | resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" |
5151 | integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= | 5079 | integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= |
5152 | 5080 | ||
5153 | source-map@^0.4.2: | 5081 | source-map@^0.5.0, source-map@^0.5.6: |
5154 | version "0.4.4" | ||
5155 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" | ||
5156 | integrity sha1-66T12pwNyZneaAMti092FzZSA2s= | ||
5157 | dependencies: | ||
5158 | amdefine ">=0.0.4" | ||
5159 | |||
5160 | source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: | ||
5161 | version "0.5.7" | 5082 | version "0.5.7" |
5162 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" | 5083 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" |
5163 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= | 5084 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= |
5164 | 5085 | ||
5165 | source-map@^0.6.1, source-map@~0.6.1: | 5086 | source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: |
5166 | version "0.6.1" | 5087 | version "0.6.1" |
5167 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" | 5088 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" |
5168 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== | 5089 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== |
5169 | 5090 | ||
5170 | spdx-correct@^3.0.0: | 5091 | spdx-correct@^3.0.0: |
5171 | version "3.1.0" | 5092 | version "3.1.1" |
5172 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" | 5093 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" |
5173 | integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== | 5094 | integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== |
5174 | dependencies: | 5095 | dependencies: |
5175 | spdx-expression-parse "^3.0.0" | 5096 | spdx-expression-parse "^3.0.0" |
5176 | spdx-license-ids "^3.0.0" | 5097 | spdx-license-ids "^3.0.0" |
5177 | 5098 | ||
5178 | spdx-exceptions@^2.1.0: | 5099 | spdx-exceptions@^2.1.0: |
5179 | version "2.2.0" | 5100 | version "2.3.0" |
5180 | resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" | 5101 | resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" |
5181 | integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== | 5102 | integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== |
5182 | 5103 | ||
5183 | spdx-expression-parse@^3.0.0: | 5104 | spdx-expression-parse@^3.0.0: |
5184 | version "3.0.0" | 5105 | version "3.0.1" |
5185 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" | 5106 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" |
5186 | integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== | 5107 | integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== |
5187 | dependencies: | 5108 | dependencies: |
5188 | spdx-exceptions "^2.1.0" | 5109 | spdx-exceptions "^2.1.0" |
5189 | spdx-license-ids "^3.0.0" | 5110 | spdx-license-ids "^3.0.0" |
5190 | 5111 | ||
5191 | spdx-license-ids@^3.0.0: | 5112 | spdx-license-ids@^3.0.0: |
5192 | version "3.0.4" | 5113 | version "3.0.6" |
5193 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1" | 5114 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" |
5194 | integrity sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA== | 5115 | integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== |
5116 | |||
5117 | specificity@^0.4.1: | ||
5118 | version "0.4.1" | ||
5119 | resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" | ||
5120 | integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== | ||
5195 | 5121 | ||
5196 | split-string@^3.0.1, split-string@^3.0.2: | 5122 | split-string@^3.0.1, split-string@^3.0.2: |
5197 | version "3.1.0" | 5123 | version "3.1.0" |
@@ -5205,20 +5131,24 @@ sprintf-js@~1.0.2: | |||
5205 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" | 5131 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" |
5206 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= | 5132 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= |
5207 | 5133 | ||
5208 | sshpk@^1.7.0: | 5134 | ssri@^6.0.1: |
5209 | version "1.16.1" | 5135 | version "6.0.1" |
5210 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" | 5136 | resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" |
5211 | integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== | 5137 | integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== |
5212 | dependencies: | 5138 | dependencies: |
5213 | asn1 "~0.2.3" | 5139 | figgy-pudding "^3.5.1" |
5214 | assert-plus "^1.0.0" | 5140 | |
5215 | bcrypt-pbkdf "^1.0.0" | 5141 | ssri@^8.0.0: |
5216 | dashdash "^1.12.0" | 5142 | version "8.0.0" |
5217 | ecc-jsbn "~0.1.1" | 5143 | resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" |
5218 | getpass "^0.1.1" | 5144 | integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== |
5219 | jsbn "~0.1.0" | 5145 | dependencies: |
5220 | safer-buffer "^2.0.2" | 5146 | minipass "^3.1.1" |
5221 | tweetnacl "~0.14.0" | 5147 | |
5148 | state-toggle@^1.0.0: | ||
5149 | version "1.0.3" | ||
5150 | resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" | ||
5151 | integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== | ||
5222 | 5152 | ||
5223 | static-extend@^0.1.1: | 5153 | static-extend@^0.1.1: |
5224 | version "0.1.2" | 5154 | version "0.1.2" |
@@ -5228,13 +5158,6 @@ static-extend@^0.1.1: | |||
5228 | define-property "^0.2.5" | 5158 | define-property "^0.2.5" |
5229 | object-copy "^0.1.0" | 5159 | object-copy "^0.1.0" |
5230 | 5160 | ||
5231 | stdout-stream@^1.4.0: | ||
5232 | version "1.4.1" | ||
5233 | resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" | ||
5234 | integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== | ||
5235 | dependencies: | ||
5236 | readable-stream "^2.0.1" | ||
5237 | |||
5238 | stream-browserify@^2.0.1: | 5161 | stream-browserify@^2.0.1: |
5239 | version "2.0.2" | 5162 | version "2.0.2" |
5240 | resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" | 5163 | resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" |
@@ -5243,6 +5166,14 @@ stream-browserify@^2.0.1: | |||
5243 | inherits "~2.0.1" | 5166 | inherits "~2.0.1" |
5244 | readable-stream "^2.0.2" | 5167 | readable-stream "^2.0.2" |
5245 | 5168 | ||
5169 | stream-each@^1.1.0: | ||
5170 | version "1.2.3" | ||
5171 | resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" | ||
5172 | integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== | ||
5173 | dependencies: | ||
5174 | end-of-stream "^1.1.0" | ||
5175 | stream-shift "^1.0.0" | ||
5176 | |||
5246 | stream-http@^2.7.2: | 5177 | stream-http@^2.7.2: |
5247 | version "2.8.3" | 5178 | version "2.8.3" |
5248 | resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" | 5179 | resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" |
@@ -5254,34 +5185,56 @@ stream-http@^2.7.2: | |||
5254 | to-arraybuffer "^1.0.0" | 5185 | to-arraybuffer "^1.0.0" |
5255 | xtend "^4.0.0" | 5186 | xtend "^4.0.0" |
5256 | 5187 | ||
5188 | stream-shift@^1.0.0: | ||
5189 | version "1.0.1" | ||
5190 | resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" | ||
5191 | integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== | ||
5192 | |||
5257 | strict-uri-encode@^1.0.0: | 5193 | strict-uri-encode@^1.0.0: |
5258 | version "1.1.0" | 5194 | version "1.1.0" |
5259 | resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" | 5195 | resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" |
5260 | integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= | 5196 | integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= |
5261 | 5197 | ||
5262 | string-width@^1.0.1, string-width@^1.0.2: | 5198 | string-width@^3.0.0, string-width@^3.1.0: |
5263 | version "1.0.2" | 5199 | version "3.1.0" |
5264 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" | 5200 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" |
5265 | integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= | 5201 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== |
5266 | dependencies: | 5202 | dependencies: |
5267 | code-point-at "^1.0.0" | 5203 | emoji-regex "^7.0.1" |
5268 | is-fullwidth-code-point "^1.0.0" | 5204 | is-fullwidth-code-point "^2.0.0" |
5269 | strip-ansi "^3.0.0" | 5205 | strip-ansi "^5.1.0" |
5270 | 5206 | ||
5271 | "string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: | 5207 | string-width@^4.2.0: |
5272 | version "2.1.1" | 5208 | version "4.2.0" |
5273 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" | 5209 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" |
5274 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== | 5210 | integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== |
5275 | dependencies: | 5211 | dependencies: |
5276 | is-fullwidth-code-point "^2.0.0" | 5212 | emoji-regex "^8.0.0" |
5277 | strip-ansi "^4.0.0" | 5213 | is-fullwidth-code-point "^3.0.0" |
5214 | strip-ansi "^6.0.0" | ||
5278 | 5215 | ||
5279 | string_decoder@^1.0.0: | 5216 | string.prototype.trimend@^1.0.1: |
5280 | version "1.2.0" | 5217 | version "1.0.1" |
5281 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" | 5218 | resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" |
5282 | integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== | 5219 | integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== |
5283 | dependencies: | 5220 | dependencies: |
5284 | safe-buffer "~5.1.0" | 5221 | define-properties "^1.1.3" |
5222 | es-abstract "^1.17.5" | ||
5223 | |||
5224 | string.prototype.trimstart@^1.0.1: | ||
5225 | version "1.0.1" | ||
5226 | resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" | ||
5227 | integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== | ||
5228 | dependencies: | ||
5229 | define-properties "^1.1.3" | ||
5230 | es-abstract "^1.17.5" | ||
5231 | |||
5232 | string_decoder@^1.0.0, string_decoder@^1.1.1: | ||
5233 | version "1.3.0" | ||
5234 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" | ||
5235 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== | ||
5236 | dependencies: | ||
5237 | safe-buffer "~5.2.0" | ||
5285 | 5238 | ||
5286 | string_decoder@~1.1.1: | 5239 | string_decoder@~1.1.1: |
5287 | version "1.1.1" | 5240 | version "1.1.1" |
@@ -5290,185 +5243,277 @@ string_decoder@~1.1.1: | |||
5290 | dependencies: | 5243 | dependencies: |
5291 | safe-buffer "~5.1.0" | 5244 | safe-buffer "~5.1.0" |
5292 | 5245 | ||
5293 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: | 5246 | stringify-entities@^3.0.0: |
5294 | version "3.0.1" | 5247 | version "3.0.1" |
5295 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" | 5248 | resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.0.1.tgz#32154b91286ab0869ab2c07696223bd23b6dbfc0" |
5296 | integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= | 5249 | integrity sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ== |
5297 | dependencies: | 5250 | dependencies: |
5298 | ansi-regex "^2.0.0" | 5251 | character-entities-html4 "^1.0.0" |
5252 | character-entities-legacy "^1.0.0" | ||
5253 | is-alphanumerical "^1.0.0" | ||
5254 | is-decimal "^1.0.2" | ||
5255 | is-hexadecimal "^1.0.0" | ||
5299 | 5256 | ||
5300 | strip-ansi@^4.0.0: | 5257 | strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: |
5301 | version "4.0.0" | 5258 | version "5.2.0" |
5302 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" | 5259 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" |
5303 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= | 5260 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== |
5304 | dependencies: | 5261 | dependencies: |
5305 | ansi-regex "^3.0.0" | 5262 | ansi-regex "^4.1.0" |
5306 | 5263 | ||
5307 | strip-bom@^2.0.0: | 5264 | strip-ansi@^6.0.0: |
5308 | version "2.0.0" | 5265 | version "6.0.0" |
5309 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" | 5266 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" |
5310 | integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= | 5267 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== |
5311 | dependencies: | 5268 | dependencies: |
5312 | is-utf8 "^0.2.0" | 5269 | ansi-regex "^5.0.0" |
5313 | 5270 | ||
5314 | strip-bom@^3.0.0: | 5271 | strip-bom@^3.0.0: |
5315 | version "3.0.0" | 5272 | version "3.0.0" |
5316 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" | 5273 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" |
5317 | integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= | 5274 | integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= |
5318 | 5275 | ||
5319 | strip-eof@^1.0.0: | 5276 | strip-indent@^3.0.0: |
5320 | version "1.0.0" | 5277 | version "3.0.0" |
5321 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" | 5278 | resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" |
5322 | integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= | 5279 | integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== |
5323 | |||
5324 | strip-indent@^1.0.1: | ||
5325 | version "1.0.1" | ||
5326 | resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" | ||
5327 | integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= | ||
5328 | dependencies: | 5280 | dependencies: |
5329 | get-stdin "^4.0.1" | 5281 | min-indent "^1.0.0" |
5330 | |||
5331 | strip-json-comments@~1.0.1: | ||
5332 | version "1.0.4" | ||
5333 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" | ||
5334 | integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= | ||
5335 | 5282 | ||
5336 | strip-json-comments@~2.0.1: | 5283 | strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: |
5337 | version "2.0.1" | 5284 | version "3.1.1" |
5338 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" | 5285 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" |
5339 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= | 5286 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== |
5340 | 5287 | ||
5341 | style-loader@^0.19.1: | 5288 | style-search@^0.1.0: |
5342 | version "0.19.1" | 5289 | version "0.1.0" |
5343 | resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.19.1.tgz#591ffc80bcefe268b77c5d9ebc0505d772619f85" | 5290 | resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" |
5344 | integrity sha512-IRE+ijgojrygQi3rsqT0U4dd+UcPCqcVvauZpCnQrGAlEe+FUIyrK93bUDScamesjP08JlQNsFJU+KmPedP5Og== | 5291 | integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= |
5345 | dependencies: | ||
5346 | loader-utils "^1.0.2" | ||
5347 | schema-utils "^0.3.0" | ||
5348 | 5292 | ||
5349 | supports-color@^2.0.0: | 5293 | stylelint-config-recommended@^3.0.0: |
5294 | version "3.0.0" | ||
5295 | resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz#e0e547434016c5539fe2650afd58049a2fd1d657" | ||
5296 | integrity sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ== | ||
5297 | |||
5298 | stylelint-config-standard@^20.0.0: | ||
5299 | version "20.0.0" | ||
5300 | resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-20.0.0.tgz#06135090c9e064befee3d594289f50e295b5e20d" | ||
5301 | integrity sha512-IB2iFdzOTA/zS4jSVav6z+wGtin08qfj+YyExHB3LF9lnouQht//YyB0KZq9gGz5HNPkddHOzcY8HsUey6ZUlA== | ||
5302 | dependencies: | ||
5303 | stylelint-config-recommended "^3.0.0" | ||
5304 | |||
5305 | stylelint-scss@^3.18.0: | ||
5306 | version "3.18.0" | ||
5307 | resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.18.0.tgz#8f06371c223909bf3f62e839548af1badeed31e9" | ||
5308 | integrity sha512-LD7+hv/6/ApNGt7+nR/50ft7cezKP2HM5rI8avIdGaUWre3xlHfV4jKO/DRZhscfuN+Ewy9FMhcTq0CcS0C/SA== | ||
5309 | dependencies: | ||
5310 | lodash "^4.17.15" | ||
5311 | postcss-media-query-parser "^0.2.3" | ||
5312 | postcss-resolve-nested-selector "^0.1.1" | ||
5313 | postcss-selector-parser "^6.0.2" | ||
5314 | postcss-value-parser "^4.1.0" | ||
5315 | |||
5316 | stylelint@^13.7.1: | ||
5317 | version "13.7.1" | ||
5318 | resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.1.tgz#bee97ee78d778a3f1dbe3f7397b76414973e263e" | ||
5319 | integrity sha512-qzqazcyRxrSRdmFuO0/SZOJ+LyCxYy0pwcvaOBBnl8/2VfHSMrtNIE+AnyJoyq6uKb+mt+hlgmVrvVi6G6XHfQ== | ||
5320 | dependencies: | ||
5321 | "@stylelint/postcss-css-in-js" "^0.37.2" | ||
5322 | "@stylelint/postcss-markdown" "^0.36.1" | ||
5323 | autoprefixer "^9.8.6" | ||
5324 | balanced-match "^1.0.0" | ||
5325 | chalk "^4.1.0" | ||
5326 | cosmiconfig "^7.0.0" | ||
5327 | debug "^4.1.1" | ||
5328 | execall "^2.0.0" | ||
5329 | fast-glob "^3.2.4" | ||
5330 | fastest-levenshtein "^1.0.12" | ||
5331 | file-entry-cache "^5.0.1" | ||
5332 | get-stdin "^8.0.0" | ||
5333 | global-modules "^2.0.0" | ||
5334 | globby "^11.0.1" | ||
5335 | globjoin "^0.1.4" | ||
5336 | html-tags "^3.1.0" | ||
5337 | ignore "^5.1.8" | ||
5338 | import-lazy "^4.0.0" | ||
5339 | imurmurhash "^0.1.4" | ||
5340 | known-css-properties "^0.19.0" | ||
5341 | lodash "^4.17.20" | ||
5342 | log-symbols "^4.0.0" | ||
5343 | mathml-tag-names "^2.1.3" | ||
5344 | meow "^7.1.1" | ||
5345 | micromatch "^4.0.2" | ||
5346 | normalize-selector "^0.2.0" | ||
5347 | postcss "^7.0.32" | ||
5348 | postcss-html "^0.36.0" | ||
5349 | postcss-less "^3.1.4" | ||
5350 | postcss-media-query-parser "^0.2.3" | ||
5351 | postcss-resolve-nested-selector "^0.1.1" | ||
5352 | postcss-safe-parser "^4.0.2" | ||
5353 | postcss-sass "^0.4.4" | ||
5354 | postcss-scss "^2.1.1" | ||
5355 | postcss-selector-parser "^6.0.2" | ||
5356 | postcss-syntax "^0.36.2" | ||
5357 | postcss-value-parser "^4.1.0" | ||
5358 | resolve-from "^5.0.0" | ||
5359 | slash "^3.0.0" | ||
5360 | specificity "^0.4.1" | ||
5361 | string-width "^4.2.0" | ||
5362 | strip-ansi "^6.0.0" | ||
5363 | style-search "^0.1.0" | ||
5364 | sugarss "^2.0.0" | ||
5365 | svg-tags "^1.0.0" | ||
5366 | table "^6.0.1" | ||
5367 | v8-compile-cache "^2.1.1" | ||
5368 | write-file-atomic "^3.0.3" | ||
5369 | |||
5370 | sugarss@^2.0.0: | ||
5350 | version "2.0.0" | 5371 | version "2.0.0" |
5351 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" | 5372 | resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d" |
5352 | integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= | 5373 | integrity sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ== |
5353 | |||
5354 | supports-color@^3.2.3: | ||
5355 | version "3.2.3" | ||
5356 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" | ||
5357 | integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= | ||
5358 | dependencies: | 5374 | dependencies: |
5359 | has-flag "^1.0.0" | 5375 | postcss "^7.0.2" |
5360 | 5376 | ||
5361 | supports-color@^4.2.1: | 5377 | supports-color@^5.3.0: |
5362 | version "4.5.0" | ||
5363 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" | ||
5364 | integrity sha1-vnoN5ITexcXN34s9WRJQRJEvY1s= | ||
5365 | dependencies: | ||
5366 | has-flag "^2.0.0" | ||
5367 | |||
5368 | supports-color@^5.3.0, supports-color@^5.4.0: | ||
5369 | version "5.5.0" | 5378 | version "5.5.0" |
5370 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" | 5379 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" |
5371 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== | 5380 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== |
5372 | dependencies: | 5381 | dependencies: |
5373 | has-flag "^3.0.0" | 5382 | has-flag "^3.0.0" |
5374 | 5383 | ||
5375 | svgo@^0.7.0: | 5384 | supports-color@^6.1.0: |
5376 | version "0.7.2" | 5385 | version "6.1.0" |
5377 | resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" | 5386 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" |
5378 | integrity sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U= | 5387 | integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== |
5379 | dependencies: | 5388 | dependencies: |
5380 | coa "~1.0.1" | 5389 | has-flag "^3.0.0" |
5381 | colors "~1.1.2" | ||
5382 | csso "~2.3.1" | ||
5383 | js-yaml "~3.7.0" | ||
5384 | mkdirp "~0.5.1" | ||
5385 | sax "~1.2.1" | ||
5386 | whet.extend "~0.9.9" | ||
5387 | 5390 | ||
5388 | table@4.0.2: | 5391 | supports-color@^7.0.0, supports-color@^7.1.0: |
5389 | version "4.0.2" | 5392 | version "7.2.0" |
5390 | resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" | 5393 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" |
5391 | integrity sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA== | 5394 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== |
5392 | dependencies: | ||
5393 | ajv "^5.2.3" | ||
5394 | ajv-keywords "^2.1.0" | ||
5395 | chalk "^2.1.0" | ||
5396 | lodash "^4.17.4" | ||
5397 | slice-ansi "1.0.0" | ||
5398 | string-width "^2.1.1" | ||
5399 | |||
5400 | table@^3.7.8: | ||
5401 | version "3.8.3" | ||
5402 | resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" | ||
5403 | integrity sha1-K7xULw/amGGnVdOUf+/Ys/UThV8= | ||
5404 | dependencies: | ||
5405 | ajv "^4.7.0" | ||
5406 | ajv-keywords "^1.0.0" | ||
5407 | chalk "^1.1.1" | ||
5408 | lodash "^4.0.0" | ||
5409 | slice-ansi "0.0.4" | ||
5410 | string-width "^2.0.0" | ||
5411 | |||
5412 | tapable@^0.2.7: | ||
5413 | version "0.2.9" | ||
5414 | resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.9.tgz#af2d8bbc9b04f74ee17af2b4d9048f807acd18a8" | ||
5415 | integrity sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A== | ||
5416 | |||
5417 | tar@^2.0.0: | ||
5418 | version "2.2.2" | ||
5419 | resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" | ||
5420 | integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== | ||
5421 | dependencies: | 5395 | dependencies: |
5422 | block-stream "*" | 5396 | has-flag "^4.0.0" |
5423 | fstream "^1.0.12" | ||
5424 | inherits "2" | ||
5425 | 5397 | ||
5426 | tar@^4: | 5398 | svg-tags@^1.0.0: |
5427 | version "4.4.8" | 5399 | version "1.0.0" |
5428 | resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" | 5400 | resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" |
5429 | integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== | 5401 | integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= |
5402 | |||
5403 | table@^5.2.3: | ||
5404 | version "5.4.6" | ||
5405 | resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" | ||
5406 | integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== | ||
5407 | dependencies: | ||
5408 | ajv "^6.10.2" | ||
5409 | lodash "^4.17.14" | ||
5410 | slice-ansi "^2.1.0" | ||
5411 | string-width "^3.0.0" | ||
5412 | |||
5413 | table@^6.0.1: | ||
5414 | version "6.0.3" | ||
5415 | resolved "https://registry.yarnpkg.com/table/-/table-6.0.3.tgz#e5b8a834e37e27ad06de2e0fda42b55cfd8a0123" | ||
5416 | integrity sha512-8321ZMcf1B9HvVX/btKv8mMZahCjn2aYrDlpqHaBFCfnox64edeH9kEid0vTLTRR8gWR2A20aDgeuTTea4sVtw== | ||
5417 | dependencies: | ||
5418 | ajv "^6.12.4" | ||
5419 | lodash "^4.17.20" | ||
5420 | slice-ansi "^4.0.0" | ||
5421 | string-width "^4.2.0" | ||
5422 | |||
5423 | tapable@^1.0.0, tapable@^1.1.3: | ||
5424 | version "1.1.3" | ||
5425 | resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" | ||
5426 | integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== | ||
5427 | |||
5428 | tar@^6.0.2: | ||
5429 | version "6.0.5" | ||
5430 | resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" | ||
5431 | integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== | ||
5432 | dependencies: | ||
5433 | chownr "^2.0.0" | ||
5434 | fs-minipass "^2.0.0" | ||
5435 | minipass "^3.0.0" | ||
5436 | minizlib "^2.1.1" | ||
5437 | mkdirp "^1.0.3" | ||
5438 | yallist "^4.0.0" | ||
5439 | |||
5440 | terser-webpack-plugin@^1.4.3: | ||
5441 | version "1.4.5" | ||
5442 | resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" | ||
5443 | integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== | ||
5444 | dependencies: | ||
5445 | cacache "^12.0.2" | ||
5446 | find-cache-dir "^2.1.0" | ||
5447 | is-wsl "^1.1.0" | ||
5448 | schema-utils "^1.0.0" | ||
5449 | serialize-javascript "^4.0.0" | ||
5450 | source-map "^0.6.1" | ||
5451 | terser "^4.1.2" | ||
5452 | webpack-sources "^1.4.0" | ||
5453 | worker-farm "^1.7.0" | ||
5454 | |||
5455 | terser-webpack-plugin@^4.2.2: | ||
5456 | version "4.2.2" | ||
5457 | resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.2.tgz#d86200c700053bba637913fe4310ba1bdeb5568e" | ||
5458 | integrity sha512-3qAQpykRTD5DReLu5/cwpsg7EZFzP3Q0Hp2XUWJUw2mpq2jfgOKTZr8IZKKnNieRVVo1UauROTdhbQJZveGKtQ== | ||
5459 | dependencies: | ||
5460 | cacache "^15.0.5" | ||
5461 | find-cache-dir "^3.3.1" | ||
5462 | jest-worker "^26.3.0" | ||
5463 | p-limit "^3.0.2" | ||
5464 | schema-utils "^2.7.1" | ||
5465 | serialize-javascript "^5.0.1" | ||
5466 | source-map "^0.6.1" | ||
5467 | terser "^5.3.2" | ||
5468 | webpack-sources "^1.4.3" | ||
5469 | |||
5470 | terser@^4.1.2: | ||
5471 | version "4.8.0" | ||
5472 | resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" | ||
5473 | integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== | ||
5430 | dependencies: | 5474 | dependencies: |
5431 | chownr "^1.1.1" | 5475 | commander "^2.20.0" |
5432 | fs-minipass "^1.2.5" | 5476 | source-map "~0.6.1" |
5433 | minipass "^2.3.4" | 5477 | source-map-support "~0.5.12" |
5434 | minizlib "^1.1.1" | ||
5435 | mkdirp "^0.5.0" | ||
5436 | safe-buffer "^5.1.2" | ||
5437 | yallist "^3.0.2" | ||
5438 | 5478 | ||
5439 | text-table@~0.2.0: | 5479 | terser@^5.3.2: |
5480 | version "5.3.2" | ||
5481 | resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.2.tgz#f4bea90eb92945b2a028ceef79181b9bb586e7af" | ||
5482 | integrity sha512-H67sydwBz5jCUA32ZRL319ULu+Su1cAoZnnc+lXnenGRYWyLE3Scgkt8mNoAsMx0h5kdo758zdoS0LG9rYZXDQ== | ||
5483 | dependencies: | ||
5484 | commander "^2.20.0" | ||
5485 | source-map "~0.6.1" | ||
5486 | source-map-support "~0.5.12" | ||
5487 | |||
5488 | text-table@^0.2.0: | ||
5440 | version "0.2.0" | 5489 | version "0.2.0" |
5441 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" | 5490 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" |
5442 | integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= | 5491 | integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= |
5443 | 5492 | ||
5444 | through@^2.3.6: | 5493 | through2@^2.0.0: |
5445 | version "2.3.8" | 5494 | version "2.0.5" |
5446 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" | 5495 | resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" |
5447 | integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= | 5496 | integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== |
5497 | dependencies: | ||
5498 | readable-stream "~2.3.6" | ||
5499 | xtend "~4.0.1" | ||
5448 | 5500 | ||
5449 | timers-browserify@^2.0.4: | 5501 | timers-browserify@^2.0.4: |
5450 | version "2.0.10" | 5502 | version "2.0.11" |
5451 | resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" | 5503 | resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" |
5452 | integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg== | 5504 | integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== |
5453 | dependencies: | 5505 | dependencies: |
5454 | setimmediate "^1.0.4" | 5506 | setimmediate "^1.0.4" |
5455 | 5507 | ||
5456 | tmp@^0.0.33: | ||
5457 | version "0.0.33" | ||
5458 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" | ||
5459 | integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== | ||
5460 | dependencies: | ||
5461 | os-tmpdir "~1.0.2" | ||
5462 | |||
5463 | to-arraybuffer@^1.0.0: | 5508 | to-arraybuffer@^1.0.0: |
5464 | version "1.0.1" | 5509 | version "1.0.1" |
5465 | resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" | 5510 | resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" |
5466 | integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= | 5511 | integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= |
5467 | 5512 | ||
5468 | to-fast-properties@^1.0.3: | 5513 | to-fast-properties@^2.0.0: |
5469 | version "1.0.3" | 5514 | version "2.0.0" |
5470 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" | 5515 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" |
5471 | integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= | 5516 | integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= |
5472 | 5517 | ||
5473 | to-object-path@^0.3.0: | 5518 | to-object-path@^0.3.0: |
5474 | version "0.3.0" | 5519 | version "0.3.0" |
@@ -5485,6 +5530,13 @@ to-regex-range@^2.1.0: | |||
5485 | is-number "^3.0.0" | 5530 | is-number "^3.0.0" |
5486 | repeat-string "^1.6.1" | 5531 | repeat-string "^1.6.1" |
5487 | 5532 | ||
5533 | to-regex-range@^5.0.1: | ||
5534 | version "5.0.1" | ||
5535 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" | ||
5536 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== | ||
5537 | dependencies: | ||
5538 | is-number "^7.0.0" | ||
5539 | |||
5488 | to-regex@^3.0.1, to-regex@^3.0.2: | 5540 | to-regex@^3.0.1, to-regex@^3.0.2: |
5489 | version "3.0.2" | 5541 | version "3.0.2" |
5490 | resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" | 5542 | resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" |
@@ -5495,108 +5547,194 @@ to-regex@^3.0.1, to-regex@^3.0.2: | |||
5495 | regex-not "^1.0.2" | 5547 | regex-not "^1.0.2" |
5496 | safe-regex "^1.1.0" | 5548 | safe-regex "^1.1.0" |
5497 | 5549 | ||
5498 | tough-cookie@~2.4.3: | 5550 | trim-newlines@^3.0.0: |
5499 | version "2.4.3" | 5551 | version "3.0.0" |
5500 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" | 5552 | resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" |
5501 | integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== | 5553 | integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== |
5502 | dependencies: | ||
5503 | psl "^1.1.24" | ||
5504 | punycode "^1.4.1" | ||
5505 | 5554 | ||
5506 | trim-newlines@^1.0.0: | 5555 | trim-trailing-lines@^1.0.0: |
5507 | version "1.0.0" | 5556 | version "1.1.3" |
5508 | resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" | 5557 | resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz#7f0739881ff76657b7776e10874128004b625a94" |
5509 | integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= | 5558 | integrity sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA== |
5510 | 5559 | ||
5511 | trim-right@^1.0.1: | 5560 | trim@0.0.1: |
5512 | version "1.0.1" | 5561 | version "0.0.1" |
5513 | resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" | 5562 | resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" |
5514 | integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= | 5563 | integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= |
5515 | 5564 | ||
5516 | "true-case-path@^1.0.2": | 5565 | trough@^1.0.0: |
5517 | version "1.0.3" | 5566 | version "1.0.5" |
5518 | resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" | 5567 | resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" |
5519 | integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew== | 5568 | integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== |
5569 | |||
5570 | tsconfig-paths@^3.9.0: | ||
5571 | version "3.9.0" | ||
5572 | resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" | ||
5573 | integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== | ||
5520 | dependencies: | 5574 | dependencies: |
5521 | glob "^7.1.2" | 5575 | "@types/json5" "^0.0.29" |
5576 | json5 "^1.0.1" | ||
5577 | minimist "^1.2.0" | ||
5578 | strip-bom "^3.0.0" | ||
5579 | |||
5580 | tslib@^1.9.0: | ||
5581 | version "1.13.0" | ||
5582 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" | ||
5583 | integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== | ||
5522 | 5584 | ||
5523 | tty-browserify@0.0.0: | 5585 | tty-browserify@0.0.0: |
5524 | version "0.0.0" | 5586 | version "0.0.0" |
5525 | resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" | 5587 | resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" |
5526 | integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= | 5588 | integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= |
5527 | 5589 | ||
5528 | tunnel-agent@^0.6.0: | 5590 | type-check@^0.4.0, type-check@~0.4.0: |
5529 | version "0.6.0" | 5591 | version "0.4.0" |
5530 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" | 5592 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" |
5531 | integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= | 5593 | integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== |
5532 | dependencies: | 5594 | dependencies: |
5533 | safe-buffer "^5.0.1" | 5595 | prelude-ls "^1.2.1" |
5534 | 5596 | ||
5535 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: | 5597 | type-fest@^0.13.1: |
5536 | version "0.14.5" | 5598 | version "0.13.1" |
5537 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" | 5599 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" |
5538 | integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= | 5600 | integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== |
5539 | 5601 | ||
5540 | type-check@~0.3.2: | 5602 | type-fest@^0.6.0: |
5541 | version "0.3.2" | 5603 | version "0.6.0" |
5542 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" | 5604 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" |
5543 | integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= | 5605 | integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== |
5606 | |||
5607 | type-fest@^0.8.1: | ||
5608 | version "0.8.1" | ||
5609 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" | ||
5610 | integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== | ||
5611 | |||
5612 | typedarray-to-buffer@^3.1.5: | ||
5613 | version "3.1.5" | ||
5614 | resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" | ||
5615 | integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== | ||
5544 | dependencies: | 5616 | dependencies: |
5545 | prelude-ls "~1.1.2" | 5617 | is-typedarray "^1.0.0" |
5546 | 5618 | ||
5547 | typedarray@^0.0.6: | 5619 | typedarray@^0.0.6: |
5548 | version "0.0.6" | 5620 | version "0.0.6" |
5549 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" | 5621 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" |
5550 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= | 5622 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= |
5551 | 5623 | ||
5552 | uglify-js@^2.8.29: | 5624 | unherit@^1.0.4: |
5553 | version "2.8.29" | 5625 | version "1.1.3" |
5554 | resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" | 5626 | resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" |
5555 | integrity sha1-KcVzMUgFe7Th913zW3qcty5qWd0= | 5627 | integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== |
5556 | dependencies: | 5628 | dependencies: |
5557 | source-map "~0.5.1" | 5629 | inherits "^2.0.0" |
5558 | yargs "~3.10.0" | 5630 | xtend "^4.0.0" |
5559 | optionalDependencies: | ||
5560 | uglify-to-browserify "~1.0.0" | ||
5561 | 5631 | ||
5562 | uglify-to-browserify@~1.0.0: | 5632 | unicode-canonical-property-names-ecmascript@^1.0.4: |
5563 | version "1.0.2" | 5633 | version "1.0.4" |
5564 | resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" | 5634 | resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" |
5565 | integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= | 5635 | integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== |
5566 | 5636 | ||
5567 | uglifyjs-webpack-plugin@^0.4.6: | 5637 | unicode-match-property-ecmascript@^1.0.4: |
5568 | version "0.4.6" | 5638 | version "1.0.4" |
5569 | resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" | 5639 | resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" |
5570 | integrity sha1-uVH0q7a9YX5m9j64kUmOORdj4wk= | 5640 | integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== |
5571 | dependencies: | 5641 | dependencies: |
5572 | source-map "^0.5.6" | 5642 | unicode-canonical-property-names-ecmascript "^1.0.4" |
5573 | uglify-js "^2.8.29" | 5643 | unicode-property-aliases-ecmascript "^1.0.4" |
5574 | webpack-sources "^1.0.1" | 5644 | |
5645 | unicode-match-property-value-ecmascript@^1.2.0: | ||
5646 | version "1.2.0" | ||
5647 | resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" | ||
5648 | integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== | ||
5649 | |||
5650 | unicode-property-aliases-ecmascript@^1.0.4: | ||
5651 | version "1.1.0" | ||
5652 | resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" | ||
5653 | integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== | ||
5654 | |||
5655 | unified@^9.0.0: | ||
5656 | version "9.2.0" | ||
5657 | resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" | ||
5658 | integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== | ||
5659 | dependencies: | ||
5660 | bail "^1.0.0" | ||
5661 | extend "^3.0.0" | ||
5662 | is-buffer "^2.0.0" | ||
5663 | is-plain-obj "^2.0.0" | ||
5664 | trough "^1.0.0" | ||
5665 | vfile "^4.0.0" | ||
5575 | 5666 | ||
5576 | union-value@^1.0.0: | 5667 | union-value@^1.0.0: |
5577 | version "1.0.0" | 5668 | version "1.0.1" |
5578 | resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" | 5669 | resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" |
5579 | integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= | 5670 | integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== |
5580 | dependencies: | 5671 | dependencies: |
5581 | arr-union "^3.1.0" | 5672 | arr-union "^3.1.0" |
5582 | get-value "^2.0.6" | 5673 | get-value "^2.0.6" |
5583 | is-extendable "^0.1.1" | 5674 | is-extendable "^0.1.1" |
5584 | set-value "^0.4.3" | 5675 | set-value "^2.0.1" |
5585 | 5676 | ||
5586 | uniq@^1.0.1: | 5677 | uniq@^1.0.1: |
5587 | version "1.0.1" | 5678 | version "1.0.1" |
5588 | resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" | 5679 | resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" |
5589 | integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= | 5680 | integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= |
5590 | 5681 | ||
5591 | uniqs@^2.0.0: | 5682 | unique-filename@^1.1.1: |
5592 | version "2.0.0" | 5683 | version "1.1.1" |
5593 | resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" | 5684 | resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" |
5594 | integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= | 5685 | integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== |
5686 | dependencies: | ||
5687 | unique-slug "^2.0.0" | ||
5595 | 5688 | ||
5596 | universalify@^0.1.0: | 5689 | unique-slug@^2.0.0: |
5597 | version "0.1.2" | 5690 | version "2.0.2" |
5598 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" | 5691 | resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" |
5599 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== | 5692 | integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== |
5693 | dependencies: | ||
5694 | imurmurhash "^0.1.4" | ||
5695 | |||
5696 | unist-util-find-all-after@^3.0.1: | ||
5697 | version "3.0.1" | ||
5698 | resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-3.0.1.tgz#95cc62f48812d879b4685a0512bf1b838da50e9a" | ||
5699 | integrity sha512-0GICgc++sRJesLwEYDjFVJPJttBpVQaTNgc6Jw0Jhzvfs+jtKePEMu+uD+PqkRUrAvGQqwhpDwLGWo1PK8PDEw== | ||
5700 | dependencies: | ||
5701 | unist-util-is "^4.0.0" | ||
5702 | |||
5703 | unist-util-is@^4.0.0: | ||
5704 | version "4.0.2" | ||
5705 | resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.2.tgz#c7d1341188aa9ce5b3cff538958de9895f14a5de" | ||
5706 | integrity sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ== | ||
5707 | |||
5708 | unist-util-remove-position@^2.0.0: | ||
5709 | version "2.0.1" | ||
5710 | resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" | ||
5711 | integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== | ||
5712 | dependencies: | ||
5713 | unist-util-visit "^2.0.0" | ||
5714 | |||
5715 | unist-util-stringify-position@^2.0.0: | ||
5716 | version "2.0.3" | ||
5717 | resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" | ||
5718 | integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== | ||
5719 | dependencies: | ||
5720 | "@types/unist" "^2.0.2" | ||
5721 | |||
5722 | unist-util-visit-parents@^3.0.0: | ||
5723 | version "3.1.0" | ||
5724 | resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.0.tgz#4dd262fb9dcfe44f297d53e882fc6ff3421173d5" | ||
5725 | integrity sha512-0g4wbluTF93npyPrp/ymd3tCDTMnP0yo2akFD2FIBAYXq/Sga3lwaU1D8OYKbtpioaI6CkDcQ6fsMnmtzt7htw== | ||
5726 | dependencies: | ||
5727 | "@types/unist" "^2.0.0" | ||
5728 | unist-util-is "^4.0.0" | ||
5729 | |||
5730 | unist-util-visit@^2.0.0: | ||
5731 | version "2.0.3" | ||
5732 | resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" | ||
5733 | integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== | ||
5734 | dependencies: | ||
5735 | "@types/unist" "^2.0.0" | ||
5736 | unist-util-is "^4.0.0" | ||
5737 | unist-util-visit-parents "^3.0.0" | ||
5600 | 5738 | ||
5601 | unset-value@^1.0.0: | 5739 | unset-value@^1.0.0: |
5602 | version "1.0.0" | 5740 | version "1.0.0" |
@@ -5607,14 +5745,14 @@ unset-value@^1.0.0: | |||
5607 | isobject "^3.0.0" | 5745 | isobject "^3.0.0" |
5608 | 5746 | ||
5609 | upath@^1.1.1: | 5747 | upath@^1.1.1: |
5610 | version "1.1.2" | 5748 | version "1.2.0" |
5611 | resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" | 5749 | resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" |
5612 | integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== | 5750 | integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== |
5613 | 5751 | ||
5614 | uri-js@^4.2.2: | 5752 | uri-js@^4.2.2: |
5615 | version "4.2.2" | 5753 | version "4.4.0" |
5616 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" | 5754 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" |
5617 | integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== | 5755 | integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== |
5618 | dependencies: | 5756 | dependencies: |
5619 | punycode "^2.1.0" | 5757 | punycode "^2.1.0" |
5620 | 5758 | ||
@@ -5623,15 +5761,6 @@ urix@^0.1.0: | |||
5623 | resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" | 5761 | resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" |
5624 | integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= | 5762 | integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= |
5625 | 5763 | ||
5626 | url-loader@^0.6.2: | ||
5627 | version "0.6.2" | ||
5628 | resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7" | ||
5629 | integrity sha512-h3qf9TNn53BpuXTTcpC+UehiRrl0Cv45Yr/xWayApjw6G8Bg2dGke7rIwDQ39piciWCWrC+WiqLjOh3SUp9n0Q== | ||
5630 | dependencies: | ||
5631 | loader-utils "^1.0.2" | ||
5632 | mime "^1.4.1" | ||
5633 | schema-utils "^0.3.0" | ||
5634 | |||
5635 | url@^0.11.0: | 5764 | url@^0.11.0: |
5636 | version "0.11.0" | 5765 | version "0.11.0" |
5637 | resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" | 5766 | resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" |
@@ -5645,14 +5774,7 @@ use@^3.1.0: | |||
5645 | resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" | 5774 | resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" |
5646 | integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== | 5775 | integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== |
5647 | 5776 | ||
5648 | user-home@^2.0.0: | 5777 | util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: |
5649 | version "2.0.0" | ||
5650 | resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" | ||
5651 | integrity sha1-nHC/2Babwdy/SGBODwS4tJzenp8= | ||
5652 | dependencies: | ||
5653 | os-homedir "^1.0.0" | ||
5654 | |||
5655 | util-deprecate@~1.0.1: | ||
5656 | version "1.0.2" | 5778 | version "1.0.2" |
5657 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" | 5779 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" |
5658 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= | 5780 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= |
@@ -5664,13 +5786,6 @@ util@0.10.3: | |||
5664 | dependencies: | 5786 | dependencies: |
5665 | inherits "2.0.1" | 5787 | inherits "2.0.1" |
5666 | 5788 | ||
5667 | util@^0.10.3: | ||
5668 | version "0.10.4" | ||
5669 | resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" | ||
5670 | integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== | ||
5671 | dependencies: | ||
5672 | inherits "2.0.3" | ||
5673 | |||
5674 | util@^0.11.0: | 5789 | util@^0.11.0: |
5675 | version "0.11.1" | 5790 | version "0.11.1" |
5676 | resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" | 5791 | resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" |
@@ -5678,10 +5793,10 @@ util@^0.11.0: | |||
5678 | dependencies: | 5793 | dependencies: |
5679 | inherits "2.0.3" | 5794 | inherits "2.0.3" |
5680 | 5795 | ||
5681 | uuid@^3.3.2: | 5796 | v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: |
5682 | version "3.3.2" | 5797 | version "2.1.1" |
5683 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" | 5798 | resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" |
5684 | integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== | 5799 | integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== |
5685 | 5800 | ||
5686 | validate-npm-package-license@^3.0.1: | 5801 | validate-npm-package-license@^3.0.1: |
5687 | version "3.0.4" | 5802 | version "3.0.4" |
@@ -5691,214 +5806,222 @@ validate-npm-package-license@^3.0.1: | |||
5691 | spdx-correct "^3.0.0" | 5806 | spdx-correct "^3.0.0" |
5692 | spdx-expression-parse "^3.0.0" | 5807 | spdx-expression-parse "^3.0.0" |
5693 | 5808 | ||
5694 | vendors@^1.0.0: | 5809 | vfile-location@^3.0.0: |
5695 | version "1.0.3" | 5810 | version "3.1.0" |
5696 | resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.3.tgz#a6467781abd366217c050f8202e7e50cc9eef8c0" | 5811 | resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.1.0.tgz#81cd8a04b0ac935185f4fce16f270503fc2f692f" |
5697 | integrity sha512-fOi47nsJP5Wqefa43kyWSg80qF+Q3XA6MUkgi7Hp1HQaKDQW4cQrK2D0P7mmbFtsV1N89am55Yru/nyEwRubcw== | 5812 | integrity sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g== |
5698 | 5813 | ||
5699 | verror@1.10.0: | 5814 | vfile-message@^2.0.0: |
5700 | version "1.10.0" | 5815 | version "2.0.4" |
5701 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" | 5816 | resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" |
5702 | integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= | 5817 | integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== |
5703 | dependencies: | 5818 | dependencies: |
5704 | assert-plus "^1.0.0" | 5819 | "@types/unist" "^2.0.0" |
5705 | core-util-is "1.0.2" | 5820 | unist-util-stringify-position "^2.0.0" |
5706 | extsprintf "^1.2.0" | 5821 | |
5822 | vfile@^4.0.0: | ||
5823 | version "4.2.0" | ||
5824 | resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.0.tgz#26c78ac92eb70816b01d4565e003b7e65a2a0e01" | ||
5825 | integrity sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw== | ||
5826 | dependencies: | ||
5827 | "@types/unist" "^2.0.0" | ||
5828 | is-buffer "^2.0.0" | ||
5829 | replace-ext "1.0.0" | ||
5830 | unist-util-stringify-position "^2.0.0" | ||
5831 | vfile-message "^2.0.0" | ||
5832 | |||
5833 | vm-browserify@^1.0.1: | ||
5834 | version "1.1.2" | ||
5835 | resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" | ||
5836 | integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== | ||
5707 | 5837 | ||
5708 | vm-browserify@0.0.4: | 5838 | watchpack-chokidar2@^2.0.0: |
5709 | version "0.0.4" | 5839 | version "2.0.0" |
5710 | resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" | 5840 | resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" |
5711 | integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM= | 5841 | integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== |
5712 | dependencies: | 5842 | dependencies: |
5713 | indexof "0.0.1" | 5843 | chokidar "^2.1.8" |
5714 | 5844 | ||
5715 | watchpack@^1.4.0: | 5845 | watchpack@^1.7.4: |
5716 | version "1.6.0" | 5846 | version "1.7.4" |
5717 | resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" | 5847 | resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" |
5718 | integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== | 5848 | integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== |
5719 | dependencies: | 5849 | dependencies: |
5720 | chokidar "^2.0.2" | ||
5721 | graceful-fs "^4.1.2" | 5850 | graceful-fs "^4.1.2" |
5722 | neo-async "^2.5.0" | 5851 | neo-async "^2.5.0" |
5723 | 5852 | optionalDependencies: | |
5724 | webpack-sources@^1.0.1: | 5853 | chokidar "^3.4.1" |
5725 | version "1.3.0" | 5854 | watchpack-chokidar2 "^2.0.0" |
5726 | resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" | 5855 | |
5727 | integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== | 5856 | webpack-cli@^3.3.12: |
5857 | version "3.3.12" | ||
5858 | resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" | ||
5859 | integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== | ||
5860 | dependencies: | ||
5861 | chalk "^2.4.2" | ||
5862 | cross-spawn "^6.0.5" | ||
5863 | enhanced-resolve "^4.1.1" | ||
5864 | findup-sync "^3.0.0" | ||
5865 | global-modules "^2.0.0" | ||
5866 | import-local "^2.0.0" | ||
5867 | interpret "^1.4.0" | ||
5868 | loader-utils "^1.4.0" | ||
5869 | supports-color "^6.1.0" | ||
5870 | v8-compile-cache "^2.1.1" | ||
5871 | yargs "^13.3.2" | ||
5872 | |||
5873 | webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: | ||
5874 | version "1.4.3" | ||
5875 | resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" | ||
5876 | integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== | ||
5728 | dependencies: | 5877 | dependencies: |
5729 | source-list-map "^2.0.0" | 5878 | source-list-map "^2.0.0" |
5730 | source-map "~0.6.1" | 5879 | source-map "~0.6.1" |
5731 | 5880 | ||
5732 | webpack@^3.10.0: | 5881 | webpack@^4.44.2: |
5733 | version "3.12.0" | 5882 | version "4.44.2" |
5734 | resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.12.0.tgz#3f9e34360370602fcf639e97939db486f4ec0d74" | 5883 | resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72" |
5735 | integrity sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ== | 5884 | integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q== |
5736 | dependencies: | 5885 | dependencies: |
5737 | acorn "^5.0.0" | 5886 | "@webassemblyjs/ast" "1.9.0" |
5738 | acorn-dynamic-import "^2.0.0" | 5887 | "@webassemblyjs/helper-module-context" "1.9.0" |
5739 | ajv "^6.1.0" | 5888 | "@webassemblyjs/wasm-edit" "1.9.0" |
5740 | ajv-keywords "^3.1.0" | 5889 | "@webassemblyjs/wasm-parser" "1.9.0" |
5741 | async "^2.1.2" | 5890 | acorn "^6.4.1" |
5742 | enhanced-resolve "^3.4.0" | 5891 | ajv "^6.10.2" |
5743 | escope "^3.6.0" | 5892 | ajv-keywords "^3.4.1" |
5744 | interpret "^1.0.0" | 5893 | chrome-trace-event "^1.0.2" |
5745 | json-loader "^0.5.4" | 5894 | enhanced-resolve "^4.3.0" |
5746 | json5 "^0.5.1" | 5895 | eslint-scope "^4.0.3" |
5747 | loader-runner "^2.3.0" | 5896 | json-parse-better-errors "^1.0.2" |
5748 | loader-utils "^1.1.0" | 5897 | loader-runner "^2.4.0" |
5749 | memory-fs "~0.4.1" | 5898 | loader-utils "^1.2.3" |
5750 | mkdirp "~0.5.0" | 5899 | memory-fs "^0.4.1" |
5751 | node-libs-browser "^2.0.0" | 5900 | micromatch "^3.1.10" |
5752 | source-map "^0.5.3" | 5901 | mkdirp "^0.5.3" |
5753 | supports-color "^4.2.1" | 5902 | neo-async "^2.6.1" |
5754 | tapable "^0.2.7" | 5903 | node-libs-browser "^2.2.1" |
5755 | uglifyjs-webpack-plugin "^0.4.6" | 5904 | schema-utils "^1.0.0" |
5756 | watchpack "^1.4.0" | 5905 | tapable "^1.1.3" |
5757 | webpack-sources "^1.0.1" | 5906 | terser-webpack-plugin "^1.4.3" |
5758 | yargs "^8.0.2" | 5907 | watchpack "^1.7.4" |
5759 | 5908 | webpack-sources "^1.4.1" | |
5760 | whet.extend@~0.9.9: | ||
5761 | version "0.9.9" | ||
5762 | resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" | ||
5763 | integrity sha1-+HfVv2SMl+WqVC+twW1qJZucEaE= | ||
5764 | |||
5765 | which-module@^1.0.0: | ||
5766 | version "1.0.0" | ||
5767 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" | ||
5768 | integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= | ||
5769 | 5909 | ||
5770 | which-module@^2.0.0: | 5910 | which-module@^2.0.0: |
5771 | version "2.0.0" | 5911 | version "2.0.0" |
5772 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" | 5912 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" |
5773 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= | 5913 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= |
5774 | 5914 | ||
5775 | which@1, which@^1.2.9: | 5915 | which@^1.2.14, which@^1.2.9, which@^1.3.1: |
5776 | version "1.3.1" | 5916 | version "1.3.1" |
5777 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" | 5917 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" |
5778 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== | 5918 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== |
5779 | dependencies: | 5919 | dependencies: |
5780 | isexe "^2.0.0" | 5920 | isexe "^2.0.0" |
5781 | 5921 | ||
5782 | wide-align@^1.1.0: | 5922 | which@^2.0.1: |
5783 | version "1.1.3" | 5923 | version "2.0.2" |
5784 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" | 5924 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" |
5785 | integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== | 5925 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== |
5786 | dependencies: | 5926 | dependencies: |
5787 | string-width "^1.0.2 || 2" | 5927 | isexe "^2.0.0" |
5788 | |||
5789 | window-size@0.1.0: | ||
5790 | version "0.1.0" | ||
5791 | resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" | ||
5792 | integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= | ||
5793 | 5928 | ||
5794 | wordwrap@0.0.2: | 5929 | word-wrap@^1.2.3: |
5795 | version "0.0.2" | 5930 | version "1.2.3" |
5796 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" | 5931 | resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" |
5797 | integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= | 5932 | integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== |
5798 | 5933 | ||
5799 | wordwrap@~1.0.0: | 5934 | worker-farm@^1.7.0: |
5800 | version "1.0.0" | 5935 | version "1.7.0" |
5801 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" | 5936 | resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" |
5802 | integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= | 5937 | integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== |
5938 | dependencies: | ||
5939 | errno "~0.1.7" | ||
5803 | 5940 | ||
5804 | wrap-ansi@^2.0.0: | 5941 | wrap-ansi@^5.1.0: |
5805 | version "2.1.0" | 5942 | version "5.1.0" |
5806 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" | 5943 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" |
5807 | integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= | 5944 | integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== |
5808 | dependencies: | 5945 | dependencies: |
5809 | string-width "^1.0.1" | 5946 | ansi-styles "^3.2.0" |
5810 | strip-ansi "^3.0.1" | 5947 | string-width "^3.0.0" |
5948 | strip-ansi "^5.0.0" | ||
5811 | 5949 | ||
5812 | wrappy@1: | 5950 | wrappy@1: |
5813 | version "1.0.2" | 5951 | version "1.0.2" |
5814 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" | 5952 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" |
5815 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= | 5953 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= |
5816 | 5954 | ||
5817 | write@^0.2.1: | 5955 | write-file-atomic@^3.0.3: |
5818 | version "0.2.1" | 5956 | version "3.0.3" |
5819 | resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" | 5957 | resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" |
5820 | integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= | 5958 | integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== |
5959 | dependencies: | ||
5960 | imurmurhash "^0.1.4" | ||
5961 | is-typedarray "^1.0.0" | ||
5962 | signal-exit "^3.0.2" | ||
5963 | typedarray-to-buffer "^3.1.5" | ||
5964 | |||
5965 | write@1.0.3: | ||
5966 | version "1.0.3" | ||
5967 | resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" | ||
5968 | integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== | ||
5821 | dependencies: | 5969 | dependencies: |
5822 | mkdirp "^0.5.1" | 5970 | mkdirp "^0.5.1" |
5823 | 5971 | ||
5824 | xtend@^4.0.0: | 5972 | xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: |
5825 | version "4.0.1" | 5973 | version "4.0.2" |
5826 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" | 5974 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" |
5827 | integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= | 5975 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== |
5828 | 5976 | ||
5829 | y18n@^3.2.1: | 5977 | y18n@^4.0.0: |
5830 | version "3.2.1" | 5978 | version "4.0.0" |
5831 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" | 5979 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" |
5832 | integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= | 5980 | integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== |
5833 | 5981 | ||
5834 | yallist@^2.1.2: | 5982 | yallist@^3.0.2: |
5835 | version "2.1.2" | 5983 | version "3.1.1" |
5836 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" | 5984 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" |
5837 | integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= | 5985 | integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== |
5838 | 5986 | ||
5839 | yallist@^3.0.0, yallist@^3.0.2: | 5987 | yallist@^4.0.0: |
5840 | version "3.0.3" | 5988 | version "4.0.0" |
5841 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" | 5989 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" |
5842 | integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== | 5990 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== |
5843 | 5991 | ||
5844 | yargs-parser@^5.0.0: | 5992 | yaml@^1.10.0: |
5845 | version "5.0.0" | 5993 | version "1.10.0" |
5846 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" | 5994 | resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" |
5847 | integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= | 5995 | integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== |
5996 | |||
5997 | yargs-parser@^13.1.2: | ||
5998 | version "13.1.2" | ||
5999 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" | ||
6000 | integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== | ||
5848 | dependencies: | 6001 | dependencies: |
5849 | camelcase "^3.0.0" | 6002 | camelcase "^5.0.0" |
6003 | decamelize "^1.2.0" | ||
5850 | 6004 | ||
5851 | yargs-parser@^7.0.0: | 6005 | yargs-parser@^18.1.3: |
5852 | version "7.0.0" | 6006 | version "18.1.3" |
5853 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" | 6007 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" |
5854 | integrity sha1-jQrELxbqVd69MyyvTEA4s+P139k= | 6008 | integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== |
5855 | dependencies: | 6009 | dependencies: |
5856 | camelcase "^4.1.0" | 6010 | camelcase "^5.0.0" |
5857 | 6011 | decamelize "^1.2.0" | |
5858 | yargs@^7.0.0: | 6012 | |
5859 | version "7.1.0" | 6013 | yargs@^13.3.2: |
5860 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" | 6014 | version "13.3.2" |
5861 | integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= | 6015 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" |
5862 | dependencies: | 6016 | integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== |
5863 | camelcase "^3.0.0" | 6017 | dependencies: |
5864 | cliui "^3.2.0" | 6018 | cliui "^5.0.0" |
5865 | decamelize "^1.1.1" | 6019 | find-up "^3.0.0" |
5866 | get-caller-file "^1.0.1" | 6020 | get-caller-file "^2.0.1" |
5867 | os-locale "^1.4.0" | ||
5868 | read-pkg-up "^1.0.1" | ||
5869 | require-directory "^2.1.1" | ||
5870 | require-main-filename "^1.0.1" | ||
5871 | set-blocking "^2.0.0" | ||
5872 | string-width "^1.0.2" | ||
5873 | which-module "^1.0.0" | ||
5874 | y18n "^3.2.1" | ||
5875 | yargs-parser "^5.0.0" | ||
5876 | |||
5877 | yargs@^8.0.2: | ||
5878 | version "8.0.2" | ||
5879 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" | ||
5880 | integrity sha1-YpmpBVsc78lp/355wdkY3Osiw2A= | ||
5881 | dependencies: | ||
5882 | camelcase "^4.1.0" | ||
5883 | cliui "^3.2.0" | ||
5884 | decamelize "^1.1.1" | ||
5885 | get-caller-file "^1.0.1" | ||
5886 | os-locale "^2.0.0" | ||
5887 | read-pkg-up "^2.0.0" | ||
5888 | require-directory "^2.1.1" | 6021 | require-directory "^2.1.1" |
5889 | require-main-filename "^1.0.1" | 6022 | require-main-filename "^2.0.0" |
5890 | set-blocking "^2.0.0" | 6023 | set-blocking "^2.0.0" |
5891 | string-width "^2.0.0" | 6024 | string-width "^3.0.0" |
5892 | which-module "^2.0.0" | 6025 | which-module "^2.0.0" |
5893 | y18n "^3.2.1" | 6026 | y18n "^4.0.0" |
5894 | yargs-parser "^7.0.0" | 6027 | yargs-parser "^13.1.2" |
5895 | |||
5896 | yargs@~3.10.0: | ||
5897 | version "3.10.0" | ||
5898 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" | ||
5899 | integrity sha1-9+572FfdfB0tOMDnTvvWgdFDH9E= | ||
5900 | dependencies: | ||
5901 | camelcase "^1.0.2" | ||
5902 | cliui "^2.1.0" | ||
5903 | decamelize "^1.0.0" | ||
5904 | window-size "0.1.0" | ||